找回密码
 立即注册
楼主: utjafams

机缘巧合,跟AI8051U握个手

[复制链接]
  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2026-02-06 08:59:52
已绑定手机

1

主题

18

回帖

143

积分

注册会员

积分
143
发表于 2025-12-21 09:16:22 | 显示全部楼层
第九课: 数码管

1. 数码管的类型

根据管脚的接法,可分为两种, 共阳极 和 共阴极。

2. 段码和位码

段码: 一个“8”字,刚好由 a、b、c、d、e、f、g、dp 8个段构成,8个段的排列组合,构成不同含义的外观,这些排列组合由一个16进制的数标识,就是段码;
位码:共极数码管上有多个“8”字,例如实验箱上有8个“8”字,第n个“8”的位置,用一个16进制的数表示,就是位码。


两个码的数据,就可以向数码管发送指令,点亮那个位置、什么形状的灯。

3. 原理图

数码管由两个74HC595芯片共同实现控制。一个控制“段”,一个控制“位”。


4. 取段码的原理

实验箱是共阴极的设计,需要点亮的管脚为 0 , 不需要点亮的管脚为 1, 由下往上排序,就得到8位二进制码,转为16进制数就是段码。

5. 取位码的原理

根据第一个74CH595芯片管脚与com口对应的序号,将序号转为16进制,就是位码。

6. 数码管的数据发送“接口”

(1)移位寄存器

    通过74CH595发送数据,移位寄存器扮演着重要的角色。它是AI8051U的能力之一,具体可参考手册 12.2.3 程序状态寄存器。CY 是进位标志。段码和位码数据的发送,就是通过位操作,将数据送出去。
    发送段码和位码的方法
  1. void Send595(unsigned char dat)
  2. {
  3.         unsigned char i;
  4.         for(i=0; i < 8; i++)
  5.         {
  6.                 dat <<= 1;                // 左边1位移入 CY
  7.                 HC595_SER = CY;
  8.                 HC595_SCK = 1;
  9.                 HC595_SCK = 0;
  10.         }
  11. }
复制代码
   最后的 HC595_SCK 切换高低电平操作,是所谓的“发送上升沿”。
    发送段码和位码后,还要发送一次“上升沿”,才能点亮数码管。发送方法:

  1. void DisplaySeg(unsigned char segCode, unsigned char posCode)
  2. {
  3.         Send595(segCode);
  4.         Send595(posCode);
  5.         HC595_RCK = 1;
  6.         HC595_RCK = 0;
  7. }
复制代码
   注意,发送段码和位码,使用 P32(HC595_SCK)发送,最后点亮数码管,使用 P35(HC595_RCK)发送。

    (这里的控制方法与江协科技的STC89C52有很大不同,STC89C52控制数码管的方式与LED类似,仅需注意段码、位码取码的方式)
    江协科技基于89C52芯片的实验版,静态显示数码管的方法(供对照参考,没有发送“上升沿”操作)

  1. // 静态显示数码管
  2. // @param unsigned char pos 位码索引,取值范围 1 - 8
  3. // @param unsigned char num 段码索引,取值范围 0 - 16
  4. void Nixie(unsigned char pos, num){
  5.         // 位码
  6.         // P2_4 , P2_3, P2_2 构成 3 位二进制数,指向数码管特定的“位”
  7.         switch(pos){
  8.                 case 1: P2_4 = 1; P2_3 = 1; P2_2 = 1;break;
  9.                 case 2: P2_4 = 1; P2_3 = 1; P2_2 = 0;break;
  10.                 case 3: P2_4 = 1; P2_3 = 0; P2_2 = 1;break;
  11.                 case 4: P2_4 = 1; P2_3 = 0; P2_2 = 0;break;
  12.                 case 5: P2_4 = 0; P2_3 = 1; P2_2 = 1;break;
  13.                 case 6: P2_4 = 0; P2_3 = 1; P2_2 = 0;break;
  14.                 case 7: P2_4 = 0; P2_3 = 0; P2_2 = 1;break;
  15.                 case 8: P2_4 = 0; P2_3 = 0; P2_2 = 0;break;
  16.         }
  17.        
  18.         // 段码赋值
  19.         P0 = digit[num];
  20. }
复制代码

7. 数码管的静态显示和动态显示。

由于数码管的8个数字,阴极的管脚都是接通的,同一时间,只能发送一个段码+位码,只能显示一个 管位 。 这是静态显示。
利用人眼影像暂留原理,在极短的时间内进行循环切换,可以显示多个 管位 ,这是动态显示。这跟老式的电影播放机的原理相同,只要频率大于24HZ,人眼就觉察不到闪烁。

8. ISP 段码生成工具

ISP 提供了字库生成工具,其中就包括了段码生成功能。可根据数码段的排列组合,自动生成16进制段码。

9. ISP 的虚拟外设

ISP提供了部分虚拟的外设,通过配置后,可接入擎天柱灯简化的实验板,模拟真实硬件的运行情况。例如LED-DIP40、数码管等,同时提供了使用接口说明。

10. 作业:免单计数器。(待补充)


ISP的段码生成工具.png
波形图.png
定义引脚(别名).png
动态显示.png
控制数码管的74HC595芯片.png
免单计数器.png
数码管类型.png
向HC595发送一个字节.png
虚拟显示接口.png
移位寄存器.png
回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:324
  • 最近打卡:2026-02-12 09:05:32

835

主题

1万

回帖

2万

积分

管理员

积分
22170
发表于 2025-12-21 10:53:55 | 显示全部楼层

2025/12/21:
可以申请实验箱了,等实验箱的同时,可以实战 USB !

截图202512211053452658.jpg

永远用最新版 软件
截图202512211055167990.jpg
深圳国芯人工智能有限公司-工具软件


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2026-02-06 08:59:52
已绑定手机

1

主题

18

回帖

143

积分

注册会员

积分
143
发表于 2025-12-23 13:57:40 | 显示全部楼层
第九课 数码管作业: 10秒免单挑战器


设计目标:

前面 4 位,静态显示 10.00 作为挑战目标

后面 4 位,默认静态显示 00.00
按下 P33 按钮后,开始显示计时器,前面2位显示单位秒,后面两位分别显示百毫秒、十毫秒
当计时器到达 99.99 秒后,计时器归零。
在计时期间,按下 P33 按钮,可暂停计时,时间以1HZ的频率,闪烁显示按下时刻的值。
当计时暂停时,按下 P33 按钮,计时器归零,并且停止闪烁。
当再次按下 P33 按钮时,重新开始计时。


实现的过程,遇到了一些问题

1. P32 按钮作为 usb 中断,使用 P32 按钮作为挑战按钮,会导致程序出现 BUG
2. 模块化代码设计,固然可以让逻辑更为清晰,同时也带来代码分享的障碍
3. C语言的数组长度,与高级语言的数组长度计算有很大差异。 使用 sizeof 计算数组占用字节总长度和单个元素长度,从而计算数组元素个数,这是C语言的经典做法。
4. 使用定时器实现无阻塞中断非常好,但是也带来了程序的复杂度,尤其是在嵌套使用时。
5. 设计程序时,要牢牢把握住, 定时器回调函数,用于周期性更新, main 函数中的 while 循环,用于即时获取更新后的结果。

