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

AI8051U学习打卡

[复制链接]
  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-05-21 00:03:02
已绑定手机

1

主题

7

回帖

105

积分

注册会员

积分
105
发表于 2024-12-26 22:46:01 | 显示全部楼层 |阅读模式
我的AI8051学习实战
第一集
1.首先是安装编译环境,这里我们选择C251,官网的调查问卷我们可以随便填写,之后就可以跳转到下载页面

截图202412262322532074.jpg 截图202412262325253212.jpg
2.确保均为全英文路径后安装到D盘,尽量避开系统盘以防重装系统导致的文件丢失,打开keil软件选择图中选项,然后复制CID到keygen CID处得到破解代码,最后到图2所示红圈处点击Add Lic,便破解成功与我图中一样即可。
截图202412262330238879.jpg 截图202412262334034589.jpg 截图202412262332187871.jpg

最后也是尝试了一下硬件USB下载程序的快乐,可以说很轻松,直接节省了一个CH340及其外围电路节省成本的同时还能压缩元件布局。
截图202412262331091794.jpg

955b41552b5c7650a26cbdd4ce818c60.mp4

607.73 KB, 下载次数: 47

20241226_224711.mp4

10.48 MB, 下载次数: 47

keygen_new2032.exe

497.63 KB, 下载次数: 49

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-05-21 00:03:02
已绑定手机

1

主题

7

回帖

105

积分

注册会员

积分
105
发表于 2024-12-27 15:38:53 | 显示全部楼层

第二集 点亮一颗LED

按照《8051U深度入门到32位51大型实战教学视频》

6.5新建与设置超64K程序代码的项目(Source)模式

1735283953495.png

通过读取手册可以得知:

AI8051U目前只支持Source模式,由于中断的压栈与出栈都是4字节建议选中 4Byte Intemupt Frame Size

并且Memory Model选择为XSmall模式,首先AI8051U逻辑地址为FF:0000H~FF:FFFFH,必须使用24位地址线才能正确访问,默认常量类型必须使用"far"类型,指针变量为4字节。

Memory Model模式区别:

XSmall模式将变量定义在内部RAM也就是edata中,单时钟存取,访问速度快,且有2k的edata供使用。

Small模式也是默认将变量定义在内部RAM(data)中,而data默认只有128字节,超过128字节会报错

Large模式虽然能够正确访问全部的16M寻址空间,但是该模式默认将变量定义在内部扩展RAM(xdata)里,存取需要2~3个时钟,访问速度慢。

故设置为XSmall模式

Code Rom Size模式区别:

Small模式: 跳转/调用指令 为AJMP/ACALL ,单个函数/模块/文件的代码大小为 2K ,总代码大小为 2K

Medium模式: 内部模块代码使用 为AJMP/ACALL ,外部模块代码使用 为LJMP/LCALL,单个函数/模块/文件的代码大小为 2K ,总代码大小为 64K

Compact模式: 跳转/调用指令 为LCALL/AJMP ,单个函数/模块/文件的代码大小为 2K ,总代码大小为64K

Large模式: 跳转/调用指令 为LCALL/AJMP ,单个函数/模块/文件的代码大小为 64K ,总代码大小为64K

Huge模式: 内部模块代码使用 为LJMP/ECALL ,外部模块代码使用 为EJMP/ECALL,单个函数/模块/文件的代码大小为 2K ,总代码大小为 64K。(多文件项目中,文件内部的代码为内部模块代码,其他文件的代码为外部模块代码)

这里设置为 Large模式

终于到了代码环节,这里必须得搞明白AI8051U的端口定义!

比方说:

我们可以把P0M0、P0M1理解为一个8位的变量,他可以对0~7位进行二进制的操作,赋予0或者是1

P0M0中的P0指的是端口P0,M0指的是P0.0

P0M1中的P0指的是端口P0,M1指的是P0.0

P1M0中的P1指的是端口P1,M0指的是P1.0

P1M1中的P1指的是端口P1,M1指的是P1.0

那么P0.0的引脚模式就是由这个P0M0与P0M1的第0位的状态共同决定的

那么P1.0的引脚模式就是由这个P1M0与P1M1的第0位的状态共同决定的

1735286304900.png

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

void main(void)
{

P2M0 = 0x00; 
  P2M1 = 0x00; 




while(1)
{


	P2 = 0X00;
}

}

前面我们知道了P2M0、P2M1是8位变量,这里赋值的方式为16进制,相当于0000 0000 也就是0~7的引脚设置为 准双向口

程序执行为 先将P2的0~7位设置为准双向口,之后无限将P2的 0 ~ 7位拉低,由于在板子上LED灯与电阻串联,一端在高电平上,而另一端在我们的单片机AI8051U上,也就是通过单片机控制引脚来决定LED灯是否发光

电流的产生总是从高电平流向低电平,所以我们的8个LED灯均会发光

