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

Jellyfish从89C51到AI8051学习贴

[复制链接]
  • 打卡等级:以坛为家I
  • 打卡总天数:366
  • 最近打卡:2026-03-31 16:31:44

844

主题

1万

回帖

2万

积分

管理员

积分
22789
发表于 2026-1-27 08:21:15 | 显示全部楼层
在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载。(已完成)
===可以直接申请个强大的 【免费+包邮送】的 【AI8051U 实验箱】 来做实验了,
       当然也可以 申请 STCAI-万能实验板-V2.3
       凭我这段认可,就可以去申请了
【免费 + 包邮 送】及 如何【扫码 小量购买】 单片机/烧录器/开发工具 - 其他技术交流 国芯人工智能技术交流网站 - AI32位8051交流社区
截图202601270818085059.jpg
用【STCAI-万能实验板】做实验,DIY 拿奖励,500元/人,前20名 ! - 做实验拿奖励@STCAI万能板,500元 国芯人工智能技术交流网站 - AI32位8051交流社区
截图202601270818426966.jpg
STCAI-万能实验板-V2.3,支持 封装形式/接口:
LQFP48, LQFP32,DFN8;
TSSOP28/24/20/16/14;
SOP28/24/20;
WSOP16/8;
SOP16/8;
SOT23-6/5/4/3;
DIP40/28/20/16/8;

贴片 电阻 / 电容 也可直接焊在插件的2个焊盘之间;
TF卡 插座,FPC接口
FPC焊接/插座支持间距:

0.5mm、0.62mm、0.65mm、0.7mm、0.8mm
回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:72
  • 最近打卡:2026-03-31 00:08:41

2

主题

16

回帖

260

积分

中级会员

积分
260
发表于 2026-1-29 02:16:40 | 显示全部楼层

Jellyfish从89C51到AI8051学习贴第十天!

今天继续学习《8051U深度入门到32位51大型实战教学视频》第九集 数码管

0x01 学习重点

  1. 通过74HC595芯片实现少端口控制多端口。
  2. 数码管显示数字。
  3. 高位溢出会存储到CY寄存器
  4. AiCube-ISP-v6.96P虚拟仿真显示

0x02 学习心得

  1. 74HC595使用

    • DS (SER): 串行数据输入。像排队一样,数据一位一位进场。
    • SHCP (SRCLK): 移位时钟。上升沿时,DS 上的数据进入移位寄存器,原有的数据向后移一位。
    • STCP (RCLK): 锁存时钟(关键)。上升沿时,将移位寄存器里的 8 位数据一次性搬运到外部引脚输出。
    • Q7' (Q7S): 级联输出。用于将数据传给下一个 595 的 DS 引脚。
    /**
     * @brief 向 595 系统发送一个字节
     * 左移操作会触发 RLC 指令,最高位溢出到 PSW 寄存器的 CY 位。
     */
    void HC595_SendByte(unsigned char dat) {
        unsigned char i;
        for (i = 0; i < 8; i++) {
            HC595_DS = dat >> 7; // 取最高位送到 DS或者直接移位后使用CY寄存器
            dat <<= 1;           // 左移,CY 标志位在底层被使用
            HC595_SHCP = 0;      // 产生移位脉冲
            HC595_SHCP = 1;
        }
    }
    
    /**
     * @brief 级联更新函数(控制 16 个端口)
     * @param data1 第一个 595 的输出 (Q0-Q7)
     * @param data2 第二个 595 的输出 (Q8-Q15)
     */
    void HC595_Update(unsigned char data1, unsigned char data2) {
        HC595_STCP = 0;
    
        HC595_SendByte(data2); // 先发送远端(第二片)数据
        HC595_SendByte(data1); // 后发送近端(第一片)数据
    
        HC595_STCP = 1;        // 锁存脉冲,16 位数据同时更新输出
    }
    
  2. 数码管8位bit与数字对应关系(通过AiCube-ISP-v6.96P字库生成工具一键生成)

    0x3F,       /*'0', 0*/
        0x06,       /*'1', 1*/
        0x5B,       /*'2', 2*/
        0x4F,       /*'3', 3*/
        0x66,       /*'4', 4*/
        0x6D,       /*'5', 5*/
        0x7D,       /*'6', 6*/
        0x07,       /*'7', 7*/
        0x7F,       /*'8', 8*/
        0x6F,       /*'9', 9*/
    
  3. 高位溢出会存储到CY寄存器

    在 8051/STC 架构的指令集中,左移操作通常是通过 补零移位带进位循环移位 指令实现的。

    最高位(MSB)溢出:当 8 位变量的最左边一位(第 7 位)被移出时,它并没有消失,而是被推入了 CPU 的 程序状态字寄存器 (PSW) 中的 CY (Carry) 位 [1]。

0x03 待学习重点

  1. 89C51的经验有多少可以迁移到STC32G上(已完成)

    目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。

  2. 如何将传统51单片机项目迁移到STC32G平台上(已完成)

    通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!

  3. 硬件USB控制器可以玩出什么花样?(持续学习 持续探索)

    • USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上MAX232打上RJ45就可以用来配置交换机了。
  4. 在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载。(已完成)

  5. 定时器一次只能定时一个此,如果有很多个任务怎么办?(已完成)

    初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。

    答案:想的差不多,方法如下

    • 通过数组(包括结构体数组),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。
  6. 如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?

    会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。

  7. 74HC595会不会跟不上单片机的速度而导致了数据的丢失?如果会,那么速度多快就会丢失数据?

回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:72
  • 最近打卡:2026-03-31 00:08:41

2

主题

16

回帖

260

积分

中级会员

积分
260
发表于 2026-1-29 22:24:45 | 显示全部楼层

Jellyfish从89C51到AI8051学习贴第十天!

今天继续学习《8051U深度入门到32位51大型实战教学视频》第九集 数码管

0x01 学习重点

  1. 通过74HC595芯片实现少端口控制多端口。
  2. 数码管显示数字。
  3. 高位溢出会存储到CY寄存器
  4. AiCube-ISP-v6.96P虚拟仿真显示