下面是运行结果及源代码

  1. #include "ai8051u.h"                        //调用头文件
  2. #include "stc32_stc8_usb.h"                //调用头文件
  3. #include "stdlib.h"
  4. //注意:擎天柱的LED端口在P2,且没有三极管的电源控制,所以只要控制P2端口即可,本节课程的其余内容(USB不停电下载)均通用!
  5. char* USER_DEVICEDESC = NULL;
  6. char* USER_PRODUCTDESC = NULL;
  7. char* USER_STCISPCMD = "@STCISP#";
  8. /* ----------------------------- config module ----------------------------- */
  9. void Timer0_Init(void);                //1毫秒@24.000MHz
  10. void Delay(unsigned int ms);        //@24.000MHz
  11. void sys_init(void);                // 初始化
  12. void Timer0_Init(void)                //1毫秒@24.000MHz
  13. {
  14.         TM0PS = 0x00;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
  15.         AUXR &= 0x7F;                        //定时器时钟12T模式
  16.         TMOD &= 0xF0;                        //设置定时器模式
  17.         TL0 = 0x30;                                //设置定时初始值
  18.         TH0 = 0xF8;                                //设置定时初始值
  19.         TF0 = 0;                                //清除TF0标志
  20.         TR0 = 1;                                //定时器0开始计时
  21.         ET0 = 1;                                //使能定时器0中断
  22. }
  23. void Delay(unsigned int ms)        //@24.000MHz
  24. {
  25.         unsigned char data i, j;
  26.         while(ms--)
  27.         {
  28.                 i = 24;
  29.                 j = 85;
  30.                 do
  31.                 {
  32.                         while(--j);
  33.                 }
  34.                 while(--i);
  35.         }
  36. }
  37. void sys_init(void)
  38. {
  39.         
  40.         //P_SW2 |= 0x80;                //B7位写1,使能访问XFR  等同于  EAFXR = 1;
  41.         WTST = 0;
  42.         EAXFR = 1;
  43.         CKCON = 0;
  44.         P0M1 = 0x00;
  45.         P0M0 = 0x00;
  46.         P1M1 = 0x00;
  47.         P1M0 = 0x00;
  48.         P2M1 = 0x00;
  49.         P2M0 = 0x00;
  50.         P3M1 = 0x00;
  51.         P3M0 = 0x00;
  52.         P4M1 = 0x00;
  53.         P4M0 = 0x00;
  54.         P5M1 = 0x00;
  55.         P5M0 = 0x00;
  56.         P6M1 = 0x00;
  57.         P6M0 = 0x00;
  58.         P7M1 = 0x00;
  59.         P7M0 = 0x00;
  60.         usb_init();                                     //USB CDC 接口配置
  61.         IE2 |= 0x80;                                    //使能USB中断
  62.         EA = 1;                                                                                        //IE |= 0X80;
  63.         while(DeviceState != DEVSTATE_CONFIGURED);      //等待USB完成配置        
  64.         
  65. }
  66. /* -----------------------------END config module END----------------------------- */
  67. /* ----------------------------- BEGIN button module BEGIN ----------------------------- */
  68. // 按钮状态
  69. typedef enum
  70. {
  71.         KEY_STATE_IDLE,
  72.         KEY_STATE_PRESS,
  73.         KEY_STATE_CONFIRM,
  74.         KEY_STATE_RELEASE
  75. } KeyState;
  76. // 按钮结构体
  77. typedef struct
  78. {
  79.         unsigned char pin;          // 按钮引脚电压 0 / 1
  80.         KeyState state;
  81.         unsigned int debounce_cnt;
  82.         unsigned char press_flag;
  83.         unsigned char change_flag;
  84. } KeyStruct;
  85. // 每毫秒扫描一次按钮状态变化
  86. // 找到遵循<抖动、按下、抖动、稳定>物理规律的按钮状态改变
  87. void KeyScan(KeyStruct* key, unsigned char current_pin);
  88. // 检查按键事件
  89. unsigned char Key_GetEvent(KeyStruct* key);
  90. // 每毫秒扫描一次按钮状态变化
  91. // 找到遵循<抖动、按下、抖动、稳定>物理规律的按钮状态改变
  92. void KeyScan(KeyStruct* key, unsigned char current_pin)
  93. {
  94.         switch(key->state)
  95.         {
  96.                 case KEY_STATE_IDLE:
  97.                         if(current_pin == 0)                                 // 检测到按钮按下动作
  98.                         {
  99.                                 key->state = KEY_STATE_PRESS;
  100.                                 key->debounce_cnt = 0;
  101.                         }
  102.                         break;
  103.                 case KEY_STATE_PRESS:
  104.                         key->debounce_cnt++;
  105.                         if(key->debounce_cnt >= 20)
  106.                         {
  107.                                 if(current_pin == 0)                         // 确实被按下,已停止抖动
  108.                                 {
  109.                                         key->state = KEY_STATE_CONFIRM;
  110.                                         key->press_flag = 1;
  111.                                         key->change_flag = 1;
  112.                                 }
  113.                                 else                                                         // 是抖动,按钮弹回原位
  114.                                 {
  115.                                         key->state = KEY_STATE_IDLE;
  116.                                 }
  117.                                 key->debounce_cnt = 0;                         // 不管是哪种情况, 消抖计数都归零
  118.                         }
  119.                         break;
  120.                 case KEY_STATE_CONFIRM:
  121.                         if(current_pin == 1)                                 // 检测到按钮释放
  122.                         {
  123.                                 key->state = KEY_STATE_RELEASE;
  124.                                 key->debounce_cnt = 0;
  125.                         }
  126.                         break;
  127.                 case KEY_STATE_RELEASE:
  128.                         key->debounce_cnt++;
  129.                         if(key->debounce_cnt >= 20)                 // 20ms 释放消抖
  130.                         {
  131.                                 if(current_pin == 1)                         // 确认按钮释放
  132.                                 {
  133.                                         key->state = KEY_STATE_IDLE;
  134.                                         key->press_flag = 0;
  135.                                 }
  136.                                 else
  137.                                 {
  138.                                         key->state = KEY_STATE_CONFIRM;                // 按钮弹回
  139.                                 }
  140.                                 key->debounce_cnt = 0;
  141.                         }
  142.                         break;
  143.         }
  144. }
  145. // 检查按键事件
  146. unsigned char Key_GetEvent(KeyStruct* key)
  147. {        
  148.         if(key->change_flag)
  149.         {
  150.                 key->change_flag = 0;                // 有按键事件
  151.                 return 1;
  152.         }
  153.         return 0;                                                // 无按键事件
  154. }        
  155.         
  156. /* ----------------------------- END button module END ----------------------------- */
  157. /* ----------------------------- BEGIN nixie module BEGIN ----------------------------- */
  158. // 定义 595 引脚
  159. #define HC595_SER P34
  160. #define HC595_RCK P35
  161. #define HC595_SCK P32
  162. // 10秒状态
  163. typedef enum
  164. {
  165.         DS_STOP = 0,
  166.         DS_RUN = 1,
  167.         DS_CLEAN_STOP = 2,
  168. } DigitState;
  169. // 灯管模块
  170. typedef struct
  171. {
  172.         unsigned int tms;                // 灯管数值, 取值范围 0 - 9999
  173.         DigitState state;                // 灯光状态
  174.         
  175. } LightModule;
  176. // 灯管模块
  177. LightModule digitLights = { 0, DS_STOP };
  178.         
  179. void Init595(void);
  180. void Send595(unsigned char dat);
  181. void DisplaySeg(unsigned char segCode, unsigned char posCode);
  182. void targetLights(void);
  183. void DisplayDigitLights(void);
  184. // 段码
  185. unsigned char segNum[] =
  186. {
  187.         0x3F,       /*'0', 0*/
  188.         0x06,       /*'1', 1*/
  189.         0x5B,       /*'2', 2*/
  190.         0x4F,       /*'3', 3*/
  191.         0x66,       /*'4', 4*/
  192.         0x6D,       /*'5', 5*/
  193.         0x7D,       /*'6', 6*/
  194.         0x07,       /*'7', 7*/
  195.         0x7F,       /*'8', 8*/
  196.         0x6F,       /*'9', 9*/
  197.         0x77,       /*'A', 10*/
  198.         0x7C,       /*'B', 11*/
  199.         0x39,       /*'C', 12*/
  200.         0x5E,       /*'D', 13*/
  201.         0x79,       /*'E', 14*/
  202.         0x71,       /*'F', 15*/
  203.         0x40,       /*'-', 16*/
  204.         0x00,       /*' ', 17*/
  205.         0x80,       /*'.', 18*/
  206.         0x78,       /*'T', 19*/
  207.         0xBF,       /*'0', 20*/
  208.     0x86,       /*'1', 21*/
  209.     0xDB,       /*'2', 22*/
  210.     0xCF,       /*'3', 23*/
  211.     0xE6,       /*'4', 24*/
  212.     0xED,       /*'5', 25*/
  213.     0xFD,       /*'6', 26*/
  214.     0x87,       /*'7', 27*/
  215.     0xFF,       /*'8', 28*/
  216.     0xEF,       /*'9', 29*/
  217.         
  218. };
  219. // 位码 0000 0001, 0000 0010, 0000 0100 ... 为方便赋值,使用了相反的二进制,使用时需要取反
  220. unsigned char posNum[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};        
  221. void Init595(void)
  222. {
  223.         HC595_SER = 0;
  224.         HC595_RCK = 0;
  225.         HC595_SCK = 0;
  226. }
  227. void Send595(unsigned char dat)
  228. {
  229.         unsigned char i;
  230.         for(i=0; i < 8; i++)
  231.         {
  232.                 dat <<= 1;                // 左边1位移入 CY
  233.                 HC595_SER = CY;
  234.                 HC595_SCK = 1;
  235.                 HC595_SCK = 0;
  236.         }
  237. }
  238. void DisplaySeg(unsigned char segCode, unsigned char posCode)
  239. {
  240.         Send595(segCode);
  241.         Send595(posCode);
  242.         HC595_RCK = 1;
  243.         HC595_RCK = 0;
  244. }
  245. void targetLights(void)
  246. {
  247.         DisplaySeg(segNum[1], ~posNum[0]);
  248.         Delay(1);
  249.         DisplaySeg(segNum[20], ~posNum[1]);
  250.         Delay(1);
  251.         DisplaySeg(segNum[0], ~posNum[2]);
  252.         Delay(1);
  253.         DisplaySeg(segNum[0], ~posNum[3]);
  254.         Delay(1);
  255. }
  256. // 10秒挑战 程序逻辑
  257. // 后面 4 位,默认静态显示 00.00
  258. // 按下 P32 按钮后,开始显示计时器,前面2位显示单位秒,后面两位分别显示百毫秒、十毫秒
  259. // 当计时器到达 99.99 秒后,计时器归零。
  260. // 在计时期间,按下 P32 按钮,可暂停计时,时间以1HZ的频率,闪烁显示按下时刻的值。
  261. // 当计时暂停时,按下 P32 按钮,计时器归零,并且停止闪烁。
  262. // 当再次按下 P32 按钮时,重新开始计时。
  263. void DisplayDigitLights(void)
  264. {
  265.         unsigned char digit_1 = 0;
  266.         unsigned char digit_2 = 0;
  267.         unsigned char digit_3 = 0;
  268.         unsigned char digit_4 = 0;        
  269.         digit_1 = digitLights.tms / 1000;
  270.         digit_2 = (digitLights.tms % 1000) / 100;
  271.         digit_3 = (digitLights.tms % 100) / 10;
  272.         digit_4 = digitLights.tms % 10;
  273.         DisplaySeg(segNum[digit_1], ~posNum[4]);
  274.         Delay(1);
  275.         DisplaySeg(segNum[digit_2 + 20], ~posNum[5]);
  276.         Delay(1);
  277.         DisplaySeg(segNum[digit_3], ~posNum[6]);
  278.         Delay(1);
  279.         DisplaySeg(segNum[digit_4], ~posNum[7]);
  280.         Delay(1);
  281. }
  282. // 刷新灯管计时器
  283. void freshDigitLight(void)
  284. {
  285.         switch((&digitLights)->state)
  286.         {
  287.                 case DS_STOP:                                // 灯管处于准备状态
  288.                         if( (&digitLights)->tms > 0 )
  289.                         {
  290.                                 (&digitLights)->tms = 0;
  291.                         }
  292.                         break;
  293.                 case DS_RUN:                                // 灯管处于运行状态, 计时器自增
  294.                         if( (&digitLights)->tms < 9999 )
  295.                         {
  296.                                 (&digitLights)->tms++;
  297.                         }
  298.                         else
  299.                         {
  300.                                 (&digitLights)->tms = 0;
  301.                         }
  302.                         break;
  303.                 case DS_CLEAN_STOP:                        // 灯管处于展示结果状态
  304.                         break;
  305.         }
  306. }
  307. /* ----------------------------- END nixie module END ----------------------------- */
  308. /* ----------------------------- BEGIN  task module BEGIN ----------------------------- */
  309. // 任务对象
  310. typedef struct
  311. {
  312.         unsigned char run_state;                        //         执行状态
  313.         int timer;                        //  倒计时
  314.         int cycle_length;                        //  执行周期
  315.         void (*TaskHook)(void);                // 任务回调函数        
  316. } TASK_COMPONENT;
  317. void timer_callback(void);
  318. void task_callback(void);
  319. // 静态任务数组
  320. // 编译前已确定数组长度
  321. static TASK_COMPONENT tasks[] =
  322. {
  323.         {0, 10, 10, freshDigitLight }
  324. };
  325. // 任务数组长度
  326. unsigned int tasks_length = sizeof(tasks) / sizeof(tasks[0]);
  327. // 定时器回调函数
  328. // 定时触发 run_state
  329. void timer_callback(void)
  330. {
  331.         unsigned int i = 0;
  332.         for(i = 0; i < tasks_length; i++)
  333.         {
  334.                
  335.                 if(tasks[i].timer == 0 && tasks[i].run_state == 0){
  336.                         tasks[i].timer = tasks[i].cycle_length;
  337.                 }
  338.                
  339.                 if(tasks[i].timer > 0)
  340.                 {
  341.                         tasks[i].timer--;
  342.                         if(tasks[i].timer == 0)
  343.                         {
  344.                                 tasks[i].timer = tasks[i].cycle_length;
  345.                                 tasks[i].run_state = 1;
  346.                         }
  347.                 }
  348.         }
  349. }
  350. // 任务处理回调函数
  351. // 定时执行任务
  352. void task_callback(void)
  353. {
  354.         
  355.         unsigned int i = 0;
  356.         for(i = 0; i < tasks_length; i++)
  357.         {
  358.                 if(tasks[i].run_state == 1)
  359.                 {
  360.                         tasks[i].run_state = 0;
  361.                         tasks[i].TaskHook();
  362.                 }
  363.         }
  364.         
  365. }
  366. /* ----------------------------- END task module END ----------------------------- */
  367. // 按钮
  368. KeyStruct key33 = {1, KEY_STATE_IDLE, 0, 0, 0};
  369. void main(void)
  370. {
  371.         sys_init();
  372.         Timer0_Init();
  373.         
  374.         Init595();
  375.         
  376.         while(1)
  377.         {
  378.                
  379.                 task_callback();
  380.                
  381.                 // 如果按钮被按下
  382.                 if(Key_GetEvent(&key33))
  383.                 {
  384.                         switch ( (&digitLights)->state )
  385.                         {
  386.                                 case DS_STOP:
  387.                                         (&digitLights)->state = DS_RUN;                                // 开始计时
  388.                                         break;
  389.                                 case DS_RUN:
  390.                                         (&digitLights)->state = DS_CLEAN_STOP;                // 停止计时
  391.                                         break;
  392.                                 case DS_CLEAN_STOP:
  393.                                         (&digitLights)->state = DS_STOP;                                // 清理计时结果
  394.                                         break;
  395.                         }
  396.                 }
  397.                
  398.                 // 显示挑战目标 10.00 秒
  399.                 targetLights();
  400.                
  401.                 // 显示挑战结果,挑战前、挑战中、结果
  402.                 DisplayDigitLights();
  403.                
  404.                 if(bUsbOutReady)
  405.                 {
  406.                         //USB_SendData(UsbOutBuffer,OutNumber);   //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
  407.                         
  408.                         usb_OUT_done();
  409.                 }
  410.         }
  411. }
  412. // 定时器中断回调函数
  413. void Timer0_Isr(void) interrupt 1
  414. {
  415.         timer_callback();
  416.         KeyScan(&key33, P33);
  417. }
