AI8051U学习打卡
我的AI8051学习实战第一集
1.首先是安装编译环境,这里我们选择C251,官网的调查问卷我们可以随便填写,之后就可以跳转到下载页面
2.确保均为全英文路径后安装到D盘,尽量避开系统盘以防重装系统导致的文件丢失,打开keil软件选择图中选项,然后复制CID到keygen CID处得到破解代码,最后到图2所示红圈处点击Add Lic,便破解成功与我图中一样即可。
最后也是尝试了一下硬件USB下载程序的快乐,可以说很轻松,直接节省了一个CH340及其外围电路节省成本的同时还能压缩元件布局。
<p>第二集 点亮一颗LED</p>
<p>按照《8051U深度入门到32位51大型实战教学视频》</p>
<p>6.5新建与设置超64K程序代码的项目(Source)模式</p>
<p><img src="data/attachment/forum/202412/27/151917ry9zbbhzyi3n0rhu.png" alt="1735283953495.png" title="1735283953495.png" /></p>
<p>通过读取手册可以得知:</p>
<p>AI8051U目前只支持Source模式,由于中断的压栈与出栈都是4字节建议选中 4Byte Intemupt Frame Size</p>
<p>并且Memory Model选择为XSmall模式,首先AI8051U逻辑地址为FF:0000H~FF:FFFFH,必须使用24位地址线才能正确访问,默认常量类型必须使用"far"类型,指针变量为4字节。</p>
<p>Memory Model模式区别:</p>
<p>XSmall模式将变量定义在内部RAM也就是edata中,单时钟存取,访问速度快,且有2k的edata供使用。</p>
<p>Small模式也是默认将变量定义在内部RAM(data)中,而data默认只有128字节,超过128字节会报错</p>
<p>Large模式虽然能够正确访问全部的16M寻址空间,但是该模式默认将变量定义在内部扩展RAM(xdata)里,存取需要2~3个时钟,访问速度慢。</p>
<p>故设置为XSmall模式</p>
<p>Code RomSize模式区别:</p>
<p>Small模式: 跳转/调用指令为AJMP/ACALL ,单个函数/模块/文件的代码大小为 2K ,总代码大小为 2K</p>
<p>Medium模式: 内部模块代码使用为AJMP/ACALL ,外部模块代码使用为LJMP/LCALL,单个函数/模块/文件的代码大小为 2K ,总代码大小为 64K</p>
<p>Compact模式: 跳转/调用指令为LCALL/AJMP ,单个函数/模块/文件的代码大小为 2K ,总代码大小为64K</p>
<p>Large模式: 跳转/调用指令为LCALL/AJMP ,单个函数/模块/文件的代码大小为 64K ,总代码大小为64K</p>
<p>Huge模式: 内部模块代码使用为LJMP/ECALL ,外部模块代码使用为EJMP/ECALL,单个函数/模块/文件的代码大小为 2K ,总代码大小为 64K。(多文件项目中,文件内部的代码为内部模块代码,其他文件的代码为外部模块代码)</p>
<p>这里设置为 Large模式</p>
<p>终于到了代码环节,这里必须得搞明白AI8051U的端口定义!</p>
<p>比方说:</p>
<p>我们可以把P0M0、P0M1理解为一个8位的变量,他可以对0~7位进行二进制的操作,赋予0或者是1</p>
<p>P0M0中的P0指的是端口P0,M0指的是P0.0</p>
<p>P0M1中的P0指的是端口P0,M1指的是P0.0</p>
<p>P1M0中的P1指的是端口P1,M0指的是P1.0</p>
<p>P1M1中的P1指的是端口P1,M1指的是P1.0</p>
<p>那么P0.0的引脚模式就是由这个P0M0与P0M1的第0位的状态共同决定的</p>
<p>那么P1.0的引脚模式就是由这个P1M0与P1M1的第0位的状态共同决定的</p>
<p><img src="data/attachment/forum/202412/27/155832hnknomp2axo7p3ns.png" alt="1735286304900.png" title="1735286304900.png" /></p>
<p>#include "ai8051u.h" //调用头文件</p>
<p>void main(void)<br />
{</p>
<pre><code>P2M0 = 0x00;
P2M1 = 0x00;
while(1)
{
P2 = 0X00;
}
</code></pre>
<p>}</p>
<p>前面我们知道了P2M0、P2M1是8位变量,这里赋值的方式为16进制,相当于0000 0000 也就是0~7的引脚设置为 准双向口</p>
<p>程序执行为 先将P2的0~7位设置为准双向口,之后无限将P2的0 ~ 7位拉低,由于在板子上LED灯与电阻串联,一端在高电平上,而另一端在我们的单片机AI8051U上,也就是通过单片机控制引脚来决定LED灯是否发光</p>
<p>电流的产生总是从高电平流向低电平,所以我们的8个LED灯均会发光</p>
<p><img src="data/attachment/forum/202412/27/161354uj6z7lxv67jgz642.jpg" alt="5df10fe43302d52cc3a8cf9dd4a4224.jpg" title="5df10fe43302d52cc3a8cf9dd4a4224.jpg" /></p>
<p>第三集 不停电USB下载</p>
<p>1.<a href="https://www.stcai.com/gjrj">深圳国芯人工智能有限公司-工具软件</a>下载好USB库</p>
<p>2.选择查询模式STC-CDC范例程序-> stc32g_cdc_query_demo将stc_usb_cdc_32g.LIB、stc32_stc8_usb.h这俩个文件放入我们上节课的点灯程序中</p>
<p><img src="data/attachment/forum/202412/27/204603jzxqmebehtjc857d.png" alt="1735303551270.png" title="1735303551270.png" /><br />
<img src="data/attachment/forum/202412/27/204620wnwdgrz4wr7gw8of.png" alt="1735303576066.png" title="1735303576066.png" /><br />
<img src="data/attachment/forum/202412/27/204636c6pp15pv7utsztvt.png" alt="1735303592080.png" title="1735303592080.png" /></p>
<p><img src="data/attachment/forum/202412/27/204657azbvkkshkpt7kuyw.png" alt="1735303613030.png" title="1735303613030.png" /></p>
<p>3.进行不停电USB下载代码的移植,可以看到官方的头文件是STC32G,而我们是AI8051U</p>
<p>将stc_usb_cdc_32g.LIB加入到文件工程下,在mian.c文件中#include“stc32_stc8_usb.h”这样我们第一步完成了。</p>
<p>第二步、将以下变量声明,并对在mian函数中添加P_SW2 |= 0x80;这一步是在保持原有P_SW2各个位不变的情况下,将最高位置1,否则我们将无法使用特殊功能寄存器。</p>
<p>char *USER_DEVICEDESC = NULL;<br />
char *USER_PRODUCTDESC = NULL;<br />
char *USER_STCISPCMD = "@STCISP#";</p>
<p><img src="data/attachment/forum/202412/27/205505jr9rn3s22ejsktek.png" alt="1735304096148.png" title="1735304096148.png" /></p>
<p><img src="data/attachment/forum/202412/27/205631bi2a1bbbiv9atmdv.png" alt="1735304184917.png" title="1735304184917.png" /></p>
<p>第三步、初始化usb_init();</p>
<p>IE2 |= 0x80;将IE2的最高位拉高,以便打开USB中断允许位。</p>
<p>EA=1,将EA拉高,以便打开中断控制,如图所示。</p>
<p><img src="data/attachment/forum/202412/27/205854v3mlldzvr0mrrovd.png" alt="1735304317731.png" title="1735304317731.png" /></p>
<p><img src="data/attachment/forum/202412/27/210126v0ra9mhnhsz8k96e.png" alt="1735304478485.png" title="1735304478485.png" /></p>
<p>第四步、在main函数添加 while(DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置</p>
<p>打开stc32_stc8_usb.h我们可以在35行找到#define DEVSTATE_CONFIGURED 4</p>
<p>也就是说如果DeviceState返回的值为4,while循环条件不满足跳出循环,说明UBS配置完成了可以进行正常的程序运行。</p>
<p>第五步、在while循环中添加 这一段代码的意思是 USB不管接收到什么都会将接收到的代码发送回去</p>
<p>因为stc32_stc8_usb.h 声明了许多的函数,我们没有调用变回报错L57的错误</p>
<p><img src="data/attachment/forum/202412/27/211240kk4ee3ihda4hq43d.png" alt="1735305156372.png" title="1735305156372.png" /></p>
<video controls="controls" src="forum.php?mod=attachment&aid=77369"></video>
<p>以下为代码展示</p>
<p>#include "ai8051u.h" //调用头文件<br />
#include "stc32_stc8_usb.h" //调用头文件</p>
<p>char *USER_DEVICEDESC = NULL;<br />
char *USER_PRODUCTDESC = NULL;<br />
char *USER_STCISPCMD = "@STCISP#";</p>
<p>void main(void)<br />
{<br />
P_SW2 |= 0x80;</p>
<pre><code>P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
P6M0 = 0x00; P6M1 = 0x00;
P7M0 = 0x00; P7M1 = 0x00;
usb_init();
IE2 |= 0x80;
EA = 1;
while(DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置
while(1)
{
P2 = 0x70;
if (bUsbOutReady)
{
USB_SendData(UsbOutBuffer,OutNumber); //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
usb_OUT_done();
}
}
</code></pre>
<p>}</p>
<p>第四集、C语言基础</p>
<p>1.使用printf函数</p>
<p>由于版本的更新,现在用PRINTF_USB来进行重定向。</p>
<p>在97行存在一个</p>
<p>#elif defined PRINTF_HID || defined PRINTF_USB</p>
<p>#define printfprintf_usb</p>
<p>这一段的代码意思是如果当你使能defined PRINTF_HID或者是defined PRINTF_USB 时,printf 相当于是printf_usb,便实现了重定向的过程。</p>
<p><img src="data/attachment/forum/202412/27/234458jrfc6zmxd6zuwmb7.png" alt="1735314292465.png" title="1735314292465.png" /></p>
<p><img src="data/attachment/forum/202412/27/234554z0q71nz9jjtn0xdy.png" alt="1735314348986.png" title="1735314348986.png" /></p>
<p>那么就让我们尝试一下AI8050U的printf函数吧。</p>
<p>这个不断电下载相当舒适,节省时间开发,这一点要点赞。</p>
<p>那么printf函数也是非常合适,中英文字符支持的同时也能支持转换。</p>
<video controls="controls" src="forum.php?mod=attachment&aid=77404"></video>
<p>#include "ai8051u.h" //调用头文件<br />
#include "stc32_stc8_usb.h" //调用头文件</p>
<p>char *USER_DEVICEDESC = NULL;<br />
char *USER_PRODUCTDESC = NULL;<br />
char *USER_STCISPCMD = "@STCISP#";</p>
<p>void main(void)<br />
{<br />
int a = 50;<br />
int b = 5;</p>
<pre><code> P_SW2 |= 0x80;
P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
P6M0 = 0x00; P6M1 = 0x00;
P7M0 = 0x00; P7M1 = 0x00;
usb_init();
IE2 |= 0x80;
EA = 1;
while(DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置
while(1)
{
P2 = 0x00;
if (bUsbOutReady)
{
//USB_SendData(UsbOutBuffer,OutNumber); //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
printf("你好世界\r\n");
printf("a+b= %d a/b= %d \r\n",a+b,a/b);
usb_OUT_done();
}
}
</code></pre>
<p>}</p>
<p>第五集 I/O输入输出</p>
<p>GPIO(General Purpose Input Output,通用输入输出)是嵌入式系统中常见的接口。它允许微控制器或单片机直接控制和监测外部设备的状态。可以通过输入高低电平或者通过它们读入引脚的状态-是高电平或者是低电平。</p>
<p>高电平:接近于电源正极的电压,也称为逻辑‘1’</p>
<p>低电平:接近于GND的电压,也称为逻辑‘0’</p>
<p>单片机输出高电平就是输出VCC的电压,输出低电压就是输出GND的电压。</p>
<p>单片机VDD端极限电压为5.5V,则其他IO对地电压等于5.5+0.3=5.8V。</p>
<p>单片机VDD端极限电压为3.3V,则其他IO对地电压等于3.3+0.3=3.6V。</p>
<p>灌电流可以看作向IO口流入电流。</p>
<p>拉电流可以看作IO向外部输出电流。</p>
<p><img src="data/attachment/forum/202412/28/155140zzukicmuwnjk6pni.png" alt="1735372291533.png" title="1735372291533.png" /></p>
<p><img src="data/attachment/forum/202412/28/155249poosa1ga1joaclcv.png" alt="1735372362070.png" title="1735372362070.png" /></p>
<p>打开手册可以看到电压不同导致施密特开关的电压也是不同的,在5V电压下低电平不能高于1.32V,高电平不能低于1.48V,在5V电压下低电平不能高于0.99V,高电平不能低于1.07V。</p>
<p>上电默认使能施密特触发。</p>
<p><img src="data/attachment/forum/202412/28/155456z29y3ds6skkd62rt.png" alt="1735372485993.png" title="1735372485993.png" /></p>
<p><img src="data/attachment/forum/202412/28/155528dv611r6lbmjrft41.png" alt="1735372518973.png" title="1735372518973.png" /></p>
<p>由于擎天柱板的P2端口为LED灯,故修改。</p>
<video controls="controls" src="forum.php?mod=attachment&aid=77652"></video>
<video controls="controls" src="forum.php?mod=attachment&aid=77653"></video>
<video controls="controls" src="forum.php?mod=attachment&aid=77654"></video>
<video controls="controls" src="forum.php?mod=attachment&aid=77655"></video>
<video controls="controls" src="forum.php?mod=attachment&aid=77656"></video>
<p>#include "ai8051u.h" //调用头文件<br />
#include "stc32_stc8_usb.h" //调用头文件 <br />
#include "intrins.h"</p>
<p>#define u8 unsigned char<br />
#define u16 unsigned int</p>
<p>char *USER_DEVICEDESC = NULL;<br />
char *USER_PRODUCTDESC = NULL;<br />
char *USER_STCISPCMD = "@STCISP#";<br />
u8 state = 0;<br />
u8 i = 0;</p>
<p>void Delay20ms(void) //@24.000MHz<br />
{<br />
unsigned long edata i;</p>
<pre><code>_nop_();
_nop_();
i = 119998UL;
while (i) i--;
</code></pre>
<p>}</p>
<p>void main(void)<br />
{<br />
WTST= 0;<br />
EAXFR = 1;<br />
CKCON = 0;</p>
<pre><code>P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
P6M0 = 0x00; P6M1 = 0x00;
P7M0 = 0x00; P7M1 = 0x00;
usb_init();
IE2 |= 0x80;
EA = 1;
while(DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置
while(1)
{
if (bUsbOutReady)
{
//USB_SendData(UsbOutBuffer,OutNumber); //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
usb_OUT_done();
}
//任务1 按下P32发光 松开P32灭
</code></pre>
<p>// if(P32 == 0)<br />
// {<br />
// P20 = 0;<br />
// <br />
// }<br />
// else<br />
// {<br />
// P20 = 1;<br />
// }<br />
//任务2 按下P32灭 松开P32亮<br />
// if(P32 == 0)<br />
// {<br />
// P20 = 1;<br />
// <br />
// }<br />
// else<br />
// {<br />
// P20 = 0;<br />
// }<br />
//任务3 按一下亮,按一下灭。<br />
// if(P32 == 0)<br />
// {<br />
// Delay20ms();<br />
// if(P32 == 0)<br />
// {<br />
// state = !state;<br />
// P20 = state;<br />
// while(P32 == 0);<br />
// <br />
// }<br />
// <br />
// <br />
// }<br />
//课后作业1<br />
// if(P32 == 0)<br />
// {<br />
// Delay20ms();<br />
// if(P32 == 0)<br />
// {<br />
// P20 = 1;<br />
// while(P32 == 0);<br />
// <br />
// }<br />
// <br />
// }<br />
// else if(P33 == 0)<br />
// {<br />
// Delay20ms();<br />
// if(P33 == 0)<br />
// {<br />
// P20 = 0;<br />
// while(P33 == 0);<br />
// <br />
// }<br />
// <br />
// } <br />
//课后作业2<br />
if(P32 == 0)<br />
{<br />
Delay20ms();<br />
if(i>8)<br />
{<br />
i = 0;<br />
}<br />
if(P32 == 0)<br />
{<br />
P2 = 0xff << i ;<br />
i++;<br />
while(P32 == 0);</p>
<pre><code> }
}
}
</code></pre>
<p>}</p>
<p>第六集、定时器中断</p>
<p>通过AIapp-ISP-v6.95C 选择定时器计数器,时钟频率选24Mhz、定时器0、24位自动重载、12T、定时长度为3秒</p>
<p>使能定时器中断。<br />
<img src="data/attachment/forum/202412/28/214036i0tgada2z1bjh0e1.png" alt="1735393232466.png" title="1735393232466.png" /></p>
<p>将代码粘贴置keil5后,打开手册可以得知,TM0PS可调节定时器0的时钟,如果TM0PS=0,则直接为系统时钟频率,TM0PS = 0x5B;也就是TM0PS=91;说明24Mhz/91 ≈263,736Khz</p>
<p>AUXR &= 0x7F;也就是01111111 将最高位置0其它位保持不变,结合手册我们得知CPU时钟为12分频。</p>
<p>TMOD &= 0xF0;也就是1111 0000 高四位保持不变,低四位全部置0,集合手册我们得知</p>
<p>T0_C/T:置0用作对内部系统时钟进行计数,置1用作计数器对P3.4外部脉冲进行计数。</p>
<p>当GATE=0 ,若TR0=1,则定时器计数。</p>
<p>TL0 = 0x3F;TH0 = 0x01;说明初始值为319。</p>
<p>TF0 = 0; 清除TF0标志</p>
<p>TR0 = 1; 定时器0开始计时</p>
<p>ET0 = 1; 使能定时器0中断</p>
<p><img src="data/attachment/forum/202412/28/220139gyyzddyv73g3vos9.png" alt="1735394491387.png" title="1735394491387.png" /></p>
<p><img src="data/attachment/forum/202412/28/220154ssla3g8sh8zsgl3r.png" alt="1735394507963.png" title="1735394507963.png" /></p>
<p><img src="data/attachment/forum/202412/28/220506gem9ejtmk99388sb.png" alt="1735394699141.png" title="1735394699141.png" /></p>
<p><img src="data/attachment/forum/202412/28/221048oy231zyn9nvzyb5z.png" alt="1735395042349.png" title="1735395042349.png" /></p>
<p><img src="data/attachment/forum/202412/28/221616mjpffvvphqipq1fi.png" alt="1735395369874.png" title="1735395369874.png" /></p>
<p><img src="data/attachment/forum/202412/28/221839hivizpdh5hkov8id.png" alt="1735395513632.png" title="1735395513632.png" /></p>
<p>(91+1) * (65536-319) * 12 / 24000000</p>
<p>71999568 ÷ 24000000 约等于2.99将近3秒了</p>
<p><img src="data/attachment/forum/202412/28/222043g1nzizrkrflzlnur.png" alt="5d8e1ac7567d5353b9bd9f2fae53221.png" title="5d8e1ac7567d5353b9bd9f2fae53221.png" /></p>
<video controls="controls" src="forum.php?mod=attachment&aid=77795"></video>
<p>第七集、定时器周期性调度任务</p>
<p>typedef struct<br />
{<br />
u8 Run; //任务状态:Run/Stop<br />
u16 TIMCount; //定时计数器<br />
u16 TRITime; //重载计数器<br />
void (*TaskHook) (void); //任务函数<br />
} TASK_COMPONENTS;</p>
<p>代码理解如下:创建TASK_COMPONENTS结构体包含四个成员,为之后的调度做准备。</p>
<p>static TASK_COMPONENTS Task_Comps[]=<br />
{<br />
//状态计数周期函数</p>
<p>{0, 300, 300, LED0_Blink}, /* task 1 Period: 300ms <em>/<br />
{0, 600, 600, LED1_Blink}, /</em> task 1 Period: 600ms <em>/<br />
{0, 900, 900, LED2_Blink}, /</em> task 1 Period: 600ms <em>/<br />
{0, 10, 10, KEY_Task}, /</em> task 1 Period: 600ms */<br />
};</p>
<p>代码理解如下:静态结构体成员赋给Task_Comps的数组,一共有四个元素,每个元素都进行了初始化,也就是创造了四个任务。</p>
<p>u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps);</p>
<p>代码理解如下:通过总大小除以单个元素的大小来得出一共有多少个任务</p>
<p>//========================================================================<br />
// 函数: Task_Handler_Callback<br />
// 描述: 任务标记回调函数.<br />
// 参数: None.<br />
// 返回: None.<br />
// 版本: V1.0, 2012-10-22<br />
//========================================================================<br />
void Task_Marks_Handler_Callback(void)<br />
{<br />
u8 i;<br />
for(i=0; i<Tasks_Max; i++)<br />
{<br />
if(Task_Comps.TIMCount) /* If the time is not 0 <em>/<br />
{<br />
Task_Comps.TIMCount--; /</em> Time counter decrement <em>/<br />
if(Task_Comps.TIMCount == 0) /</em> If time arrives */<br />
{<br />
/*Resume the timer value and try again <em>/<br />
Task_Comps.TIMCount = Task_Comps.TRITime;<br />
Task_Comps.Run = 1; /</em> The task can be run */<br />
}<br />
}<br />
}<br />
}</p>
<p>代码理解如下:很明显,让任务运行一定是遵循什么规律的,进入这个函数时for循环使得他对所有任务的计时器-1,如果-1后发现有任务的定时器归零,那么说明延时时间到了,Task_Comps.TIMCount = Task_Comps.TRITime;将该任务的重装载值重新赋给定时计数器,Task_Comps.Run = 1;将他的运行状态置1,为之后的运行做准备。</p>
<p>//========================================================================<br />
// 函数: Task_Pro_Handler_Callback<br />
// 描述: 任务处理回调函数.<br />
// 参数: None.<br />
// 返回: None.<br />
// 版本: V1.0, 2012-10-22<br />
//========================================================================<br />
void Task_Pro_Handler_Callback(void)<br />
{<br />
u8 i;<br />
for(i=0; i<Tasks_Max; i++)<br />
{<br />
if(Task_Comps.Run) /* If task can be run <em>/<br />
{<br />
Task_Comps.Run = 0; /</em> Flag clear 0 <em>/<br />
Task_Comps.TaskHook(); /</em> Run task */<br />
}<br />
}<br />
}</p>
<p>代码理解如下:任务回调函数,当Task_Comps.Run 为真,先将该任务的运行状态清零,之后进入执行函数,运行该函数的代码。</p>
<p>所谓的执行函数就是Task_Comps数组中每个元素的最后一位成员函数。</p>
<video controls="controls" src="forum.php?mod=attachment&aid=78031"></video>
<video controls="controls" src="forum.php?mod=attachment&aid=78032"></video>
页:
[1]