找回密码
 立即注册
查看: 1629|回复: 15

8051U学习打卡+笔记

[复制链接]
  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2025-06-18 13:05:22
已绑定手机

2

主题

18

回帖

334

积分

中级会员

积分
334
发表于 2025-1-25 20:01:55 | 显示全部楼层 |阅读模式

一、序言——8051U 强在哪里

(一)功能优势

  1. 屏幕显示和视频播放(flash 编程器):具备强大的屏幕显示与视频播放功能,依托于 flash 编程器实现高效稳定的运行。
  2. IlS 录放音:支持 IlS 录放音功能,满足多种音频处理需求。
  3. PWM DMA:PWM 与 DMA 协同工作,优化系统性能。
  4. 频谱分析仪(上位机):配备频谱分析仪,通过上位机实现对信号的精准分析。
  5. 手写计算器:具备手写计算器功能,方便快捷进行数据运算。
  6. QSPI,PWM 移相,硬件乘除,单精度浮点:支持 QSPI 接口,具备 PWM 移相功能,拥有硬件乘除及单精度浮点运算能力,提升数据处理速度和精度。

(二)产品简介

Ai8051U 属于 USB 型 1T 8051,支持 32 位和 8 位指令集,价格仅为 RMB2.3。其管脚兼容天王级别的 89C52RC 和 12C5A60S2。

  • 8 位 8051 指令集兼容:如需兼容 8 位 8051 指令集,可使用 Keil C51/IAR/SDCC 编译器,性能相当于更强大的 8H8K64U。
  • 32 位 8051 指令集兼容:若要兼容 32 位 8051 指令集,可采用 Keil C251 编译器,采用双核兼容设计,性能相当于更强大的 32G12K128 和 32G8K64。

Ai8051U 还拥有以下硬件配置:

  • 存储资源:34K SRAM(2K edata,32K xdata),64K Flash。
  • 运算单元:TFPU@120MHz,配备硬件浮点 / 硬件三角函数运算器。
  • DMA 功能:DMA 支持 PWM,且支持外设直接到外设(P2P)的数据传输。
  • PWM 与 ADC:120MHz - PWM 支持硬件移相,为 16 位 PWM,配备真 12 位 ADC。
  • 接口资源:具备 USB、4 组串口、12 位 ADC、轨到轨比较器,以及 QSPI、SPI、I2S、I2C、TFT - i8080/M6800 接口。
  • 封装形式:提供 PDIP40、LQFP44、LQFP48 多种封装形式。
回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2025-06-18 13:05:22
已绑定手机

2

主题

18

回帖

334

积分

中级会员

积分
334
发表于 2025-1-25 20:59:24 | 显示全部楼层

二、硬件及工具介绍

(一)硬件介绍

image-20250125201613443

image-20250125201627349

(二)软件和工具

  1. 安装 KEIL 编程软件

image-20250125202958393

  1. 下载 ISP 软件

image-20250125203036829

  1. 添加头文件

image-20250125203949494

  1. 下载插件

image-20250125203112285

  1. 下载代码包和手册

image-20250125203020028

(三)下载第一个程序

  1. 单片机型号选择 “AI8051U-34K64”。
  2. 实验箱使用硬件 USB 接口下载。具体操作步骤如下:
  • 先按住实验箱上的 P3.2/INTO 按键(P32 接地)。
  • 然后按一下 ON/OFF 电源按键进行断电操作。
  • 接着松开 ON/OFF 电源按键完成上电操作。
  • 最后松开 P3.2/INT0 按键。正常情况下就能识别出 “(HID1) USB Writer” 设备。
  • 点击界面中的 “打开程序文件” 按钮,在出现的打开程序代码文件的对话框中选择需要下载的文件。

注意事项:下载的时候一定要记得选对 IRC 时钟频率,可在代码中查看。

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2025-06-18 13:05:22
已绑定手机

2

主题

18

回帖

334

积分

中级会员

积分
334
发表于 2025-1-25 22:38:17 | 显示全部楼层

三、点亮第一颗 LED

(一)新建工程

  1. 创建空工程:参考手册 6.5 章节进行操作。
  2. 添加头文件:利用 ISP 软件,结合手册 6.4 章节完成添加。
  3. 输入代码并编译
