在我们使用单片机的时候,很多情况下需要用到精确的延时。比如在跟DS18B20进行通讯的时候需要遵循严格的时序,这就需要我们严格把控程序执行的时间。
一般我们都是通过执行空语句的方式来使程序延时。这种方法是用循环嵌套的方式使程序执行空操作,达到延时的目的;除此之外还有使用中断的方式等。
我们可以通过debug来验证我们的函数延时是否准确。本次实验采用12M的晶振进行仿真模拟,应当对软件进行一些设置。在option中将晶振频率改为12M。用12M的晶振是因为这时候一条指令周期恰好是1us,方便我们计算。
首先我们构建一个延时函数void delay_10us( unsigned char tick ),即每次延时的最小单位是10us,通过控制tick的取值来改变延时的长度。在51单片机中有一个内置的指令_nop_( ),其执行一次的时间恰好是一个指令周期。这里我们使用的晶振是12M,那么执行一次_nop_( )就是1us。
在函数内部,通过tick的值来控制循环执行_nop_( )的次数。即:
void delay_10us(unsigned char tick)
{
for(; tick>1; tick--)
{
_nop_();
_nop_();
}
}
这里本来应该是10个_nop_( ),但是却只写了两个。这是因为在实际调用的时候,进入函数、返回、循环跳转等都是会耗费时间的。这里具体写几个_nop_( )可以通过实际的调试来得到。
在主循环中构建下面的代码:
void main()
{
while(1)
{
P1_0 = 1;
delay_10us(10);
P1_0 = 0;
delay_10us(10);
}
}
进入debug模式,然后将P1_0添加到虚拟逻辑分析仪中,通过观察高低电平翻转的时间来检验延时的正确性。
可以看到时间差非常接近100us,除去执行P1_0 = 1耗费的时间基本满足我们的需求。我们可以通过改变不同的延时值来验证我们的函数是否正确。这里不再赘述。
现在我们利用刚才构建的延时函数来让单片机的引脚输出2KHz的占空比20%的方波;占空比为2KHZ,则周期为500us,简单计算得到方波需要保持100us高电平400us低电平。
需要注意,由于我们的10us延时是存在一些微小的偏差的,如果需要延时的tick数比较大,那么误差将会被放大,导致时间偏差较大。为了解决这个问题,我们可以少写一个tick,剩余的时间再额外用_nop_()或其它方式补上。具体需要补多少根据实际调试的结果去加,这里肯定不能一次性就做到合适,需要多次尝试。
void main()
{
while(1)
{
P1_0 = 1;
delay_10us(9);
delayNOP();
_nop_();
_nop_();
P1_0 = 0;
delay_10us(39);
delayNOP();
}
}
这里的delayNOP()是在前面define的
#define delayNOP(); {_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();};
仿真结果如下,可以看到实验结果是符合我们的预期的。
附完整程序:
#include "reg52.h"
#include "intrins.h"
#define delayNOP(); {_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();};
sbit P1_0 = P1^0;
void delay_10us(unsigned char tick);
void main()
{
while(1)
{
P1_0 = 1;
delay_10us(9);
delayNOP();
_nop_();
_nop_();
P1_0 = 0;
delay_10us(39);
delayNOP();
}
}
void delay_10us(unsigned char tick)
{
for(; tick>1; tick--)
{
_nop_();
_nop_();
}
}