XHB7906021
发表于 2024-12-21 13:54:38
<p>周期性多任务,讲的不错!<img alt="shengli" class="emoji" src="https://www.stcaimcu.com/static/image/smiley/default/shengli.gif" title="shengli" /> 有点理解了。多发点视让大家学习学习。祝愿国产单片机越做越好!</p>
lclbf
发表于 2024-12-21 14:03:15
macrofei 发表于 2024-12-16 04:26
第七集 定时器中断
了解了定时器的执行过程
配置定时器:设置定时器的工作模式、初值、是否启用中断等。
好思路。
askalai
发表于 2024-12-21 21:29:31
第九集和第十集是还没有发资料吗?
我行我速
发表于 2024-12-21 22:40:54
<p>打卡第四集,继续努力学习。。。</p>
蓝鹰
发表于 2024-12-22 07:18:55
神农鼎 发表于 2024-11-19 11:23
学到了
小垃圾
发表于 2024-12-22 12:02:09
<pre><code># 第一集新建工程相关
- 51的新建工程需要注意Memory model,一般选择XSmall即可,tiny模式串口无法正常工作,原理至今还不是很清楚。
- 因为新建的工程默认为tiny模式,修改到XSmall即可。
!(data/attachment/forum/202412/22/114726qlswlzw2idwfkpsv.png "image.png")
- 然后添加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口为低电平
}
}
</code></pre>
<h1>第二集 不停电烧录</h1>
<ul>
<li>使用手册中的电路可以进行不停电烧录,挺方便的。<br />
<img src="data/attachment/forum/" alt="image-1.png" title="image-1.png" /></li>
<li>打开AIapp-ISP-v6.xx.exe软件,选择你需要烧录的hex文件,如果没有的话,去keil的魔法棒(options for target)中的output那一栏,勾选Create HEX file,点击OK,关闭这个(options for target)之后编译一下即可。hex文件一般在工程文件所在的目录下,找Objects。一般来说,默认的hex创建路径就在这个目录下。</li>
<li>然后直接下载编程即可。</li>
</ul>
<h1>第三集 点亮rgb灯</h1>
<ul>
<li>先上代码,</li>
<li>基本上就是根据ws2812b的手册,确定发送数据的高低电平的长度就行。</li>
</ul>
<pre><code class="language-c">#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; // 红色(R=255, G=0, B=0)
u8 LED2; // 绿色(R=0, G=255, B=0)
u8 color_table = {
{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 = color_table; // G
LED = color_table; // R
LED = color_table; // B
for (i = 0; i < 3; i++)
{
send_byte(LED); // 发送每个颜色字节(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);
}
}
- 效果图
!(data/attachment/forum/202412/22/115343m21a668fk6cm8k7j.jpg "IMG_20241222_112051.jpg")
</code></pre>
<h1>第四集 串口使用</h1>
<pre><code class="language-c">#include "AI8051U.h"
#include "intrins.h"
#include "stdio.h"
#define FOSC 40000000UL
#define BRT (65536 - (FOSC / 115200 + 2) / 4)
char buffer;
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 = 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;
}
!(data/attachment/forum/202412/22/114917yldlkwxot9kdizk3.png "image-2.png")
</code></pre>
<h1>第五集 按键使用</h1>
<ol>
<li>按键io初始化</li>
</ol>
<pre><code class="language-c">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;
}
</code></pre>
<ol start="2">
<li>中断执行</li>
</ol>
<pre><code class="language-c">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;
}
</code></pre>
<p>把下面的代码。新建一个文件isr.asm,添加到项目中。由于中断向量大于31,借用13号中断地址入口。</p>
<pre><code class="language-asm">CSEG AT 0133H
JMP P1INT_ISR
P1INT_ISR:
JMP 006BH
CSEG AT 0153H
JMP P5INT_ISR
P5INT_ISR:
JMP 006BH
END
</code></pre>
<h1>第六集 多文件编译</h1>
<ul>
<li>全局变量一定定义在头文件下面,函数声明和定义的前面</li>
<li>函数在单独的C文件中需要有定义,在头文件中声明。需要调用这个函数的文件夹,需要extern 该函数的声明。</li>
<li>具体demo见附件</li>
</ul>
<h1>第七集 pwm使用</h1>
<ul>
<li>这个代码实现的功能为在PWMA的P00 和P01输出一个互补带死区的PWM波。用于驱动电机。</li>
</ul>
<pre><code class="language-c">#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;
}
}
</code></pre>
<p><img src="data/attachment/forum/" alt="wx_camera_17345370721871.jpg" title="wx_camera_1734537072187.jpg" /></p>
<h1>补充说明</h1>
<h2>时钟周期、机器周期、指令周期</h2>
<p>机器周期、时钟周期和指令周期是8051单片机中的重要概念。</p>
<ul>
<li><strong>时钟周期</strong>:单片机时钟信号的一个完整周期,即时钟源的周期,通常由晶振频率决定。</li>
<li><strong>机器周期</strong>:单片机执行基本操作所需的时间,通常由时钟周期数来决定。例如8051的一个机器周期为12个时钟周期。AI8051是单时钟/机器周期(1T)的单片机</li>
<li><strong>指令周期</strong>:执行一条指令的时间,通常由多个机器周期组成。<br />
由于AI8051为单时钟机器周期的单片机,所以其执行一条指令的时间为一个时钟周期。<br />
在40MHz的时钟下,时钟周期为1/40,000,000 = 25ns,因此一个机器周期为25ns。</li>
<li><strong>延时1us</strong>:1us = 1000ns,因此需要延时1000ns / 25ns =40;</li>
<li><strong>延时1ms</strong>:1ms = 1,000,000ns,延时1000000ns / 25ns = 40000。<br />
所以ai8051的较为精确的延时函数计算公式为</li>
</ul>
<p>1/Fosc=x ns<br />
1000ns/(1/Fosc)ns= x 个时钟周期<br />
实际计算时:1000e-9*Fosc=x 个时钟周期<br />
while(i--)四个时钟周期,(x-4)=NOP(val);延时1us所需要的时间</p>
<p>可以通过以下代码实现延时:<br />
while(--us)</p>
<pre><code class="language-C">MOV R6,R7 //一个指令周期
DEC R7,#0X01//一个指令周期
CJNE R6,#0X00,C:0X0005//如果发生跳转,就是3个指令周期,正常情况为2个指令周期。
</code></pre>
<p><img src="data/attachment/forum/" alt="alt text" /></p>
<pre><code class="language-c">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
</code></pre>
<p>这种方法给出了粗略的延时,可以根据需要微调延时精度。</p>
<h1>上面文件的源代码,这里</h1>
<p><a href="forum.php?mod=attachment&aid=75482" title="attachment"><img src="data/attachment/forum/" alt="upload" /> 附件:test_51.zip</a></p>
<h1>我使用板子的原理图</h1>
<p><a href="forum.php?mod=attachment&aid=75487" title="attachment"><img src="data/attachment/forum/" alt="upload" /> 附件:SCH_Schematic1_2024-12-22.pdf</a></p>
小垃圾
发表于 2024-12-22 12:03:37
小垃圾 发表于 2024-12-22 12:02
# 第一集新建工程相关
- 51的新建工程需要注意Memory model,一般选择XSmall即可,tiny模式串口无法正常工作 ...
图片暂时看不了不知道为啥
白水大虾2016
发表于 2024-12-22 12:40:48
<p>学习中</p>
jackfangxq
发表于 2024-12-22 13:06:06
522810886 发表于 2024-12-20 10:34
学习打卡区在那里?
论坛主页下边
wamcncn
发表于 2024-12-22 14:19:28
定时器编译警告,哪里出错了
找到问题了,原来是魔法棒里设置的4字节中断不打√