5df10fe43302d52cc3a8cf9dd4a4224.jpg

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-05-21 00:03:02
已绑定手机

1

主题

7

回帖

105

积分

注册会员

积分
105
发表于 2024-12-27 20:45:11 | 显示全部楼层

第三集 不停电USB下载

1.深圳国芯人工智能有限公司-工具软件 下载好USB库

2.选择查询模式STC-CDC范例程序 -> stc32g_cdc_query_demo 将stc_usb_cdc_32g.LIB、stc32_stc8_usb.h这俩个文件放入我们上节课的点灯程序中

1735303551270.png
1735303576066.png
1735303592080.png

1735303613030.png

3.进行不停电USB下载代码的移植,可以看到官方的头文件是STC32G,而我们是AI8051U

将stc_usb_cdc_32g.LIB加入到文件工程下,在mian.c文件中#include “stc32_stc8_usb.h”这样我们第一步完成了。

第二步、将以下变量声明,并对在mian函数中添加 P_SW2 |= 0x80;这一步是在保持原有P_SW2各个位不变的情况下,将最高位置1,否则我们将无法使用特殊功能寄存器。

char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";

1735304096148.png

1735304184917.png

第三步、初始化usb_init();

IE2 |= 0x80;将IE2的最高位拉高,以便打开USB中断允许位。

EA=1,将EA拉高,以便打开中断控制,如图所示。

1735304317731.png

1735304478485.png

第四步、在main函数添加 while(DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置

打开stc32_stc8_usb.h我们可以在35行找到#define DEVSTATE_CONFIGURED 4

也就是说如果DeviceState返回的值为4,while循环条件不满足跳出循环,说明UBS配置完成了可以进行正常的程序运行。

第五步、在while循环中添加 这一段代码的意思是 USB不管接收到什么都会将接收到的代码发送回去

因为stc32_stc8_usb.h 声明了许多的函数,我们没有调用变回报错L57的错误

1735305156372.png

以下为代码展示

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

char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";

void main(void)
{
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 = 0x70;

	    if (bUsbOutReady)
    {
        USB_SendData(UsbOutBuffer,OutNumber);   //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
  
        usb_OUT_done();
    }
}

}

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-05-21 00:03:02
已绑定手机

1

主题

7

回帖

105

积分

注册会员

积分
105
发表于 2024-12-27 23:48:45 | 显示全部楼层

第四集、C语言基础

1.使用printf函数

由于版本的更新,现在用PRINTF_USB来进行重定向。

在97行存在一个

#elif defined PRINTF_HID || defined PRINTF_USB

#define printf printf_usb

这一段的代码意思是如果当你使能defined PRINTF_HID或者是defined PRINTF_USB 时,printf 相当于是 printf_usb,便实现了重定向的过程。

1735314292465.png

1735314348986.png

那么就让我们尝试一下AI8050U的printf函数吧。

这个不断电下载相当舒适,节省时间开发,这一点要点赞。

那么printf函数也是非常合适,中英文字符支持的同时也能支持转换。

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

char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";

void main(void)
{
int a = 50;
int b = 5;

	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();
    }
}

}

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-05-21 00:03:02
已绑定手机

1

主题

7

回帖

105

积分

注册会员

积分
105
发表于 2024-12-28 15:49:28 | 显示全部楼层

第五集 I/O输入输出

GPIO(General Purpose Input Output,通用输入输出)是嵌入式系统中常见的接口。它允许微控制器或单片机直接控制和监测外部设备的状态。可以通过输入高低电平或者通过它们读入引脚的状态-是高电平或者是低电平。

高电平:接近于电源正极的电压,也称为逻辑‘1’

低电平:接近于GND的电压,也称为逻辑‘0’

单片机输出高电平就是输出VCC的电压,输出低电压就是输出GND的电压。

单片机VDD端极限电压为5.5V,则其他IO对地电压等于5.5+0.3=5.8V。

单片机VDD端极限电压为3.3V,则其他IO对地电压等于3.3+0.3=3.6V。

灌电流可以看作向IO口流入电流。

拉电流可以看作IO向外部输出电流。

1735372291533.png

1735372362070.png

打开手册可以看到电压不同导致施密特开关的电压也是不同的,在5V电压下低电平不能高于1.32V,高电平不能低于1.48V,在5V电压下低电平不能高于0.99V,高电平不能低于1.07V。

上电默认使能施密特触发。

1735372485993.png

1735372518973.png

由于擎天柱板的P2端口为LED灯,故修改。

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

#define u8 unsigned char
#define u16 unsigned int

char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";
u8 state = 0;
u8 i = 0;

void Delay20ms(void) //@24.000MHz
{
unsigned long edata i;

_nop_();
_nop_();
i = 119998UL;
while (i) i--;

}