0x02 学习心得

  1. 74HC595使用

    • DS (SER): 串行数据输入。像排队一样,数据一位一位进场。
    • SHCP (SRCLK): 移位时钟。上升沿时,DS 上的数据进入移位寄存器,原有的数据向后移一位。
    • STCP (RCLK): 锁存时钟(关键)。上升沿时,将移位寄存器里的 8 位数据一次性搬运到外部引脚输出。
    • Q7' (Q7S): 级联输出。用于将数据传给下一个 595 的 DS 引脚。
    /**
     * @brief 向 595 系统发送一个字节
     * 左移操作会触发 RLC 指令,最高位溢出到 PSW 寄存器的 CY 位。
     */
    void HC595_SendByte(unsigned char dat) {
        unsigned char i;
        for (i = 0; i < 8; i++) {
            HC595_DS = dat >> 7; // 取最高位送到 DS或者直接移位后使用CY寄存器
            dat <<= 1;           // 左移,CY 标志位在底层被使用
            HC595_SHCP = 0;      // 产生移位脉冲
            HC595_SHCP = 1;
        }
    }
    
    /**
     * @brief 级联更新函数(控制 16 个端口)
     * @param data1 第一个 595 的输出 (Q0-Q7)
     * @param data2 第二个 595 的输出 (Q8-Q15)
     */
    void HC595_Update(unsigned char data1, unsigned char data2) {
        HC595_STCP = 0;
    
        HC595_SendByte(data2); // 先发送远端(第二片)数据
        HC595_SendByte(data1); // 后发送近端(第一片)数据
    
        HC595_STCP = 1;        // 锁存脉冲,16 位数据同时更新输出
    }
    
  2. 数码管8位bit与数字对应关系(通过****AiCube-ISP-v6.96P字库生成工具一键生成)

        0x3F,       /*'0', 0*/
        0x06,       /*'1', 1*/
        0x5B,       /*'2', 2*/
        0x4F,       /*'3', 3*/
        0x66,       /*'4', 4*/
        0x6D,       /*'5', 5*/
        0x7D,       /*'6', 6*/
        0x07,       /*'7', 7*/
        0x7F,       /*'8', 8*/
        0x6F,       /*'9', 9*/
    
  3. 高位溢出会存储到CY寄存器

    在 8051/STC 架构的指令集中,左移操作通常是通过 补零移位带进位循环移位 指令实现的。

    最高位(MSB)溢出:当 8 位变量的最左边一位(第 7 位)被移出时,它并没有消失,而是被推入了 CPU 的 程序状态字寄存器 (PSW) 中的 CY (Carry) 位 [1]。

0x03 待学习重点

  1. 89C51的经验有多少可以迁移到STC32G上(已完成)

    目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。

  2. 如何将传统51单片机项目迁移到STC32G平台上(已完成)

    通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!

  3. 硬件USB控制器可以玩出什么花样?(持续学习 持续探索)

    • USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上MAX232打上RJ45就可以用来配置交换机了。
  4. 在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载。(已完成)

  5. 定时器一次只能定时一个此,如果有很多个任务怎么办?(已完成)

    初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。

    答案:想的差不多,方法如下

    通过数组(包括结构体数组),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。

  6. 如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?

    会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。

  7. 74HC595会不会跟不上单片机的速度而导致了数据的丢失?如果会,那么速度多快就会丢失数据?

回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:72
  • 最近打卡:2026-03-31 00:08:41

2

主题

16

回帖

260

积分

中级会员

积分
260
发表于 2026-1-31 02:25:27 | 显示全部楼层

Jellyfish从89C51到AI8051学习贴第十一天!实验箱已到货!!!

今天交作业:第九集的数码管课后小练习。

image.png

实验箱效果展示

VID20260131015909.gif

IMG20260130222317.jpg

IMG20260130222347.jpg

0x01 学习重点

  1. 定时器中断中不可以放printf,会导致定时器失效。
  2. 使用定时器实现多个任务并行。
  3. 多任务并行调度,通过按键更改调度情况。
  4. 通过HC595实现数码管控制。