#include "AI8051U.h"            //调用头文件
void main(void)
{
    while(1)
    {
    
    }
}

拓展知识:C 语言中 include 用法

#include 命令是预处理命令的一种,其作用是将别的源代码内容插入到所指定的位置。有两种指定插入头文件的方式:

  • #include<文件名.h>:编译器会到系统路径下查找头文件。
  • #include"文件名.h":编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。

推荐使用 #include"文件名.h"的方式,并把所有用到的文件放到自己的文件夹里,这样将工程发给别人时不容易出现问题。

(二)点亮第一个 LED

  1. 实验箱代码及操作

输入以下代码,下载到板子上查看效果。

#include "AI8051U.h"            //调用头文件
void main(void)
{
    P0M1 = 0x00;   P0M0 = 0x00; //P0端口(P00-P07为准双向口)
    P4M1 = 0x00;   P4M0 = 0x00; //P4端口(P40-P47为准双向口)

    while(1)
    {
        P40 = 0;                //P40端口输出0V
        P00 = 0;                //P40端口输出0V
    }
}
  1. 核心板代码

如果是核心板,使用以下代码。

#include "AI8051U.h"            //调用头文件
void main(void)
{
    P2M1 = 0x00;   P2M0 = 0x00;//P2端口(P20-P27为准双向口)  

    while(1)
    {
        P20 = 0;                //P20端口输出0V
    }
}

(三)硬件介绍

image-20250125222429973

image-20250125222512370

(四)为什么能点亮 LED

image-20250125222753868

image-20250125222913503

image-20250125223047190

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2025-06-18 13:05:22
已绑定手机

2

主题

18

回帖

334

积分

中级会员

积分
334
发表于 2025-1-26 00:30:56 | 显示全部楼层

四、USB 不停电下载

1. 下载所需文件

从 STC 官网(stcai.com - 软件工具 - 库函数 - USB 库文件)获取所需文件。

图片展示:

image

image

知识拓展

  • 查询模式(轮询方式):CPU 通过不断地查询设备的状态寄存器或相关标志位,来判断设备是否准备好进行数据传输或操作。
  • 中断模式:当设备完成特定操作或发生特定事件时,会主动向 CPU 发送一个中断请求信号。CPU 在接收到中断请求后,会暂停当前正在执行的程序,转去执行相应的中断服务程序来处理该事件,处理完成后再返回原来的程序继续执行。为了让代码最稳定高效地运行,一般选择查询模式
  • CDC 模式(通信设备类模式):即 Communication Device Class 模式,是 USB 协议中用于通信设备的一类标准模式。主要用于模拟串口通信等数据传输方式,让 USB 设备能够像传统串口设备一样与主机进行数据交互。
  • HID 模式(人机接口设备模式):即 Human Interface Device 模式,是用于人机交互设备的一种 USB 设备模式,定义了一套人机接口协议,用于规范设备与主机之间的交互方式。在两者之中,推荐使用CDC 模式

图片展示:

image

操作步骤

stc_usb_adc_32.LIBstc_stc8_usb.h两个文件复制到自己的工程目录下。

图片展示:

image

image

LIB 文件说明

LIB 文件是一种库文件,库的开发者可以将代码的具体实现隐藏在 LIB 文件中,只向外部提供函数接口等,防止代码被篡改。将常用的代码封装在 LIB 文件中,多个项目可以共享这些代码,提高了开发效率,避免了重复开发。

2. 移植关键部分到工程

2.1 添加头文件

#include "stc32_stc8_usb.h"

2.2 USB 初始化函数(tib+.h 库实现)

usb_init(); 

2.3 命令参数

image

image

image

2.4 打开 P_SW2 寄存器和 IE2 寄存器(只打开一个位)

P_SW2 |= 0x80;      //B7位写1,使能访问XFR
IE2 |= 0x80;           //使能USB中断

整体代码

#include "AI8051U.h"            //调用头文件
#include "stc32_stc8_usb.h"

char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";