复制代码



usb中断冲突.png
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2026-02-06 08:59:52
已绑定手机

1

主题

18

回帖

143

积分

注册会员

积分
143
发表于 2025-12-25 15:07:42 | 显示全部楼层
第九课 数码管 仿真接口

使用仿真接口的方法:

1. 打开 ISP 工具,打开菜单 “调试仿真接口” -- “接口设置”, 设置为“将所有调试接口绑定到 USB-CDC / 串口助手”

2. 打开 “CDC/HID-串口助手” , 选择串口与左侧的“扫描串口”选项一致(我这里是 com4 USB-CDC),然后“打开串口”

3. 再打开“调试仿真接口” - “擎天柱 - LED - DIP40” 。

在打开仿真接口时走了一些弯路。

1. 每个人的电脑打开的 com 口数量、序号是不同的。想我开发用的电脑, 实际使用的是 com4 ,并且只有一个。实际应用中,只要打开的com口与左侧使用的com口一致就行。(教学视频中有多个,让我以为可能存在多个)

2. 物理的 LED 灯,开启只要在 main 函数中打开 1 次就能点亮。 仿真接口的不可以, 我猜测是当main函数调用时, 仿真接口还没有就绪,因此做了个实验,在 main 函数中,放在 while(1) 循环之前,先 Delay(1000),再调用 PLED_40(), 就可以点亮了。这对初学者的我造成了一段时间的困惑,以为仿真接口没有正常工作。