0x02 学习心得

  1. 使用定时器实现多任务并行。

    TASK_COMPONENTS Task_Comps[]=
    {
    //状态  计数  周期  函数
    
     {0, 300,   300,   LED0_Blink},      /* task 1 Period: 300ms */
    // {0, 600,   600,   LED1_Blink},      /* task 1 Period: 600ms */ 
    // {0, 900,   900,   LED2_Blink},      /* task 1 Period: 600ms */  
     {0, 10,    10,    KEY_Task},      /* task 1 Period: 600ms */  
     {0, 1,    1,    Seg_Task},      /* task 1 Period: 1000ms */ 
     {0, 0,    0,    Time_Task},
    };
    
    u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps[0]);
    
    //========================================================================
    // 函数: Task_Handler_Callback
    // 描述: 任务标记回调函数.
    // 参数: None.
    // 返回: None.
    // 版本: V1.0, 2012-10-22
    //========================================================================
    //在中断函数中执行这个函数代码,实现数量累加
    void Task_Marks_Handler_Callback(void)
    {
        u8 i;
        for(i=0; i<Tasks_Max; i++)
        {
            if(Task_Comps[i].TIMCount)      /* If the time is not 0 */
            {
                Task_Comps[i].TIMCount--;   /* Time counter decrement */
                if(Task_Comps[i].TIMCount == 0) /* If time arrives */
                {
                    /*Resume the timer value and try again */
                    Task_Comps[i].TIMCount = Task_Comps[i].TRITime;  
                    Task_Comps[i].Run = 1;      /* The task can be run */
                }
            }
        }
    }
    
    //========================================================================
    // 函数: Task_Pro_Handler_Callback
    // 描述: 任务处理回调函数.
    // 参数: None.
    // 返回: None.
    // 版本: V1.0, 2012-10-22
    //========================================================================
    //这个函数在main函数里面持续执行,当判断发现有任务时间到了就开始执行
    void Task_Pro_Handler_Callback(void)
    {
        u8 i;
        for(i=0; i<Tasks_Max; i++)
        {
            if(Task_Comps[i].Run) /* If task can be run */
            {
                Task_Comps[i].Run = 0;      /* Flag clear 0 */
                Task_Comps[i].TaskHook();   /* Run task */
            }
        }
    }
    
  2. 实现595芯片控制数码管

    void Init_595(void)
    {
         HC595_SER =  0;
         HC595_SCK =  0;
         HC595_RCK =  0;
    }
    
    void Send_595(u8 dat){
        u8 i;
        for(i=0; i<8; i++){
            dat <<= 1;
            HC595_SER = CY;
            HC595_SCK = 1;
            HC595_SCK = 0;
        }
    }
    
    void Display_Seg(u8 HC595_1,u8 HC595_2){
        Send_595(HC595_1);      //数码管段码
        Send_595(HC595_2);      //数码管位码
        HC595_RCK = 1;
        HC595_RCK = 0;
    }
    
    
    
    void Seg_Task(void){
        if (Seg_no==0){
            Display_Seg(SEG_NUM[1],~T_NUM[Seg_no]);
        }else if (Seg_no==1){
            Display_Seg(SEG_NUM[0]|SEG_NUM[18],~T_NUM[Seg_no]);
        }else if (Seg_no==2){
            Display_Seg(SEG_NUM[0],~T_NUM[Seg_no]);
        }else if (Seg_no==3){
            Display_Seg(SEG_NUM[0],~T_NUM[Seg_no]);
        }else if(Seg_no==4){
            Display_Seg(SEG_NUM[(int)((haomiao/10000)%10)],~T_NUM[Seg_no]);
        }else if (Seg_no==5){
            Display_Seg(SEG_NUM[(int)((haomiao/1000)%10)]|SEG_NUM[18],~T_NUM[Seg_no]);
        }else if (Seg_no==6){
            Display_Seg(SEG_NUM[(int)((haomiao/100)%10)],~T_NUM[Seg_no]);
        }else if (Seg_no==7){
            Display_Seg(SEG_NUM[(int)((haomiao/10)%10)],~T_NUM[Seg_no]);
        }
    
        Seg_no++;
        if (Seg_no>=8) Seg_no=0;
    }
    
    1. 实现计时功能
      重要!这里变量类型如果是 unsigned int会很快溢出,根本跑不上秒位,或者不用毫秒做单位。

      unsigned long haomiao =0;
      
      void Time_Task(){
          haomiao+=10;
      }
      
    2. 实现按键开始\停止计时
      定义结构体为extend变量,可以在io.c中修改!

      void KEY_Task(void)
      {
      	if( P33 == 0 )
      	{
      		Key_Vol++;
      		if( Key_Vol==5 )
      		{
      			//按键按下的任务
                  if(time_state==1){
                      Task_Comps[3].TRITime=0;
                      Task_Comps[3].TIMCount=0;
                      time_state=0;
                  }else{
                      Task_Comps[3].TRITime=10;
                      Task_Comps[3].TIMCount=10;
                      time_state=1;
                  }
      			//printf( "按键单击\r\n" );
      		}
      	}
      	else
      	{
      		Key_Vol = 0;
      	}
      
      }
      

    0x03 待学习重点

  3. 89C51的经验有多少可以迁移到STC32G上(已完成)

    目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。

  4. 如何将传统51单片机项目迁移到STC32G平台上(已完成)

    通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!

  5. 硬件USB控制器可以玩出什么花样?(持续学习 持续探索)

    • USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上MAX232打上RJ45就可以用来配置交换机了。
  6. 在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载。(已完成)

  7. 定时器一次只能定时一个此,如果有很多个任务怎么办?(已完成)

    初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。

    答案:想的差不多,方法如下

    • 通过数组(包括结构体数组),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。
  8. 如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?(已完成)

    会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。

  9. 74HC595会不会跟不上单片机的速度而导致了数据的丢失?如果会,那么速度多快就会丢失数据?(已完成,实测实验箱44.2368MHz以上数码管显示会出现问题。)

回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:72
  • 最近打卡:2026-03-31 00:08:41

2

主题

16

回帖

260

积分

中级会员

积分
260
发表于 2026-1-31 18:22:52 | 显示全部楼层

Jellyfish从89C51到AI8051学习贴第十二天!

今天继续学习《8051U深度入门到32位51大型实战教学视频》第十集 虚拟键盘LED和数码管

0x01 学习重点

  1. 使用AiCube-ISP-v6.96P进行 键盘 LED 数码管仿真

0x02 学习心得

  1. 密码锁课后小练习

image-20260131180036898.png

VID20260131181153.gif

        if (bUsbOutReady){
            u8 i;
            for(i=0;i<8;i++){
                if(DisplayStr[i]=='-'){
                    if(DisplayStr[7] == 'n'){
                        strcpy(DisplayStr, "--------");
                    }
                    DisplayStr[i]=UsbOutBuffer[5];
                    if(i==7){
                        // 3. 使用 strcmp 比较字符串内容
                        if(strcmp(DisplayStr, "12345678") == 0) {
                            strcpy(DisplayStr, "----open");
                        }else{
                            strcpy(DisplayStr, "--------");
                        }
                    }
                    break;
                }
            }
            usb_OUT_done();             //查询方式处理USB接收的数据
        }
//定时器定时调度这个
//SEG7_ShowString函数不能处理‘open’字符串,所以单独使用段码展示。
u8 open[]={
    0x40,       /*'-', 36*/
    0x40,       /*'-', 36*/
    0x40,       /*'-', 36*/
    0x40,       /*'-', 36*/
    0x5C,       /*'O', 24*/
    0x73,       /*'P', 25*/
    0x79,       /*'E', 14*/
    0x54,       /*'N', 23*/
};
char DisplayStr[] = "--------";
void Display_Task(){
    if(strcmp(DisplayStr, "----open") == 0) {
        SEG7_ShowCode(open);
    }else{
        SEG7_ShowString(DisplayStr);
    }
}

