找回密码
 立即注册
查看: 1124|回复: 6

跟着冲哥学习AI8051U的打卡记录帖

[复制链接]
  • 打卡等级:偶尔看看III
  • 打卡总天数:37
  • 最近打卡:2025-02-01 23:25:00

5

主题

16

回帖

261

积分

中级会员

积分
261
发表于 2024-12-7 16:17:04 | 显示全部楼层 |阅读模式

赶上了STC和嘉立创的活动,想着趁机会好好学一下AI8051U,下面是学习打卡记录
(前六次的不是很懂,发了很多个零散的帖子,现在是整合成一个帖子,后续的学习都会在这里记录,保持好奇心,继续学习吧)


今天看了序言和第一集。
AI8051U强在
1:优化了qspi,使得屏幕显示和视频播放更加的流畅。
2:能完成IIS的录音和放音
3:更好的兼容了PWM_DMA
4:结合IIS的录音放音,能在isp软件中的fft调试接口中显示出来
5:手写计算器,使之更加像是AI
6:QSPI、PWM移相、硬件乘除、单精度浮点等方面。

第一集:介绍了实验箱的功能和必要的软件工具


同时,PCB是空板子,PCBA是完全焊接好元器件的板子。
软件工具下载部分:
1:keil软件,需要下载C51和C251版本,分别对应8位和32位的开发环境,建议使用32位环境,这样能更好的发挥AI8051U-34K64的性能。
keil下载地址:Keil Product Downloads

手册里面有说加上,mdk(开发stm32等arm内核芯片)三者可以共存,需要在下载的时候把文件放在同一个地方,然后安装的时候一直跳过,就好了。

实测安装完能够使用:

2:ISP软件
直接在这里下载:深圳国芯人工智能有限公司-工具软件
然后把Keil 中断拓展插件也给下载安装。
3:添加头文件到keil_v5的目录下

然后这里一个是c251使用的头文件,一个是c51使用的头文件,两者是同名不同地址的文件,内容也不一样,官方不建议修改和复制。
4:然后就是下载实验箱的资料,然后找对应的跑马灯历程,测试开发环境。

5:下载,这里使用的是HID下载,具体实现流程是
先连接type-c,将p3.2接地,然后按下电源按键,进行单片机的断电上电。然后会提示,识别到了HID-设备,然后点击烧录就能烧录程序了。
视频是演示。板子是我借来的,我画的板子刚发券提交打样。{:5_312:}(确实是我搞得晚了点){:5_344:}
PS:板子上没预留led,所以直接飞了一个led,然后视频剪辑有点粗糙。省流(演示了hid识别和烧录,成功闪烁p0.0。)

代码是把官方的跑马灯小改了一下

好啦,今天就到这了,明天继续学习,加油

AI8051U环境配置和p0.0灯闪烁.mp4

23.58 MB, 下载次数: 64

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:37
  • 最近打卡:2025-02-01 23:25:00

5

主题

16

回帖

261

积分

中级会员

积分
261
发表于 2024-12-7 17:28:55 | 显示全部楼层
跟着冲哥学习打卡第二天:
今天学习了点亮一个led灯。
额外小tips:
keil中有三个按钮:1:translate:编译当前改动的文件。只检查语法错误,不生成可执行文件(也就是hex文件)。2:build:编译工程中当前修改的文件及依赖于此文件的所有文件。检查语法错误,并重新链接生成可执行文件。如果工程首次编译,会直接调用Rebuild,所以常使用build。3:rebuild:全部文件进行编译,并重新链接生成可执行文件。所以一般有多个.c文件的时候使用rebuild。
一:新建工程:
1:创建空工程(参考手册6.5章节)