void main(void)
{
    P_SW2 |= 0x80;      //B7位写1,使能访问XFR
    
    P0M1 = 0x00;   P0M0 = 0x00;
    P1M1 = 0x00;   P1M0 = 0x00;
    P2M1 = 0x00;   P2M0 = 0x00;
    P3M1 = 0x00;   P3M0 = 0x00;
    P4M1 = 0x00;   P4M0 = 0x00;
    P5M1 = 0x00;   P5M0 = 0x00;
    P6M1 = 0x00;   P6M0 = 0x00;
    P7M1 = 0x00;   P7M0 = 0x00;
    
    usb_init();                                     //USB CDC 接口配置

    IE2 |= 0x80;                                    //使能USB中断
    EA = 1;                                         //IE |= 0X80;
    
    while (DeviceState!= DEVSTATE_CONFIGURED);     //等待USB完成配置
    
    while(1)
    {
        if (bUsbOutReady)
        {
            USB_SendData(UsbOutBuffer,OutNumber);   //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
            
            usb_OUT_done();
        }
    }
}
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:101
  • 最近打卡:2025-06-18 14:57:36

734

主题

1万

回帖

1万

积分

管理员

积分
16728
发表于 2025-1-26 10:07:19 | 显示全部楼层
认真学习的
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2025-06-18 13:05:22
已绑定手机

2

主题

18

回帖

334

积分

中级会员

积分
334
发表于 2025-1-26 12:55:11 | 显示全部楼层

五、C 语言基础

1. C 语言 USB - CDC 串口之 printf 函数的实现