0x03 待学习重点

  1. 89C51的经验有多少可以迁移到STC32G上**(已完成)**

    目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。

  2. 如何将传统51单片机项目迁移到STC32G平台上**(已完成)**

    通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!

  3. 硬件USB控制器可以玩出什么花样?(持续学习 持续探索)

    • USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上MAX232打上RJ45就可以用来配置交换机了。
  4. 在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载。(已完成)

  5. 定时器一次只能定时一个此,如果有很多个任务怎么办?(已完成)

    初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。

    答案:想的差不多,方法如下:

    通过数组(包括结构体数组),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。

  6. 如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?(已完成)

    会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。

  7. 74HC595会不会跟不上单片机的速度而导致了数据的丢失?如果会,那么速度多快就会丢失数据?(已完成)

    实测实验箱44.2368MHz以上数码管显示会出现问题。)

回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:72
  • 最近打卡:2026-03-31 00:08:41

2

主题

16

回帖

260

积分

中级会员

积分
260
发表于 2026-2-3 01:43:52 | 显示全部楼层

Jellyfish从89C51到AI8051学习贴第十三天!

今天继续学习《8051U深度入门到32位51大型实战教学视频》第十一集 矩阵键盘、十二集 复位系统

0x01 学习重点

  1. 通过x+y个引脚实现x*y个引脚的实时按下状态检测。
  2. 了解4种复位模式(上电复位、低压复位、复位脚、看门狗复位),以及触发方式。

0x02 学习心得

  • 扫描矩阵键盘思路

    逐行扫描法(最常用)

    1. 初始化:将所有列线(Col)设为输入并上拉(高电平),行线(Row)设为输出。
    2. 扫描:依次将某一行拉低(其余行为高)。
    3. 判断:读取列线状态。如果有列线变低,说明该行与该列的交点按键被按下。
    4. 计算键值 = 行号 × 总列数 + 列号

    行列反转法(速度快)

    1. 第一步:行输出全 0,列输入,读取列线值(记录哪一列为低)。
    2. 第二步:列输出刚才读取到的值,行输入,读取行线值(记录哪一行为低)。
    3. 合并:将两次读取的电平状态组合,即可精确定位坐标。
  • 4种复位模式

    1. 上电复位 (POR - Power-On Reset)
    • 触发方式:当 VCC 电源从 0V 攀升至系统工作电压时,内部硬件检测到电压跨越阈值,自动触发。
    • 特点
      • 冷启动:这是最彻底的复位,会重置所有寄存器、I/O 状态及内部存储空间。
      • 标志位:通常在寄存器中表现为 POF (Power-On Flag)。
    • 典型场景:设备第一次插入电池或接通电源。
    1. 低压复位 (LVR - Low Voltage Reset / Brown-out)
    • 触发方式:系统运行过程中,电源电压跌落至设定的阈值(如 2.4V 或 3.3V)以下时触发。
    • 特点
      • 保护机制:防止电压过低导致 CPU 逻辑混乱、写 Flash 错误或程序跑飞。
      • 可调性:现代单片机通常允许在 ISP 下载软件 或软件寄存器中设置不同的掉电检测电压(LVD)。
    • 典型场景:电池电量耗尽瞬间、大电流负载(如电机启动)导致电压掉闸。
    1. 复位脚复位 (NRST - External Reset Pin)
    • 触发方式:外部复位引脚(RST/NRST)被施加了特定的电平信号(通常是持续一定时间的低电平,STC 某些型号为高电平)。
    • 特点
      • 硬件干预:由外部电路(如复位按键、外部监控芯片)物理控制。
      • 电路设计:通常需要外接 RC 复位电路(10k电阻 + 0.1μF电容)来过滤干扰波形。
    • 典型场景:用户按下设备上的物理“Reset”小孔按键。
    1. 看门狗复位 (WDT - Watchdog Timer Reset)
    • 触发方式:看门狗计数器溢出。程序必须在规定时间内“喂狗”(重置计数值),如果程序死锁或跑飞未能及时喂狗,计数器归零即触发复位。
    • 特点
      • 自愈功能:是嵌入式系统摆脱“死机”状态的最后防线。
      • 软硬结合:需要在软件中初始化看门狗频率,并在主循环(while(1))中妥善放置喂狗代码。
    • 典型场景:程序因强电磁干扰进入死循环,或逻辑陷入阻塞无法退出。

0x03 待学习重点

  1. 89C51的经验有多少可以迁移到STC32G上**(已完成)**

    目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。

  2. 如何将传统51单片机项目迁移到STC32G平台上**(已完成)**

    通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!

  3. 硬件USB控制器可以玩出什么花样?(持续学习 持续探索)

    • USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上MAX232打上RJ45就可以用来配置交换机了。
  4. 在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载。(已完成)

  5. 定时器一次只能定时一个此,如果有很多个任务怎么办?(已完成)

    初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。

    答案:想的差不多,方法如下:

    通过数组(包括结构体数组),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。

  6. 如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?(已完成)

    会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。

  7. 74HC595会不会跟不上单片机的速度而导致了数据的丢失?如果会,那么速度多快就会丢失数据?(已完成)

    实测实验箱44.2368MHz以上数码管显示会出现问题。)

回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:72
  • 最近打卡:2026-03-31 00:08:41

2

主题

16

回帖

260

积分

中级会员

积分
260
发表于 2026-2-13 21:30:57 | 显示全部楼层

image.png

Jellyfish从89C51到AI8051学习贴第十四天!

今天继续学习《8051U深度入门到32位51大型实战教学视频》第十一集 矩阵键盘的课后作业

0x01 学习重点

  • 练习1:简易洗衣机面板

