xiao_a_bin 发表于 2024-12-7 16:17:04

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


赶上了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。)

代码是把官方的跑马灯小改了一下
https://www.stcaimcu.com/data/attachment/forum/202411/26/212621t1u8rpjmpe8jte6f.jpg
好啦,今天就到这了,明天继续学习,加油{:4_164:}

xiao_a_bin 发表于 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。


2:添加头文件
(1):可以在ISP软件中找到,然后保存文件到当前工程目录下。命名和头文件定义一样为AI8051U.h。


头文件的引用建议使用""格式,方便后期将代码分享给他人,他人能直接编译使用。

(2):设置keil的代码缩进、字体、字号和编码。
点开扳手工具,然后设置:

(1:将缩进设置为4

(2:设置字体、字号。

(3:设置编码格式。我这里使用utf-8,因为在keil中这个编码对应的字体,我觉得比较好看。

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
      }
}

编译通过,无报错无警告。
(2):部分代码讲解:

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

如图实验箱中的跑马灯端口为P0,P40为控制开关,为0时,三极管导通,led给电。

同时ai8051u的io可配置为四种模式:

并且用户使用之前需要配置io端口模式,一般准双向口即可完成大部分需求。
如何配置为准双向口:



对应代码即为

      P0M0 = 0X00;
      P0M1 = 0X00;

可以在isp软件中快速配置


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


xiao_a_bin 发表于 2024-12-7 17:31:58




跟着冲哥学习打卡第三天-USB不断电下载!
嫌弃每次下载都需要按下P3.2接地和断电按键?
没关系,现在可以使用USB配合官方库实现USB不断电下载!
配置流程:
https://www.stcaimcu.com/data/attachment/forum/202411/28/105705gw18hy5x8u129wur.jpg
1:实验演示最后面放视频。
2:下载所需文件:
(1):官方库文件
(2):官方例程
深圳国芯人工智能有限公司-库函数里面的USB库文件,下载代码+例程,解压缩。
https://www.stcaimcu.com/data/attachment/forum/202411/28/105935afe3sed3nnk3zzdd.jpg
https://www.stcaimcu.com/data/attachment/forum/202411/28/110008s0p334r3z0zu6u3u.jpg
打开这个
https://www.stcaimcu.com/data/attachment/forum/202411/28/110029vfwwwzu729cfqis7.jpg
还有前面的这个
https://www.stcaimcu.com/data/attachment/forum/202411/28/110053lmzeexy9999eh0sb.jpg
把这两个文件复制到你的keil工程中
https://www.stcaimcu.com/data/attachment/forum/202411/28/110135nmt03lliz77mt71t.jpg
查询模式是等待代码执行完一圈之后才去执行,而中断模式是一有信号就马上执行,为了保证代码的完整运行,这里选择查询模式。
3:移植关键部分到工程
(1):添加头文件
https://www.stcaimcu.com/data/attachment/forum/202411/28/110335adtbqdkwwnj8liwi.jpg
https://www.stcaimcu.com/data/attachment/forum/202411/28/110358uqh15n5r465114vn.jpg
双击第一张图片中的Source Group 1,选中.lib文件,并在代码中加入
#include "stc32_stc8_usb.h"                //USB CDC 头文件
这句代码。
(2):USB初始化函数
https://www.stcaimcu.com/data/attachment/forum/202411/28/110629x8lu1bx5x3orczo3.jpg
(3):命令参数
https://www.stcaimcu.com/data/attachment/forum/202411/28/110536sql19zu3jq71u9uk.jpg
这里需要和isp下载软件中的一致,是用作命令识别的,识别成功才会进入hid下载模式
https://www.stcaimcu.com/data/attachment/forum/202411/28/110805t3t1lnl7u1hsoz82.jpg
这里选择默认即可。
(4):打开P_SW2和IE2寄存器(只修改某一个位)
https://www.stcaimcu.com/data/attachment/forum/202411/28/110918f0b6qibft1d3qiw6.jpg
如图,(1:使能访问XFR寄存器。 (2:使能USB中断和总中断
https://www.stcaimcu.com/data/attachment/forum/202411/28/111037oxxqn1l22vx1vltr.jpg
https://www.stcaimcu.com/data/attachment/forum/202411/28/111049yvg44gvyjzjg8bka.jpg
https://www.stcaimcu.com/data/attachment/forum/202411/28/111106dvt3uuf2b4572fsv.jpg
具体可看手册对应章节。
https://www.stcaimcu.com/data/attachment/forum/202411/28/111157fi9atqh8931z7kjk.jpg
这部分代码直接移植官方例程的即可。
最后附上演示视频。
PS(又是学习的一天{:4_164:})


xiao_a_bin 发表于 2024-12-7 17:42:44



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

嗨嗨嗨,第四天咯!今天学习了使用USB-CDC串口打印数据+C语言基础学习。
https://www.stcaimcu.com/data/attachment/forum/202411/29/163904ps72eqa7swq7wawx.jpg
1:先来看看USB-CDC串口,printf函数打印数据:
(1)、在stc32_stc8_usb.h中把#define PRINTF_HID的注释给去掉。
https://www.stcaimcu.com/data/attachment/forum/202411/29/164051cn6yfvfnztrfzfvt.jpg
(2)、编写代码:
https://www.stcaimcu.com/data/attachment/forum/202411/29/164258gwgtg3iexicxdvzd.jpg
代码的效果是USB如果接收到了数据,执行printf语句,输出”知道了知道了“\r\n是换行。
(3)、设置ISP里面的选项,烧录代码:
(1:注意这里的串口波特率需要和CDC-HID串口助手中的波特率一致,不然会出现通信超时(之前遇到了,找了好久才看到,我波特率不匹配{:4_184:})
(2:芯片的主频选择是24mhz。
(3:串口的波特率可以随意选择,因为实际是USB通信。
https://www.stcaimcu.com/data/attachment/forum/202411/29/164438bngfb3b7nbd022ww.jpg
https://www.stcaimcu.com/data/attachment/forum/202411/29/164453jynlwnt9tl0ll4st.jpg
(4)、实际测试:
https://www.stcaimcu.com/data/attachment/forum/202411/29/164845pyu1nq0fdgkwkhzp.jpg
这里选择921600的波特率,CDC串口收发也是正常的。
(5)、可能的bug:


https://www.stcaimcu.com/data/attachment/forum/202411/29/165022addfv9d9udcoav46.jpg

接收乱码。编码格式不对,ASCII改成UTF-8就行了。
https://www.stcaimcu.com/data/attachment/forum/202411/29/165114af2z1jj461z0x0cq.jpg
(6)、printf代码大致讲解:
https://www.stcaimcu.com/data/attachment/forum/202411/29/165236kwn3pz1n3onksbos.jpg
这里说的fmt有两种参数:一种就是普通字符,就是在代码中原封不动打印到CDC串口上的数据
还有一种就是转换说明:不直接输出,用于参数的格式转化和打印,由%开始,表示输出数据的类型、宽度、精度。
比如: https://www.stcaimcu.com/data/attachment/forum/202411/29/165604l5o5o57ghoxi5jtd.jpg
代码前面,已经定义X的数据为20。
fmt的参数可以有多个,但是得对应前面的%,不然大概率会报错,或者输出不合理的数据。
然后具体一点的解释和相对应的%后面的格式字符和转义字符可以看下面。
https://www.stcaimcu.com/data/attachment/forum/202411/29/165746skw4x8wawn3za6du.jpg
https://www.stcaimcu.com/data/attachment/forum/202411/29/165801hrb460u6mblrubr5.jpg

2、数的进制:2进制、10进制、16进制
(1):主打一个其他进制转化成10进制的计算方法:(对,这个叫做按权相加)
可看博客:二进制、八进制、十六进制与转换_计算机二进制八进制十六进制转换成十进制例题-CSDN博客
https://www.stcaimcu.com/data/attachment/forum/202411/29/170011a8h2y1x4d4yfqye3.jpg

3、数据的基本类型:
https://www.stcaimcu.com/data/attachment/forum/202411/29/170728cw1cjz6wuzvufujj.jpg
如果需要用到double,就需要加上#pragma float64
因为double在C251中是64位的。
然后基本的数据类型就多看多用就好了。

4、C语言的运算符:
(1):算术运算符:
https://www.stcaimcu.com/data/attachment/forum/202411/29/170859mvix2ec5cgi2b75x.jpg
加减乘除取模加加减减。
(2)、关系运算符:
https://www.stcaimcu.com/data/attachment/forum/202411/29/170951i44u7pcuc7p9xv44.jpg
比较关系的,然后0为假,1为真。
(3)、逻辑运算符:
https://www.stcaimcu.com/data/attachment/forum/202411/29/171214roce6t6fcpe34opy.jpg
与或非!
(4)、赋值运算符:
https://www.stcaimcu.com/data/attachment/forum/202411/29/171330x59gc0ysqywcqz4e.jpg
按位异或、与、或还是比较常见的
(5)、位运算符:
https://www.stcaimcu.com/data/attachment/forum/202411/29/171431bfzhjjjcfuqdoke3.jpg
位运算用的比较多,操作寄存器嘛。
(6)、其他运算符:
https://www.stcaimcu.com/data/attachment/forum/202411/29/171516mnttt2kntt4d74nb.jpg
得学,会用到的,特别是指针和结构体。

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



xiao_a_bin 发表于 2024-12-7 17:49:23


一眨眼也是第五天了。今天跟着冲哥学习了IO的输入输出。

还是先上冲哥的摘要图:
https://www.stcaimcu.com/data/attachment/forum/202411/30/223256oi9l2li8llnslvv7.jpg

1:什么是GPIO
(1):GPIO就是通用输入/输出端口。就是一些引脚,然后可以通过他们输出高低电平,或者通过他们读入引脚的状态-高电平或者低电平。
然后1高0低。具体的解释可以看下面的图片:
https://www.stcaimcu.com/data/attachment/forum/202411/30/223445zyyz9rvj0hhqa6yj.jpg

(2)、GPIO的模式:
https://www.stcaimcu.com/data/attachment/forum/202411/30/225109dzetmeiwy91i91wt.jpg
一般最常用的就是准双向口,然后如果配置使用led的话,用灌电流
好一点,比较通用。

(3)、引脚输入电压的判断(高低电平):
https://www.stcaimcu.com/data/attachment/forum/202411/30/230015vq7dcpql3gvqc77c.jpg
要注意是否打开施密特触发器。和留意电压数值,方便后期用万用表测量电压找问题。

2、按键检测:
(1)、按键原理:
https://www.stcaimcu.com/data/attachment/forum/202411/30/230202u5jiow052iez8ka8.jpg
就是按下,按键两脚就导通,松开就断开。

(2)、当堂练习:
这里举例第三个
https://www.stcaimcu.com/data/attachment/forum/202411/30/230439cxrgxxxggg6tlgx8.jpg
https://www.stcaimcu.com/data/attachment/forum/202411/30/230456k46j4hee3kc1e48k.jpg
为什么呢?
这里需要给按键进行消抖。

(3)、按键消抖:
包括硬件消抖(电容滤波)和软件消抖(使用软件延时来度过按键抖动期)
这里使用软件延时。
https://www.stcaimcu.com/data/attachment/forum/202411/30/230732lj37llv4l31a11dw.jpg
同时需要加上三句话:

    WTST = 0;                                          //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    EAXFR = 1;                                       //扩展寄存器(XFR)访问使能
    CKCON = 0;                                       //提高访问XRAM速度

ISP软件里面可以计算,选好参数,复制粘贴就行。
https://www.stcaimcu.com/data/attachment/forum/202411/30/230941b6rmy9qmwy7j8gjg.jpg

(4)、keil小tips:
https://www.stcaimcu.com/data/attachment/forum/202411/30/231025xamhzp5zdl19ei5i.jpg

3、课后练习:
https://www.stcaimcu.com/data/attachment/forum/202411/30/231116di68iikz5z686i96.jpg
也是写了一下,代码在代码里面了,练习一没有验证,练习二验证应该是没问题的。
https://www.stcaimcu.com/data/attachment/forum/202411/30/231247ediuarzrtsjcaj5u.jpg

安啦,又是学习的一天!{:4_165:}



xiao_a_bin 发表于 2024-12-7 17:55:03


跟着冲哥学AI8051U的第六天-IO定时器中断:

{:4_165:}终终终终终终终于更新啦。今天学习IO定时器中断:
老规矩,先上图。
https://www.stcaimcu.com/data/attachment/forum/202412/03/224156nko0rnz18pkzdksk.jpg
这一部分是讲述了软件空循环延时的占用CPU,然后引进了定时器中断。
https://www.stcaimcu.com/data/attachment/forum/202412/03/224315nccoqis8btsqd933.jpg
经典本章摘要。
1、定时器的介绍:
https://www.stcaimcu.com/data/attachment/forum/202412/03/224410t2vb0f8o7bbf523o.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/224424zm50ev4ayxgei9ee.jpg
(1)、任务一:
https://www.stcaimcu.com/data/attachment/forum/202412/03/224501ql60llf7n57fct1b.jpg
然后这里演示的是在ISP软件中配置需要的定时器中断参数:
这里是:
https://www.stcaimcu.com/data/attachment/forum/202412/03/224657un6zw1tgvjbogj0k.jpg
(2)、GB2312的\xfd字符纠正和isp设置下载完成一秒钟后自动打开串口:
https://www.stcaimcu.com/data/attachment/forum/202412/03/224843rtiijwx333wtwewe.jpg
相关手册说明:
https://www.stcaimcu.com/data/attachment/forum/202412/03/224857grb8565rkr6k8o65.jpg

https://www.stcaimcu.com/data/attachment/forum/202412/03/224912uaqzp8yyq50p9084.jpg
如果用的UTF-8,好像是没有出现这个问题的,(至少我打”数“能正常的显示)

2、定时器应用:(介绍相关的硬件配置和参数说明)(为了节省一些时间,我直接贴笔记了)
https://www.stcaimcu.com/data/attachment/forum/202412/03/225107kj01w20zp6zwpb16.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225123niyrgng6nvmjmx2n.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225134udtkn253cl2invj2.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225153szc9s0x7t3615xbx.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225205xw38mwkfq0rkmk00.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225216dan9rxrmuzuuj9pa.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225234hayprj3vvxun7ayz.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225312m4yk4qu2eyikphi3.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225432w1kwjssw10dplp8s.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225455gf50hefdd0rfe4lh.jpg
定时器的频率、定时时间的计算:
https://www.stcaimcu.com/data/attachment/forum/202412/03/225505b8687dch48pu668g.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225551lvi3cozi3b62z137.jpg

3、函数的定义、声明、调用:
(1)、基础知识:
https://www.stcaimcu.com/data/attachment/forum/202412/03/225622rji2s62rirj4ngir.jpg
https://www.stcaimcu.com/data/attachment/forum/202412/03/225638dtlaiod5ggd0skvv.jpg
这里的函数没有返回值,所以直接使用void开头。
https://www.stcaimcu.com/data/attachment/forum/202412/03/225700q2waksyqihynpdbq.jpg
(2)、具体的使用:
https://www.stcaimcu.com/data/attachment/forum/202412/03/225952a43z8b3u4c668426.jpg

(3)、今日任务:
https://www.stcaimcu.com/data/attachment/forum/202412/03/230014ulldgjjjvd11d0jz.jpg
没有实验箱,所以直接使用串口调试的方式。具体代码见:code_demo.zip

(4)、思考题:
https://www.stcaimcu.com/data/attachment/forum/202412/03/230122hjj6dyfz4dyida6p.jpg
复用定时器?应该就是结合之前的函数,写成传参类型的函数,把定时器的初值传进去,然后就能定时不一样的时间了。

4、课后练习:
https://www.stcaimcu.com/data/attachment/forum/202412/03/230222z4p6p4p47uxus3zu.jpg
太典了,功德!!!
当然写了,但是感觉和题目的意思有点偏离了。先看效果:
https://www.stcaimcu.com/data/attachment/forum/202412/03/230555y0up20j99g207lnj.jpg
可以看到这里能正常的切换功德时间,然后也能正常的点亮功德和计算功德。
https://www.stcaimcu.com/data/attachment/forum/202412/03/230648r0nq0q14uwtuee11.jpg
但是再点亮功德的过程中就算你多次按下按键,再功德点亮成功之前,功德数是不会增加的。
可能题目要求的意思是点亮功德完成后,显示当前的功德数,就是再点亮期间按键累加的功德数也一并加上去,最后显示当前功德数量。
具体看代码把(Code_AfterClass_Test.zip)写的不是很好,讲究看看(狗头保命.jpg){:4_164:}
期待明天的课程!{:4_186:}


xiao_a_bin 发表于 2024-12-8 11:36:16

<h2>今天是跟着冲哥学习AI8051U的第七天啦!——定时器周期性任务调度</h2>
<p><strong>在开始今天的摘要之前,先来看一下前一节课——IO定时器中断可能会出现的一些错误。</strong></p>
<ol>
<li><strong>u8写成了大写的U8</strong>,导致 <code>#define u8 unsigned char</code>识别错误了</li>
<li><strong>中文分号混淆 <code>,(中文)、,(英文)</code></strong>,这个应该很明显,编译的时候,报错,点击进去看看前后应该是很好发现的。</li>
<li>**缺少了相对应的 <code>}</code>**这个就应该学一下代码的编写风格,然后注意好缩进,也比较方便看出来的。<br />
<strong>然后还有一个小tip:</strong><br />
<strong>在ISP软件中显示串口打印的时间戳:</strong>
<ul>
<li>
<p><strong>开启****显示发送的数据和数据分包显示就行了</strong>,这样也可以通过串口打印的时间来大致查看定时器的定时是否准确。如下图:</p>
</li>
<li>
<p><img src="data/attachment/forum/202412/08/111616aais9aiygygkman0.png" alt="image-20241208091109932.png" title="image-20241208091109932.png" /></p>
</li>
<li>
<h3>OK,也是进入今日的摘要部分啦,如下图</h3>
<p><img src="data/attachment/forum/202412/08/111627u1or9v7vrznvntnn.png" alt="image-20241208091335757.png" title="image-20241208091335757.png" /></p>
<p><strong>分成以下四个部分:</strong></p>
<ol>
<li>
<p><strong>周期性任务介绍</strong></p>
</li>
<li>
<p><strong>文件的创建(.c和.h)</strong></p>
</li>
<li>
<p><strong>结构体的介绍</strong></p>
</li>
<li>
<p><strong>结构体数组的周期性任务调度</strong><br />
<strong>一、先来看第一点(周期性任务介绍):</strong><br />
<em>周期性任务介绍</em>:所谓的周期性任务就是<em>每隔一定的时间就去执行一个任务,比如每隔300ms点亮一次LED</em></p>
<p><img src="data/attachment/forum/202412/08/112901gn8rdffnuvqqpwdq.png" alt="image-20241208091749897.png" title="image-20241208091749897.png" /><br />
<strong>对应这里的任务一:很简单明了,只需要</strong></p>
<ul>
<li>
<p><em>通过设置变量来计数,每进一次1ms的定时器设置中断,这个变量就自加一次,再判断满足临界条件就可以执行任务了。以下列出部分代码,具体代码可以看附件。</em></p>
<pre><code>//变量定义部分:
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 = { 0, 0, 0 };      //三个计时变量

//函数定义 这部分是定时器的1ms中断代码
void Timer0_Init(void)      //1毫秒@24.000MHz 24位自动重载
{
    TM0PS = 0x00;         //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
    AUXR &amp;= 0x7F;         //定时器时钟12T模式
    TMOD &amp;= 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 &lt; 3; i++)                  //可以使用for循环+数组
    {                                       //或者是注释部分的三个变量
      Count_ms ++;
    }

//Count_300++;
//Count_600++;
//Count_900++;

}

//主函数部分代码:printf打印是为了串口显示灯的状态(没实验箱(悲伤),但是应该快有了
      if(Count_300 &gt;= 300)                //300MS达到
      {
            Count_300 = 0;
            State1 = !State1;
            P00 = State1;                   //LED1取反
//          printf(&quot;P00 = %d\r\n&quot;,State1);
      }
      if(Count_600 &gt;= 600)                //600MS达到
      {
            Count_600 = 0;
            State2 = !State2;
            P01 = State2;                   //LED2取反
//          printf(&quot;P01 = %d\r\n&quot;,State2);
      }
      if(Count_900 &gt;= 900)                //900MS达到
      {
            Count_900 = 0;
            State3 = !State3;
            P02 = State3;                   //LED3取反
//          printf(&quot;P02 = %d\r\n&quot;,State3);
      }
</code></pre>
<p><strong>然后这里贴出一张结果截图:</strong></p>
<p><img src="data/attachment/forum/202412/08/112918hfr7r53tsnqs6333.png" alt="image-20241208093042046.png" title="image-20241208093042046.png" /></p>
<p><em>然后这里也可以用作是一些需要周期性定时的任务上,比如说需要1秒钟使得蜂鸣器响一下。使用这个的好处是不会干扰主函数的执行,不像</em>nop<em>延时那样在主函数中执行完才能执行下面的代码。</em></p>
</li>
<li>
<p><em>数组的定义和使用</em><br />
<strong>可以理解成数组就是加长版的变量?或者说一次性定义多个变量吧,这样比较好理解。</strong></p>
<p><img src="data/attachment/forum/202412/08/112926qhssfstljfhrlgst.png" alt="image-20241208094005616.png" title="image-20241208094005616.png" /></p>
<p><strong>数组是从0开始索引的,老师说过一句话,计算机人的数学是从0开始的,而非计算机人是从1开始的。类似1G等于1024M,而不是1000M.</strong></p>
<ul>
<li>
<p><strong>数组的定义和赋初始值:例如</strong> <code>u16 Count_ms = { 0, 0, 0 };                //三个计时变量</code></p>
</li>
<li>
<p><strong>数组变量的修改(数组名称+索引+等号+数据:</strong><code>State = 2;          //给数组写入数据</code></p>
</li>
<li>
<p><strong>Keil小Tip:选中多行,按住shift+tab,可以向前缩进。</strong></p>
</li>
<li>
<p><strong>所以在中断函数代码中就可以这样修改</strong></p>
<pre><code>//中断服务函数可以这样编写,串口打印老配方、同时把点灯放在了里面,不占同主函数了:
    if(Count_ms &gt;= 300)
    {
      Count_ms = 0;
      State1 = !State1;
      P00 = State1;                   //LED1取反
      printf(&quot;P00 = %d\r\n&quot;,State1);
    }
//      printf(&quot;当前是State数组的第%d个数据%d\r\n&quot;, (int)i);
</code></pre>
</li>
</ul>
</li>
<li>
<p><em>用数组点亮流水灯</em></p>
<ul>
<li><strong>需要按照这样的顺序依次点亮P0端口的LED灯,以便形成一个流水灯效果:</strong></li>
<li>
<pre><code>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
    */
</code></pre>
</li>
<li><strong>很简单,就用一个数组,里面的每一个变量单独表示点亮一个灯,对应上面的某一行的状态。</strong></li>
<li><strong>正向写比较麻烦,这里使用逆向写法,同时在后续P0端口赋值的地方,取反再赋值就好了。</strong></li>
<li><code>u8 State = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};</code></li>
<li>
<pre><code>//代码:
    if( Count_ms &gt;= 500 )
    {
      Count_ms = 0;
      P0 = ~State;
      printf(&quot;P0数值:%x\r\n&quot;, P0);
      num ++;
      if(num &gt; 7)
      {
            num = 0;
      }
    }
</code></pre>
</li>
<li><strong>效果:</strong><img src="https://www.stcaimcu.com/data/attachment/forum/202412/08/112935idmmzd14ml4tpzx9.png" alt="image-20241208095651079.png" title="image-20241208095651079.png" /></li>
</ul>
</li>
<li>
<p><strong>*周期性任务介绍:任务3:按键P32按一下,LED通过数组移动一下****这里不能使用while来判断是不是没有松开按键了,不然会一直卡再while循环里面,导致下面的代码无法执行</strong></p>
<ul>
<li>
<pre><code>//对应这里就是当一直按住P32 num++ 及其后面的语句一直无法执行。
      if(P32 == 0)
      {
            Delay10ms();
            if(P32 == 0)
            {
                while(P32 == 0);
                num ++;
                printf(&quot;P0数值:%x\r\n&quot;, P0);
            }
      }
</code></pre>
</li>
<li><strong>*可以用成定时器周期性任务来解决****具体就是检测到按键连续按下,按键计数变量+1,只要松开一下,计数清0,计数累计到50ms的时候判定为按下。</strong></li>
<li>
<pre><code>//对应的就是下面,实现的效果就是按键一直按下的,printf打印不会受到干扰,依旧可以执行下去。
    if(Count_ms &gt;= 1000)
    {
      Count_ms = 0;
      printf(&quot;Ai8051U\r\n&quot;);
    }

    if(Count_ms &gt;= 10)         //按键持续按下50ms也会判定为按下按键,然后按一下加一下,实现了按键检测和串口打印的互不干扰。
    {
      Count_ms = 0;
      if(P32 == 0)
      {
            Key_vol ++;
            if(Key_vol == 5)
            {
                num ++;
            }
            printf(&quot;num数值:%d\r\n&quot;, num);
      }
      else
      {
            Key_vol = 0;
      }
    }
</code></pre>
</li>
<li><strong>效果图:</strong></li>
<li><strong><img src="data/attachment/forum/202412/08/113014zfk3xeieyfzfuifm.png" alt="image-20241208100604430.png" title="image-20241208100604430.png" /><br />
二、接着看第二点:文件的创建(.c和.h)</strong></li>
</ul>
</li>
<li>
<p><strong>模块化编程,方便移植!?反正是很有用处的,文件多的时候,总不可能一直都放在一个main.c里面吧,看着多累啊。</strong></p>
<p><img src="data/attachment/forum/202412/08/113026dhz44t4tvosul7jv.png" alt="image-20241208100928698.png" title="image-20241208100928698.png" /></p>
</li>
<li>
<ol>
<li>
<p><strong>先建立一个用户文件夹:user</strong></p>
</li>
<li>
<p><strong>在keil里面新建.c和.h文件,然后保存,命名为config.c(.h)。</strong></p>
</li>
<li>
<p><strong>添加文件到keil工程里面。</strong></p>
</li>
<li>
<p><strong>写好必备的语句,像是一下main.c里面的头文件啥的都可以放过来,然后再main.c里面就引用</strong> <code>#include &quot;config.h&quot;</code>。</p>
</li>
<li>
<pre><code>//1.在.h文件里面:
#ifndef __CONFIG_H
    #define __CONFIG_H

    #include &quot;ai8051u.h&quot;            //调用头文件
    #include &quot;stc32_stc8_usb.h&quot;   //USB CDC 头文件
    #include &quot;intrins.h&quot;            //调用头文件


    #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 &quot;config.h&quot;
</code></pre>
</li>
<li>
<p><strong>在锤子里面的C251选项把新建的子文件夹添加到工程的目录里面,不然是找不到的。</strong></p>
</li>
<li>
<p><img src="data/attachment/forum/202412/08/113045t6o6pjooowu1zvwo.png" alt="image-20241208101726153.png" title="image-20241208101726153.png" /></p>
<p><img src="data/attachment/forum/202412/08/113051atrqwrkrdazq9deq.png" alt="image-20241208101737374.png" title="image-20241208101737374.png" /></p>
</li>
<li>
<p>**编译,没有报错,恭喜你拥有了自己的自定义函数!<br />
<img src="data/attachment/forum/202412/08/113056uddavidkddvchcdd.png" alt="image-20241208102045236.png" title="image-20241208102045236.png" /><br />
**</p>
</li>
<li>
<p><strong>接下来就是移植其他的一些配置代码了,然后这里就不说了,后面看附件吧。</strong>?</p>
</li>
</ol>
</li>
</ul>
<p><strong>三、重中之重啊:结构体数组的周期性任务调度</strong></p>
<ul>
<li>
<p><strong>老规则的PPT介绍</strong></p>
<p><img src="data/attachment/forum/202412/08/113102s8neblpbkpvclrvf.png" alt="image-20241208102255517.png" title="image-20241208102255517.png" /></p>
</li>
<li>
<p><strong>结构体还是很重要的,在c语言中,嵌入式编程中很常见的。</strong></p>
<p><img src="data/attachment/forum/202412/08/113108qm5av43zvum4s455.png" alt="image-20241208102419830.png" title="image-20241208102419830.png" /></p>
</li>
<li>
<p><em>结构体可以理解成用户自定义的一个数据类型,然后使用也是数组一样,类型+名称+赋值</em></p>
</li>
<li>
<p><strong>然后下面的代码都可以在官方提供的实验箱代码里面找到,具体是第27个(27-通过定时器周期性调度任务综合例程,简单实用的任务调度系统,推荐)整体下载</strong><a href="https://www.stcai.com/syx" title="点进去,下拉找到*AI8051U实验箱1.2*">链接</a></p>
</li>
<li>
<p><strong>这里新建了一个Task.h和Task.c来编写结构体相关的代码。</strong></p>
<ul>
<li><strong>所以这里</strong> <code>task.h</code>的使用:这里有三个变量和一个函数组合成一个新的结构体类型</li>
<li>
<pre><code>typedef struct                                  //固定搭配
{
    u8 Run;               //任务状态:Run/Stop   //1的时候执行
    u16 TIMCount;         //定时计数器         //自减到0,置位run
    u16 TRITime;          //重载计数器         //重载给TIMCount
    void (*TaskHook) (void); //任务函数         //run到1,就执行的函数
} TASK_COMPONENTS;                           //结构体的名字
</code></pre>
</li>
</ul>
</li>
<li>
<p><strong>然后这里是结构体的使用:这里定义了一个结构体数组,对应上面的三个变量和一个函数</strong></p>
<ul>
<li>
<pre><code>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 */

};
</code></pre>
</li>
</ul>
</li>
<li>
<p><strong>然后记录一下关于如何使用</strong></p>
<ul>
<li>
<pre><code>u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps);
//这里使用一个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&lt;Tasks_Max; i++)         //for循环执行每一个任务
    {
      if(Task_Comps.TIMCount)      /* If the time is not 0 */ //当不为0时
      {
            Task_Comps.TIMCount--;   /* Time counter decrement */ //自减
            if(Task_Comps.TIMCount == 0) /* If time arrives *///为0
            {
                /*Resume the timer value and try again */
                Task_Comps.TIMCount = Task_Comps.TRITime;//先重载,方便下一次的使用
                Task_Comps.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&lt;Tasks_Max; i++)          //一样的循环
    {
      if(Task_Comps.Run) /* If task can be run */      //判断run的状态
      {
            Task_Comps.Run = 0;      /* Flag clear 0 *///也是重新设置为初始状态,run=0
            Task_Comps.TaskHook();   /* Run task */      //去执行下面的函数
      }
    }
}
</code></pre>
</li>
</ul>
</li>
<li>
<p><strong>然后后面的事情就很简单了,就是使用部分了。</strong></p>
<ul>
<li>
<pre><code>//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 &quot;io.h&quot;

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(&quot;P00 = %d\r\n&quot;,(int)State1);
}

void LED1_Blink(void)
{
    State2 = !State2;
    P01 = State2;
    printf(&quot;P01 = %d\r\n&quot;,(int)State2);
}

void LED2_Blink(void)
{
    State3 = !State3;
    P02 = State3;
    printf(&quot;P02 = %d\r\n&quot;,(int)State3);
}

void Printf_1S(void)
{
    printf(&quot;AI8051U\r\n&quot;);
}

void Key_Task(void)
{
    if(P32 == 0)
    {
      Key_Vol ++;
      if(Key_Vol == 5)
      {
            //执行按键按下的任务
            printf(&quot;按键单击\r\n&quot;);
      }
    }
    else
    {
      Key_Vol = 0;
    }
}
</code></pre>
</li>
<li>
<p><strong>最后整理一下文件,重新编译,烧录,看看ISP串口打印效果。可以看到完美执行了前面设置的五个定时器周期性任务。完美</strong>?<strong>?</strong></p>
<p><img src="data/attachment/forum/202412/08/113242x1ciqbub613u6ukf.png" alt="image-20241208105459093.png" title="image-20241208105459093.png" /></p>
</li>
<li>
<p><strong>嗷嗷,keil工程尽量英文命名,防止出现各种编译报错:</strong><br />
<img src="data/attachment/forum/202412/08/113247l9czzyikcxzlxxmc.png" alt="image-20241208105323725.png" title="image-20241208105323725.png" /></p>
</li>
<li>
<p><strong>附件的说明和下载:</strong></p>
<ol>
<li><strong>demo_code.zip是前面的结构体之前的代码</strong></li>
<li><strong>code_creat_file.zip是创建结构体和编写结构体周期性函数的代码</strong></li>
<li><strong>按需下载</strong>?<br />
<strong>四、酣畅淋漓的学习</strong></li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>一次很棒的教程,学到了很多,感谢冲哥、STC、立创!</strong>?</p>
</li>
<li>
<p><strong>也是首次使用markdown来编写文章</strong>?</p>
</li>
<li>
<p>很奇怪的感觉,写是写了,但是好像不能直接复制粘贴上来显示。有些格式好像不对?</p>
</li>
<li>
<p><a href="forum.php?mod=attachment&amp;aid=70332" title="attachment"><img src="/source/plugin/zhanmishu_markdown/template/editor/images/upload.svg" alt="upload" /> 附件:demo_code.zip</a><a href="forum.php?mod=attachment&amp;aid=70333" title="attachment"><img src="/source/plugin/zhanmishu_markdown/template/editor/images/upload.svg" alt="upload" /> 附件:demo_code.zip</a></p>
</li>
</ol>
</li>
</ul>
</li>
</ol>
页: [1]
查看完整版本: 跟着冲哥学习AI8051U的打卡记录帖