使用AT89S52构建延时函数与输出PWM波
任务要求:
51单片机精准延时以及中断的设计-无RTOS模式。设单片机的时钟12MHz,型号为AT89S52。
1.构造一个不依赖定时器(采用nop+nop()的研视函数;非中断模式);
2.用单片机的引脚输出2KHz占空比为20%的方波;
3.用中断模式实现单片机的引脚输出2KHz占空比为20%的方波;
注:在TIMER0、1或2中断中修改单片机的引脚电平,并修改下一轮溢出的时间(如设置TLx和THx的值);
这个操作耗时较少,而且是TIMER的仅有任务(修改引脚电平、修改THx和TLx)无必要另行封装为函数;
4.将波形在虚拟逻辑分析仪显示出来;
任务分析:
首先,使用nop模式的任务要求最终输出一个占空比位20%,频率位2KHz的方波,通过公式T=1/f可知,每个PWM周期为500微秒。因为占空比为20%,所以输出高电平100微秒,低电平400微秒。那么我们需要构建两个延时函数,一个延时100微秒,一个延时400微秒,并且在延时的时候改变引脚极性就可以输出实验要求的PWM波。
实验要求使用nop();方式构造延时函数。nop();代表空指令,即运行一次整个指令,单片机就会空闲一个机器周期什么都不工作。实验给出的单片机是AT89S52,时钟频率为12MHz。AT89S52是51架构单片机,受限于51的指令集,他的机器周期为时钟周期的十二分之一,即1MHz。那么每运行一次nop指令就可以看作单片机延时1微秒。
可以构造一个循环函数,这个循环函数内执行nop指令,就可以延时较长时间而不用重复写很多行nop指令。由于循环函数每次进入时需要对循环条件进行判断,而这部分远远大于1微秒,可以记循环一次的时间为k,循环次数为x,需要延时的时间长度为y,那么可以将这个延时时间长度看做一元方程组:y=kx
假设k的大小不够合适,比如要延时100微秒,k为3,那么可以进一步修改为:y=kx+b,b为多少,就在循环函数结束后运行多少次nop指令。
接着要求使用中断输出方波,并且每次修改定时器重装载值来完成延时长度的变化。
对于中断方式,首先我们需要一个变量用于记录进入中断的次数,通过次数不同,来修改中断时间,因为中断时间就两个,一个是100微秒,一个是400微秒,那么这个变量可以任意定义,只需要每次在中断结束后对这个变量自加,要修改重装载值得时候,判断一下这个变量得奇偶就行了。
修改重装载值这个步骤本身会占据一定的时长,即中断函数的运行会占用时间,因此下一轮溢出的时间要比100us短才对。
任务实现:
首先新建一个keil5工程。
打开keil,project->New uVision Project 新建本项目的keil工程文件。接着在新的工程中选择单片机为AT89S52,配置仿真时钟频率为12MHz如图所示。
接着新建”main.c”,并且在”main.c”中添加包含有初始化代码的头文件”init.h”、包含有51寄存器声明的头文件"C8051F020_defs.h"以及包含有nop指令的头文件”intrins.h”。假设使用P10输出PWM,所以在定义完头文件后,对P10进行定义:sbit P1^0 = P1_0,方便书写识别引脚电平转换。
下一步构建main函数:
1 void main() 2 { 3 Init_Device(); 4 5 for (;;) 6 { 7 P1_0 = 0; 8 delay400us(17); 9 P1_0 = 1; 10 delay100us(3); 11 } 12 }
如上所示的main函数中只有第8行和第10行的延时函数是没有定义的,其他都在前面步骤里完成了。
所以最后,也是最重要的,本实验的核心内容:构建精准延时函数。
按照实验分析中的思路,我使用函数入口参数控制循环的次数,并且在循环外加上固定的nop指令,试凑出指定时常的延时函数。
下面以delay400us(17);为例,传递给这个函数的参数“17”为试凑出来的结果,因此在后期调用时,不应该修改其数值,也就是“17”不可以修改。
在P1_0=0处和P1_0=1处设置断点,通过仿真读出各自运行过的机器周期,再将它们相减就是delay400us(17);延时的时间长度。
其中,监视窗口的states代表着机器周期。
通过上两张图可知程序第一次停止在P1_0 = 0处单片机运行过的机器周期为22255,程序第一次停止在P1_0 = 1处,单片机运行过的机器周期为22655,两者相减为400,即delay400us(17)这个函数延时了400个机器周期,按照之前项目分析那样,一个机器周期对应一微秒,所以这个延时函数算是成功了。
同理,继续运行仿真下去。
通过上图可以看到程序第二次运行到P1_0 = 0处单片机运行过的机器周期为22755。也就是delay100us(3)这个函数延时了100个机器周期也就是100us。
再通过示波器观看P1_0引脚的波形,检查是否按照需求产生了PWM脉冲,如下图所示。
上图左侧P1_0代表波形为P1_0口产生的,通过观察高低电平的比值可知确实产生了一个占空比为20%的方波,但是由于仿真系统的问题,所以逻辑分析仪中的时间和设计的时间对不上。按照机器周期来计算是正确的,所以可以判断是仿真系统出现BUG导致时间显示错误。
使用中断方式关键在于中断函数的书写,主函数与非中断模式大同小异:
1 int a = 0; 2 3 void Timer0_ISR() interrupt 1 4 { 5 6 if (a == 0) 7 { 8 TH0 = (65535 - 32) / 256; 9 TL0 = (65535 - 32) % 256; 10 P1_0 = 0; 11 a++; 12 } 13 14 else 15 { 16 TH0 = (65535 - 6) / 256; 17 TL0 = (65535 - 6) % 256; 18 P1_0 = 1; 19 a = 0; 20 } 21 }
以上为中断函数的代码。
首先定义一个用于计数的变量a,每次进入中断函数时,首先判断a是否为0,如果为0,就将下一次中断设置为400微秒,并且将a自加,在这次中断里,引脚应该输出低电平;如果a不等于0,那么将下一次中断设置为100微妙,然后将a置0,在这次中断引脚应该输出高电平。
验证的方法和上一个实验一样,在P1_0 = 0处 和 P1_0 = 1处各设置一个断点,并且观察每次进入断点的时间间隔。
通过计算可以得出,引脚输出低电平保持了409个机器周期,即409微秒,而高电平保持了106个机器周期,106微秒。
通过逻辑分析仪也可以看到引脚正常输出PWM波了。
至此,以上实验圆满完成。