0x02 学习心得

  • 扫描矩阵键盘思路

    逐行扫描法(最常用)

    1. 初始化:将所有列线(Col)设为输入并上拉(高电平),行线(Row)设为输出。
    2. 扫描:依次将某一行拉低(其余行为高)。
    3. 判断:读取列线状态。如果有列线变低,说明该行与该列的交点按键被按下。
    4. 计算键值 = 行号 × 总列数 + 列号

    行列反转法(速度快)

    1. 第一步:行输出全 0,列输入,读取列线值(记录哪一列为低)。
    2. 第二步:列输出刚才读取到的值,行输入,读取行线值(记录哪一行为低)。
    3. 合并:将两次读取的电平状态组合,即可精确定位坐标。
    //先完成端口定义
    #define ROW1	P06			//端口定义
    #define ROW2	P07
    #define COL1	P00
    #define COL2	P01
    #define COL3	P02
    #define COL4	P03
    
    //初始化的时候得使用准双向口
    P0M0 = 0x00; P0M1 = 0x00; 
    
    //定时器数组如下
    TASK_COMPONENTS Task_Comps[]=
    {
    //状态  计数  周期  函数
     {0, 1,     1,       Seg_Task},      /* 显示函数: 1ms */ 
     {0, 1000,  1000,    Time_Task},	 /* 倒数函数: 1s */ 
     {0, 10,    10,      Key_Task}, 	 /* 矩阵键盘按键函数: 10ms */ 
     {0, 10,    10,      Key32_Task}, 	 /* P3.2开始按键检测函数: 10ms */ 
    
    };
    
    //关键函数
    bit time_state=0;
    u8 key_num = 0x01;
    u8 last_key = 0xff; // 记录上次状态
    u32 total_sec=0;
    u8 Disp_Buf[4]; // 定义一个全局或静态数组,存放 4 位数码管要显示的数字
    // --- 数码管显示 ---
    void Seg_Task(void){
        if (Seg_no==0){
            Display_Seg(SEG_NUM[key_num],~T_NUM[Seg_no]);
        }else if (Seg_no==1){
            Display_Seg(SEG_NUM[17],~T_NUM[Seg_no]);
        }else if (Seg_no==2){
            Display_Seg(SEG_NUM[17],~T_NUM[Seg_no]);
        }else if (Seg_no==3){
            Display_Seg(SEG_NUM[17],~T_NUM[Seg_no]);
        }else if(Seg_no==4){
            Display_Seg(SEG_NUM[Disp_Buf[0]],~T_NUM[Seg_no]);
        }else if (Seg_no==5){
            Display_Seg(SEG_NUM[Disp_Buf[1]]|SEG_NUM[18],~T_NUM[Seg_no]);
        }else if (Seg_no==6){
            Display_Seg(SEG_NUM[Disp_Buf[2]],~T_NUM[Seg_no]);
        }else if (Seg_no==7){
            Display_Seg(SEG_NUM[Disp_Buf[3]],~T_NUM[Seg_no]);
        }
    
        Seg_no++;
        if (Seg_no>=8) Seg_no=0;
    
    }
    // --- 倒计时 ---
    void Time_Task(){
        u8 Second, Min;
        if (total_sec!=0) {
    //        Hour = total_sec / 3600;         // 算出小时
            Min  = (total_sec % 3600) / 60;  // 算出分钟
            Second = (total_sec % 60);
    
    //        Disp_Buf[0] = Hour / 10;         // 小时十位
    //        Disp_Buf[1] = Hour % 10;         // 小时个位
            Disp_Buf[0] = Min / 10;          // 分钟十位
            Disp_Buf[1] = Min % 10;          // 分钟个位
            Disp_Buf[2] = Second / 10;          // 分钟十位
            Disp_Buf[3] = Second % 10;          // 分钟个位
            total_sec-=1;
        };
    
    }
    
    // --- 矩阵键盘 ---
    void Key_Task(void)
    {
        static u8 last_raw_key = 0xff; // 记录上一次扫描到的原始物理键值
        u8 current_raw_key = 0xff;     // 本次扫描到的原始物理键值
        u8 r_temp = 0, c_temp = 0;
    
        // --- 步骤一:扫描行 ---
        COL1=0; COL2=0; COL3=0; COL4=0; 
        ROW1=1; ROW2=1;
        //_nop_();  //给电平建立时间
    
        if(ROW1 == 0) r_temp = 0;      // 第一行按下
        else if(ROW2 == 0) r_temp = 4; // 第二行按下
        else { goto scan_process; }        // 没按键,直接跳到末尾清零
    
        // --- 步骤二:扫描列 ---
        COL1=1; COL2=1; COL3=1; COL4=1;
        ROW1=0; ROW2=0;
        //_nop_();  // 给电平建立时间 
        if (total_sec!=0) return;//当前没有洗衣任务才允许重置倒数时间,放这里刚好代替延时
        if(COL1 == 0) c_temp = 0;
        else if(COL2 == 0) c_temp = 1;
        else if(COL3 == 0) c_temp = 2;
        else if(COL4 == 0) c_temp = 3;
        else { goto scan_process; }
    
        current_raw_key = r_temp + c_temp; // 计算出当前物理键值
    
    scan_process:
        // --- 步骤三:双稳态消抖 + 松手检测 ---
        if(current_raw_key == last_raw_key) // 连续两次(20ms)读到一样的值
        {
            if(current_raw_key != 0xff && current_raw_key != key_num)  // 只有当检测到确实有按键(不等于0xff),且跟上次存的值不一样时,才更新
            {
                key_num = current_raw_key;  // 更新最终键值
            }
        }
        last_raw_key = current_raw_key;    // 备份本次结果给下一次比对
    
        // 恢复扫描初态,准备下一次 10ms
        COL1=0; COL2=0; COL3=0; COL4=0; ROW1=1; ROW2=1;
    }
    // --- 按键检测 ---
    void Key32_Task(void)
    {
    	if( P33 == 0 )
    	{
    		Key_Vol++;
    		if( Key_Vol==5 && total_sec==0 )	//当前没有洗衣任务才允许重置倒数时间
    		{
    			//按键按下的任务,模式1需要5分钟,模式2需要10分钟以此类推
                total_sec=key_num*300;
    		}
    	}
    	else
    	{
    		Key_Vol = 0;
    	}
    }
    

0x03 效果展示

