我心飞扬 发表于 2025-4-8 18:53:25

学习Ai8051U的总结笔记--《8051U深度入门到32位51大型实战视频》--打卡

情况介绍:

对电子技术有兴趣,单片机的知识也了解一些。
8位的51系列单片机应用很广,但是在深圳国芯人工智能公司这里看到支持32位/8位51指令集的MCU还是非常令人鼓舞的。
从指标上看这款超越51系列的型号为Ai8051U的MCU功能很强大,运算能力强,接口众多,性能出众,希望能用其做出一些在测量方面常用的智能化仪表。
近期有些时间,注册了论坛会员,开始按照教学视频学习Ai8051U这款MCU。


学习网址:
https://www.stcaimcu.com/thread-11902-1-1.html

我心飞扬 发表于 前天 00:56

《8051U深度入门到32位51大型实战教学视频》--学习打卡12

第十二集:复位系统

笔记:


Ai8051U系列单片机的复位分为硬件复位和软件复位两种。
硬件复位时,所有的寄存器的值会复位到初始值,系统会重新读取所有的硬件选项。
同时根据硬件选项所设置的上电等待时间进行上电等待。
硬件复位主要包括:上电复位、低压复位、复位脚复位(低电平复位、 看门狗复位。
软件复位时,除与时钟相关的寄存器保持不变外,其余的所有寄存器的值会复位到初始值,软件复位不会重新读取所有的硬件选项。
软件复位主要包括:写 IAP_CONTR 的 SWRST 所触发的复位。

复位操作可以确保系统处于确定状态,单片机在开始工作时处于已知的状态,使其能够正确初始化各个寄存器和外设。
复位操作可以避免不确定行为。没有进行复位时,内部控制寄存器的内容可能是随机的,这可能导致定时器溢出、中断异常、外设误操作等不确定行为。
复位操作可以初始化系统,包括清除寄存器、设置默认值、配置时钟等,为系统正常运行做好准备。
复位操作可以保证程序正常开始执行,确保程序从正确的地址开始执行,避免跳转到未知的地址或执行错误的指令。


1、硬件复位。
1.1 上电复位(复位电压为1.7-1.9V)。

1.2 低压复位。

1.3 复位脚复位。
1.4 看门狗复位。

2、软件复位。

学习心得:

总结:

后记:

2025年4月30日。

我心飞扬 发表于 2025-4-22 02:05:14

《8051U深度入门到32位51大型实战教学视频》--学习打卡11

第十一集:矩阵按键

笔记:

1、矩阵按键的原理。
1.1 独立按键,每个按键连接到一个独立的I/O口上,一端连MCU的I/O口,另一端连接Vcc或Gnd。用在按键少的应用中,编程简单。

1.2 矩阵键盘,按键两端都接到I/O口上,将I/O口逻辑上分组,形成按键矩阵的行和列,行与列的I/O口分别设置高低电平,按键按下时,程序读取(扫描)I/O口,得到这组I/O口的状态值,对应按键码值知道哪儿按键按下。占用的I/O口少。


2、矩阵按键的程序实现。
2.1 按键识别步骤:
第一步:先将P0.0-P0.3输出低电平,P0.6-P0.7输出高电平,此时有某按键按下,按下的那一行的I/O口就会变成低电平,就可以判断哪一行按下。
第二步:再将P0.0-P0.3输出高电平,P0.6-P0.7输出低电平,此时有某按键按下,按下的那一列的I/O口就会变成低电平,就可以判断哪一列按下。
第三步:行列组合一下就可以判断出是哪个按键按下了。
2.2 实现任务1:数码管显示当前的按键号。
代码打包在:

2.3 实现任务2:密码锁。
(a) 没有输入时,显示“- - - - - - - -”;
(b) 有输入时,按下一个按键,开始按顺序写入;
   例如,第一个按下1,显示“1 - - - - - - -”;
   例如,第二个按下3,显示“1 3 - - - - - -”;
(c) 当按下的密码为“0 1 2 3 4 5 6 7”时,数码管显示“open”的字符;否则还是显示“- - - - - - - -”。
代码见2.2章节中的压缩文件。

学习心得:

1、独立按键适用于按键少,IO口少的应用,优点是代码简单。
2、矩阵键盘可以有更多的按键,占用的IO口比一键一口少很多,代码处理稍微复杂。
3、这两种按键电子逻辑都是按下按键连接到高电平或低电平,程序读取(扫描)各个IO口状态,综合比较后得出哪个按键按下。

总结:

1、学习了独立按键、矩阵键盘的原理以及电子电路。
2、学习了程序中读取(扫描)按键的方法。

后记:

2025年4月30日。

我心飞扬 发表于 2025-4-21 18:14:07

《8051U深度入门到32位51大型实战教学视频》--学习打卡10

第十集:虚拟键盘LED和数码管

笔记:

1、虚拟显示--LED
1.1 准备好擎天柱核心板或Ai8051U试验箱。
1.2 准备好最新AIapp-ISP程序下载软件,最新版本已经到了V6.95M。
1.3 在AIapp-ISP软件菜单中的“调试仿真接口”-->“接口设置”按照下图所示设置。

1.4 参考AIapp-ISP软件菜单中的“调试仿真接口”-->“接口协议及帮助”中的内容,在源代码中调用接口函数,完成需要的功能。


1.5 任务1:P2口流水灯,P10闪烁。
代码如下:


2、虚拟显示--数码管
2.1 参考AIapp-ISP软件菜单中的“调试仿真接口”-->“接口协议及帮助”中的内容,在源代码中调用接口函数,完成需要的功能。


2.2 任务2:左边数码管显示P32按下次数,右边数码管显示P33按下次数。
代码见上面1.5节。

3、虚拟键盘
3.1 参考AIapp-ISP软件菜单中的“调试仿真接口”-->“接口协议及帮助”中的内容,在源代码中调用接口函数,完成需要的功能。

3.2 任务3:按下数字按键在数码管显示对应的按键数字。
代码见上面1.5节。

学习心得:
1、第十集主要是延续上一集继续讲解调试仿真接口的使用,对LED、数码管、虚拟键盘的使用已经初步掌握。
2、要注意调试仿真接口中传输的数据,分析数据能发现问题。目前有一个问题还没解决,在传输的数据中发现问题。问题在:向高手请教:《8051U深度入门到32位51大型实战教学视频》第九集做实验时发现跳码。有详细描述。

总结:

1、学习了调试仿真接口中LED、数码管、虚拟键盘的使用。
2、使用别的代码中定义的变量要用"extern 数据类型 变量名"方式引入。
3、程序结构框图参考如下截图:


后记:

上集中发现的问题还是没能解决。

2021年4月22日。

我心飞扬 发表于 2025-4-19 13:12:57

补记一下:
第八集视频讲解开始时提到一些编码时遇到的问题和解决办法,觉得需要特别注意一下。
从教学视频发布的帖子中拷贝过来,内容如下:

常见错误:
1.关键词/变量函数名称的大小写错误或者漏了一个字符,P00写出p00,u8写成U8等等;
2.大括号缺了半个;(大括号上下一定要对齐,就不容易遗漏)
3.符号的中英文错误;
4.语句结尾没加分号;
5.变量/函数定义之后没有声明/重复定义;
6.#include在调用头文件时后面要先加一个空格,在+引号头文件名称。

我心飞扬 发表于 2025-4-18 01:38:24

《8051U深度入门到32位51大型实战教学视频》--学习打卡9

第九集:数码管

笔记:

1、数码管介绍

1.1 数码管也叫LED数码管(LED Segment Displays)内部是由多个发光二极管封装在一起组成的“8”字型器件,引线已在内部连接完成,只需引出它们的各个笔划电极,供电路中控制发光二极管的亮灭。它可以有很多种颜色,很多种外形,很多种样式,但是本质来说它们都是通过点亮内部的LED来显示的,只要面板做好了,理论可以显示任意的字符或者图案。一个“8”称之为1位数码管,两个“8”就是2位数码管,以此类推。
1.2 发光二极管的类型按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管。


2、数码管显示原理

2.1 Ai8051U-32K64实验箱数码管电路图。

2.2 2个4位8段数码管由2个74HC595级联(Q7’引脚与下一个芯片的SER引脚相连)后控制其显示内容。74HC595是一个8位串行输入、并行输出(SIPO)的移位缓存器,并具有并行三态输出的芯片。在SCK 的上升沿,串行数据由SER输入到内部的8位移位缓存器,同时移位缓存器溢出数据位由Q7’输出;而并行输出则是在RCK的上升沿将在8位移位缓存器的数据存入到8位并行输出缓存器;当串行数据输入完成后,OE的控制信号为低使能时,并行输出端的三态门输出值等于并行输出缓存器所存储的值。
2.3 74HC595时序逻辑的讲解比较复杂,通过学习其它网上视频才基本了解。注意数据的高位优先还是低位优先,还有SER、SCK、RCK的配合时序。


3、数码管静态显示
3.1 手头只有擎天柱核心板,只是看视频教程,任务1:数码管静态的显示一个数字,代码实现部分没有编写。根据试验箱数码管部分的实际电路,要先送段码后送位码。教程中主要代码为:


4、数码管动态显示
4.1 动态显示的过程如下:

4.2 每个延时不能太短,视频教学中以1ms举例,实际使用中要保证总共一个显示循环结束的时间不能大于20ms,因为人眼的视觉暂留特点造成不容易分辨出50HZ以上的动态刷新。
4.3 任务2:数码管显示“12345678” ,任务3:数码管显示“00-00-00”分别代表时分秒 ,每过1秒钟秒+1,代码实现部分没有编写。

5、虚拟显示——LED和数码管
5.1 学习虚拟仿真口的调用方法。

5.2 实现对仿真接口编码,代码核心部分如下:

完整代码见下面压缩包:


学习心得:

1、为节约MCU的接口,用少量IO口控制数码管需要用外围芯片,这就需要对外围芯片进行控制。
2、数码管显示码值从AIapp-ISP工具中获取更快捷。
3、仿真接口很方便。
4、中断很方便。
5、74HC595芯片的控制时序理解有些难度,从网上搜索相关视频得以解决。

总结:

1、学习了数码管的相关知识。
2、学习了74HC595的原理,级联方法,控制数码管的电路,控制数码管的时序,以及代码控制数码管的编写方法。
3、针对实验器材不匹配的情况,学习了仿真接口的使用方法。

后记:
在自己实现视频教程的代码时,发现问题,已有新帖描述。

帖子地址:向高手请教:《8051U深度入门到32位51大型实战教学视频》第九集做实验时发现跳码。

2025年4月21日。






我心飞扬 发表于 2025-4-16 22:37:03

《8051U深度入门到32位51大型实战教学视频》--学习打卡8

第八集:定时器周期性调度任务

笔记:

1、周期性任务介绍
通过任务代码实现过程的讲解,融入了Ai8051U定时器/计数器知识和中断机制的讲解,以及编辑中的技巧、C语言知识点数组和for语句、按键防抖、工具软件的使用等知识点的讲解。
1.1 定义变量实现任务一,用了多个变量分别计数的方法实现一个定时器服务多个任务。
代码如下:

//擎天柱核心板-Ai8051U-32K64,定时器周期性调度任务实验程序。
//任务一,方法一:多个变量分别计数的方法实现一个定时器服务多个任务。
//attach://94320.c
//用到2025年4月8日发布的USB库(CDC):stc_usb_cdc_32g.LIB。
#include "ai8051u.h"
#include "ai_usb.h"

u16 Count_300 = 0;
u16 Count_600 = 0;
u16 Count_900 = 0;

u16 Count_ms = {0, 0, 0};

u8 i;

void Delay20ms(void)      //@40.000MHz
{
      unsigned long edata i;
      _nop_(); _nop_();
      i = 199998UL;
      while (i--);
}

void Timer0_Isr(void) interrupt 1
{
      Count_300++;
      Count_600++;
      Count_900++;
}

void Timer0_Init(void)                //1毫秒@40.000MHz
{
      TM0PS = 0x00;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
      AUXR &= 0x7F;                        //定时器时钟12T模式
      TMOD &= 0xF0;                        //设置定时器模式
      TL0 = 0xFB;                              //设置定时初始值
      TH0 = 0xF2;                              //设置定时初始值
      TF0 = 0;                              //清除TF0标志
      TR0 = 1;                              //定时器0开始计时
      ET0 = 1;                              //使能定时器0中断
}

void main(void)
{
      EAXFR = 1;                //允许访问扩展的特殊寄存器,XFR。(32 位模式请使用这句,注释下一句)。
//      P_SW2 |= 0x80;      //允许访问扩展的特殊寄存器,XFR。(8 位模式请使用这句,注释上一句)。
      WTST = 0;                //设置取程序代码等待时间:赋值为0表示不等待,程序以最快速度运行。
      CKCON = 0;                //设置访问片内的xdata速度:赋值为0表示用最快速度访问,不增加额外的等待时间。
      
      P2M0 = 0x00; P2M1 = 0x00;                //设置P2口为准双向口模式。
      P3M0 = 0x00; P3M1 = 0x00;                //设置P3口为准双向口模式。

      usb_init();                //调用USB初始化函数。
      Delay20ms();
      Timer0_Init();      //调用T0初始化函数。
      Delay20ms();
      EA = 1;                        //打开总中断。
      
      {int i = 1; for (i = 1; i <= 300; i++) Delay20ms();}                //等待AIapp-ISP下载软件中串口自动打开。
      printf_usb("程序开始执行!\r\n");

      while (1)
      {
                //任务一,方法一:多个变量分别计数的方法实现一个定时器服务多个任务。
                if (Count_300 >= 300)
                {
                        Count_300 = 0;
                        P20 = ~P20;
                }
                if (Count_600 >= 600)
                {
                        Count_600 = 0;
                        P21 = ~P21;
                }
                if (Count_900 >= 900)
                {
                        Count_900 = 0;
                        P22 = ~P22;
                }

                if(bUsbOutReady) //bUsbOutReady 标志位是用于标明 USB 主机发送完成。
                {
                        USB_SendData(UsbOutBuffer, OutNumber);
                        printf_usb("\r\n国芯MCU收到数\xfd据:%X\r\n", UsbOutBuffer);
                        P2 = UsbOutBuffer;
                        LED40_SetPort(2, P2);                //使用AIapp-ISP软件菜单“调试仿真接口”中的“擎天柱-LED-DIP40”进行模拟显示P2口。
                        usb_OUT_done();                        //本包接收的数据处理完毕,准备接收下一包数据。
                }
      }
}

1.2 定义数组实现任务一,用数组计数的方法实现一个定时器服务多个任务。
代码如下:

//擎天柱核心板-Ai8051U-32K64,定时器周期性调度任务实验程序。
//
//任务一,方法二:数组计数的方法实现一个定时器服务多个任务。
//用到2025年4月8日发布的USB库(CDC):stc_usb_cdc_32g.LIB。
#include "ai8051u.h"
#include "ai_usb.h"

u16 Count_300 = 0;
u16 Count_600 = 0;
u16 Count_900 = 0;

u16 Count_ms = {0, 0, 0};

u8 j;

void Delay20ms(void)      //@40.000MHz
{
      unsigned long edata i;
      _nop_(); _nop_();
      i = 199998UL;
      while (i--);
}

void Timer0_Isr(void) interrupt 1
{
      for (j = 0; j < 3; j++)
      {
                Count_ms++;
      }
      if (Count_ms >= 300)
      {
                P20 = ~P20;
                Count_ms = 0;
      }
      if (Count_ms >= 600)
      {
                P21 = ~P21;
                Count_ms = 0;
      }
      if (Count_ms >= 900)
      {
                P22 = ~P22;
                Count_ms = 0;
      }
}

void Timer0_Init(void)                //1毫秒@40.000MHz
{
      TM0PS = 0x00;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
      AUXR &= 0x7F;                        //定时器时钟12T模式
      TMOD &= 0xF0;                        //设置定时器模式
      TL0 = 0xFB;                              //设置定时初始值
      TH0 = 0xF2;                              //设置定时初始值
      TF0 = 0;                              //清除TF0标志
      TR0 = 1;                              //定时器0开始计时
      ET0 = 1;                              //使能定时器0中断
}

void main(void)
{
      EAXFR = 1;                //允许访问扩展的特殊寄存器,XFR。(32 位模式请使用这句,注释下一句)。
//      P_SW2 |= 0x80;      //允许访问扩展的特殊寄存器,XFR。(8 位模式请使用这句,注释上一句)。
      WTST = 0;                //设置取程序代码等待时间:赋值为0表示不等待,程序以最快速度运行。
      CKCON = 0;                //设置访问片内的xdata速度:赋值为0表示用最快速度访问,不增加额外的等待时间。
      
      P2M0 = 0x00; P2M1 = 0x00;                //设置P2口为准双向口模式。
      P3M0 = 0x00; P3M1 = 0x00;                //设置P3口为准双向口模式。

      usb_init();                //调用USB初始化函数。
      Delay20ms();
      Timer0_Init();      //调用T0初始化函数。
      Delay20ms();
      EA = 1;                        //打开总中断。
      
      {int i = 1; for (i = 1; i <= 300; i++) Delay20ms();}                //等待AIapp-ISP下载软件中串口自动打开。
      printf_usb("程序开始执行!\r\n");

      while (1)
      {
                //任务一,方法二:数组计数的方法实现一个定时器服务多个任务。
                //此处不需要写代码。

                if(bUsbOutReady) //bUsbOutReady 标志位是用于标明 USB 主机发送完成。
                {
                        USB_SendData(UsbOutBuffer, OutNumber);
                        printf_usb("\r\n国芯MCU收到数\xfd据:%X\r\n", UsbOutBuffer);
                        P2 = UsbOutBuffer;
                        LED40_SetPort(2, P2);                //使用AIapp-ISP软件菜单“调试仿真接口”中的“擎天柱-LED-DIP40”进行模拟显示P2口。
                        usb_OUT_done();                        //本包接收的数据处理完毕,准备接收下一包数据。
                }
      }
}

2.3 定义数组实现任务2,有循环数组中存在的值来点亮LED,实现流水灯。
代码如下:

//擎天柱核心板-Ai8051U-32K64,定时器周期性调度任务实验程序。
//
//任务2:数组点亮LED,实现流水灯。
//用到2025年4月8日发布的USB库(CDC):stc_usb_cdc_32g.LIB。
#include "ai8051u.h"
#include "ai_usb.h"

u16 Count_300 = 0;
u16 Count_600 = 0;
u16 Count_900 = 0;

u16 Count_ms = {0, 0, 0};
u8 State = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
u8 i;
u8 num = 0;

void Delay20ms(void)      //@40.000MHz
{
      unsigned long edata i;
      _nop_(); _nop_();
      i = 199998UL;
      while (i--);
}

void Timer0_Isr(void) interrupt 1
{
      for (i = 0; i < 3; i++)
      {
                Count_ms++;
      }
}

void Timer0_Init(void)                //1毫秒@40.000MHz
{
      TM0PS = 0x00;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
      AUXR &= 0x7F;                        //定时器时钟12T模式
      TMOD &= 0xF0;                        //设置定时器模式
      TL0 = 0xFB;                              //设置定时初始值
      TH0 = 0xF2;                              //设置定时初始值
      TF0 = 0;                              //清除TF0标志
      TR0 = 1;                              //定时器0开始计时
      ET0 = 1;                              //使能定时器0中断
}

void main(void)
{
      EAXFR = 1;                //允许访问扩展的特殊寄存器,XFR。(32 位模式请使用这句,注释下一句)。
//      P_SW2 |= 0x80;      //允许访问扩展的特殊寄存器,XFR。(8 位模式请使用这句,注释上一句)。
      WTST = 0;                //设置取程序代码等待时间:赋值为0表示不等待,程序以最快速度运行。
      CKCON = 0;                //设置访问片内的xdata速度:赋值为0表示用最快速度访问,不增加额外的等待时间。
      
      P2M0 = 0x00; P2M1 = 0x00;                //设置P2口为准双向口模式。
      P3M0 = 0x00; P3M1 = 0x00;                //设置P3口为准双向口模式。

      usb_init();                //调用USB初始化函数。
      Delay20ms();
      Timer0_Init();      //调用T0初始化函数。
      Delay20ms();
      EA = 1;                        //打开总中断。
      
      {int i = 1; for (i = 1; i <= 300; i++) Delay20ms();}                //等待AIapp-ISP下载软件中串口自动打开。
      printf_usb("程序开始执行!\r\n");

      while (1)
      {
                //任务2:数组点亮LED,实现流水灯。
                if (Count_ms >= 500)
                {
                        Count_ms = 0;
                        P2 = ~State;
                        num++;
                        if (num > 7)
                        {
                              num = 0;
                        }
                }
                if(bUsbOutReady) //bUsbOutReady 标志位是用于标明 USB 主机发送完成。
                {
                        USB_SendData(UsbOutBuffer, OutNumber);
                        printf_usb("\r\n国芯MCU收到数\xfd据:%X\r\n", UsbOutBuffer);
                        P2 = UsbOutBuffer;
                        LED40_SetPort(2, P2);                //使用AIapp-ISP软件菜单“调试仿真接口”中的“擎天柱-LED-DIP40”进行模拟显示P2口。
                        usb_OUT_done();                        //本包接收的数据处理完毕,准备接收下一包数据。
                }
      }
}

2.4 任务3:按键1按一下,LED通过数组移动一下。
用新的实现方式实现了按键防抖,改进了以前方法,堵上一个漏洞。
代码如下:

//擎天柱核心板-Ai8051U-32K64,定时器周期性调度任务实验程序。
//
//任务3:按键按一下,LED通过数组移动一下。
//用到2025年4月8日发布的USB库(CDC):stc_usb_cdc_32g.LIB。
#include "ai8051u.h"
#include "ai_usb.h"

u16 Count_300 = 0;
u16 Count_600 = 0;
u16 Count_900 = 0;

u16 Count_ms = {0, 0, 0};
u8 State = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
u8 i;
u8 num = 0;
u16 Key_Val = 0;

void Delay20ms(void)      //@40.000MHz
{
      unsigned long edata i;
      _nop_(); _nop_();
      i = 199998UL;
      while (i--);
}

void Timer0_Isr(void) interrupt 1
{
      for (i = 0; i < 3; i++)
      {
                Count_ms++;
      }
}

void Timer0_Init(void)                //1毫秒@40.000MHz
{
      TM0PS = 0x00;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
      AUXR &= 0x7F;                        //定时器时钟12T模式
      TMOD &= 0xF0;                        //设置定时器模式
      TL0 = 0xFB;                              //设置定时初始值
      TH0 = 0xF2;                              //设置定时初始值
      TF0 = 0;                              //清除TF0标志
      TR0 = 1;                              //定时器0开始计时
      ET0 = 1;                              //使能定时器0中断
}

void main(void)
{
      EAXFR = 1;                //允许访问扩展的特殊寄存器,XFR。(32 位模式请使用这句,注释下一句)。
//      P_SW2 |= 0x80;      //允许访问扩展的特殊寄存器,XFR。(8 位模式请使用这句,注释上一句)。
      WTST = 0;                //设置取程序代码等待时间:赋值为0表示不等待,程序以最快速度运行。
      CKCON = 0;                //设置访问片内的xdata速度:赋值为0表示用最快速度访问,不增加额外的等待时间。
      
      P2M0 = 0x00; P2M1 = 0x00;                //设置P2口为准双向口模式。
      P3M0 = 0x00; P3M1 = 0x00;                //设置P3口为准双向口模式。

      usb_init();                //调用USB初始化函数。
      Delay20ms();
      Timer0_Init();      //调用T0初始化函数。
      Delay20ms();
      EA = 1;                        //打开总中断。
      
      {int i = 1; for (i = 1; i <= 300; i++) Delay20ms();}                //等待AIapp-ISP下载软件中串口自动打开。
      printf_usb("程序开始执行!\r\n");

      while (1)
      {
                //任务3:按键按一下,LED通过数组移动一下。
                P2 = ~State;
                if (Count_ms >= 1000)
                {
                        Count_ms = 0;
                        printf_usb("国芯MCU--Ai8051U\r\n");
                }
                if (Count_ms >= 5)
                {
                        Count_ms = 0;
                        if (0 == P32)                                                                //判断P32按钮是否按下。
                        {
                              Key_Val++;
                              if (5 == Key_Val)
                              {
                                        num = (num > 7) ? 0 : ++num;
                                        printf_usb("Ai8051U--%d--%d\r\n", num, Key_Val);
                              }
                        }
                        else
                        {
                              Key_Val = 0;
                        }
                }
                if(bUsbOutReady) //bUsbOutReady 标志位是用于标明 USB 主机发送完成。
                {
                        USB_SendData(UsbOutBuffer, OutNumber);
                        printf_usb("\r\n国芯MCU收到数\xfd据:%X\r\n", UsbOutBuffer);
                        P2 = UsbOutBuffer;
                        LED40_SetPort(2, P2);                //使用AIapp-ISP软件菜单“调试仿真接口”中的“擎天柱-LED-DIP40”进行模拟显示P2口。
                        usb_OUT_done();                        //本包接收的数据处理完毕,准备接收下一包数据。
                }
      }
}

2、文件的创建(.c和.h)
此部分讲解了多文件项目中如何创建新文件并添加到项目中,项目中如何在其它文件中引用别的文件,以及如何将代码拆分归类到不同文件。
2.1 新建新的“.c和.h”后缀的文件,并添加到项目中。两个文件同时建立,名称上只有后缀不同。一般一个.c和一个.h文件归类为执行一个外设或者一个任务或功能。这样可以让代码看起来简洁明了。


2.2 按照如下规则编写.c和.h文件。
新建的xxx.c和xxx.h文件是一组,代表一个功能块。xxx用实际名称全部代换。
(a)xxx.h格式:
#ifndef __XXX_H
#define __XXX_H
调用头文件
函数声明...
#endif
(b)xxx.c格式
#include “xxx.h”
函数定义
添加文件一定要记得引用路径和添加到工程里。
2.3 在项目其它文件的头部引用.h文件。
格式为:
#include "xxx.h"

3、结构体的介绍


结构体定义由关键字 struct 和结构体名组成,结构体名可以根据需要自行定义。
struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:

struct tag {
    member-list;
    member-list;
    member-list;
    ...
} variable-list;
tag 是结构体标签。
member-list 是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。

更多详细信息,请参考网址:https://www.runoob.com/cprogramming/c-structures.html

4、结构体数组的周期性任务调度

4.1 定义结构体,实现任务一,用结构体封装了与定时器有关的成员变量,用来保存对应的数据和函数实现的指向。
定义示例如下:

typedef struct
{
      u8 Run;                               //任务状态:Run/Stop
      u16 TIMCount;                         //定时计数器
      u16 TRITime;                        //重载计数器
      void (*TaskHook) (void);      //任务函数
} TASK_COMPONENTS;

static TASK_COMPONENTS Task_Comps[] =
{
      //状态计数周期函数
      {0,   1,   1,   执行功能},         
      {0,   10,    10,执行功能},      
};

4.2 用结构体实现任务一的代码。
代码如下:
attach://94320.c
attach://94317.h
attach://94316.c
attach://94322.h
attach://94321.c
attach://94319.h
attach://94318.c

心得感悟:

通过对周期性任务实现方法的学习,加深了对定时器/计数器功能的理解,同时也加深了对中断机制的理解。
在学习过程中,也学习到了一些代码编辑技巧以及出现问题如何解决的方法。
视频教程中讲解了3个任务,通过对这3个任务的学习,更能体会到条条大路通罗马的感觉,以及积累经验后才能找到相对比较优的路径的感觉,同时不断探索才可能发现新的路径。

总结:

通过学习本期视频,学到如下知识点:
1、定时器/计数器功能和中断机制相结合,能简化代码,提高程序性能,模拟出多任务的“假象”。
2、学习了数组的定义、使用。
3、学习了for循环的使用。
4、学习了在代码中如何注多行。
5、学习了新的按键防抖代码如何编写。
6、学习了项目文件中如何新建和引入.c和.h文件以及编写文件规则。
7、学习了结构体的定义、使用。

后记:
2025年4月16日,开始学习第八集:定时器周期性调度任务。








草木灰06 发表于 2025-4-13 21:38:35

学习了,厚书读薄{:qiang:}

我心飞扬 发表于 2025-4-12 15:05:30

《8051U深度入门到32位51大型实战教学视频》--学习打卡7

第七集:定时器中断

笔记:

1、定时器的介绍
1.1 定时器逻辑结构。

1.2 定时器/计数器:Ai8051U 系列单片机内部设置了 6 个 24 位定时器/计数器(8 位预分频+16 位计数)。6 个 16 位定时器 T0、T1、T2、T3、T4 和 T11 都具有计数方式和定时方式两种工作方式。

1.3 定时器/计数器0有4种工作模式:模式0(16位自动重装载模式),模式1(16位不可重装载模式),模式2(8位自动重装模式),模式3(不可屏蔽中断的16位自动重装载模式)。定时器/计数器1除模式3外,其它工作模式与定时器/计数器0相同。T1在模式3时无效,停止计数。定时器T2的工作模式固定为16位自动重装载模式。T2可以当定时器使用,也可以当串口的波特率发生器和可编程时钟输出。定时器3、定时器4、定时器11与定时器T2一样,它们的工作模式固定为16位自动重装载模式。T3/T4可以当定时器使用,也可以当串口的波特率发生器和可编程时钟输出。定时器11的工作模式固定为16位自动重装载模式。T11可以当定时器使用,也可编程时钟输出。
1.4 定时器寄存器:TM0PS、AUXR、TMOD、TL0、TH0、TF0、TR0、ET0的设置。










1.5 定时器用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作。
1.6 定时器功能与中断机制结合使用,可替代长时间的延时操作,释放MCU的运算资源,提高程序的运行效率和处理速度(可以打断主循环)。

2、定时器的应用

1.1 任务1:P2口的LED灯三秒取反一次,这期间任意时刻按下按钮P32/INT0,串口打印按键次数。
代码1如下:

//擎天柱核心板-Ai8051U-32K64,时器实验程序。
//任务1:P2口的LED灯三秒取反一次,这期间任意时刻按下按钮P32/INT0,串口打印按键次数。
//
//用到2025年4月8日发布的USB库(CDC):stc_usb_cdc_32g.LIB。
#include "ai8051u.h"
#include "ai_usb.h"

void Delay20ms(void)      //@40.000MHz
{
      unsigned long edata i;
      _nop_(); _nop_();
      i = 199998UL;
      while (i--);
}

void Timer0_Isr(void) interrupt 1
{
      P2 = ~P2;
      //LED40_SetPort(2, P2);
}

void Timer0_Init(void)                //3秒@40.000MHz
{
      TM0PS = 0x98;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
      AUXR &= 0x7F;                        //定时器时钟12T模式
      TMOD &= 0xF0;                        //设置定时器模式
      TL0 = 0xB1;                        //设置定时初始值
      TH0 = 0x00;                        //设置定时初始值
      TF0 = 0;                              //清除TF0标志
      TR0 = 1;                              //定时器0开始计时
      ET0 = 1;                              //使能定时器0中断
}

void main(void)
{
      int count = 0;      //按键计数变量
      unsigned char dat = 0xff;
      
      EAXFR = 1;                //允许访问扩展的特殊寄存器,XFR。(32 位模式请使用这句,注释下一句)。
//      P_SW2 |= 0x80;      //允许访问扩展的特殊寄存器,XFR。(8 位模式请使用这句,注释上一句)。
      WTST = 0;                //设置取程序代码等待时间:赋值为0表示不等待,程序以最快速度运行。
      CKCON = 0;                //设置访问片内的xdata速度:赋值为0表示用最快速度访问,不增加额外的等待时间。
      
      P2M0 = 0x00; P2M1 = 0x00;                //设置P2口为准双向口模式。
      P3M0 = 0x00; P3M1 = 0x00;                //设置P3口为准双向口模式。

      usb_init();                //调用USB初始化函数。
      Delay20ms();
      Timer0_Init();      //调用T0初始化函数。
      Delay20ms();
//      IE2 |= 0x80;      //使能USB中断。
      EA = 1;                        //打开总中断。
      
      {int i = 1; for (i = 1; i <= 300; i++) Delay20ms();}                //等待AIapp-ISP下载软件中串口自动打开。
      printf_usb("程序开始执行!\r\n");

      P2 = 0xAA;
      while (1)
      {
                if (0 == P32)                                                                //判断P32按钮是否按下。
                {
                        Delay20ms();                                                      //延时20ms消抖。
                        if (0 == P32)
                        {
                              printf_usb("按键按下次数\xfd:%d次\r\n", ++count);
                              while (0 == P32);                                        //等待P32松开。
                        }
                }
      
                if(bUsbOutReady) //bUsbOutReady 标志位是用于标明 USB 主机发送完成。
                {
                        USB_SendData(UsbOutBuffer, OutNumber);
                        printf_usb("\r\n国芯MCU收到数\xfd据:%X\r\n", UsbOutBuffer);
                        P2 = UsbOutBuffer;
                        LED40_SetPort(2, P2);                //使用AIapp-ISP软件菜单“调试仿真接口”中的“擎天柱-LED-DIP40”进行模拟显示P2口。
                        usb_OUT_done();                        //本包接收的数据处理完毕,准备接收下一包数据。
                }
      }
}

1.2 任务2:P2口的LED灯按一下点亮三秒后熄灭。
在任务1代码上修改部分如下:
(a)
void Timer0_Isr(void) interrupt 1
{
      P2 = 0xFF;
      TR0 = 0;                              //定时器关闭计时
}


(b)
//      Timer0_Init();      //调用T0初始化函数。

(c)
                //任务2:灯按一下点亮三秒后熄灭。
                if (0 == P32)                                                                //判断P32按钮是否按下。
                {
                        Delay20ms();                                                      //延时20ms消抖。
                        if (0 == P32)
                        {
                              P2 = 0x00;
                              Timer0_Init();                                                //调用T0初始化函数。
                              while (0 == P32);                                        //等待P32松开。
                        }
                }


1.3 任务3:救护车灯控制器,按下报警按钮,红蓝交替闪烁(LED1和LED2表示红和蓝灯),再按一下报警按钮,红蓝灯停止。
在任务1代码上修改部分如下:
(a)

void Timer0_Init(void)                //500毫秒@40.000MHz
{
        TM0PS = 0x19;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
        AUXR &= 0x7F;                        //定时器时钟12T模式
        TMOD &= 0xF0;                        //设置定时器模式
        TL0 = 0x99;                        //设置定时初始值
        TH0 = 0x05;                        //设置定时初始值
        TF0 = 0;                                //清除TF0标志
        TR0 = 1;                                //定时器0开始计时
        ET0 = 1;                                //使能定时器0中断
}


(b)

void Timer0_Isr(void) interrupt 1
{
        P2 = ~P2;
}


(c)


        u8 Run_State = 0;                //运行状态


(d)
//        Timer0_Init();        //调用T0初始化函数。


(e)

                //任务3:救护车灯控制器,按下报警按钮,红蓝交替闪烁(LED1和LED2表示红和蓝灯),再按一下报警按钮,红蓝灯停止。
                if (0 == P32)                                                                //判断P32按钮是否按下。
                {
                        Delay20ms();                                                        //延时20ms消抖。
                        if (0 == P32)
                        {
                                Run_State = !Run_State;
                                if (1 == Run_State)                                        //运行
                                {
                                        P2 = 0x0F;
                                        Timer0_Init();
                                }
                                else
                                {
                                        TR0 = 0;                                                //关闭定时器
                                        P2 = 0xFF;
                                }
                                while (0 == P32);                                        //等待P32松开。
                        }
                }


3、函数的定义、声明、调用
3.1 函数定义:包含返回值,函数名和入口参数,并定义了函数具体功能。
a. 函数的名称应当能够描述函数的功能,便于代码的阅读和理解。
b. 函数名称应当使用有意义的英文单词或者组合的英文单词,避免使用特殊字符或数字。
c. 函数名称不能与C语言的关键字同名。

返回值类型 函数名(入口参数)
{
      // 函数体
      // 函数执行的代码
      return 返回值;
}

3.2 函数声明:在头文件中或者被调用之前使用,注意末尾要加分号。


返回值类型 函数名(入口参数);


3.3 函数调用:在需要调用的地方直接使用函数名,加上括号和分号。如果有入口参数的,需要在括号的多个参数之间加逗号隔开。

函数名(入口参数);

学习心得:

1、此集视频虽然内容不多,但是定时器/计数器很基础,基于MCU的核心功能加法器,基于中断,在时间维度上巧妙地解决了MCU实现多任务的方法。
2、此集看过多遍,了解了定时器/计数器的逻辑结构以及设置步骤。
3、学习了设置定时器/计数器时用到的寄存器的功能、作用。
4、掌握了利用AIapp-ISP下载软件中生成代码的技巧;掌握在芯片手册中由端口到寄存器,再到设置寄存器每一位的方法。
5、此集在讲解知识点时也并行地介绍了一些代码编辑的技巧和方法,对提高编程效率有帮助。

总结:

1、此集看过多遍,在陆陆续续整理思路,我心里清楚我对MCU的理解又深了一层。
2、比较清晰地掌握了定时器/计数器的逻辑结构、功能、设置、使用方法,更高级的使用后续要继续学习与研究。
3、初步掌握了中断的知识。
4、通过3个实例代码的编写、修改,发现初学者往往困在知识经验少上,积累需要时间。有一本通俗易懂、简洁全面的好芯片手册很重要。

后记:

修修改改终于写完此集笔记。
2025年4月16日。

我心飞扬 发表于 2025-4-12 12:17:30

《8051U深度入门到32位51大型实战教学视频》--学习打卡6

第六集:IO输入输出

笔记:

1、什么是GPIO
1.1 GPIO(General Purpose I/O Ports)是通用输入/输出端口,是一些芯片引脚,可以通过它们输出高低电平或者读入引脚的状态(是高电平或是低电平)。
1.2 实践中高电平是指接近于芯片电源正极电压(Vcc)的电平,也叫逻辑“1”;低电平就是接近芯片GND电压(0V)的电平,也叫逻辑“0”。
1.3 GPIO的工作模式有4种。


1.4 芯片上电时默认施密特触发模式是打开的,可以在程序中设置端口寄存器的值为“0”来关闭。在Vdd=3.3V时,高电平为大于1.18V(打开)或1.09V(关闭),低电平为低于0.99V(打开)或1.07V(关闭)。


2、按键输入检测
2.1 按键是接在MCU的端口上的,端口状态可在端口对应的寄存器中读写。读取按键的状态就是读取按键的对应IO口的电平,也就是读取端口寄存器的状态。

2.2 因为机械按键按下或者松开有抖动,一般在20ms内。按键抖动的出现会影响程序的正常执行。代码中要加入按键防抖处理。

2.3 利用AIapp-ISP下载软件中的“软件延时计算器”功能生成延时函数代码,用于按键防抖处理。


3、课后小练
3.1 任务1:按一下P32按钮灯亮,按一下P33按钮灯灭。

3.2 任务2:按一下亮一颗灯,在按一下亮两颗灯,直到全亮(变量+加法和乘法)


感受:

1、实现端口功能时要先设置端口状态。
2、要了解接口电路才能理解代码为什么这样编写。
3、编码出现问题时要仔细检查端口设置,仔细看芯片手册。

总结:

1、学习了端口的硬件和端口寄存器的对应,端口如何表示不同状态。
2、初步学习了端口的4中工作模式。
3、学习了如何读写端口状态。
4、学习了一种按键防抖的技术。

页: [1] 2
查看完整版本: 学习Ai8051U的总结笔记--《8051U深度入门到32位51大型实战视频》--打卡