(1):创建新工程,选择mcu型号为AI8051U-32Bits Series
(2):新建文件,保存为main.c然后添加到工程中。
(3):工程设置:(1:选择Source 251、(2:勾上4 Byte、(3:选择xsmall模式、(4:选择large 64。如果超过64k,则需要设置以下地址:(5:勾选创建hex文件,格式为hex-80。
截图202412071723462007.jpg

2:添加头文件
(1):可以在ISP软件中找到,然后保存文件到当前工程目录下。命名和头文件定义一样为AI8051U.h。
截图202412071724062945.jpg
截图202412071724217641.jpg
头文件的引用建议使用""格式,方便后期将代码分享给他人,他人能直接编译使用。

(2):设置keil的代码缩进、字体、字号和编码。
点开扳手工具,然后设置:
截图202412071724468632.jpg
(1:  将缩进设置为4
截图202412071725021605.jpg
(2:  设置字体、字号。
截图202412071725172716.jpg
(3:  设置编码格式。我这里使用utf-8,因为在keil中这个编码对应的字体,我觉得比较好看。
截图202412071725334769.jpg
3:编写代码,烧录测试
(1):输入如下代码并且编译:

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

void main (void)                //主函数
{  
        P0M0 = 0X00;
        P0M1 = 0X00;                //配置P0端口为准双向口
      
        P4M0 = 0X00;
        P4M1 = 0X00;                //配置P4端口为准双向口
      
        P40 = 0;                        //P40输出低电平,打开控制LED的开关
      
        while(1)                        //逐行执行的死循环
        {
                P00 = 0;                //P00输出低电平,点亮LED
        }
}
截图202412071725544146.jpg
编译通过,无报错无警告。
(2):部分代码讲解:

        P0M0 = 0X00;
        P0M1 = 0X00;                //配置P0端口为准双向口
      
        P4M0 = 0X00;
        P4M1 = 0X00;                //配置P4端口为准双向口

如图实验箱中的跑马灯端口为P0,P40为控制开关,为0时,三极管导通,led给电。
截图202412071726072233.jpg
同时ai8051u的io可配置为四种模式:
截图202412071726201364.jpg
并且用户使用之前需要配置io端口模式,一般准双向口即可完成大部分需求。
如何配置为准双向口:
截图202412071726332684.jpg
截图202412071726439494.jpg
截图202412071726524113.jpg
对应代码即为

        P0M0 = 0X00;
        P0M1 = 0X00;

可以在isp软件中快速配置
截图202412071727117342.jpg

4:烧录测试。
ps:先欠着,板子还在打样
最后附上完整工程,加油!


demo_led.zip

39.42 KB, 下载次数: 67

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:37
  • 最近打卡:2025-02-01 23:25:00

5

主题

16

回帖

261

积分

中级会员

积分
261
发表于 2024-12-7 17:31:58 | 显示全部楼层

跟着冲哥学习打卡第三天-USB不断电下载!

嫌弃每次下载都需要按下P3.2接地和断电按键?
没关系,现在可以使用USB配合官方库实现USB不断电下载!
配置流程:

1:实验演示最后面放视频。
2:下载所需文件:
(1):官方库文件
(2):官方例程
深圳国芯人工智能有限公司-库函数里面的USB库文件,下载代码+例程,解压缩。


打开这个

还有前面的这个

把这两个文件复制到你的keil工程中

查询模式是等待代码执行完一圈之后才去执行,而中断模式是一有信号就马上执行,为了保证代码的完整运行,这里选择查询模式。
3:移植关键部分到工程
(1):添加头文件


双击第一张图片中的Source Group 1,选中.lib文件,并在代码中加入
#include "stc32_stc8_usb.h"                //USB CDC 头文件
这句代码。
(2):USB初始化函数

(3):命令参数

这里需要和isp下载软件中的一致,是用作命令识别的,识别成功才会进入hid下载模式

这里选择默认即可。
(4):打开P_SW2和IE2寄存器(只修改某一个位)

如图,(1:使能访问XFR寄存器。 (2:使能USB中断和总中断



具体可看手册对应章节。

这部分代码直接移植官方例程的即可。
最后附上演示视频。
PS(又是学习的一天


USB不断电下载.mp4

3.82 MB, 下载次数: 65

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:37
  • 最近打卡:2025-02-01 23:25:00

5

主题

16

回帖

261

积分

中级会员

积分
261
发表于 2024-12-7 17:42:44 | 显示全部楼层


跟着冲哥学习AI8051U的第四天-USB-CDC串口打印数据+C语言基础学习

嗨嗨嗨,第四天咯!今天学习了使用USB-CDC串口打印数据+C语言基础学习。

1:先来看看USB-CDC串口,printf函数打印数据:
(1)、在stc32_stc8_usb.h中把#define PRINTF_HID的注释给去掉。

(2)、编写代码:

代码的效果是USB如果接收到了数据,执行printf语句,输出”知道了知道了“\r\n是换行。
(3)、设置ISP里面的选项,烧录代码:
(1:注意这里的串口波特率需要和CDC-HID串口助手中的波特率一致,不然会出现通信超时(之前遇到了,找了好久才看到,我波特率不匹配
(2:芯片的主频选择是24mhz。
(3:串口的波特率可以随意选择,因为实际是USB通信。


(4)、实际测试:

这里选择921600的波特率,CDC串口收发也是正常的。
(5)、可能的bug:




接收乱码。编码格式不对,ASCII改成UTF-8就行了。

(6)、printf代码大致讲解:

这里说的fmt有两种参数:一种就是普通字符,就是在代码中原封不动打印到CDC串口上的数据
还有一种就是转换说明:不直接输出,用于参数的格式转化和打印,由%开始,表示输出数据的类型、宽度、精度。
比如:
代码前面,已经定义X的数据为20。
fmt的参数可以有多个,但是得对应前面的%,不然大概率会报错,或者输出不合理的数据。
然后具体一点的解释和相对应的%后面的格式字符和转义字符可以看下面。



2、数的进制:2进制、10进制、16进制
(1):主打一个其他进制转化成10进制的计算方法:(对,这个叫做按权相加)
可看博客:二进制、八进制、十六进制与转换_计算机二进制八进制十六进制转换成十进制例题-CSDN博客


3、数据的基本类型:

如果需要用到double,就需要加上#pragma float64
因为double在C251中是64位的。
然后基本的数据类型就多看多用就好了。

4、C语言的运算符:
(1):算术运算符:

加减乘除取模加加减减。
(2)、关系运算符:

比较关系的,然后0为假,1为真。
(3)、逻辑运算符:

与或非!
(4)、赋值运算符:

按位异或、与、或还是比较常见的
(5)、位运算符:

位运算用的比较多,操作寄存器嘛。
(6)、其他运算符:

得学,会用到的,特别是指针和结构体。


5:代码和附件(是前一天的代码基础上改的,其实没太多变化)
PS:加油,继续学习!





demo_code.zip

88.08 KB, 下载次数: 60

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:37
  • 最近打卡:2025-02-01 23:25:00

5

主题

16

回帖

261

积分

中级会员

积分
261
发表于 2024-12-7 17:49:23 | 显示全部楼层
一眨眼也是第五天了。今天跟着冲哥学习了IO的输入输出


还是先上冲哥的摘要图:



1:什么是GPIO

(1):GPIO就是通用输入/输出端口。就是一些引脚,然后可以通过他们输出高低电平,或者通过他们读入引脚的状态-高电平或者低电平。

然后1高0低。具体的解释可以看下面的图片:



(2)、GPIO的模式:


一般最常用的就是准双向口,然后如果配置使用led的话,用灌电流

好一点,比较通用。


(3)、引脚输入电压的判断(高低电平):


要注意是否打开施密特触发器。和留意电压数值,方便后期用万用表测量电压找问题。


2、按键检测:

(1)、按键原理:


就是按下,按键两脚就导通,松开就断开。


(2)、当堂练习:

这里举例第三个



为什么呢?

这里需要给按键进行消抖。


(3)、按键消抖:

包括硬件消抖(电容滤波)和软件消抖(使用软件延时来度过按键抖动期)

这里使用软件延时。


同时需要加上三句话:


    WTST = 0;                                          //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快

    EAXFR = 1;                                         //扩展寄存器(XFR)访问使能

    CKCON = 0;                                         //提高访问XRAM速度


ISP软件里面可以计算,选好参数,复制粘贴就行。



(4)、keil小tips:



3、课后练习:


也是写了一下,代码在代码里面了,练习一没有验证,练习二验证应该是没问题的。



安啦,又是学习的一天!



GPIO.zip

91.58 KB, 下载次数: 65

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:37
  • 最近打卡:2025-02-01 23:25:00

5

主题

16

回帖

261

积分

中级会员

积分
261
发表于 2024-12-7 17:55:03 | 显示全部楼层
跟着冲哥学AI8051U的第六天-IO定时器中断:


终终终终终终终于更新啦。今天学习IO定时器中断:

老规矩,先上图。


这一部分是讲述了软件空循环延时的占用CPU,然后引进了定时器中断。


经典本章摘要。

1、定时器的介绍:



(1)、任务一:


然后这里演示的是在ISP软件中配置需要的定时器中断参数:

这里是:


(2)、GB2312的\xfd字符纠正和isp设置下载完成一秒钟后自动打开串口:


相关手册说明:




如果用的UTF-8,好像是没有出现这个问题的,(至少我打”数“能正常的显示)


2、定时器应用:(介绍相关的硬件配置和参数说明)(为了节省一些时间,我直接贴笔记了)











定时器的频率、定时时间的计算:




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

(1)、基础知识:



这里的函数没有返回值,所以直接使用void开头。


(2)、具体的使用:



(3)、今日任务:


没有实验箱,所以直接使用串口调试的方式。具体代码见:code_demo.zip


(4)、思考题:


复用定时器?应该就是结合之前的函数,写成传参类型的函数,把定时器的初值传进去,然后就能定时不一样的时间了。


4、课后练习:


太典了,功德!!!

当然写了,但是感觉和题目的意思有点偏离了。先看效果:


可以看到这里能正常的切换功德时间,然后也能正常的点亮功德和计算功德。


但是再点亮功德的过程中就算你多次按下按键,再功德点亮成功之前,功德数是不会增加的。

可能题目要求的意思是点亮功德完成后,显示当前的功德数,就是再点亮期间按键累加的功德数也一并加上去,最后显示当前功德数量。

具体看代码把(Code_AfterClass_Test.zip)写的不是很好,讲究看看(狗头保命.jpg)

期待明天的课程!


Code_AfterClass_Test.zip

96.1 KB, 下载次数: 60

demo_code.zip

93.99 KB, 下载次数: 67

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:37
  • 最近打卡:2025-02-01 23:25:00

5

主题

16

回帖

261

积分

中级会员

积分
261
发表于 2024-12-8 11:36:16 | 显示全部楼层

今天是跟着冲哥学习AI8051U的第七天啦!——定时器周期性任务调度

在开始今天的摘要之前,先来看一下前一节课——IO定时器中断可能会出现的一些错误。

  1. u8写成了大写的U8,导致 #define u8 unsigned char识别错误了
  2. 中文分号混淆 ,(中文)、,(英文),这个应该很明显,编译的时候,报错,点击进去看看前后应该是很好发现的。
  3. **缺少了相对应的 }**这个就应该学一下代码的编写风格,然后注意好缩进,也比较方便看出来的。
    然后还有一个小tip:
    在ISP软件中显示串口打印的时间戳:
    • 开启****显示发送的数据和数据分包显示就行了,这样也可以通过串口打印的时间来大致查看定时器的定时是否准确。如下图:

    • image-20241208091109932.png

    • OK,也是进入今日的摘要部分啦,如下图

      image-20241208091335757.png

      分成以下四个部分:

      1. 周期性任务介绍

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

      3. 结构体的介绍

      4. 结构体数组的周期性任务调度
        一、先来看第一点(周期性任务介绍):
        周期性任务介绍:所谓的周期性任务就是每隔一定的时间就去执行一个任务,比如每隔300ms点亮一次LED

        image-20241208091749897.png
        对应这里的任务一:很简单明了,只需要

        • 通过设置变量来计数,每进一次1ms的定时器设置中断,这个变量就自加一次,再判断满足临界条件就可以执行任务了。以下列出部分代码,具体代码可以看附件。

          //变量定义部分:
          u8 State1 = 0;                  //LED1初始状态为0
          u8 State2 = 0;                  //LED2初始状态为0
          u8 State3 = 0;                  //LED3初始状态为0
          
          //u16 Count_300 = 0;                //计数300ms变量
          //u16 Count_600 = 0;                //计数600ms变量
          //u16 Count_900 = 0;                //计数900ms变量
          u16 Count_ms[3] = { 0, 0, 0 };      //三个计时变量
          
          //函数定义 这部分是定时器的1ms中断代码
          void Timer0_Init(void)      //1毫秒@24.000MHz 24位自动重载
          {
              TM0PS = 0x00;           //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
              AUXR &= 0x7F;           //定时器时钟12T模式
              TMOD &= 0xF0;           //设置定时器模式
              TL0 = 0x30;             //设置定时初始值
              TH0 = 0xF8;             //设置定时初始值
              TF0 = 0;                //清除TF0标志
              TR0 = 1;                //定时器0开始计时
              ET0 = 1;                //使能定时器0中断
          }
          
          //定时器1ms中断函数
          void Timer0_Isr(void) interrupt 1           //1ms执行一次。
          {
              for(i = 0; i < 3; i++)                  //可以使用for循环+数组
              {                                       //或者是注释部分的三个变量
                  Count_ms[i] ++;
              }
          
          //  Count_300++;
          //  Count_600++;
          //  Count_900++;
          
          }
          
          //主函数部分代码:printf打印是为了串口显示灯的状态(没实验箱(悲伤),但是应该快有了
                  if(Count_300 >= 300)                //300MS达到
                  {
                      Count_300 = 0;
                      State1 = !State1;
                      P00 = State1;                   //LED1取反
          //          printf("P00 = %d\r\n",State1);
                  }
                  if(Count_600 >= 600)                //600MS达到
                  {
                      Count_600 = 0;
                      State2 = !State2;
                      P01 = State2;                   //LED2取反
          //          printf("P01 = %d\r\n",State2);
                  }
                  if(Count_900 >= 900)                //900MS达到
                  {
                      Count_900 = 0;
                      State3 = !State3;
                      P02 = State3;                   //LED3取反
          //          printf("P02 = %d\r\n",State3);
                  }
          

          然后这里贴出一张结果截图:

          image-20241208093042046.png

          然后这里也可以用作是一些需要周期性定时的任务上,比如说需要1秒钟使得蜂鸣器响一下。使用这个的好处是不会干扰主函数的执行,不像nop延时那样在主函数中执行完才能执行下面的代码。

        • 数组的定义和使用
          可以理解成数组就是加长版的变量?或者说一次性定义多个变量吧,这样比较好理解。

          image-20241208094005616.png

          数组是从0开始索引的,老师说过一句话,计算机人的数学是从0开始的,而非计算机人是从1开始的。类似1G等于1024M,而不是1000M.

          • 数组的定义和赋初始值:例如 u16 Count_ms[3] = { 0, 0, 0 }; //三个计时变量

          • 数组变量的修改(数组名称+索引+等号+数据:State[0] = 2; //给数组写入数据

          • Keil小Tip:选中多行,按住shift+tab,可以向前缩进。

          • 所以在中断函数代码中就可以这样修改

            //中断服务函数可以这样编写,串口打印老配方、同时把点灯放在了里面,不占同主函数了:  
                if(Count_ms[0] >= 300)
                {
                    Count_ms[0] = 0;
                    State1 = !State1;
                    P00 = State1;                   //LED1取反
                    printf("P00 = %d\r\n",State1);
                }
            //      printf("当前是State数组的第%d个数据%d\r\n", (int)i);
            
        • 用数组点亮流水灯

          • 需要按照这样的顺序依次点亮P0端口的LED灯,以便形成一个流水灯效果:
          • b0          b7
                低           高
                LED0    -   LED7
                0 1 1 1 1 1 1 1
                1 0 1 1 1 1 1 1
                1 1 0 1 1 1 1 1
                1 1 1 0 1 1 1 1
                1 1 1 1 0 1 1 1
                1 1 1 1 1 0 1 1
                1 1 1 1 1 1 0 1
                1 1 1 1 1 1 1 0
                */
            
          • 很简单,就用一个数组,里面的每一个变量单独表示点亮一个灯,对应上面的某一行的状态。
          • 正向写比较麻烦,这里使用逆向写法,同时在后续P0端口赋值的地方,取反再赋值就好了。
          • u8 State[9] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
          • //代码:
                if( Count_ms[0] >= 500 )
                {
                    Count_ms[0] = 0;
                    P0 = ~State[num];
                    printf("P0数值:%x\r\n", P0);
                    num ++;
                    if(num > 7)
                    {
                        num = 0;
                    }
                }
            
          • 效果:image-20241208095651079.png
        • *周期性任务介绍:任务3:按键P32按一下,LED通过数组移动一下****这里不能使用while来判断是不是没有松开按键了,不然会一直卡再while循环里面,导致下面的代码无法执行

          • //对应这里就是当一直按住P32 num++ 及其后面的语句一直无法执行。
                    if(P32 == 0)
                    {
                        Delay10ms();
                        if(P32 == 0)
                        {
                            while(P32 == 0);
                            num ++;
                            printf("P0数值:%x\r\n", P0);
                        }
                    }
            
          • *可以用成定时器周期性任务来解决****具体就是检测到按键连续按下,按键计数变量+1,只要松开一下,计数清0,计数累计到50ms的时候判定为按下。
          • //对应的就是下面,实现的效果就是按键一直按下的,printf打印不会受到干扰,依旧可以执行下去。
                if(Count_ms[1] >= 1000)
                {
                    Count_ms[1] = 0;
                    printf("Ai8051U\r\n");
                }
            
                if(Count_ms[2] >= 10)           //按键持续按下50ms也会判定为按下按键,然后按一下加一下,实现了按键检测和串口打印的互不干扰。
                {
                    Count_ms[2] = 0;
                    if(P32 == 0)
                    {
                        Key_vol ++;
                        if(Key_vol == 5)
                        {
                            num ++;
                        }
                        printf("num数值:%d\r\n", num);
                    }
                    else
                    {
                        Key_vol = 0;
                    }
                }
            
          • 效果图:
          • image-20241208100604430.png
            二、接着看第二点:文件的创建(.c和.h)
        • 模块化编程,方便移植!?反正是很有用处的,文件多的时候,总不可能一直都放在一个main.c里面吧,看着多累啊。

          image-20241208100928698.png

          1. 先建立一个用户文件夹:user

          2. 在keil里面新建.c和.h文件,然后保存,命名为config.c(.h)。

          3. 添加文件到keil工程里面。

          4. 写好必备的语句,像是一下main.c里面的头文件啥的都可以放过来,然后再main.c里面就引用 #include "config.h"

          5. //1.在.h文件里面:
            #ifndef __CONFIG_H
                #define __CONFIG_H
            
                #include "ai8051u.h"            //调用头文件
                #include "stc32_stc8_usb.h"     //USB CDC 头文件
                #include "intrins.h"            //调用头文件
            
            
                #define u8 unsigned char        //8位无符号变量(0-255)
                #define u16 unsigned int        //16位无符号变量(0-65535)
            
                void Sys_Init(void);            //函数声明
                void Timer0_Init(void);         //1ms定时函数声明
            
                #endif
            
            //2. 在.c文件里面
            #include "config.h"
            
          6. 在锤子里面的C251选项把新建的子文件夹添加到工程的目录里面,不然是找不到的。

          7. image-20241208101726153.png

            image-20241208101737374.png

          8. **编译,没有报错,恭喜你拥有了自己的自定义函数!
            image-20241208102045236.png
            **

          9. 接下来就是移植其他的一些配置代码了,然后这里就不说了,后面看附件吧。?

        三、重中之重啊:结构体数组的周期性任务调度

        • 老规则的PPT介绍

          image-20241208102255517.png

        • 结构体还是很重要的,在c语言中,嵌入式编程中很常见的。

          image-20241208102419830.png

        • 结构体可以理解成用户自定义的一个数据类型,然后使用也是数组一样,类型+名称+赋值

        • 然后下面的代码都可以在官方提供的实验箱代码里面找到,具体是第27个(27-通过定时器周期性调度任务综合例程,简单实用的任务调度系统,推荐)整体下载链接

        • 这里新建了一个Task.h和Task.c来编写结构体相关的代码。

          • 所以这里 task.h的使用:这里有三个变量和一个函数组合成一个新的结构体类型
          • typedef struct                                  //固定搭配
            {
                u8 Run;               //任务状态:Run/Stop   //1的时候执行
                u16 TIMCount;         //定时计数器           //自减到0,置位run
                u16 TRITime;          //重载计数器           //重载给TIMCount
                void (*TaskHook) (void); //任务函数         //run到1,就执行的函数
            } TASK_COMPONENTS;                             //结构体的名字
            
        • 然后这里是结构体的使用:这里定义了一个结构体数组,对应上面的三个变量和一个函数

          • static TASK_COMPONENTS Task_Comps[]=
            {
            //状态  计数  周期  函数
                {0, 300,  300,  LED0_Blink},          /* task 1 Period: 300ms */
                {0, 600,  600,  LED1_Blink},          /* task 2 Period: 600ms */
                {0, 900,  900,  LED2_Blink},          /* task 3 Period: 900ms */
                {0, 1000, 1000, Printf_1S },          /* task 4 Period: 1000ms */
                {0, 10,   10,   Key_Task },           /* task 5 Period: 10ms */
                /* Add new task here */
            
            };
            
        • 然后记录一下关于如何使用

          • u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps[0]); 
            //这里使用一个sizeof,通过判断字节数来看Task_Comps[]数组里面有多少个任务,这里是五个。后面的for循环需要用到。
            
            //========================================================================
            // 函数: 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++)         //for循环执行每一个任务
                {
                    if(Task_Comps[i].TIMCount)      /* If the time is not 0 */ //当不为0时
                    {
                        Task_Comps[i].TIMCount--;   /* Time counter decrement */ //自减
                        if(Task_Comps[i].TIMCount == 0) /* If time arrives *///为0
                        {
                            /*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 *///设置标志位状态,run为1了,该去执行下面函数了。
                        }
                    }
                }
            }
            
            //========================================================================
            // 函数: Task_Pro_Handler_Callback
            // 描述: 任务处理回调函数.
            // 参数: None.
            // 返回: None.
            // 版本: V1.0, 2012-10-22
            //========================================================================
            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 */      //判断run的状态
                    {
                        Task_Comps[i].Run = 0;      /* Flag clear 0 */  //也是重新设置为初始状态,run=0
                        Task_Comps[i].TaskHook();   /* Run task */      //去执行下面的函数
                    }
                }
            }
            
        • 然后后面的事情就很简单了,就是使用部分了。

          • //task.c    这里设置了五个,对应300、600、900、1000、10ms的周期性定时任务。
            static TASK_COMPONENTS Task_Comps[]=
            {
            //状态  计数  周期  函数
                {0, 300,  300,  LED0_Blink},          /* task 1 Period: 300ms */
                {0, 600,  600,  LED1_Blink},          /* task 2 Period: 600ms */
                {0, 900,  900,  LED2_Blink},          /* task 3 Period: 900ms */
                {0, 1000, 1000, Printf_1S },          /* task 4 Period: 1000ms */
                {0, 10,   10,   Key_Task },           /* task 5 Period: 10ms */
                /* Add new task here */
            
            };
            
            //然后是新建了一个IO.c和.h专门来存放关于IO操作的函数
            #include "io.h"
            
            u8 State1 = 0;                  //LED1初始状态为0
            u8 State2 = 0;                  //LED2初始状态为0
            u8 State3 = 0;                  //LED3初始状态为0
            
            u16 Key_Vol =0;                 //按键维持时间
            
            void LED0_Blink(void)
            {
                State1 = !State1;
                P00 = State1;
                printf("P00 = %d\r\n",(int)State1);
            }
            
            void LED1_Blink(void)
            {
                State2 = !State2;
                P01 = State2;
                printf("P01 = %d\r\n",(int)State2);
            }
            
            void LED2_Blink(void)
            {
                State3 = !State3;
                P02 = State3;
                printf("P02 = %d\r\n",(int)State3);
            }
            
            void Printf_1S(void)
            {
                printf("AI8051U\r\n");
            }
            
            void Key_Task(void)
            {
                if(P32 == 0)
                {
                    Key_Vol ++;
                    if(Key_Vol == 5)
                    {
                        //执行按键按下的任务
                        printf("按键单击\r\n");
                    }
                }
                else
                {
                    Key_Vol = 0;
                }
            }
            
          • 最后整理一下文件,重新编译,烧录,看看ISP串口打印效果。可以看到完美执行了前面设置的五个定时器周期性任务。完美??

            image-20241208105459093.png

          • 嗷嗷,keil工程尽量英文命名,防止出现各种编译报错:
            image-20241208105323725.png

          • 附件的说明和下载:

            1. demo_code.zip是前面的结构体之前的代码
            2. code_creat_file.zip是创建结构体和编写结构体周期性函数的代码
            3. 按需下载?
              四、酣畅淋漓的学习
      5. 一次很棒的教程,学到了很多,感谢冲哥、STC、立创!?

      6. 也是首次使用markdown来编写文章?

      7. 很奇怪的感觉,写是写了,但是好像不能直接复制粘贴上来显示。有些格式好像不对?

      8. upload 附件:demo_code.zipupload 附件:demo_code.zip

回复 支持 反对

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2025-5-8 15:00 , Processed in 0.129676 second(s), 86 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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