1.1 打开 USB 库中的 PRINTF.HID 宏定义 (去掉 //)

#define PRINTF_HID            //printf输出直接重定向到USB口(早期命名方式)

1.2 理解 PRINTF 的函数原型的定义

#define printf printf_hid
int printf_hid (const char *fmt, ...);

1.3 参数 fmt说明

参数 fmt是格式控制字符串,包含了两种类型的对象:普通字符和转换说明。

  • 普通字符:在输出时,普通字符将原样不动地复制到标准输出。
    示例:
    printf("8051U深度入门到32位51大型实战视频\r\n");
    
  • 转换说明:不直接输出,用于控制 printf中参数的转换和打印。每个转换说明都由一个百分号字符(%)开始,以转换说明符结束,从而说明输出数据的类型、宽度、精度等。
    示例:
    printf("8051U深度入门到32位51大型实战视频,%s\r\n", "加油");
    

1.4 转换说明简介

  1. 类型:根据不同的 fmt字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 fmt参数中指定的每个 %标签。关于附加参数,既可以是变量,也可以是常量。
  2. 位置printf()函数的普通字符和转换说明放在 ""双引号内,附加参数放在双引号外,每个附加参数之间用逗号隔开。
  3. 数量printf()的附加参数与转换说明符是一一对应关系,如果有 n个转换说明符,printf()的参数就应该有 n + 1个。如果参数个数少于对应的转换说明符,printf()可能会输出内存中的任意值。

1.5 标志

标志 含义 实例
n,m n 表示整数占几行,m 表示小数占几行 %2.3f
- 输出的结果左对齐 %-d
空格 输出值为正时冠以空格,为负时冠以负号
# 输出带有前导的数据(八进制为 0---,十六进制为 X---,- 代表数字) %#d

1.6 格式字符

格式字符 含义
%d 以十进制整数形式输出
%ld 以十进制长整形输出
%f 以单精度浮点型输出
%lf 以双精度浮点型输出
%o 以八进制整型输出整数
% x 或 % X 以十六进制形式输出整数
%u 以十进制无符号整形输出
%i 以十进制整形输出(与 % d 无异)
%c 输出单个字符
%s 输出字符串
% e 或 % E 以指数形式输出
% g 或 % G 自适应数据输出(数据够大或够小则以指数形式输出,否则以小数形式输出)
%p 输出地址

1.7 转义字符

转义字符 释义
? 在书写连续多个问号时使用,防止它们被解析成三字字词
' 用于表示字符常量
" 用于表示一个字符串内部的双引号
\ 用于表示一个反斜杠,防止它被解释为一个转义序列符
\a 警告字符,蜂鸣
\b 退格符
\f 换页符
\n 换行符
\r 回车
\t 水平制表符 (8 个空格)
\v 垂直制表符
\ddd ddd 表示 1 - 3 个八进制的数字。如:\120
\xdd dd 表示 2 个十六进制数字,如:\x30

1.8. ASCII 可显示字符(共 95 个)

二进制 十进制 十六进制 图形
0010 0000 32 20 (space)
0010 0001 33 21 !
0010 0010 34 22 "
0010 0011 35 23 #
0010 0100 36 24 $
0010 0101 37 25 %
0010 0110 38 26 &
0010 0111 39 27 '
0010 1000 40 28 (
0010 1001 41 29 )
0010 1010 42 2A *
0010 1011 43 2B +
0010 1100 44 2C ,
0010 1101 45 2D -
0010 1110 46 2E .
0010 1111 47 2F /
0011 0000 48 30 0
0011 0001 49 31 1
0011 0010 50 32 2
0011 0011 51 33 3
0011 0100 52 34 4
0011 0101 53 35 5
0011 0110 54 36 6
0011 0111 55 37 7
0011 1000 56 38 8
0011 1001 57 39 9
0011 1010 58 3A :
0011 1011 59 3B ;
0011 1100 60 3C <
0011 1101 61 3D =
0011 1110 62 3E >
0011 1111 63 3F ?
0100 0000 64 40 @
0100 0001 65 41 A
0100 0010 66 42 B
0100 0011 67 43 C
0100 0100 68 44 D
0100 0101 69 45 E
0100 0110 70 46 F
0100 0111 71 47 G
0100 1000 72 48 H
0100 1001 73 49 I
0100 1010 74 4A J
0100 1011 75 4B K
0100 1100 76 4C L
0100 1101 77 4D M
0100 1110 78 4E N
0100 1111 79 4F O
0101 0000 80 50 P
0101 0001 81 51 Q
0101 0010 82 52 R
0101 0011 83 53 S
0101 0100 84 54 T
0101 0101 85 55 U
0101 0110 86 56 V
0101 0111 87 57 W
0101 1000 88 58 X
0101 1001 89 59 Y
0101 1010 90 5A Z
0101 1011 91 5B [
0101 1100 92 5C \
0101 1101 93 5D ]
0101 1110 94 5E ^
0101 1111 95 5F _
0110 0000 96 60 `
0110 0001 97 61 a
0110 0010 98 62 b
0110 0011 99 63 c
0110 0100 100 64 d
0110 0101 101 65 e
0110 0110 102 66 f
0110 0111 103 67 g
0110 1000 104 68 h
0110 1001 105 69 i
0110 1010 106 6A j

2. 数的进制:2进制、10进制、16进制

十进制 二进制 八进制 十六进制
1 1 1 0x1
2 10 2 0x2
3 11 3 0x3
4 100 4 0x4
5 101 5 0x5
6 110 6 0x6
7 111 7 0x7
8 1000 10 0x8
9 1001 11 0x9
10 1010 12 0xA
11 1011 13 0xB
12 1100 14 0xC
13 1101 15 0xD
14 1110 16 0xE
15 1111 17 0xF
16 10000 20 0x10
17 10001 21 0x11
18 10010 22 0x12

各进制之间互相转换的方法:

(1) 十进制转二进制

采用 “除 2 取余,逆序排列” 法。将十进制数除以 2,得到商和余数,再将商继续除以 2,直到商为 0。将每次得到的余数从右到左排列,即为对应的二进制数。例如,将十进制数 10 转换为二进制: 10 ÷ 2 = 5 余 0 5 ÷ 2 = 2 余 1 2 ÷ 2 = 1 余 0 1 ÷ 2 = 0 余 1 从下往上取余数得到 1010,即十进制 10 对应的二进制是 1010。

(2) 十进制转八进制

“除 8 取余,逆序排列” 法。将十进制数除以 8,得到商和余数,再将商继续除以 8,直到商为 0。将每次得到的余数从右到左排列,就是对应的八进制数。比如,把十进制数 20 转换为八进制: 20 ÷ 8 = 2 余 4 2 ÷ 8 = 0 余 2 从下往上取余数得到 24,即十进制 20 对应的八进制是 24。

(3) 十进制转十六进制

“除 16 取余,逆序排列” 法。十进制数除以 16,得到商和余数,商再除以 16,直至商为 0。将余数从右到左排列得到十六进制数。十六进制中 10 - 15 分别用 A - F 表示。例如,十进制数 26 转换为十六进制: 26 ÷ 16 = 1 余 10(用 A 表示) 1 ÷ 16 = 0 余 1 从下往上取余数得到 1A,即十进制 26 对应的十六进制是 1A。

(4) 二进制转十进制

按位加权求和法。从右至左,将二进制数的每一位乘以 2 的相应位数次幂(幂次从 0 开始),然后将所有结果相加。例如,二进制数 1011 转换为十进制:

(5) 八进制转十进制

同样是按位加权求和法。从右至左,八进制数的每一位乘以 8 的相应位数次幂(幂次从 0 开始),再把结果相加。比如,八进制数 35 转换为十进制:

(6) 十六进制转十进制

按位加权求和。从右至左,十六进制数的每一位乘以 16 的相应位数次幂(幂次从 0 开始),相加得到十进制数。十六进制中 A - F 分别对应 10 - 15 。例如,十六进制数 2A 转换为十进制:

(7) 二进制转八进制

从右向左,每 3 位一组,不足 3 位的在左边补 0,然后将每组二进制数转换为对应的八进制数。例如,二进制数 11010 转换为八进制: 先分组为 011 010,011 对应八进制 3,010 对应八进制 2,所以结果是 32。

(8) 二进制转十六进制

从右向左,每 4 位一组,不足 4 位的在左边补 0,将每组二进制数转换为对应的十六进制数。比如,二进制数 111011 转换为十六进制: 分组为 0011 1011,0011 对应十六进制 3,1011 对应十六进制 B,结果就是 3B。

(9) 八进制转二进制

将八进制数的每一位转换为对应的 3 位二进制数。例如,八进制数 57 转换为二进制: 5 对应二进制 101,7 对应二进制 111,结果是 101111。

(10) 十六进制转二进制

把十六进制数的每一位转换为对应的 4 位二进制数。例如,十六进制数 A3 转换为二进制: A(10)对应二进制 1010,3 对应二进制 0011,结果是 10100011。

(11) 八进制转十六进制

可以先将八进制转换为二进制,再将二进制转换为十六进制。

(12) 十六进制转八进制

先把十六进制转换为二进制,再将二进制转换为八进制 。

3.数据的基本类型

想要使用64位变量,需要在程序文件里面添加申明:#pragma float64

image-20250126123309805

4.C语言常用运算符

算术运算符

下表显示了 C 语言支持的所有算术运算符。假设变量 A 的值为 18,变量 B 的值为 5,则:

运算符 描述 实例
+ 两个数相加 A+B 将得到 23
- 一个数减另一个数 A-B 将得到 13
* 两个数相乘 A*B 将得到 90
/ 分子除以分母 A/B 将得到 3.6
% 余数运算符,整除后的余数 B%A 将得到 3
++ 自增运算符,整数值增加 1 A++ 将得到 19
-- 自减运算符,整数值减少 1 A-- 将得到 17

关系运算符

关系运算(Relational Operators),用于判断条件,决定程序的流程。

下表列出了 C 语言支持的关系运算符。假设两个整型数为 operand1 = 11, operand2 = 2,则

关系运算符 说明 示例
== 判断两个操作数是否相等,若相等则值为1,反之值为0 operand1 == operand2 的值为 0
!= 判断两个操作数是否不相等,若不相等则值为1,反之值为0 operand1 != operand2 的值为 1
> 判断第1个操作数是否大于第2个操作数,若大于则值为1,反之值为0 operand1 > operand2 的值为 1
< 判断第1个操作数是否小于第2个操作数,若小于则值为1,反之值为0 operand1 < operand2 的值为 0
>= 判断第1个操作数是否大于或等于第2个操作数,若大于或等于则值为1,反之值为0 operand1 >= operand2 的值为 1
<= 判断第1个操作数是否小于或等于第2个操作数,若小于或等于则值为1,反之值为0 operand1 <= operand2 的值为 0

逻辑运算符

下表列出了 C 语言支持的逻辑运算符。假设两个整型数为 operand1 = 1, operand2 = 0,则:

逻辑运算符 说明 示例
&& 如果两个操作数均为非0,则表达式的值为1,反之为0 operand1 && operand2 的值为 0
丨丨 如果两个操作数至少有一个为非0,则表达式的值为1,反之为0 operand1丨丨operand2 的值为 1
! 如果操作数的值为非0,则表达式的值为0,反之亦反 !operand1 的值为 0

赋值运算符

下表列出了 C 语言支持的赋值运算符:

赋值运算符 说明 示例
= 普通赋值运算符 val = 2
+= 加并赋值操作 val += 2 等价于 val = val + 2
-= 减并赋值操作 val -= 2 等价于 val = val - 2
*= 乘并赋值操作 val *= 2 等价于 val = val * 2
/= 除并赋值操作 val /= 2 等价于 val = val / 2
%= 取余并赋值操作 val %= 2 等价于 val = val % 2
<<= 左移并赋值操作 val <<= 1 等价于 val = val << 1
>>= 右移并赋值操作 val >>= 1 等价于 val = val >> 1
&= 按位与并赋值操作 val &= 1 等价于 val = val & 1
^= 按位异或并赋值操作 val ^= 1 等价于 val = val ^ 1
|= 按位或并赋值操作 val |= 1等价于 val = val | 1

赋值运算符支持的是C语言的基本数据类型,包括char、int和double,字符串(字符数组)不能使用赋值运算符。

位运算符

假设A=5(0000 0101),B=11(0000 1011)

运算符 描述 运算法则 实例
& 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中 同一为一,其它为零 (A&B)得到 00000001
| 如果存在任一操作数中,二进制 OR 运算符复制一位到结果中 有一为一,皆零为零 (A|B)得到 00001111
^ 如果只存在于一个操作数中,二进制异或运算符复制一位到结果中 相同为零,不同为一 (A^B)得到 00001110
~ 取反,二进制将每一位 0 变为 1,1 变为 0 零变一,一变零 (~A)得到 11111101
<< 二进制左移运算符,左操作数的值向左移动右操作数指定位数 向高位移动两位,低位补零 (A<<2)得到 00010100
>> 二进制右移运算符,左操作数的值向右移动右操作数指定位数 向低位移动两位,高位补零 (A>>2)得到 00000001

其他运算符

运算符 描述
Condition? X : Y 条件运算符,如果 Condition为真,则值为 X,否则为 Y
.(点) 和 ->(箭头) 成员运算符,用于引用类、结构体和共用体成员
& 取地址运算符,返回变量的存储地址
* 指针运算符,*指向一个变量,如 *a将指向变量 a
, 逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2025-06-18 13:05:22
已绑定手机

2

主题

18

回帖

334

积分

中级会员

积分
334
发表于 2025-1-27 20:52:29 | 显示全部楼层

六、I/O 输入输出

1. 什么是 GPIO

GPIO(General Purpose I/O Ports)意思为通用输入 / 输出端口,通俗地说,就是一些引脚,可以通过它们输出高低电平或者通过它们读入引脚的状态 —— 是高电平或是低电平。

问:什么是高低电平?

高电平就是指接近于电源正极电压的电平,也叫逻辑 “1”;

低电平就是指接近于电源负极(地)电压的电平,也叫逻辑 “0”;

单片机输出高电平就是输出 VCC 电压,输出低电平就是输出 GND 的电压。

2. 按键输入检测

image-20250127201558306

代码实现原理:程序直接读取按键的 IO 的电平即可,一般使用 “==” 即可。

机械按键按下或者松开有抖动,一般在 20ms 内。 因此需要进行按键消抖,初学采用延时消抖,通过软件生成的延时函数来延时 20ms。

if(按键是否按下)
{
    //延时20ms消去抖动
    if(按键是否按下)//按键再次确认按下
    {
        //执行按键功能
        while(按键是否松开);
    }
}

image-20250127203139799

任务 1:按下 P32 按钮灯亮,松开 P32 按钮灯灭;

任务 2:按下 P32 按钮灯灭,松开 P32 按钮灯亮;

任务 3:按一下灯亮,按一下灯灭;

代码如下

#include "ai8051u.h"            //调用头文件
#include "stc32_stc8_usb.h"     //调用头文件
#include "intrins.h"            //d调用头文件

#define u8  unsigned char       //8位无符号变量(0-255)
#define u16 unsigned int        //16位无符号变量(0-65535)
  
u8 state = 0;                   //初始状态

char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";

void Delay20ms(void)    //@24.000MHz  Delay20ms();
{
    unsigned long edata i;

    _nop_();
    _nop_();
    i = 119998UL;
    while (i) i--;
}


void main(void)
{
    WTST = 0;                                       //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    EAXFR = 1;                                      //扩展寄存器(XFR)访问使能
    CKCON = 0;                                      //提高访问XRAM速度
  
    P0M1 = 0x00;   P0M0 = 0x00;
    P1M1 = 0x00;   P1M0 = 0x00;
    P2M1 = 0x00;   P2M0 = 0x00;
    P3M1 = 0x00;   P3M0 = 0x00;
    P4M1 = 0x00;   P4M0 = 0x00;
    P5M1 = 0x00;   P5M0 = 0x00;
    P6M1 = 0x00;   P6M0 = 0x00;
    P7M1 = 0x00;   P7M0 = 0x00;
  
    usb_init();                                     //USB CDC 接口配置

    IE2 |= 0x80;                                    //使能USB中断
    EA = 1;                                         //IE |= 0X80;
  
    P40 = 0;
  
    while (DeviceState != DEVSTATE_CONFIGURED);     //等待USB完成配置
  
    while(1)
    {
  
        if (bUsbOutReady)                           //如果接收到了数据
        {
            //USB_SendData(UsbOutBuffer,OutNumber);   //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
            
            usb_OUT_done();                         //
        }
  
//      //任务1:按下P32按钮灯亮,松开P32按钮灯灭;
//      if( P32 == 0 )                              //判断P32按钮是否按下
//      {
//          P00 = 0;
//      }
//      else
//      {
//          P00 = 1;
//      }
//  
  
  
//      //任务2:按下P32按钮灯灭,松开P32按钮灯亮;
//      if( P32 == 1 )                              //判断P32按钮是否按下
//      {
//          P00 = 0;
//      }
//      else
//      {
//          P00 = 1;
//      }
  
        //任务3:按一下灯亮,按一下灯灭
        if( P32 == 0 )                              //判断P32按钮是否按下
        {
            Delay20ms();                            //延时20ms消抖
            if( P32 == 0 )
            {
                state = !state;                     //变量取反 0 1 0 1 0 1
                P00 = state;
                printf("state:%d\r\n",(int)state);
          
                while( P32 == 0 );                  //等待P32松开
          
            }
        }
  
  
    }
}
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2025-06-18 13:05:22
已绑定手机

2

主题

18

回帖

334

积分

中级会员

积分
334
发表于 2025-1-27 22:41:24 | 显示全部楼层

七、定时器中断

1. 定时器的介绍

问题:LED 三秒闪烁一下,这三秒按下按键但是没有反应了?这怎么办呢?

:因为 MCU 是单核的,同一时间只能执行一个任务,在没有特殊情况时不能被打断。所以这里就要引入一个特殊情况,即定时器中断。

定时器作用

  • (1) 用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作。
  • (2) 替代长时间的 Delay,提高程序的运行效率和处理速度(可以打断主循环)。

2. 定时器的应用

利用 ISP 软件直接生成定时函数!

image-20250127223146733

3. 函数的定义、声明、调用

函数定义

返回值类型 函数名(入口参数)
{
    // 函数体
    // 函数执行的代码
    return 返回值;
}
  • 定义包含返回值、函数名和入口参数,并定义了函数的具体功能。
  • 函数的名称应当能够描述函数的功能,便于代码的阅读和理解。
  • 函数名称应当使用有意义的英文单词或者组合的英文单词,避免使用特殊字符或数字。
  • 函数名称不能与 C 语言的关键字同名。

函数声明

返回值类型 函数名(入口参数);
  • 在头文件或者被调用之前使用,注意末尾要加分号。

函数调用

函数名(入口参数);
  • 在需要调用的地方直接使用函数名,加上括号和分号。如果有入口参数,需要在括号内的多个参数之间用逗号隔开。
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2025-06-18 13:05:22
已绑定手机

2

主题

18

回帖

334

积分

中级会员

积分
334
发表于 2025-1-28 10:49:45 | 显示全部楼层

八、定时器周期性任务调度

1. 周期性任务介绍

任务 1:用一个定时器实现这个任务。LED1 实现 0.3 秒取反一次,LED2 实现 0.6 秒取反一次,LED3 0.9 秒取反一次。

  • 通过一个变量计数,假设这个变量 1ms 自加一次,加到 300 即为 300ms,加到 600 就是 600ms;计数到达后重新清 0。

任务 2:数组点亮 LED,实现流水灯。

  • 数组使用分为如下两步:
    • 定义类型 名称 [长度] = { 数值 };
    • 使用赋值:名称 [索引] = 数值。
  • 注意事项:LED 是 0 点亮,1 熄灭,数组长度需要把握好流水灯移动的时间。

任务 3:按键 1 按一下,LED 通过数组移动一下。

  • 注意事项:按键不能再通过 while判断是否按下,松开了可以通过按键按下计数。

image-20250128103459724

  • 检测到按键连续按下,按键计数变量 +1,只要松开一下,计数清 0,计数累积到 50ms 的时候判定为按下。

2. 文件的创建(.c 和.h)

创建程序文件三步,把硬件需要的初始化弄一个 config.c

  • 新建文件并保存。
  • 添加到工程。
  • 添加引用路径。

一般一个 .c和一个 .h文件执行一个外设或者一个任务或功能。这样可以让代码看起来简洁明了。

新建 xxx.cxxx.h文件,代表一个功能块。

xxx.h格式

#ifndef __XXX_H
#define __XXX_H

// 调用头文件
// 函数声明...

#endif

xxx.c格式

#include “xxx.h”

// 函数定义

添加文件一定要记得引用路径和添加到工程里。

3. 结构体数组的周期性任务调度

LED1 0.3 秒闪一次,LED2 0.6 秒闪一次,LED3 0.9 秒闪一次。

  • 都有定时器 1ms 加的变量。
  • 都有一个设定的计数目标。
  • 都有需要执行的功能。
  • 定时时间到了才能执行。
typedef struct 
{
    u8 Run;                  //任务状态:Run/Stop
    u16 TIMCount;            //定时计数器
    u16 TRITime;             //重载计数器
    void (*TaskHook) (void); //任务函数
} TASK_COMPONENTS;  
static TASK_COMPONENTS Task_Comps[]=
{
 //状态  计数  周期  函数
    {0,      1,       1,     执行功能},       
    {0,     10,      10,     执行功能},     
}; 

参考试验箱历程 “27 - 通过定时器周期性调度任务综合例程,简单实用的任务调度系统,推荐”。

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2025-06-18 13:05:22
已绑定手机

2

主题

18

回帖

334

积分

中级会员

积分
334
发表于 2025-2-6 23:07:24 | 显示全部楼层

九、数码管

1.数码管介绍

数码管介绍——外观

image-20250203205050288image-20250203205101764image-20250203205120403image-20250203205132804image-20250203205144131

数码管也叫LED数码管,内部是由多个发光二极管封装在一起组成,他们可以有很多种颜色,很多种外形,很多种样式,但是本质来说他们都是通过点亮内部的LED来显示的,只要面板做好了,理论可以显示任意的字符或者图案。

以第一张图这种最普通的数码管来说,一个“8”我们称之为1位数码管,两个“8”就是2位数码管,以此类推。

数码管显示内容:

image.png

image-20250203205316064

数码管介绍——类型

image-20250203205524307

按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管

image-20250203205639739

“AS”代表单色,而“BS”代表双色

2.数码管显示原理

image-20250204115339208

image-20250204115432832

image-20250204115455088

3.数码管静态显示

/**************** 向HC595发送一个字节函数 ******************/
void Send_595(u8 dat)
{
    u8  i;
    for(i=0; i<8; i++)
    {
        dat <<= 1;
        P_HC595_SER   = CY;
        P_HC595_SRCLK = 1;
        P_HC595_SRCLK = 0;
    }
}
​
/********************** 显示扫描函数 ************************/
void DisplayScan(void)
{
    Send_595(t_display[LED8[display_index]]);   //输出段码
    Send_595(~T_COM[display_index]);            //输出位码
​
    P_HC595_RCLK = 1;
    P_HC595_RCLK = 0;
    if(++display_index >= 8)    display_index = 0;  //8位结束回0
}

使用ISP软件自带工具快速生成数码管数组

image-20250204120611686

4.数码管动态显示

image-20250206230159766

具体的控制的流程如图所示,N表示有几个数码管!其中需要注意每个延时不能太短,我们这边程序就以1ms为准,且需要保证总共一个循环结束的时间不能大于20ms,因为人眼的视觉不容易分辨出50HZ以上的动态刷新。

image.png

回复 支持 反对

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-6-19 05:27 , Processed in 0.137139 second(s), 106 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表