# 第一集新建工程相关
- 51的新建工程需要注意Memory model,一般选择XSmall即可,tiny模式串口无法正常工作,原理至今还不是很清楚。
- 因为新建的工程默认为tiny模式,修改到XSmall即可。

- 然后添加c文件到项目中就可以快乐的写代码了。
- AI8051 IO单独操作的方法,
- PnM1&=~0x20;给P15设置为推挽输出。P1M1的第五位置为0
- PnM0|=0x20;//P1M0的第五位置为1
```c
#include"AI8051U.h"
#include"intrins.h"//一定要加这两个头文件
typedef unsigned char u8;
typedef unsigned short int u16;
void delay_us(u16 us)
{
do
{
NOP36();
}
while(us--);//us--需要先对us进行减一次,然后赋值给us,然后判断一次,一共需要4个指令周期,NOP36是36个指令周期,40*25=1000ns=1us
//MOV R6,R7 //1个指令周期
//DEC R7,#0X01//1个指令周期
//CJNE R6,#0X00,C:0X0005//如果发生跳转,就是3个指令周期,正常情况为2个指令周期。
}
void delay_ms(u16 ms) //unsigned int
{
do
{
delay_us(1000);//减少对函数的调用,增加性能
}while(--ms);
}
void main()
{
P0M0=0XFF;
P0M1=0X00;//设置P0的IO为推挽输出
while(1)
{
P00=1;//设置P00口为高电平
delay_ms(500);
P00=0;
delay_ms(500);//设置P00口为低电平
}
}
第二集 不停电烧录
- 使用手册中的电路可以进行不停电烧录,挺方便的。

- 打开AIapp-ISP-v6.xx.exe软件,选择你需要烧录的hex文件,如果没有的话,去keil的魔法棒(options for target)中的output那一栏,勾选Create HEX file,点击OK,关闭这个(options for target)之后编译一下即可。hex文件一般在工程文件所在的目录下,找Objects。一般来说,默认的hex创建路径就在这个目录下。
- 然后直接下载编程即可。
第三集 点亮rgb灯
- 先上代码,
- 基本上就是根据ws2812b的手册,确定发送数据的高低电平的长度就行。
#include "AI8051U.h"
#include "intrins.h"
typedef unsigned char u8;
typedef unsigned short int u16;
// 延时1us的时间为1000ns/25ns=40;
// 1/Fosc=x ns
// 1000ns/(1/Fosc)ns= x 个时钟周期
// 实际计算时:1000e-9*Fosc=x 个时钟周期
// while(i--)四个时钟周期,(x-4)=NOP(val);延时1us所需要的时间
//这个代码使用的是IO口高低电平进数据发送。
u8 LED1[3]; // 红色(R=255, G=0, B=0)
u8 LED2[3]; // 绿色(R=0, G=255, B=0)
u8 color_table[5][3] = {
{0xFF, 0x00, 0x00}, // 红色
{0x00, 0xFF, 0x00}, // 绿色
{0x00, 0x00, 0xFF}, // 蓝色
{0xFF, 0xFF, 0x00}, // 黄色
{0xFF, 0x00, 0xFF} // 紫色
};
#define LED_PIN P15
// 该延时函数仅在40Mhz下是准确的,其他频率需要自行计算NOPx()的值
void delay_us(u16 us)
{
do
{
NOP(36);
} while (us--); // us--需要先对us进行减一次,然后赋值给us,然后判断一次,一共需要4个指令周期,NOP36是36个指令周期,40*25=1000ns=1us
// MOV R6,R7 //1个指令周期
// DEC R7,#0X01//1个指令周期
// CJNE R6,#0X00,C:0X0005//如果发生跳转,就是3个指令周期,正常情况为2个指令周期。
// 方案2
// NOP40();
}
void delay_ms(u16 ms) // unsigned int
{
do
{
delay_us(1000); // 减少对函数的调用,增加性能
} while (--ms);
}
void send_byte(unsigned char byte)
{
unsigned char i;
for (i = 0; i < 8; i++)
{
if (byte & 0x80)
{ // 如果最高位为 1
LED_PIN = 1; // 发1 低电平为0.3us
delay_us(1); //
LED_PIN = 0;
NOP13();
}
else
{ // 如果最高位为 0
LED_PIN = 1; // 发0 高电平为0.3us
NOP13(); //
LED_PIN = 0; //
delay_us(1);
}
byte <<= 1; // 左移 1 位,准备发送下一位
}
}
void switch_to_next_color(u8 *LED, u8 index)
{
unsigned char i;
LED[0] = color_table[index][0]; // G
LED[1] = color_table[index][1]; // R
LED[2] = color_table[index][2]; // B
for (i = 0; i < 3; i++)
{
send_byte(LED[i]); // 发送每个颜色字节(G, R, B)
}
}
void ws2812b_reset()
{
LED_PIN = 0;
delay_us(300); // 拉低时间大于 50 微秒即可
}
// 主程序
void main()
{
u8 index1 = 0; // LED1 的颜色索引
u8 index2 = 2; // LED2 的颜色索引
P_SW2 = 0X80;
CKCON = 0x00;
WTST = 0x00;
P4M0 = 0X00;
P4M1 = 0X00;
P1M0 |= 0x20;//设置P15为数据输出口
P1M1 &= ~0x20;
while (1)
{
ws2812b_reset();
switch_to_next_color(LED1, index1);
switch_to_next_color(LED2, index2);
index1 = (index1 + 1) % 5; // 颜色表循环
index2 = (index2 + 1) % 5; // 颜色表循环
// 延时 5 毫秒,避免过快发送
delay_ms(500);
}
}
- 效果图

第四集 串口使用
#include "AI8051U.h"
#include "intrins.h"
#include "stdio.h"
#define FOSC 40000000UL
#define BRT (65536 - (FOSC / 115200 + 2) / 4)
char buffer[16];
bit busy;
char wptr;
char rptr;
typedef unsigned char u8;
typedef unsigned int u16;
void delay_ms(u16 ms) // unsigned int
{
u16 i = 0;
do
{
for(i=0;i<900;++i)//11059200 243即可,40M i=900即可
NOP40(); // 减少对函数的调用,增加性能
} while (--ms);
}
void Uart2Isr() interrupt 8
{
if (S2TI)
{
S2TI = 0;
busy = 0;
}
if (S2RI)
{
S2RI = 0;
buffer[wptr++] = S2BUF;
wptr &= 0x0f;
}
}
void Uart2Init()
{
P_SW2 = 0x80;
S2CFG = 0x01;
S2CON = 0x50;
T2L = BRT;
T2H = BRT >> 8;
T2x12 = 1;
T2R = 1;
wptr = 0x00;
rptr = 0x00;
busy = 0;
P1M0&=~0X0C;
P1M1&=~0X0C;
IE2 = 0x01;
EA = 1;
}
void Uart2Send(char dat)
{
while (busy)
;
busy = 1;
S2BUF = dat;
}
void Uart2SendStr(char *p)
{
while (*p)
{
Uart2Send(*p++);
}
}
char putchar(char c)
{
Uart2Send(c);
return c;
}
// 主程序
void main()
{
u16 index=0;
P_SW2 = 0X80;
CKCON = 0x00;
WTST = 0x00;
Uart2Init();
Uart2SendStr("Uart Test !\r\n");
printf("%d\n",index);
while (1)
{
for(index=0;index<10;++index)
{
printf("Uart Test index=%d!\r\n",index);
delay_ms(1000);
}
}
}
- 主要的重点在于使用printf函数进行串口输出打印。加一个stdio.h的头文件,然后重定义一下putchar函数。
char putchar(char c)
{
Uart2Send(c);
return c;
}

第五集 按键使用
- 按键io初始化
void GPIO_init()
{
P_SW2 = 0X80;
CKCON = 0X00;
WTST = 0X00;
P2M0 = 0X00;
P2M1 = 0X00;
P1M0 &= ~0x10;//设置P14为双向口
P1M1 &= ~0x10;
P5M0 &= ~0x0c;//设置P52,P53为双向口
P5M1 &= ~0x0c;
P4M0 = 0X00;//设置P4为双向口
P4M1 = 0X00;
P1IM0 &=~0X10;//设置P14为下降沿中断
P1IM1 &=~0X10;
P1INTE|= 0x10;
P5IM0 &=~0x0c;//设置P52 P53为下降沿中断
P5IM1 &=~0x0c;//
P5INTE|= 0x0c;//使能P52 P53中断
P1INTE|= 0x10;
}
- 中断执行
void common_isr() interrupt 13
{
unsigned char intf;
unsigned char intf_5;
intf = P1INTF;
intf_5 = P5INTF;
if(intf&0x10)//使用char型临时变量判断中断是否到来。
{
flag_ = 1;
}
if(intf_5&0x08)
{
flag_ = 3;
}
if(intf_5&0x04)
{
flag_ = 2;
}
P1INTF&=~0X10;//清除中断标志位
P5INTF&=~0X0c;
}
把下面的代码。新建一个文件isr.asm,添加到项目中。由于中断向量大于31,借用13号中断地址入口。
CSEG AT 0133H
JMP P1INT_ISR
P1INT_ISR:
JMP 006BH
CSEG AT 0153H
JMP P5INT_ISR
P5INT_ISR:
JMP 006BH
END
第六集 多文件编译
- 全局变量一定定义在头文件下面,函数声明和定义的前面
- 函数在单独的C文件中需要有定义,在头文件中声明。需要调用这个函数的文件夹,需要extern 该函数的声明。
- 具体demo见附件
第七集 pwm使用
- 这个代码实现的功能为在PWMA的P00 和P01输出一个互补带死区的PWM波。用于驱动电机。
#include "AI8051U.H"
#include "intrins.h"
int main(void)
{
P_SW2 = 0X80;
CKCON = 0x00;
WTST = 0x00;
P0M1 = 0x00;
P0M0 = 0xFF;
P1M1&=~0x80;
P1M0|=0x80;
PWMA_ENO = 0xFF;
PWMA_PS = 0x01;
PWMA_PSCRH = 0x00;//预分频寄存器
PWMA_PSCRL = 0x00;
PWMA_DTR = 0x10;
PWMA_CCMR1 = 0x68;
PWMA_CCMR2 = 0x68;
PWMA_CCMR3 = 0x68;
PWMA_CCMR4 = 0x68;
PWMA_ARRH = 0x08;
PWMA_ARRL = 0x00;
PWMA_CCR1H = 0x04;
PWMA_CCR1L = 0x00;
PWMA_CCR2H = 0x02;
PWMA_CCR2L = 0x00;
PWMA_CCR3H = 0x01;
PWMA_CCR3L = 0x00;
PWMA_CCR4H = 0x01;
PWMA_CCR4L = 0x00;
PWMA_CCER1 = 0x55;
PWMA_CCER2 = 0x55;
//配置通道输出使能和极性
//配置通道输出使能和极性
PWMA_BKR = 0x80;
PWMA_IER = 0x02;
PWMA_CR1 = 0x01;
EA = 1;
P17=1;
while (1);
}
void PWMA_ISR() interrupt 26
{
if(PWMA_SR1 & 0X02)
{
P03 = ~P03;
PWMA_SR1 &=~0X02;
}
}
![wx_camera_1734537072187[1].jpg wx_camera_17345370721871.jpg](data/attachment/forum/)
补充说明
时钟周期、机器周期、指令周期
机器周期、时钟周期和指令周期是8051单片机中的重要概念。
- 时钟周期:单片机时钟信号的一个完整周期,即时钟源的周期,通常由晶振频率决定。
- 机器周期:单片机执行基本操作所需的时间,通常由时钟周期数来决定。例如8051的一个机器周期为12个时钟周期。AI8051是单时钟/机器周期(1T)的单片机
- 指令周期:执行一条指令的时间,通常由多个机器周期组成。
由于AI8051为单时钟机器周期的单片机,所以其执行一条指令的时间为一个时钟周期。
在40MHz的时钟下,时钟周期为1/40,000,000 = 25ns,因此一个机器周期为25ns。
- 延时1us:1us = 1000ns,因此需要延时1000ns / 25ns =40;
- 延时1ms:1ms = 1,000,000ns,延时1000000ns / 25ns = 40000。
所以ai8051的较为精确的延时函数计算公式为
1/Fosc=x ns
1000ns/(1/Fosc)ns= x 个时钟周期
实际计算时:1000e-9*Fosc=x 个时钟周期
while(i--)四个时钟周期,(x-4)=NOP(val);延时1us所需要的时间
可以通过以下代码实现延时:
while(--us)
MOV R6,R7 //一个指令周期
DEC R7,#0X01//一个指令周期
CJNE R6,#0X00,C:0X0005//如果发生跳转,就是3个指令周期,正常情况为2个指令周期。

void delay_us(u16 us)
{
do
{
NOP36();
}
while(us--);//us--需要先对us进行减一次,然后赋值给us,然后判断一次,一共需要4个指令周期,NOP36是36个指令周期,40*25=1000ns=1us
//MOV R6,R7 //1个指令周期
//DEC R7,#0X01//1个指令周期
//CJNE R6,#0X00,C:0X0005//如果发生跳转,就是3个指令周期,正常情况为2个指令周期。
}
void delay_ms(u16 ms) //unsigned int
{
do
{
delay_us(1000);//减少对函数的调用,增加性能
}while(--ms);
}
//如果实现ns级别的延时,直接NOP(40),40*25ns=1us
这种方法给出了粗略的延时,可以根据需要微调延时精度。
上面文件的源代码,这里
附件:test_51.zip
我使用板子的原理图
附件:SCH_Schematic1_2024-12-22.pdf