VID202602132122541.gif

回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:72
  • 最近打卡:2026-03-31 00:08:41

2

主题

16

回帖

260

积分

中级会员

积分
260
发表于 2026-2-15 01:50:54 | 显示全部楼层

image-20260213180916449.png

Jellyfish从89C51到AI8051学习贴第十五天!

今天继续学习《8051U深度入门到32位51大型实战教学视频》​第十二集 密码锁​。

0x01 学习重点

  • 练习1:密码锁

0x02 学习心得

  • 4种复位模式

    1. 上电复位 (POR - Power-On Reset)
    • 触发方式:当 VCC 电源从 0V 攀升至系统工作电压时,内部硬件检测到电压跨越阈值,自动触发。
    • 特点:
      • 冷启动:这是最彻底的复位,会重置所有寄存器、I/O 状态及内部存储空间。
      • 标志位:通常在寄存器中表现为 POF (Power-On Flag)。
    • 典型场景:设备第一次插入电池或接通电源。
    1. 低压复位 (LVR - Low Voltage Reset / Brown-out)
    • 触发方式:系统运行过程中,电源电压跌落至设定的阈值(如 2.4V 或 3.3V)以下时触发。
    • 特点:
      • 保护机制:防止电压过低导致 CPU 逻辑混乱、写 Flash 错误或程序跑飞。
      • 可调性:现代单片机通常允许在 ISP 下载软件 或软件寄存器中设置不同的掉电检测电压(LVD)。
    • 典型场景:电池电量耗尽瞬间、大电流负载(如电机启动)导致电压掉闸。
    1. 复位脚复位 (NRST - External Reset Pin)
    • 触发方式:外部复位引脚(RST/NRST)被施加了特定的电平信号(通常是持续一定时间的低电平,STC 某些型号为高电平)。
    • 特点:
      • 硬件干预:由外部电路(如复位按键、外部监控芯片)物理控制。
      • 电路设计:通常需要外接 RC 复位电路(10k电阻 + 0.1μF电容)来过滤干扰波形。
    • 典型场景:用户按下设备上的物理“Reset”小孔按键。
    1. 看门狗复位 (WDT - Watchdog Timer Reset)
    • 触发方式:看门狗计数器溢出。程序必须在规定时间内“喂狗”(重置计数值),如果程序死锁或跑飞未能及时喂狗,计数器归零即触发复位。
    • 特点:
      • 自愈功能:是嵌入式系统摆脱“死机”状态的最后防线。
      • 软硬结合:需要在软件中初始化看门狗频率,并在主循环(while(1))中妥善放置喂狗代码。
    • 典型场景:程序因强电磁干扰进入死循环,或逻辑陷入阻塞无法退出。

计算看门狗超时时间(24Mhz下)

\frac{12\times 32768\times2^{6}}{24\times10^{6} }=1.048576秒
//初始化的时候要运行这个函数断开重置usb
void USB_Reset_U(void)
{
	P3M0 = 0x00;
	P3M1 = 0x00;

	P3M0 &= ~0x03;
	P3M1 |= 0x03;

	USBCON = 0X00;
	USBCLK = 0X00;
	IRC48MCR = 0X00;
	Delay10ms();
}

//配置看门狗寄存器
EN_WDT=1;   //开启看门狗
IDL_WDT=1;  //空闲模式继续累计看门狗
WDT_CONTR|=0x05; //配置喂狗超时时间,101 分频64,12x32768x2^6/(24*10^6)=1.048576
EA=1;	//开启总中断

//定时器调度情况如下:
TASK_COMPONENTS Task_Comps[]=
{
//状态  计数  周期  函数
 {0, 1,     1,       Seg_Task},      /* task 1 Period: 1ms */ 
 {0, 10,    10,      Key_Task}, 
 {0, 1000,  1000,    Dog}, 
 {0, 10,    10,      Key33_Task}, 
};

//关键定义
u8 SEG_NUM[] = {
    0x3F, /*'0', 0*/
    0x06, /*'1', 1*/
    0x5B, /*'2', 2*/
    0x4F, /*'3', 3*/
    0x66, /*'4', 4*/
    0x6D, /*'5', 5*/
    0x7D, /*'6', 6*/
    0x07, /*'7', 7*/
    0x7F, /*'8', 8*/
    0x6F, /*'9', 9*/
    0x77, /*'A', 10*/
    0x7C, /*'B', 11*/
    0x39, /*'C', 12*/
    0x5E, /*'D', 13*/
    0x79, /*'E', 14*/
    0x71, /*'F', 15*/
    0x40, /*'-', 16*/
    0x00, /*' ', 17*/
    0x80, /*'.', 18*/
    0x5C, /*'O', 19*/
    0x73, /*'P', 20*/
    0x79, /*'E', 21*/
    0x54, /*'N', 22*/
    0x3E, /*'U', 23*/
};