void main(void)
{
WTST = 0;
EAXFR = 1;
CKCON = 0;

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灭

// if(P32 == 0)
// {
// P20 = 0;
//
// }
// else
// {
// P20 = 1;
// }
//任务2 按下P32灭 松开P32亮
// if(P32 == 0)
// {
// P20 = 1;
//
// }
// else
// {
// P20 = 0;
// }
//任务3 按一下亮,按一下灭。
// if(P32 == 0)
// {
// Delay20ms();
// if(P32 == 0)
// {
// state = !state;
// P20 = state;
// while(P32 == 0);
//
// }
//
//
// }
//课后作业1
// if(P32 == 0)
// {
// Delay20ms();
// if(P32 == 0)
// {
// P20 = 1;
// while(P32 == 0);
//
// }
//
// }
// else if(P33 == 0)
// {
// Delay20ms();
// if(P33 == 0)
// {
// P20 = 0;
// while(P33 == 0);
//
// }
//
// }
//课后作业2
if(P32 == 0)
{
Delay20ms();
if(i>8)
{
i = 0;
}
if(P32 == 0)
{
P2 = 0xff << i ;
i++;
while(P32 == 0);

				}
		
			}
		
}

}

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-05-21 00:03:02
已绑定手机

1

主题

7

回帖

105

积分

注册会员

积分
105
发表于 2024-12-28 21:40:43 | 显示全部楼层

第六集、定时器中断

通过AIapp-ISP-v6.95C 选择定时器计数器,时钟频率选24Mhz、定时器0、24位自动重载、12T、定时长度为3秒

使能定时器中断。
1735393232466.png

将代码粘贴置keil5后,打开手册可以得知,TM0PS可调节定时器0的时钟,如果TM0PS=0,则直接为系统时钟频率,TM0PS = 0x5B;也就是TM0PS=91;说明24Mhz/91 ≈263,736Khz

AUXR &= 0x7F;也就是0111 1111 将最高位置0其它位保持不变,结合手册我们得知CPU时钟为12分频。

TMOD &= 0xF0;也就是1111 0000 高四位保持不变,低四位全部置0,集合手册我们得知

T0_C/T:置0用作对内部系统时钟进行计数,置1用作计数器对P3.4外部脉冲进行计数。

当GATE=0 ,若TR0=1,则定时器计数。

TL0 = 0x3F;TH0 = 0x01;说明初始值为319。

TF0 = 0; 清除TF0标志

TR0 = 1; 定时器0开始计时

ET0 = 1; 使能定时器0中断

1735394491387.png

1735394507963.png

1735394699141.png

1735395042349.png

1735395369874.png

1735395513632.png

(91+1) * (65536-319) * 12 / 24000000

71999568 ÷ 24000000 约等于2.99将近3秒了

5d8e1ac7567d5353b9bd9f2fae53221.png

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:10
  • 最近打卡:2025-05-21 00:03:02
已绑定手机

1

主题

7

回帖

105

积分

注册会员

积分
105
发表于 2024-12-29 22:06:50 | 显示全部楼层

第七集、定时器周期性调度任务

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

代码理解如下:创建TASK_COMPONENTS结构体包含四个成员,为之后的调度做准备。

static TASK_COMPONENTS Task_Comps[]=
{
//状态 计数 周期 函数

{0, 300, 300, LED0_Blink}, /* task 1 Period: 300ms /
{0, 600, 600, LED1_Blink}, /
task 1 Period: 600ms /
{0, 900, 900, LED2_Blink}, /
task 1 Period: 600ms /
{0, 10, 10, KEY_Task}, /
task 1 Period: 600ms */
};

代码理解如下:静态结构体成员赋给Task_Comps的数组,一共有四个元素,每个元素都进行了初始化,也就是创造了四个任务。

u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps[0]);

代码理解如下:通过总大小除以单个元素的大小来得出一共有多少个任务

//========================================================================
// 函数: 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++)
{
if(Task_Comps[i].TIMCount) /* If the time is not 0 /
{
Task_Comps[i].TIMCount--; /
Time counter decrement /
if(Task_Comps[i].TIMCount == 0) /
If time arrives */
{
/*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 */
}
}
}
}

代码理解如下:很明显,让任务运行一定是遵循什么规律的,进入这个函数时for循环使得他对所有任务的计时器-1,如果-1后发现有任务的定时器归零,那么说明延时时间到了,Task_Comps[i].TIMCount = Task_Comps[i].TRITime;将该任务的重装载值重新赋给定时计数器,Task_Comps[i].Run = 1;将他的运行状态置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 /
{
Task_Comps[i].Run = 0; /
Flag clear 0 /
Task_Comps[i].TaskHook(); /
Run task */
}
}
}

代码理解如下:任务回调函数,当Task_Comps[i].Run 为真,先将该任务的运行状态清零,之后进入执行函数,运行该函数的代码。

所谓的执行函数就是Task_Comps数组中每个元素的最后一位成员函数。

回复 支持 反对

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2025-6-30 23:48 , Processed in 0.123394 second(s), 85 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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