代码片段
  1. void main(void)
  2. {
  3.         sys_init();
  4.         Timer0_Init();
  5.        
  6.         Init595();
  7.        
  8. // 延迟 1 秒, 等待仿真接口加载完毕,再发送接口数据。
  9.         Delay(1000);
  10.         PLED_40();
  11.        
  12.         while(1)
  13.         {}
复制代码



验证视频


源码
  1. #include "ai8051u.h"                        //调用头文件
  2. #include "stc32_stc8_usb.h"                //调用头文件
  3. #include "stdlib.h"
  4. //注意:擎天柱的LED端口在P2,且没有三极管的电源控制,所以只要控制P2端口即可,本节课程的其余内容(USB不停电下载)均通用!
  5. char* USER_DEVICEDESC = NULL;
  6. char* USER_PRODUCTDESC = NULL;
  7. char* USER_STCISPCMD = "@STCISP#";
  8. /* ----------------------------- config module ----------------------------- */
  9. void Timer0_Init(void);                //1毫秒@24.000MHz
  10. void Delay(unsigned int ms);        //@24.000MHz
  11. void sys_init(void);                // 初始化
  12. void Timer0_Init(void)                //1毫秒@24.000MHz
  13. {
  14.         TM0PS = 0x00;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
  15.         AUXR &= 0x7F;                        //定时器时钟12T模式
  16.         TMOD &= 0xF0;                        //设置定时器模式
  17.         TL0 = 0x30;                                //设置定时初始值
  18.         TH0 = 0xF8;                                //设置定时初始值
  19.         TF0 = 0;                                //清除TF0标志
  20.         TR0 = 1;                                //定时器0开始计时
  21.         ET0 = 1;                                //使能定时器0中断
  22. }
  23. void Delay(unsigned int ms)        //@24.000MHz
  24. {
  25.         unsigned char data i, j;
  26.         while(ms--)
  27.         {
  28.                 i = 24;
  29.                 j = 85;
  30.                 do
  31.                 {
  32.                         while(--j);
  33.                 }
  34.                 while(--i);
  35.         }
  36. }
  37. void sys_init(void)
  38. {
  39.        
  40.         //P_SW2 |= 0x80;                //B7位写1,使能访问XFR  等同于  EAFXR = 1;
  41.         WTST = 0;
  42.         EAXFR = 1;
  43.         CKCON = 0;
  44.         P0M1 = 0x00;
  45.         P0M0 = 0x00;
  46.         P1M1 = 0x00;
  47.         P1M0 = 0x00;
  48.         P2M1 = 0x00;
  49.         P2M0 = 0x00;
  50.         P3M1 = 0x00;
  51.         P3M0 = 0x00;
  52.         P4M1 = 0x00;
  53.         P4M0 = 0x00;
  54.         P5M1 = 0x00;
  55.         P5M0 = 0x00;
  56.         P6M1 = 0x00;
  57.         P6M0 = 0x00;
  58.         P7M1 = 0x00;
  59.         P7M0 = 0x00;
  60.         usb_init();                                     //USB CDC 接口配置
  61.         IE2 |= 0x80;                                    //使能USB中断
  62.         EA = 1;                                                                                        //IE |= 0X80;
  63.         while(DeviceState != DEVSTATE_CONFIGURED);      //等待USB完成配置       
  64.        
  65. }
  66. /* -----------------------------END config module END----------------------------- */
  67. /* ----------------------------- BEGIN button module BEGIN ----------------------------- */
  68. // 按钮状态
  69. typedef enum
  70. {
  71.         KEY_STATE_IDLE,
  72.         KEY_STATE_PRESS,
  73.         KEY_STATE_CONFIRM,
  74.         KEY_STATE_RELEASE
  75. } KeyState;
  76. // 按钮结构体
  77. typedef struct
  78. {
  79.         unsigned char pin;          // 按钮引脚电压 0 / 1
  80.         KeyState state;
  81.         unsigned int debounce_cnt;
  82.         unsigned char press_flag;
  83.         unsigned char change_flag;
  84. } KeyStruct;
  85. // 每毫秒扫描一次按钮状态变化
  86. // 找到遵循<抖动、按下、抖动、稳定>物理规律的按钮状态改变
  87. void KeyScan(KeyStruct* key, unsigned char current_pin);
  88. // 检查按键事件
  89. unsigned char Key_GetEvent(KeyStruct* key);
  90. // 每毫秒扫描一次按钮状态变化
  91. // 找到遵循<抖动、按下、抖动、稳定>物理规律的按钮状态改变
  92. void KeyScan(KeyStruct* key, unsigned char current_pin)
  93. {
  94.         switch(key->state)
  95.         {
  96.                 case KEY_STATE_IDLE:
  97.                         if(current_pin == 0)                                 // 检测到按钮按下动作
  98.                         {
  99.                                 key->state = KEY_STATE_PRESS;
  100.                                 key->debounce_cnt = 0;
  101.                         }
  102.                         break;
  103.                 case KEY_STATE_PRESS:
  104.                         key->debounce_cnt++;
  105.                         if(key->debounce_cnt >= 20)
  106.                         {
  107.                                 if(current_pin == 0)                         // 确实被按下,已停止抖动
  108.                                 {
  109.                                         key->state = KEY_STATE_CONFIRM;
  110.                                         key->press_flag = 1;
  111.                                         key->change_flag = 1;
  112.                                 }
  113.                                 else                                                         // 是抖动,按钮弹回原位
  114.                                 {
  115.                                         key->state = KEY_STATE_IDLE;
  116.                                 }
  117.                                 key->debounce_cnt = 0;                         // 不管是哪种情况, 消抖计数都归零
  118.                         }
  119.                         break;
  120.                 case KEY_STATE_CONFIRM:
  121.                         if(current_pin == 1)                                 // 检测到按钮释放
  122.                         {
  123.                                 key->state = KEY_STATE_RELEASE;
  124.                                 key->debounce_cnt = 0;
  125.                         }
  126.                         break;
  127.                 case KEY_STATE_RELEASE:
  128.                         key->debounce_cnt++;
  129.                         if(key->debounce_cnt >= 20)                 // 20ms 释放消抖
  130.                         {
  131.                                 if(current_pin == 1)                         // 确认按钮释放
  132.                                 {
  133.                                         key->state = KEY_STATE_IDLE;
  134.                                         key->press_flag = 0;
  135.                                 }
  136.                                 else
  137.                                 {
  138.                                         key->state = KEY_STATE_CONFIRM;                // 按钮弹回
  139.                                 }
  140.                                 key->debounce_cnt = 0;
  141.                         }
  142.                         break;
  143.         }
  144. }
  145. // 检查按键事件
  146. unsigned char Key_GetEvent(KeyStruct* key)
  147. {       
  148.         if(key->change_flag)
  149.         {
  150.                 key->change_flag = 0;                // 有按键事件
  151.                 return 1;
  152.         }
  153.         return 0;                                                // 无按键事件
  154. }       
  155.        
  156. /* ----------------------------- END button module END ----------------------------- */
  157. /* ----------------------------- BEGIN nixie module BEGIN ----------------------------- */
  158. // 定义 595 引脚
  159. #define HC595_SER P34
  160. #define HC595_RCK P35
  161. #define HC595_SCK P32
  162. // 10秒状态
  163. typedef enum
  164. {
  165.         DS_STOP = 0,
  166.         DS_RUN = 1,
  167.         DS_CLEAN_STOP = 2,
  168. } DigitState;
  169. // 灯管模块
  170. typedef struct
  171. {
  172.         unsigned int tms;                // 灯管数值, 取值范围 0 - 9999
  173.         DigitState state;                // 灯光状态
  174.        
  175. } LightModule;
  176. // 灯管模块
  177. LightModule digitLights = { 0, DS_STOP };
  178.        
  179. void Init595(void);
  180. void Send595(unsigned char dat);
  181. void DisplaySeg(unsigned char segCode, unsigned char posCode);
  182. void targetLights(void);
  183. void DisplayDigitLights(void);
  184. // 段码
  185. unsigned char segNum[] =
  186. {
  187.         0x3F,       /*'0', 0*/
  188.         0x06,       /*'1', 1*/
  189.         0x5B,       /*'2', 2*/
  190.         0x4F,       /*'3', 3*/
  191.         0x66,       /*'4', 4*/
  192.         0x6D,       /*'5', 5*/
  193.         0x7D,       /*'6', 6*/
  194.         0x07,       /*'7', 7*/
  195.         0x7F,       /*'8', 8*/
  196.         0x6F,       /*'9', 9*/
  197.         0x77,       /*'A', 10*/
  198.         0x7C,       /*'B', 11*/
  199.         0x39,       /*'C', 12*/
  200.         0x5E,       /*'D', 13*/
  201.         0x79,       /*'E', 14*/
  202.         0x71,       /*'F', 15*/
  203.         0x40,       /*'-', 16*/
  204.         0x00,       /*' ', 17*/
  205.         0x80,       /*'.', 18*/
  206.         0x78,       /*'T', 19*/
  207.         0xBF,       /*'0', 20*/
  208.     0x86,       /*'1', 21*/
  209.     0xDB,       /*'2', 22*/
  210.     0xCF,       /*'3', 23*/
  211.     0xE6,       /*'4', 24*/
  212.     0xED,       /*'5', 25*/
  213.     0xFD,       /*'6', 26*/
  214.     0x87,       /*'7', 27*/
  215.     0xFF,       /*'8', 28*/
  216.     0xEF,       /*'9', 29*/
  217.        
  218. };
  219. // 位码 0000 0001, 0000 0010, 0000 0100 ... 为方便赋值,使用了相反的二进制,使用时需要取反
  220. unsigned char posNum[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};       
  221. void Init595(void)
  222. {
  223.         HC595_SER = 0;
  224.         HC595_RCK = 0;
  225.         HC595_SCK = 0;
  226. }
  227. void Send595(unsigned char dat)
  228. {
  229.         unsigned char i;
  230.         for(i=0; i < 8; i++)
  231.         {
  232.                 dat <<= 1;                // 左边1位移入 CY
  233.                 HC595_SER = CY;
  234.                 HC595_SCK = 1;
  235.                 HC595_SCK = 0;
  236.         }
  237. }
  238. void DisplaySeg(unsigned char segCode, unsigned char posCode)
  239. {
  240.         Send595(segCode);
  241.         Send595(posCode);
  242.         HC595_RCK = 1;
  243.         HC595_RCK = 0;
  244. }
  245. // 挑战目标显示在数码管上
  246. void targetLights(void)
  247. {
  248.         DisplaySeg(segNum[1], ~posNum[0]);
  249.         Delay(1);
  250.         DisplaySeg(segNum[20], ~posNum[1]);
  251.         Delay(1);
  252.         DisplaySeg(segNum[0], ~posNum[2]);
  253.         Delay(1);
  254.         DisplaySeg(segNum[0], ~posNum[3]);
  255.         Delay(1);
  256. }
  257. // 10秒挑战 程序逻辑
  258. // 后面 4 位,默认静态显示 00.00
  259. // 按下 P32 按钮后,开始显示计时器,前面2位显示单位秒,后面两位分别显示百毫秒、十毫秒
  260. // 当计时器到达 99.99 秒后,计时器归零。
  261. // 在计时期间,按下 P32 按钮,可暂停计时,时间以1HZ的频率,闪烁显示按下时刻的值。
  262. // 当计时暂停时,按下 P32 按钮,计时器归零,并且停止闪烁。
  263. // 当再次按下 P32 按钮时,重新开始计时。
  264. void DisplayDigitLights(void)
  265. {
  266.         unsigned char digit_1 = 0;
  267.         unsigned char digit_2 = 0;
  268.         unsigned char digit_3 = 0;
  269.         unsigned char digit_4 = 0;       
  270.         digit_1 = digitLights.tms / 1000;
  271.         digit_2 = (digitLights.tms % 1000) / 100;
  272.         digit_3 = (digitLights.tms % 100) / 10;
  273.         digit_4 = digitLights.tms % 10;
  274.         DisplaySeg(segNum[digit_1], ~posNum[4]);
  275.         Delay(1);
  276.         DisplaySeg(segNum[digit_2 + 20], ~posNum[5]);
  277.         Delay(1);
  278.         DisplaySeg(segNum[digit_3], ~posNum[6]);
  279.         Delay(1);
  280.         DisplaySeg(segNum[digit_4], ~posNum[7]);
  281.         Delay(1);
  282. }
  283. // 刷新灯管计时器
  284. void freshDigitLight(void)
  285. {
  286.         switch((&digitLights)->state)
  287.         {
  288.                 case DS_STOP:                                // 灯管处于准备状态
  289.                         if( (&digitLights)->tms > 0 )
  290.                         {
  291.                                 (&digitLights)->tms = 0;
  292.                         }
  293.                         break;
  294.                 case DS_RUN:                                // 灯管处于运行状态, 计时器自增
  295.                         if( (&digitLights)->tms < 9999 )
  296.                         {
  297.                                 (&digitLights)->tms++;
  298.                         }
  299.                         else
  300.                         {
  301.                                 (&digitLights)->tms = 0;
  302.                         }
  303.                         break;
  304.                 case DS_CLEAN_STOP:                        // 灯管处于展示结果状态
  305.                         break;
  306.         }
  307. }
  308. unsigned char idx = 0;
  309. unsigned char num[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
  310. void PLED_40(void)
  311. {
  312.        
  313.         BYTE cod[8];
  314.         cod[0] = 0x0f;
  315.         cod[1] = 0x00;
  316.         cod[2] = 0x00;
  317.         cod[3] = ~num[idx];
  318.         cod[4] = 0xfe;
  319.         LED40_SendData(cod, 5);
  320.        
  321.         P2 = ~num[idx];
  322.        
  323.         idx++;
  324.         if(idx > 7)
  325.                 idx = 0;
  326.        
  327. }
  328. /* ----------------------------- END nixie module END ----------------------------- */
  329. /* ----------------------------- BEGIN  task module BEGIN ----------------------------- */
  330. // 任务对象
  331. typedef struct
  332. {
  333.         unsigned char run_state;                        //         执行状态
  334.         int timer;                        //  倒计时
  335.         int cycle_length;                        //  执行周期
  336.         void (*TaskHook)(void);                // 任务回调函数       
  337. } TASK_COMPONENT;
  338. void timer_callback(void);
  339. void task_callback(void);
  340. // 静态任务数组
  341. // 编译前已确定数组长度
  342. static TASK_COMPONENT tasks[] =
  343. {
  344.         {0, 10, 10, freshDigitLight },
  345.         {0, 500, 500, PLED_40 },
  346. };
  347. // 任务数组长度
  348. unsigned int tasks_length = sizeof(tasks) / sizeof(tasks[0]);
  349. // 定时器回调函数
  350. // 定时触发 run_state
  351. void timer_callback(void)
  352. {
  353.         unsigned int i = 0;
  354.         for(i = 0; i < tasks_length; i++)
  355.         {
  356.                
  357.                 if(tasks[i].timer == 0 && tasks[i].run_state == 0){
  358.                         tasks[i].timer = tasks[i].cycle_length;
  359.                 }
  360.                
  361.                 if(tasks[i].timer > 0)
  362.                 {
  363.                         tasks[i].timer--;
  364.                         if(tasks[i].timer == 0)
  365.                         {
  366.                                 tasks[i].timer = tasks[i].cycle_length;
  367.                                 tasks[i].run_state = 1;
  368.                         }
  369.                 }
  370.         }
  371. }
  372. // 任务处理回调函数
  373. // 定时执行任务
  374. void task_callback(void)
  375. {
  376.        
  377.         unsigned int i = 0;
  378.         for(i = 0; i < tasks_length; i++)
  379.         {
  380.                 if(tasks[i].run_state == 1)
  381.                 {
  382.                         tasks[i].run_state = 0;
  383.                         tasks[i].TaskHook();
  384.                 }
  385.         }
  386.        
  387. }
  388. /* ----------------------------- END task module END ----------------------------- */
  389. // 按钮
  390. KeyStruct key33 = {1, KEY_STATE_IDLE, 0, 0, 0};
  391. void main(void)
  392. {
  393.         sys_init();
  394.         Timer0_Init();
  395.        
  396.         Init595();
  397.        
  398.         while(1)
  399.         {
  400.                
  401. //                PLED_40();
  402. //                Delay(500);
  403.                
  404.                 task_callback();
  405.                
  406.                 // 如果按钮被按下
  407. //                if(Key_GetEvent(&key33))
  408. //                {
  409. //                        switch ( (&digitLights)->state )
  410. //                        {
  411. //                                case DS_STOP:
  412. //                                        (&digitLights)->state = DS_RUN;                                // 开始计时
  413. //                                        break;
  414. //                                case DS_RUN:
  415. //                                        (&digitLights)->state = DS_CLEAN_STOP;                // 停止计时
  416. //                                        break;
  417. //                                case DS_CLEAN_STOP:
  418. //                                        (&digitLights)->state = DS_STOP;                                // 清理计时结果
  419. //                                        break;
  420. //                        }
  421. //                }
  422.                
  423.                 // 显示挑战目标 10.00 秒
  424.                 // targetLights();
  425.                
  426.                 // 显示挑战结果,挑战前、挑战中、结果
  427.                 // DisplayDigitLights();
  428.                
  429.                 if(bUsbOutReady)
  430.                 {
  431.                         //USB_SendData(UsbOutBuffer,OutNumber);   //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
  432.                        
  433.                         usb_OUT_done();
  434.                 }
  435.         }
  436. }
  437. // 定时器中断回调函数
  438. void Timer0_Isr(void) interrupt 1
  439. {
  440.         timer_callback();
  441.         //KeyScan(&key33, P33);
  442. }
复制代码



回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2026-02-06 08:59:52
已绑定手机

1

主题

18

回帖

143

积分

注册会员

积分
143
发表于 2025-12-27 10:42:18 | 显示全部楼层
第十课 仿真接口


本课视频详细介绍了仿真接口(部分内容与上一节课后半部分重复),包括 LED , 数码管, 虚拟键盘。

1. 仿真接口的调用, 由于有头文件函数的加持, 调用难度反而比集成在板子上的接口更简单。
2. 仿真接口传参的设计,本着最省资源的原则,有的甚至一个字节传递多个不同目的的参数。
3. 使用仿真接口,可以加深二进制在单片机程序中灵活应用的理解。

课后小练: 模拟密码面板

1. 原本想通过显示字符串函数(SEG5_ShowString)实现,实际应用是发现使用这个接口其实更加复杂。设计字符串构建、修改、比较、转换,这些操作在Java中是很简单的,在C中相对复杂。
2. 后来直接用显示段码函数(SEG5_ShowCode)实现,逻辑上更加简单,知识盲区也相对较少。
3. 发现变量定义不能放在 if 语句里面(可以放在函数中)。

下面是课后小练实验视频

下面是课程截图和课后小练源码包(因为更新了库函数,打包下载更容易重现)



ASCII码表.png
P2 流水灯 P10 口闪烁.png
参与与端口对应关系.png
调用外部变量.png
仿真调试接口.png
更新 stc32_stc8_usb 头文件.png
更新库文件.png
更新头文件.png
接收虚拟键盘发送的ASCII码.png
课后小练.png
每次只控制一组LED接口.png
擎天柱 - 对应关系(部分LED没有焊接).png
数码管接口 1 -2.png
显示浮点数 和段码 接口.png
指定管脚输出高、低电平.png

第10课 仿真接口.zip

136.45 KB, 下载次数: 0

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2026-02-06 08:59:52
已绑定手机

1

主题

18

回帖

143

积分

注册会员

积分
143
发表于 2026-1-1 17:47:29 | 显示全部楼层
第11课: 矩阵按钮

1.

矩阵按钮使了 6 个针脚来接收 8 个按钮的按键信号(2✖️4),比一个按钮一个针脚,节省了 8 - 6 = 2 个针脚。
这在少量按钮判断上, 似乎差别不大。 在大量按钮时, 才有明显的差别。比如 5 ✖️ 10 矩阵,单独针脚的话,序号 50 个针脚。使用矩阵按钮的方式,只需要 5➕10 = 15 个针脚。
不过按照教程视频的说法, 矩阵按钮存在多按钮同时按下会冲突的情况。 这让我联想到了机械键盘。机械键盘号称104键无冲,这在 PS/2 接口有限的针脚是怎么做到的呢?很神奇。

2.

视频教程在处理矩阵键盘行、列与针脚对应关系时,建议使用类似 #define ROW_01 P00 这样的方法,给针脚定义一个别名。这样做的好处是编程时好记,而且程序迁移到其它板兼容性更好。
看视频时我就想,使用数组来索引这些针脚不是更好吗,用循环更方便处理。经过实践,才知道,单片机的针脚定义,通常都是位变量,而位变量,是不能使用指针引用的。

3.

下面是对视频中的显示按钮序号示例,进行了重写。数码管显示的方式,只需要传递 btn_idx 给显示函数就可以了。
  1. #define ROW_01 P06
  2. #define ROW_02 P07
  3. #define COL_01 P00
  4. #define COL_02 P01
  5. #define COL_03 P02
  6. #define COL_04 P03
  7. // 被按下按钮序号
  8. char btn_idx = 0;
  9. char current_row = 0;
  10. // 矩阵按钮
  11. // 循环判断此函数,可判断矩阵按钮按下
  12. void MatrixButtons(void)
  13. {
  14.         // 列置 0 , 行置 1
  15.         //*ROW[0] = 1; *ROW[1] = 1;
  16.         //*COL[0] = 0; *COL[1] = 0; *COL[2] = 0; *COL[3] = 0;
  17.         ROW_01 = 1;
  18.         ROW_02 = 1;
  19.         COL_01 = 0;
  20.         COL_02 = 0;
  21.         COL_03 = 0;
  22.         COL_04 = 0;
  23.         
  24.         // 两行取值不同
  25.         if(ROW_01 != ROW_02)
  26.         {
  27.                 // 判断属于哪一行
  28.                 if(ROW_02 == 0)
  29.                 {
  30.                         current_row = 1;
  31.                 }
  32.                
  33. //                *ROW[0] = 0; *ROW[1] = 0;
  34. //                *COL[0] = 1; *COL[1] = 1; *COL[2] = 1; *COL[3] = 1;
  35.                 ROW_01 = 0;
  36.                 ROW_02 = 0;
  37.                 COL_01 = 1;
  38.                 COL_02 = 1;
  39.                 COL_03 = 1;
  40.                 COL_04 = 1;
  41.                
  42.                 // 判断列
  43.                 if(COL_01 == 0)
  44.                 {
  45.                         btn_idx = current_row*4 + 1;
  46.                 }
  47.                 else if(COL_02 == 0)
  48.                 {
  49.                         btn_idx = current_row*4 + 2;
  50.                 }
  51.                 else if(COL_03 == 0)
  52.                 {
  53.                         btn_idx = current_row*4 + 3;
  54.                 }
  55.                 else if(COL_04 == 0)
  56.                 {
  57.                         btn_idx = current_row*4 + 4;
  58.                 }
  59.                
  60.                 ROW_01 = 1;
  61.                 ROW_02 = 1;
  62.                 COL_01 = 0;
  63.                 COL_02 = 0;
  64.                 COL_03 = 0;
  65.                 COL_04 = 0;               
  66.         }
  67.         else
  68.         {
  69.                 current_row = 0;
  70.         }
  71. }
复制代码



4. 教学视频中的模拟密码箱功能,我也进行了重构,从重构中学习到一些知识点。

(1) Keil 软件,专门提供了一个针对单片机位操作的数据类型sbit,且提供了一个操作位的操作符^;下面是使用这种方式,定义矩阵键盘按钮对应物理位的声明:
  1. // 矩阵键盘引脚定义
  2. sbit ROW1 = P0^6;
  3. sbit ROW2 = P0^7;
  4. sbit COL1 = P0^0;
  5. sbit COL2 = P0^1;
  6. sbit COL3 = P0^2;
  7. sbit COL4 = P0^3;
复制代码
其中的 P0^6 等价于 P06,P0^7 等价于 P07。就是取P0的第N个二进制位的意思。
(2)实验箱的P0 I/O 口是准双向口,在频繁操作时,存在“读-改-写”问题。这个问题我是怎么遇到的呢?我使用逐列扫描法来发现矩阵按钮按下时,发现第4个按钮经常无法扫描到。
  1.     // 扫描第一列
  2.     COL1 = 0; COL2 = 1; COL3 = 1; COL4 = 1;
  3.     if(!(P0 & 0x40)) currentKey = 1;  // P0.6 (ROW1)
  4.     else if(!(P0 & 0x80)) currentKey = 5;  // P0.7 (ROW2)
复制代码
最后的解决办法是在进行逐列扫描前,先把P0端口的值取出到临时变量中。
  1. unsigned char ReadP0Pin(void)
  2. {
  3.     // 先读取P0端口的值到临时变量,避免读-改-写问题
  4.     unsigned char temp = P0;
  5.     return temp;
  6. }
复制代码
完整的扫描和消抖代码如下:
  1. // 优化的矩阵键盘扫描函数 - 修复读引脚问题
  2. unsigned char MatrixKeyScan()
  3. {
  4.     unsigned char currentKey = 0;
  5.     unsigned char i;
  6.     unsigned char portValue;
  7.    
  8.     // 扫描第一列[4](@ref)
  9.     COL1 = 0; COL2 = 1; COL3 = 1; COL4 = 1;
  10.    
  11.     portValue = ReadP0Pin();  // 正确读取引脚电平
  12.     if(!(portValue & 0x40)) currentKey = 1;  // P0.6 (ROW1)
  13.     else if(!(portValue & 0x80)) currentKey = 5;  // P0.7 (ROW2)
  14.    
  15.     // 扫描第二列
  16.     COL1 = 1; COL2 = 0; COL3 = 1; COL4 = 1;
  17.    
  18.     portValue = ReadP0Pin();
  19.     if(!(portValue & 0x40)) currentKey = 2;
  20.     else if(!(portValue & 0x80)) currentKey = 6;
  21.    
  22.     // 扫描第三列
  23.     COL1 = 1; COL2 = 1; COL3 = 0; COL4 = 1;
  24.    
  25.     portValue = ReadP0Pin();
  26.     if(!(portValue & 0x40)) currentKey = 3;
  27.     else if(!(portValue & 0x80)) currentKey = 7;
  28.    
  29.     // 扫描第四列
  30.     COL1 = 1; COL2 = 1; COL3 = 1; COL4 = 0;
  31.    
  32.     portValue = ReadP0Pin();
  33.     if(!(portValue & 0x40)) currentKey = 4;  // 按键4
  34.     else if(!(portValue & 0x80)) currentKey = 8;
  35.    
  36.     // 恢复列线状态
  37.     COL1 = 1; COL2 = 1; COL3 = 1; COL4 = 1;
  38.    
  39.     // 状态机消抖逻辑
  40.     for(i = 0; i < 8; i++)
  41.     {
  42.         if(currentKey == (i + 1))  // 当前扫描到按键i+1
  43.         {
  44.             if(keyState[i] == 0)   // 之前未按下
  45.             {
  46.                 keyDebounceCnt[i]++;
  47.                 if(keyDebounceCnt[i] >= DEBOUNCE_TIME)
  48.                 {
  49.                     // 消抖完成,确认按键按下
  50.                     keyState[i] = 1;
  51.                     if(lastStableKey != currentKey)  // 防止重复触发
  52.                     {
  53.                         keyPressFlag[i] = 1;
  54.                         lastStableKey = currentKey;
  55.                     }
  56.                     keyDebounceCnt[i] = 0;
  57.                 }
  58.             }
  59.             else
  60.             {
  61.                 // 保持按下状态,重置计数器
  62.                 keyDebounceCnt[i] = 0;
  63.             }
  64.         }
  65.         else  // 当前未扫描到按键i+1
  66.         {
  67.             if(keyState[i] == 1)   // 之前是按下的
  68.             {
  69.                 keyDebounceCnt[i]++;
  70.                 if(keyDebounceCnt[i] >= DEBOUNCE_TIME)
  71.                 {
  72.                     // 消抖完成,确认按键释放
  73.                     keyState[i] = 0;
  74.                     keyDebounceCnt[i] = 0;
  75.                     if(lastStableKey == (i + 1))
  76.                     {
  77.                         lastStableKey = 0;  // 清除上次稳定按键
  78.                     }
  79.                 }
  80.             }
  81.             else
  82.             {
  83.                 keyDebounceCnt[i] = 0;  // 保持释放状态
  84.                 keyPressFlag[i] = 0;    // 清除按下标志
  85.             }
  86.         }
  87.     }
  88.    
  89.     return 0;
  90. }
复制代码
经过测试,效果稳定输出。完整代码如下:
  1. #include "ai8051u.h"
  2. #include "AI_usb.h"
  3. #include "stdlib.h"
  4. char* USER_DEVICEDESC = NULL;
  5. char* USER_PRODUCTDESC = NULL;
  6. /* ----------------------------- config module ----------------------------- */
  7. void Timer0_Init(void);
  8. void sys_init(void);
  9. void Timer0_Init(void)
  10. {
  11.     TM0PS = 0x00;
  12.     AUXR &= 0x7F;
  13.     TMOD &= 0xF0;
  14.     TL0 = 0x30;
  15.     TH0 = 0xF8;
  16.     TF0 = 0;
  17.     TR0 = 1;
  18.     ET0 = 1;
  19. }
  20. void sys_init(void)
  21. {
  22.     WTST = 0;
  23.     EAXFR = 1;
  24.     CKCON = 0;
  25.     P0M1 = 0x00; P0M0 = 0x00;
  26.     P1M1 = 0x00; P1M0 = 0x00;
  27.     P2M1 = 0x00; P2M0 = 0x00;
  28.     P3M1 = 0x00; P3M0 = 0x00;
  29.     P4M1 = 0x00; P4M0 = 0x00;
  30.     P5M1 = 0x00; P5M0 = 0x00;
  31.     P6M1 = 0x00; P6M0 = 0x00;
  32.     P7M1 = 0x00; P7M0 = 0x00;
  33.     usb_init();
  34.     IE2 |= 0x80;
  35.     EA = 1;
  36.     while(DeviceState != DEVSTATE_CONFIGURED);
  37. }
  38. /* -----------------------------END config module END----------------------------- */
  39. // 定义 595 引脚
  40. #define HC595_SER P34
  41. #define HC595_RCK P35
  42. #define HC595_SCK P32
  43. void Init595(void)
  44. {
  45.     HC595_SER = 0;
  46.     HC595_RCK = 0;
  47.     HC595_SCK = 0;
  48. }
  49. // 矩阵键盘引脚定义
  50. sbit ROW1 = P0^6;
  51. sbit ROW2 = P0^7;
  52. sbit COL1 = P0^0;
  53. sbit COL2 = P0^1;
  54. sbit COL3 = P0^2;
  55. sbit COL4 = P0^3;
  56. // 包含英文字母的段码
  57. unsigned char segNum_all[] = {
  58.     0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,  // 0-9
  59.     0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x3D, 0x76, 0x10, 0x0E,  // A-J
  60.     0x7A, 0x38, 0x55, 0x54, 0x5C, 0x73, 0x67, 0x50, 0x64, 0x78,  // K-T
  61.     0x3E, 0x62, 0x6A, 0x36, 0x6E, 0x49, 0x40, 0x00, 0x80          // U-.
  62. };
  63. // 密码定义 (12345678)
  64. unsigned char password[] = {1, 2, 3, 4, 5, 6, 7, 8};
  65. #define PASSWORD_LENGTH 8
  66. // 全局变量
  67. unsigned char inputBuffer[8] = {0};
  68. unsigned char inputCount = 0;
  69. bit passwordCorrect = 0;
  70. unsigned char displayBuffer[8] = {36, 36, 36, 36, 36, 36, 36, 36};
  71. unsigned char currentDigit = 0;
  72. // 按键检测相关变量
  73. unsigned char keyState[8] = {0};        // 每个按键的状态:0=释放,1=按下
  74. unsigned char keyPressFlag[8] = {0};    // 按键按下标志:1表示有新的按键事件
  75. unsigned int keyDebounceCnt[8] = {0};   // 消抖计数器
  76. unsigned char lastStableKey = 0;        // 上次稳定的按键值
  77. #define DEBOUNCE_TIME 20                // 消抖时间20ms
  78. // 595发送函数
  79. void Send595(unsigned char dat)
  80. {
  81.     unsigned char i;
  82.     for(i = 0; i < 8; i++)
  83.     {
  84.         dat <<= 1;
  85.         HC595_SER = CY;
  86.         HC595_SCK = 1;
  87.         HC595_SCK = 0;
  88.     }
  89. }
  90. // 显示函数
  91. void DisplaySeg(unsigned char segCode, unsigned char posCode)
  92. {
  93.     Send595(segCode);
  94.     Send595(posCode);
  95.     HC595_RCK = 1;
  96.     HC595_RCK = 0;
  97. }
  98. // 关键修复:正确的引脚读取函数
  99. unsigned char ReadP0Pin(void)
  100. {
  101.     // 先读取P0端口的值到临时变量,避免读-改-写问题
  102.     unsigned char temp = P0;
  103.     return temp;
  104. }
  105. // 优化的矩阵键盘扫描函数 - 修复读引脚问题
  106. unsigned char MatrixKeyScan()
  107. {
  108.     unsigned char currentKey = 0;
  109.     unsigned char i;
  110.     unsigned char portValue;
  111.    
  112.     // 扫描第一列[4](@ref)
  113.     COL1 = 0; COL2 = 1; COL3 = 1; COL4 = 1;
  114.     // 添加短暂延时确保电平稳定
  115.     // _nop_(); _nop_(); _nop_(); _nop_();
  116.    
  117.     portValue = ReadP0Pin();  // 正确读取引脚电平
  118.     if(!(portValue & 0x40)) currentKey = 1;  // P0.6 (ROW1)
  119.     else if(!(portValue & 0x80)) currentKey = 5;  // P0.7 (ROW2)
  120.    
  121.     // 扫描第二列
  122.     COL1 = 1; COL2 = 0; COL3 = 1; COL4 = 1;
  123.     // _nop_(); _nop_(); _nop_(); _nop_();
  124.    
  125.     portValue = ReadP0Pin();
  126.     if(!(portValue & 0x40)) currentKey = 2;
  127.     else if(!(portValue & 0x80)) currentKey = 6;
  128.    
  129.     // 扫描第三列
  130.     COL1 = 1; COL2 = 1; COL3 = 0; COL4 = 1;
  131.     // _nop_(); _nop_(); _nop_(); _nop_();
  132.    
  133.     portValue = ReadP0Pin();
  134.     if(!(portValue & 0x40)) currentKey = 3;
  135.     else if(!(portValue & 0x80)) currentKey = 7;
  136.    
  137.     // 扫描第四列 - 重点修复按键4的问题
  138.     COL1 = 1; COL2 = 1; COL3 = 1; COL4 = 0;
  139.     // _nop_(); _nop_(); _nop_(); _nop_();
  140.    
  141.     portValue = ReadP0Pin();
  142.     if(!(portValue & 0x40)) currentKey = 4;  // 按键4
  143.     else if(!(portValue & 0x80)) currentKey = 8;
  144.    
  145.     // 恢复列线状态
  146.     COL1 = 1; COL2 = 1; COL3 = 1; COL4 = 1;
  147.    
  148.     // 改进的状态机消抖逻辑[6,8](@ref)
  149.     for(i = 0; i < 8; i++)
  150.     {
  151.         if(currentKey == (i + 1))  // 当前扫描到按键i+1
  152.         {
  153.             if(keyState[i] == 0)   // 之前未按下
  154.             {
  155.                 keyDebounceCnt[i]++;
  156.                 if(keyDebounceCnt[i] >= DEBOUNCE_TIME)
  157.                 {
  158.                     // 消抖完成,确认按键按下
  159.                     keyState[i] = 1;
  160.                     if(lastStableKey != currentKey)  // 防止重复触发
  161.                     {
  162.                         keyPressFlag[i] = 1;
  163.                         lastStableKey = currentKey;
  164.                     }
  165.                     keyDebounceCnt[i] = 0;
  166.                 }
  167.             }
  168.             else
  169.             {
  170.                 // 保持按下状态,重置计数器
  171.                 keyDebounceCnt[i] = 0;
  172.             }
  173.         }
  174.         else  // 当前未扫描到按键i+1
  175.         {
  176.             if(keyState[i] == 1)   // 之前是按下的
  177.             {
  178.                 keyDebounceCnt[i]++;
  179.                 if(keyDebounceCnt[i] >= DEBOUNCE_TIME)
  180.                 {
  181.                     // 消抖完成,确认按键释放
  182.                     keyState[i] = 0;
  183.                     keyDebounceCnt[i] = 0;
  184.                     if(lastStableKey == (i + 1))
  185.                     {
  186.                         lastStableKey = 0;  // 清除上次稳定按键
  187.                     }
  188.                 }
  189.             }
  190.             else
  191.             {
  192.                 keyDebounceCnt[i] = 0;  // 保持释放状态
  193.                 keyPressFlag[i] = 0;    // 清除按下标志
  194.             }
  195.         }
  196.     }
  197.    
  198.     return 0;
  199. }
  200. // 获取按键按下事件(只触发一次)
  201. unsigned char GetKeyPress()
  202. {
  203.     unsigned char i;
  204.     for(i = 0; i < 8; i++)
  205.     {
  206.         if(keyPressFlag[i])
  207.         {
  208.             keyPressFlag[i] = 0;  // 清除标志
  209.             return i + 1;         // 返回键值(1-8)
  210.         }
  211.     }
  212.     return 0;
  213. }
  214. // 更新显示缓冲区
  215. void UpdateDisplayBuffer()
  216. {
  217.     unsigned char i;
  218.    
  219.     if(passwordCorrect)
  220.     {
  221.         displayBuffer[0] = 36; displayBuffer[1] = 36;
  222.         displayBuffer[2] = 24; displayBuffer[3] = 25;
  223.         displayBuffer[4] = 14; displayBuffer[5] = 23;
  224.         displayBuffer[6] = 36; displayBuffer[7] = 36;
  225.     }
  226.     else
  227.     {
  228.         for(i = 0; i < PASSWORD_LENGTH; i++)
  229.         {
  230.             displayBuffer[i] = (i < inputCount) ? inputBuffer[i] : 36;
  231.         }
  232.     }
  233. }
  234. // 检查密码是否正确
  235. bit CheckPassword()
  236. {
  237.     unsigned char i;
  238.     if(inputCount != PASSWORD_LENGTH) return 0;
  239.    
  240.     for(i = 0; i < PASSWORD_LENGTH; i++)
  241.     {
  242.         if(inputBuffer[i] != password[i]) return 0;
  243.     }
  244.     return 1;
  245. }
  246. // 清空输入
  247. void ClearInput()
  248. {
  249.     unsigned char i;
  250.     inputCount = 0;
  251.     for(i = 0; i < PASSWORD_LENGTH; i++)
  252.     {
  253.         inputBuffer[i] = 0;
  254.     }
  255. }
  256. // 处理键盘输入
  257. void ProcessKeyInput(unsigned char keyNum)
  258. {
  259.     if(passwordCorrect)
  260.     {
  261.         passwordCorrect = 0;
  262.         ClearInput();
  263.         return;
  264.     }
  265.    
  266.     if(keyNum >= 1 && keyNum <= 9)
  267.     {
  268.         if(inputCount < PASSWORD_LENGTH)
  269.         {
  270.             inputBuffer[inputCount] = keyNum;
  271.             inputCount++;
  272.             
  273.             if(inputCount == PASSWORD_LENGTH)
  274.             {
  275.                 passwordCorrect = CheckPassword();
  276.                 if(!passwordCorrect) ClearInput();
  277.             }
  278.         }
  279.     }
  280. }
  281. void main(void)
  282. {
  283.     unsigned char keyNum = 0;
  284.    
  285.     sys_init();
  286.     Timer0_Init();
  287.     Init595();
  288.     ClearInput();
  289.    
  290.     while(1)
  291.     {
  292.         // 扫描键盘但不阻塞
  293.         MatrixKeyScan();
  294.         
  295.         // 获取按键事件
  296.         keyNum = GetKeyPress();
  297.         if(keyNum != 0)
  298.         {
  299.             ProcessKeyInput(keyNum);
  300.             UpdateDisplayBuffer();
  301.         }
  302.         
  303.         if(bUsbOutReady)
  304.         {
  305.             usb_OUT_done();
  306.         }
  307.     }
  308. }
  309. // 定时器中断服务函数 - 数码管动态扫描
  310. // 中断函数应尽量控制运算量,只做瞬间完成的动作
  311. void Timer0_Isr(void) interrupt 1
  312. {
  313.     TL0 = 0x30;
  314.     TH0 = 0xF8;
  315.    
  316.     switch(currentDigit)
  317.     {
  318.         case 0: DisplaySeg(segNum_all[displayBuffer[0]], ~0x01); break;
  319.         case 1: DisplaySeg(segNum_all[displayBuffer[1]], ~0x02); break;
  320.         case 2: DisplaySeg(segNum_all[displayBuffer[2]], ~0x04); break;
  321.         case 3: DisplaySeg(segNum_all[displayBuffer[3]], ~0x08); break;
  322.         case 4: DisplaySeg(segNum_all[displayBuffer[4]], ~0x10); break;
  323.         case 5: DisplaySeg(segNum_all[displayBuffer[5]], ~0x20); break;
  324.         case 6: DisplaySeg(segNum_all[displayBuffer[6]], ~0x40); break;
  325.         case 7: DisplaySeg(segNum_all[displayBuffer[7]], ~0x80); break;
  326.     }
  327.    
  328.     currentDigit++;
  329.     if(currentDigit >= 8) currentDigit = 0;
  330. }
复制代码

验证视频




回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2026-02-06 08:59:52
已绑定手机

1

主题

18

回帖

143

积分

注册会员

积分
143
发表于 2026-1-7 00:10:39 | 显示全部楼层
第 11 课 :矩阵按钮 - 课后小练

1. 模拟洗衣机面板。由于数码管实际有8位,因此我稍微做了一些扩充,前 2 位显示洗衣模式; 中间 2 位显示洗衣分钟数; 最后 2 位显示洗衣秒数。
2. 矩阵数组按键 1 - 5 , 分别表示 5 中洗衣模式,为了与显示位数匹配,模式耗时设定,最高不多余 99分钟99秒。
3. 按键 6 空置。
4. 按键 7 作为启动按钮。按下启动按钮,开始洗衣服,倒计时开始运作。洗衣服开始后,无法修改洗衣模式、启动按钮锁定无效。
5. 按键 8 作为电源按钮。关机状态下:按下电源按钮,打开洗衣机电源,洗衣模式默认为 模式 1 ;洗衣机处于待机状态;开机状态下,按下电源按钮,关闭洗衣机,数码管熄灭。
6. 注意变量的类型,实际使用中,如果变量值超出类型可容纳的数据范围,将引发BUG(例如本程序的 timeCounter ,一开始设置为 unsigned char ,就无法计算 1000 毫秒 = 1秒的情形。)

完整代码(论坛发代码有点问题,改发压缩包吧):
源码包
验证视频

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2026-02-06 08:59:52
已绑定手机

1

主题

18

回帖

143

积分

注册会员

积分
143
发表于 2026-1-10 10:45:18 | 显示全部楼层

第 12 课 复位系统

一、 寄存器英文名称

在学习课程的时候,我们注意到寄存器名称、端口名称使用了一些奇怪的英文组合。这些英文组合是怎么来的呢?为什么看门狗控制寄存器的名称不是 KMGKZ_JCQ 而是 WDT_CONTR 呢?

其实这是因为 AI8051U 是继承1 8051 内核的单片机,继承了标准8051架构的寄存器体系,同时增加了扩展功能寄存器,在寄存器和端口命名上,沿用了8051架构的命名方式。而8051架构源于英语国家,因此这些名称,其实是英文单词的缩写,遵循"功能描述+Register"的命名规则,便于理解和记忆。

下面是一些主要名称对应英文单词和中文名称。

对于本课介绍的看门狗

看门狗寄存器 WDT_CONTR : Watch Dog Timmer ConTrol Register

“使能”: EN : ENable

WDT_FLAG : 看门狗溢出标志

EN_WDT : 看门狗使能位 ENable Watch Dot Timmer

CLR_WDT : 看门狗定时器清零 CLeaR Watch Dog Timmer

IDL_WDT : IDEL 模式时的看门狗控制位

WDT_PS[2:0] 看门狗定时器时钟分频系数 , Watch Dog Timmer PreScaler , 后面的 [2:0] 是借鉴数学中集合的概念,用于标识寄存器中连续的位范围,即从第2位到第0位。 这在手册中的表可以看到对应的是 B2 、B1、B0 三个位

二、看门狗复位

WDT_CONTR 是一个8位寄存器,从手册中可以看到,最后3位用于标识时钟分频系数了,前面 5 位,各自有其含义。在实际使用中,可以直接对整个寄存器赋值,例如视频中讲的程序:

// 启动看门狗,时间频率系数是 100
WDT_CONTR = 0x24;

// 对应的二进制是 0010 0100

// 等价于下面两个语句:

EN_WDT = 1;

WDT_PS = 100;

// 喂狗(启动+清空计时器,频率系数是100)
WDT_CONTR = 0x34;

// 对应二进制是 0011 0100

// 等价于下面语句

EN_WDT = 1;

CLR_WDT = 1;

WDT_PS = 100;

至于有没有对位设置单独的变量,这得查头文件。

三、看门狗复位例子

由于我们使用到了USB,要先进行USB复位。

USB复位操作:

// P3M0 、P3M1 寄存器共同控制 P3 引脚的工作模式
// 先把P3所有引脚设置为准双向口
P3M0 = 0x00;
P3M1 = 0x00;
// 再单独设置推挽输出引脚、高阻输入引脚
P3M0 &= ~0x03;
// 对应二进制 : 1111 1100
// 由于上一步 P3M0 已经全部置 0 , 
// 实际上这个操作强制将最低两位(P3M0.1 和 P3M0.0)清零,而其他所有位保持原状不变
// 其实这一步也可以省略
P3M1 |= 0x03;
// 对应二进制 : 0000 0011
// 实际上这个操作强制将最低两位(P3M0.1 和 P3M0.0)置1,而其他所有位保持原状不变
// 因此操作结果是最后两位设置为 1 - 0 的高阻模式
// 其中, 引脚 7 ~ 2 为推挽输出; 引脚 1 ~ 0 为 高阻输出。

至于后面的USB寄存器等的操作查一下手册就很好理解了。

USBCON = 0x00;  // USB控制寄存器清零 USB CONtroller
USBCLK = 0x00;  // USB时钟控制寄存器清零 USB CLocK
IRC48MCR = 0x00;  // 内部48MHz高速IRC控制寄存器 Internal 48MHz Resistor-Capacitor Oscillator Control Register

最后延迟 10 毫秒以便电气运作正常无需多加解释。

教学视频中的例子: 给上一节课的案例加上看门狗程序。

在重现教学中的例子时,意外发现了一个异常情况。加入看门狗的“密码箱”模拟器在下载时出现意外,程序无法正常下载。原因是我在 usb_init() 函数执行后,调用了 ResetUSB 函数,相当于把稳定的USB通道又给重置了。

看门狗复位、下载程序提示信息.png

经历这个教训,让我更加坚定在学习过程中,必须弄清楚每一句不起眼的代码。

完成后的代码包如下:

upload 附件:第11课 矩阵按钮-密码箱(有消抖、P33看门狗测试).zip

验证视频如下:

课后小练:

  1. 密码箱加上版本号U1.00显示(开机显示3秒,V1.00的V太小气,用U代替),
  2. 增加手动复位,以便查看版本号,
  3. 增加看门狗逻辑,
  4. 版本号显示期间,不响应矩阵键盘

源码:upload 附件:第11课 矩阵按钮-密码箱(有消抖、看门狗、版本号).zip

验证视频:

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2026-02-06 08:59:52
已绑定手机

1

主题

18

回帖

143

积分

注册会员

积分
143
发表于 2026-1-11 15:48:05 | 显示全部楼层

番外: 让 AICUBE-ISP 跨项目记住你的配置,不需要每个项目都检查下载选项

方法很简单,只需要打开ISP软件的文件菜单,启用两个选项即可:

选项1 : 启动时加载最新配置

选项2 : 关闭时检测配置是否被修改

选项3 : 关闭时自动保存配置

如此,就可以高枕无忧了,不需要每次打开ISP软件,都检查一遍“硬件配置”、“收到用户命令后复位到ISP监控程序区”的选项;如下图:

屏幕截图2026-01-11154128.png

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2026-02-06 08:59:52
已绑定手机

1

主题

18

回帖

143

积分

注册会员

积分
143
发表于 2026-1-12 15:46:53 | 显示全部楼层

第 13 课:外部中断

  1. 定时器也使用到了中断的机制。

  2. 针脚是否支持中断,可通过手册查询获得。具体见下图。

  3. 外部中断有两种类型的电平变化可引发中断:上升沿、下降沿。 上升沿概念,在前面数码管课程中有稍微提及过。其实很简单,上升沿就是电压升高,下降沿就是电压降低。

  4. INT0/INT1 同时支持上升沿、下降沿的中断检测。 INT2、INT3只支持下降沿的中断检测。

  5. 在示范程序中,中断初始化功能,使用到了 3 个配置参数: IT1 属于(1)定时器控制寄存器 TCON; EX1 、 EA 来自 (2)中断使能寄存器 IE,其实也可以这样写代码(EA 、EX1 同属 IE 寄存器,可简写成 1 行):

    // IT1 = 1;
    // 0000 0100
    TCON |= 0x04;
    // EX1 = 1;
    // EA = 1;
    // 1000 0100
    IE |= 0x84;
    
  6. 使用了中断后,只需要声明中断序号,只要中断发生,中断函数就会被调用。看第一遍的时候,还在纳闷,为啥按下P33后LED会一闪一闪的呢。原因就在于此:P33按下后,变为低电平,产生下降沿,于是触发了中断。

  7. 在写代码时,视频教程中使用了中断允许位 EX1 = 1 ,因此中断函数对应的中断号是 2 。这个中断号可以在手册中查看得到。

中断允许位与中断号.png

中断号与中断允许位表

管脚图可查询中断源.png

手册中的图,右上角方便的提供了外部中断源管脚

定时器控制寄存器TCON.png

定时器控制寄存器,这在ISP软件中,生成定时器初始化函数时,也会用到(TF0 = 0; TR0 = 1; )

中断使能寄存器.png

中断使能寄存器(在配置USB是也会用到 EA = 1)

教程视频中的范例程序

注意要打开LED模组总开关 P40 = 0;

upload 附件:第12课 外部中断 P32点亮LED-1.zip

课后小练

  1. 用到了矩阵键盘,按钮 1 按下,点亮 LED-0
  2. P33 模拟外部中断,检测到P33按下时,熄灭 LED-0
  3. 这里存在一个冲突, P00 - P03 , P06 - P07 既是LED灯的控制位,也是矩阵键盘ROW和COL的控制位,进行矩阵键盘检测时,必然与LED的亮灭起冲突。
回复

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2026-2-12 18:02 , Processed in 0.140265 second(s), 96 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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