u8 T_NUM[] =
    {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

u8 Key_Vol = 0;
u8 Key33_Vol = 0;
u8 Seg_no = 0;
u8 Disp_Buf[8] = {16, 16, 16, 16, 16, 16, 16, 16}; // 定义一个全局或静态数组,存放 8 位数码管要显示的数字
u8 open[8] = {16, 16, 16, 16, 19, 20, 21, 22};     // 定义密码
u8 passwd[8] = {1, 2, 3, 4, 5, 6, 7, 8};           // 定义密码
u8 version[3] = {23, 1, 0};                        // 定义密码
u8 dis_ver = 3;

//关键函数
// --- 数码管显示 1ms 调度任务逻辑 ---
//正常显示Disp_Buf中的内容,当dis_ver大于0时,后三位显示版本号,dis_ver会每秒减一。
void Seg_Task(void)
{
    if (Seg_no == 0)
    {
        Display_Seg(SEG_NUM[Disp_Buf[0]], ~T_NUM[Seg_no]);
    }
    else if (Seg_no == 1)
    {
        Display_Seg(SEG_NUM[Disp_Buf[1]], ~T_NUM[Seg_no]);
    }
    else if (Seg_no == 2)
    {
        Display_Seg(SEG_NUM[Disp_Buf[2]], ~T_NUM[Seg_no]);
    }
    else if (Seg_no == 3)
    {
        Display_Seg(SEG_NUM[Disp_Buf[3]], ~T_NUM[Seg_no]);
    }
    else if (Seg_no == 4)
    {
        Display_Seg(SEG_NUM[Disp_Buf[4]], ~T_NUM[Seg_no]);
    }
    else if (Seg_no == 5)
    {
        if (dis_ver == 0)
        {
            Display_Seg(SEG_NUM[Disp_Buf[5]], ~T_NUM[Seg_no]);
        }
        else
        {
            Display_Seg(SEG_NUM[version[0]], ~T_NUM[Seg_no]);
        }
    }
    else if (Seg_no == 6)
    {
        if (dis_ver == 0)
        {
            Display_Seg(SEG_NUM[Disp_Buf[6]], ~T_NUM[Seg_no]);
        }
        else
        {
            Display_Seg(SEG_NUM[version[1]] | SEG_NUM[18], ~T_NUM[Seg_no]);
        }
    }
    else if (Seg_no == 7)
    {
        if (dis_ver == 0)
        {
            Display_Seg(SEG_NUM[Disp_Buf[7]], ~T_NUM[Seg_no]);
        }
        else
        {
            Display_Seg(SEG_NUM[version[2]], ~T_NUM[Seg_no]);
        }
    }

    Seg_no++;
    if (Seg_no >= 8)
        Seg_no = 0;
}

// --- 矩阵键盘 10ms 调度任务逻辑 ---
void Key_Task(void)
{
    u8 i, j;
    u8 current_raw_key = 0xff;     // 本次扫描到的原始物理键值
    u8 r_temp = 0, c_temp = 0;

    // --- 步骤一:扫描行 ---
    COL1 = 0;
    COL2 = 0;
    COL3 = 0;
    COL4 = 0;
    ROW1 = 1;
    ROW2 = 1;
    //_nop_();  // 【核心修复】给电平建立时间

    if (ROW1 == 0)
        r_temp = 0; // 第一行按下
    else if (ROW2 == 0)
        r_temp = 4; // 第二行按下
    else
    {
        goto scan_process;
    } // 没按键,直接跳到末尾清零

    // --- 步骤二:扫描列 ---
    COL1 = 1;
    COL2 = 1;
    COL3 = 1;
    COL4 = 1;
    ROW1 = 0;
    ROW2 = 0;
    _nop_();  // 给电平建立时间
    if (COL1 == 0)
        c_temp = 0;
    else if (COL2 == 0)
        c_temp = 1;
    else if (COL3 == 0)
        c_temp = 2;
    else if (COL4 == 0)
        c_temp = 3;
    else
    {
        goto scan_process;
    }

    current_raw_key = r_temp + c_temp; // 计算出当前物理键值

scan_process:
    // --- 步骤三:双稳态消抖 + 松手检测 ---
    if (current_raw_key == 0xff)
    {
        Key_Vol = 0;
    }

    Key_Vol++;
    if (current_raw_key != 0xff && Key_Vol == 2) // 只有当检测到确实有按键(不等于0xff),才更新
    {

        for (i = 0; i < 8; i++)
        {
            if (Disp_Buf[i] == 16)
            {
                if (Disp_Buf[7] == 22)
                {
                    for (j = 0; j < 8; j++)
                    {
                        Disp_Buf[j] = 16;
                    }
                }
                Disp_Buf[i] = current_raw_key + 1;
                CLR_WDT = 1; // 按键按下的时候喂狗
                break;
            }
        }
        // 这里可以触发一个“按键按下”的标志位,供外部使用
    }

    // 恢复扫描初态,准备下一次 10ms
    COL1 = 0;
    COL2 = 0;
    COL3 = 0;
    COL4 = 0;
    ROW1 = 1;
    ROW2 = 1;
}

// --- 软件复位按键判断 10ms 调度任务逻辑 ---
void Key33_Task(void)
{
    if (P33 == 0)
    {
        Key33_Vol++;
        if (Key33_Vol == 5)
        {
            IAP_CONTR = 0x20;   //软件复位
        }
    }
    else
    {
        Key33_Vol = 0;
    }
}

// --- 看门狗及密码检测 1000ms 调度任务逻辑 ---
void Dog()
{
    bit Is_Equal = 1; // 假设相等
    u8 i;
    if (Disp_Buf[0] == 16)
    {
        CLR_WDT = 1; //当第一位是“-”时,喂狗。
    }
    if (dis_ver != 0)
    {
        dis_ver--;	//显示版本号的倒计时。
    }
    if (Disp_Buf[7] != 16 && Disp_Buf[7] != open[7])
    {
        // 3. 比较内容
        for (i = 0; i < 8; i++)
        {
            if (Disp_Buf[i] != passwd[i])
            {
                Is_Equal = 0;
                break;
            }
        }
        if (Is_Equal)
        {
            for (i = 0; i < 8; i++)
            {
                Disp_Buf[i] = open[i];
            }
        }
        else
        {
            for (i = 0; i < 8; i++)
            {
                Disp_Buf[i] = 16;
            }
        }
    }
}
  • 用户系统区

    新发现一个叫做用户系统区的东西,可以用来实现OTG升级等功能。

    新设计Ai8051U-34K64-LQFP48/LQFP44/PDIP40,可以指定用户区的64K的最后部分做用户自己完全独立的User_ISP_BootLoader区,上电后,从完全独立于用户完整的64K程序区的,独立的系统ISP_BootLoader区运行,判断要不要下载用户程序后,会复位到User_ISP_BootLoader区,复位到User_ISP_BootLoader区后,User_ISP_BootLoader区再判断是否要更新用户自己的最后要运行的用户程序。

  • 必须IDL_WDT=1; //空闲模式继续累计看门狗,不然看门狗溢出时间老是算不对

回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:72
  • 最近打卡:2026-03-31 00:08:41

2

主题

16

回帖

260

积分

中级会员

积分
260
发表于 2026-2-15 17:28:35 | 显示全部楼层

image.png

Jellyfish从89C51到AI8051学习贴第十六天!

今天继续学习《8051U深度入门到32位51大型实战教学视频》第十三集 外部中断的课后作业

0x01 学习重点

  1. 练习1:雕刻机防护系统

0x02 学习心得

  • 外部中断分上升下降沿(IT0=0时)和仅下降沿(IT0=1时),通过**IT**进行配置。

image.png

  • 需要使用LED0代表激光工作状态,矩阵键盘1代表开启激光,但是他们的P00端口重复了,屏蔽掉了矩阵键盘的0和4按键。

    //初始化寄存器,使能中断
    IT0=1;	//配置p3.2的init0中断
    EX0=1;	//开启init0中断
    EA=1;	//开启全局中断
    
    //定时器调度结构体
    TASK_COMPONENTS Task_Comps[]=
    {
    //状态  计数  周期  函数
     {0, 10,      10,      Key_Task}, 
     {0, 1000,    1000,    led_work}, 
    };
    
    // --- 控制激光点亮函数 1000ms 调度任务逻辑 ---
    void led_work(){
        if (led && P32!=0) P00=0;	//当全局控制变量为真,且p32不为0才能点亮激光
        else P00=1;
    }
    
    // --- 矩阵键盘 10ms 调度任务逻辑 ---
    void Key_Task(void)
    {
        u8 i, j;
        u8 current_raw_key = 0xff;     // 本次扫描到的原始物理键值
        u8 r_temp = 0, c_temp = 0;
    
        // --- 步骤一:扫描行 ---
    //    COL1 = 0;
        COL2 = 0;
        COL3 = 0;
        COL4 = 0;
        ROW1 = 1;
        ROW2 = 1;
        //_nop_();  // 【核心修复】给电平建立时间
    
        if (ROW1 == 0)
            r_temp = 0; // 第一行按下
        else if (ROW2 == 0)
            r_temp = 4; // 第二行按下
        else
        {
            goto scan_process;
        } // 没按键,直接跳到末尾清零
    
        // --- 步骤二:扫描列 ---
    //    COL1 = 1;
        COL2 = 1;
        COL3 = 1;
        COL4 = 1;
        ROW1 = 0;
        ROW2 = 0;
        _nop_();  // 给电平建立时间
        if (COL1 == 0)
            c_temp = 0;
        else if (COL2 == 0)
            c_temp = 1;
        else if (COL3 == 0)
            c_temp = 2;
        else if (COL4 == 0)
            c_temp = 3;
        else
        {
            goto scan_process;
        }
    
        current_raw_key = r_temp + c_temp; // 计算出当前物理键值
    
    scan_process:
        // --- 步骤三:双稳态消抖 + 松手检测 ---
        if (current_raw_key == 0xff)
        {
            Key_Vol = 0;
        }
    
        Key_Vol++;
        if (current_raw_key != 0xff && Key_Vol == 2) // 只有当检测到确实有按键(不等于0xff),且跟上次存的值不一样时,才更新
        {
            if (current_raw_key==1 &&   P32!=0) led = 1;
        }
    //    COL1 = 0;
        COL2 = 0;
        COL3 = 0;
        COL4 = 0;
        ROW1 = 1;
        ROW2 = 1;
    }
    
    //中断函数
    void INT0_Isr() interrupt 0 
    {
        led=0;	//关闭激光全局控制变量
        P00=1;	//马上关闭激光
    }
    
回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:72
  • 最近打卡:2026-03-31 00:08:41

2

主题

16

回帖

260

积分

中级会员

积分
260
发表于 2026-2-15 19:31:49 | 显示全部楼层

Jellyfish从89C51到AI8051学习贴第十六天!

今天继续学习《8051U深度入门到32位51大型实战教学视频》第十四集 IO中断

0x01 学习重点

  1. 练习1:多路抢答器

image-20260215173837352.png

0x02 学习心得

  • IO口的准双向口的高电平会带弱上拉,此时外部悬空则无法触发到上升沿。
  • IO口的准双向口的配置成底电平后就无法再次检测到上升沿了。
  • 同一个IO口中断触发过程中再次触发会先等待上一次中断完毕后再响应新得中断。此外等待过程中不响应同样的第三个中断
  • 通过复用矩阵键盘的P00-P04(P00作为抢答复位按键)实现多路抢答器。
//复用矩阵键盘的P00-P04(P00作为抢答复位按键)

//直接给595发第一个数码管为0
Init_595();
Display_Seg(0x3F, ~0x01);

//矩阵键盘配置P06为低电平,这样可以给P00-P04提供下降沿
P06=0;

//配置为下降沿触发模式
P0IM0&=0xF0;
P0IM1&=0xF0;

P0INTE|=0x0F;
P0WKUE|=0x0f;			//设置P0口中断唤醒省电模式
EA=1;

//中断函数
void P0_Isr() interrupt 37 
{
	//如果有人胜利了就不要再响应
    static win=0;
    unsigned char intf;
    intf = P0INTF;
    //判断是谁触发了中断
    if (intf)
    {
        P0INTF = 0x00;
        if (intf & 0x01)
        {
            //P00作为恢复抢答按键,顺便把数码管显示0
            win=0;
            Init_595();
            Display_Seg(0x3F, ~0x01);
        }else
        if (intf & 0x02 && win == 0)
        {
            //P01中断,把胜利标志位置1,再把数码管显示1
            win=1;
            Init_595();
            Display_Seg(0x06, ~0x01);
            
        }else
        if (intf & 0x04 && win == 0)
        {
            win=1;
            Init_595();
            Display_Seg(0x5B, ~0x01);
        }else
        if (intf & 0x08 && win == 0)
        {
            win=1;
            Init_595();
            Display_Seg(0x4F, ~0x01);
        }else
        {
            return;
        }
        
    }
}
回复

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2026-3-31 20:20 , Processed in 0.124228 second(s), 89 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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