记录我的8051u学习过程
之前通过普中学习了89c51,学到了stc89单片机寄存器工作模式与结构,但是经过这么多年的发展8位stc已经发展到32位了,有必要继续学习更新的内容了,现在看到stc发布的新的ai8051u感觉很强大,于是申请了擎天柱进行学习,手残党,本想补全LED用于观察实验现象,但是奈何发现手工太差,后面发现stc-isp提供了仿真工具,所以就不需要焊led啦,后面要焊就直接焊排针差面包板啦.感谢STC公司提供的学习机会.
感谢冲哥录制的学习教程,能够让我们快速上手ai8051u
第一课:序言
讲解了8051u的特色与试验箱的功能结构,展示了一些通过8051U可以完成的功能构想
1,屏幕刷新率和触摸演示
2,集成i2s可以直接实现录放音功能
3,PWM_DMA控制ws2812数字钟,
4,stc-isp的FFT频谱分析演示
5,手写计算器演示
6,isp在线编程演示
34K ram
64k rom
DMA支持PWM DMA直接到外设
16位PWM12位ADC
4xuartusbx1......
但是手上目前只有擎天柱,核心单元是有了,可以开始学习了
第二课:试验箱介绍 开发工具
开发工具安装步骤:
ai8051U可以支持传统c51的8位开发方式和32位开发方式,8位开发可以直接沿用之前的keil环境,但是在进行32位开发的时候需要使用keil的C251版本进行开发.
开发环境安装步骤:
1.keil官方下载KEIL5c251v560安装并注册
2.在stc_isp中添加设备数据库到keil,要不然keil找不到芯片型号
3.https://www.stcai.com/gjrj下载试验箱程序包
4.下载安装中断扩展程序
5.下载芯片手册
打开示例程序编译
下载测试程序:
按住P32按钮然后按电源键 进入usb下载模式
stc-isp检测到hid-wrtte设备就可以下载程序并观察了 第三课创建工程
打开keil-->>project-->>new project-->>选择保存的位置-->>选择STC MCU database-->>AI-->>”AI051U-32bit series”
创建main.c并添加到工程
配置工程:
Cpu mode:c251
内存模式:small
Code Rom尺寸:large 64K
勾选4Byte中断框架尺寸
勾选创建hex文件,并选择HEX-80 如果程序空间大于64k就需要选择HEX-386
在stc-isp中吧头文件放到自己的工程目录中,添加到工程
Main.c
#include "ai8051u.h"
void main(){
while(1){}
}
8051u中的端口模式由M0 M1组合设置
p0m0=0; p0m1=0 开漏输出
视频中介绍了一些关于GPIo的模式初始化,涉及到配置寄存器中的M0和M1,主要是控制上下拉电阻的开关. 第四课:usb不停电下载
深圳国芯人工智能有限公司-库函数
下载usb库文件
#include "ai8051u.h"
#include "ai_usb.h"
void main()
{
P_SW2 |= 0x80; // 打开扩展寄存器访问
P2M0 = 0x00; // 设置P2口为通用I/O口
P2M1 = 0x00; // 设置P2口为通用I/O口
P2 = 0x0f; // 设置P2口初始状态
usb_init(); // 初始化USB
EA = 1; // 允许总中断
while (1)
{
if (bUsbOutReady)//检查Usb是否准备好
{
USB_SendData(UsbOutBuffer, OutNumber);
// printf_usb("1. Read Num:%d\n", OutNumber);
// printf_usb("2. Read Num:%d\n", OutNumber);
// printf_usb("3. Read Num:%d\n", OutNumber);
// printf_usb("4. Read Num:%d\n", OutNumber);
usb_OUT_done();
}
}
}
实际上由于"ai8051u.h"后面会是不是进行调整,建议实际使用时候使用最新版的stc-isp里面的aicube生成配置,
cebu生成的项目中,用户代码应该卸载如下注释的代码段中,这样可以避免后面修改cube时候将我们自己的代码覆盖掉
//<<AICUBE_USER_MAIN_INITIAL_BEGIN>>
// 在此添加用户主函数初始化代码
//<<AICUBE_USER_MAIN_INITIAL_END>>
第五课 C语言基础语法 运算符
这一刻主要讲了C语言的基础语法和函数结构定义,比较基础.能学习ai8051u的想必大多数玩家都了解这些内容,这里就不作过多记录了
VScode中配置KEilc251
vscode中的eide不支持C251的项目导入,这时候就需要安装Keil uVision Assistant插件这个插件可以支持c51/c251项目
记得在插件配置里面吧c251的路径配置进去
"KeilAssistant.Project.CustomIncludePaths": [
"..\\Sources\\inc",
"C:\\keilc251\\C251\\INC",
],
这样vscoode就可以编辑keil c251的项目了
在编辑代码后按ctrl+s保存代码后按f7就可以直接编译,
配合stc-isp的自动下载功能可以做到随编随烧录 第六课 gpio
高电平接近VCC的电压
低电平 接近 GND的电压
接口可以承受的电压位位vcc+0.3V
如果单片机使用5V电压,那么gpio端口可以接受的对打电压就是5+0.3=5.3V
如果VCC是3.0v那么gpio最大就是3+.3=3.3V
所有GPIO在复位后默认都是打开施密特触发器的
例: VCC=3.3V高电>=1.18v低电平<=0.99
其他电器限制如下图:
https://www.stcaimcu.com/data/attachment/forum/202506/20/014037fs2ppnlupup2du1p.png
Ai8051U 系列单片机所有的 I/O 口均有 4 种工作模式:准双向口 / 弱上拉 (标准 8051 输出口模式)、
推挽输出 / 强上拉、高阻输入 (电流既不能流入也不能流出)、开漏模式。可使用软件对 I/0 口的工作模
式进行各种工作模式的设置。
施密特触发器的作用是将一个范围内的电压转换成确定的高电平或低电平
P0M0与P0M1一起决定gpio的4种工作模式
M0=0M1=0 弱上拉
M0=1M1=0 推挽输出
M0=0M1=1 高阻态
M0=1M1=1 开漏模式
gpio上电经过复位后 p30 31 是默认弱上拉,其他口都是高阻态,因此在使用io口时必须修改io模式
#include "ai8051u.h"
#include "ai_usb.h"
bit p32_status = 0;// 初始化状态
bit p33_status = 0;// 初始化状态
void Delay20ms(void) //@40.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
i = 199998UL;
while (i)
i--;
}
void main()
{
WTST = 0;// 设置程序指令延时参数
EAXFR = 1; // 设置EAXFR位为1,允许访问扩展寄存器
CKCON = 0; // 提高访问xram速度
P2M0 = 0x00; // 设置P2口为通用I/O口
P2M1 = 0x00; // 设置P2口为通用I/O口
P2 = 0x0f; // 设置P2口初始状态
usb_init(); // 初始化USB
EA = 1; // 允许总中断
printf_usb("P32: %d, P33: %d\n", P32, P33); // 打印P32和P33的状态
while (1)
{
if (bUsbOutReady) // 检查Usb是否准备好
{
USB_SendData(UsbOutBuffer, OutNumber);
// printf_usb("1. Read Num:%d\n", OutNumber);
// printf_usb("2. Read Num:%d\n", OutNumber);
// printf_usb("3. Read Num:%d\n", OutNumber);
// printf_usb("4. Read Num:%d\n", OutNumber);
usb_OUT_done();
}
if (P32 == 0)
{
Delay20ms(); // 延时20ms
if (P32 == 0)
{
p32_status = !p32_status; // 切换状态
P20 = p32_status; // 设置P2.0为高电平
while (P32 == 0)
;
printf("P32 pressed, status: %d\n", p32_status);
}
}
if (P33 == 0)
{
Delay20ms(); // 延时20ms
if (P33 == 0)
{
p33_status = !p33_status; // 切换状态
P21 = p33_status; // 设置P2.0为高电平
while (P32 == 0)
;
printf("P33 pressed, status: %d\n", p33_status);
}
}
}
}
第七课:定时器和中断
Stc-isp中可以生成定时器时钟和中断函数
8051u提供了6个24位定时器(8位预分频+16位计数)
6个定时器都支持计数模式和定时模式
定时方式设置相关寄存器:TMOD,AUXR
例子中使用定时器0 生成3秒一次的中断调用,在中断处理中控制LEd状态
中断程序优先级高于while可以比较准确的实现程序定时执行,但是中断程序中尽量不要执行耗时太长的程序,一般只作为标志的更改,具体执行可以交给while进行
TM0PS = 0x1E; //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xFC; //设置定时初始值
TH0 = 0x03; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
定时器的结构
系统时钟sysclk时钟通过TM0PS设置分频(tm0ps的值+1:如果tmops设位0则表示不分频,但是实际计算的时需要为1,因此预分频会自动加1),然后得到的时钟还可以进行12分频或者1分频,然后获得的时钟通过GATE选择是时钟脉冲技术还是外部脉冲计数,计数器中由两套寄存器完成TH0和TL0可以组成一个16位的计数器,RL_TH0和RL_TL0是他们的影子寄存器,他们有同一个地址,这里具体过程需要看手册视频并没有详细讲解他们的工作过程,在这里反正就是用作自动重装载,当计数器从初始值计数到溢出则产生一个中断,这时候如果TF0中断标志位置1就调用中断函数,这个溢出信号还可以通过因gpio输出,但是这就需要T0clk允许位控制了
函数定义:
返回值类型函数名 (参数){
函数体;
Return 返回值;
}
#include "ai8051u.h"
#include "ai_usb.h"
bit p32_status = 0; // 初始化状态
bit p33_status = 0; // 初始化状态
unsigned long systick_cont = 0; // 系统滴答计数器
void Delay1000ms(void) //@24.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
i = 5999998UL;
while (i)
i--;
}
void Timer0_Isr(void) interrupt 1
{
P20 = ~P20; // 切换P20状态
systick_cont++; // 增加系统滴答计数器
// printf_usb("systick_cont: %d\n", systick_cont);
}
void Timer0_Init(void) //1秒@24.000MHz
{
TM0PS = 0x1E; //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xFC; //设置定时初始值
TH0 = 0x03; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
}
void main()
{
WTST = 0;// 设置程序指令延时参数
EAXFR = 1; // 设置EAXFR位为1,允许访问扩展寄存器
CKCON = 0; // 提高访问xram速度
P2M0 = 0x00; // 设置P2口为通用I/O口
P2M1 = 0x00; // 设置P2口为通用I/O口
P2 = 0x0f; // 设置P2口初始状态
Timer0_Init(); // 初始化定时器0
usb_init(); // 初始化USB
EA = 1; // 允许总中断
while (1)
{
Delay1000ms(); // 延时1秒
P21 = ~P21; // 切换P21状态
printf("systick:%dP20: %d P21: %d\n", systick_cont, P20, P21); // 打印P21和P20状态
if (bUsbOutReady) // 检查Usb是否准备好
{
USB_SendData(UsbOutBuffer, OutNumber);
// printf_usb("1. Read Num:%d\n", OutNumber);
// printf_usb("2. Read Num:%d\n", OutNumber);
// printf_usb("3. Read Num:%d\n", OutNumber);
// printf_usb("4. Read Num:%d\n", OutNumber);
usb_OUT_done();
}
}
}
第八课:systick与task周期调度
结构体
typedef struct
{
u8 Run;
u16 TIMECont;
u16 TRITime;
void (*TaskFunc)(void);
} Task;
用一个结构体管理任务信息.
在系统中准备一个tasklist并确定最大长度(因为涉及到内存分配问题这里使用固定任务长度,相当于是预先确定task数组的内存大小)
#define TASK_MAX 10
Task TaskList={0};
初始化tasklist这个可以在main中调用一次,当然不调用也没什么问题
void TASK_Init(void)
{
u8 i;
for (i = 0; i < TASK_MAX; i++)
{
TaskList.Run = 0;
TaskList.TIMECont = 0;
TaskList.TRITime = 0;
TaskList.TaskFunc = NULL;
}
}
暴露三个函数用于添加,执行,运行状态管理
void task_add(u8 index, void (*taskFunc)(void), u16 triTime)
{
if (index < TASK_MAX)
{
TaskList.Run = 1;
TaskList.TaskFunc = taskFunc;
TaskList.TRITime = triTime;
}
}
void task_run(void)
{
u8 i;
for (i = 0; i < TASK_MAX; i++)
{
// if (TaskList.TRITime!=0)
// {
// printf("Run:task[%d]: tr:%d tm:%d RUN:%d \r\n",i,TaskList.TRITime,TaskList.TIMECont,TaskList.Run);
// }
if (TaskList.Run==1)
{
printf("Run:task[%d]: tr:%d tm:%d RUN:%d \r\n",i,TaskList.TRITime,TaskList.TIMECont,TaskList.Run);
if (TaskList.TaskFunc != NULL)
{
TaskList.TaskFunc();
TaskList.Run=0;
}
}
}
}
void task_timer(void)
{
u8 i;
for (i = 0; i < TASK_MAX; i++)
{
if (TaskList.Run == 0 && TaskList.TRITime != 0)
{
TaskList.TIMECont++;
if (TaskList.TIMECont >= TaskList.TRITime)
{
TaskList.TIMECont=0;
TaskList.Run = 1;
}
}
}
}
然后将task_run在main中的while里面循环调用即可,
状态控制程序库丢到while运行 或者放到定时器中断里面精确控制
高效学习,先完整的做完 这三个实验
https://www.stcaimcu.com/data/download/Datasheet/AI8051U.pdf
深圳国芯人工智能有限公司-工具软件