8051系列单片机软件精确延时研究(二)

发布时间:2024-07-23  

  由前篇可知,在DelayX10us()函数中用for循环延时会产生10个机器周期的固定误差,其中X传值、调用函数、子函数返回共5个机器周期,这是只要调用带参数子函数都有的、固定不变的;for循环判断x>0并跳转产生额外的5个机器周期的误差。


改进

  根据《在单片机KeilC开发环境中设计精确的延时函数》中提到的内容,可将for循环改为while(--x),以消除for循环产生的额外5个机器周期的误差。


  注意:应使用while(--x),这样对应生成的汇编语句才是DJNZ。如果使用while(x--),将额外产生几个指令,导致此延时函数不准。


  更改后的程序如下:


//非精确延时10*X us,固定误差5us

//@12.000MHz 12T

void DelayX10us(unsigned char x)    

{

    unsigned char i;

    do

    {

        _nop_();

        i=3;

        while(--i);

    } while (--x);

}


反汇编分析


如前,采用level8的优化等级,反汇编后的代码如下:

  

计算一下延时时间:

x 固定延时 循环延时 总计
1 5 (1+1+2*3+2)*1 15
10 5 (1+1+2*3+2)*10 105
100 5 (1+1+2*3+2)*100 1005







  

可见,误差被缩减到5us了。


官方毫秒级延时分析

  对于上述改进后的DelayX10us函数,X最大值为255,所以其最大延时为255*10=2550us=2.55ms。如果要获得更长的延时怎么办呢?在这里需注意,不能单纯将X改为unsigned int以获得更长延时,因为8051是8位单片机,对于16位的int类型,需要分成高8位、低8位运算,在"while(--x);"这句将不只需要2个机器周期。所以我们重新定义一个毫秒级的延时函数。STC官方的延时1ms程序如下:


//@12.000MHz, STC官方版本

void Delay1ms()  //此处没有赋值那个机器周期

{

    unsigned char i, j;


    i = 2;

    j = 239;    //请注意j赋值的位置

    do

    {

        while (--j);

    } while (--i);

}


  按之前的办法计算延时周期数t=函数调用LCALL+i j赋值+循环+返回RET= 2 + 1+1+(239*2+2)*2 +2= 966[在此感谢群友Smiles指出漏加的2个赋值周期]。与预期的1000个机器周期相差较大,为什么呢?


  此处需注意变量j赋值的位置是在循环外,当外层循环执行到第二次(i=1)时,j不会赋值为239,故不能用239*2来计算。这里利用了一个溢出的小技巧。简单分析过程如下:


  i=2,j=239赋值(2个机器周期)后,进入内层循环执行"while(--j);"共239次,j=0,跳出。由之前的分析可知,"while(--j);"的汇编代码为DJNZ指令,为2周期指令,周期数t1=2+239*2。


  然后执行到外层循环"while(--i);",2个机器周期,i=1,DJNZ指令跳转到内层循环继续执行,周期数t2=2。


  再次进入内层循环后,j=0,DJNZ命令为先执行寄存器减1,再判断是否为0,所以执行--j后,j溢出为0xFF(十进制255),不等于0,语句"while(--j);"继续执行,直到j再次自减到0,跳出。周期数t3=256*2。


  再次到外层循环,"while(--i);",i=0,跳转到函数末尾准备返回。周期数t4=2。


  函数调用和返回周期数t5=2+2。此处调用函数没有参数传递,所以没有赋值那1个周期。


  总的周期数t=t1+t2+t3+t4+t5=2+239*2+2+256*2+2+4=1000。正好!


毫秒级延时函数改造

  现在我们将官方只能延时1ms函数改造为可以延时多个ms,按之前的方法,给延时代码套一个do..while循环。


  do


  {


     i = 2; j = 239;


     do { while (--j); }


    while (--i); }


   while (--x);


此时延时机器周期数=1+2+(2+239*2+2+256*2+2+2)*X+2=998*X+5,倍增量不是1000,存在误差。x=1时,t=1003;x=10时,t=9985;x=100时,t=99805,已有接近200us的误差了,所以我们将括号内的机器周期改为1000,只需改j=240,即可使总周期数T=1+2+(2+240*2+2+256*2+2+2)*X+2=1000*X+5,固定误差5us,更改后的代码如下:


/**

 * 晶振12MHz,12T模式下延时1*x ms,固定误差5us。

 */

void DelayX1ms(unsigned char x)

{

    unsigned char i, j;

    do

    {

        i = 2;

        j = 240;

        do

        {

            while (--j);

        } while (--i);

    } while (--x);

}


仿照这个模式即可写出任意固定误差5us的延时程序了。


另外,对于几微秒的延时,就建议采用_nop_()延时了。同时考虑移植性,不建议在程序中直接写多个_nop_()来延时。将所有延时函数写在Delay.h和Delay.c文件中,其余程序通通调用这个库,以后要更改,比如换了STC的1T单片机,延时需要修改,也只需要改这两个文件就可以了。


最后,附上我在用的延时函数库,所有X倍延时的固定误差=5us。


#ifndef __DELAY_H__

#define __DELAY_H__


typedef unsigned char UINT8


//定义默认设置:晶振12MHz,模式12T

#define FSOC_12M_MOD_12T

/**

 * 晶振12MHz,12T模式下的延时。

 */

#ifdef FSOC_12M_MOD_12T

#define NOP()        _nop_()

#define Delay1us()    NOP()

#define Delay2us()    NOP();NOP()

#define Delay5us()    NOP();NOP();NOP();NOP();NOP()

#endif


void DelayX10us(UINT8 X);

void DelayX1ms(UINT8 X);

void DelayX10ms(UINT8 X);

void DelayX1s(UINT8 X);


#endif


/**

 **********************************************************

 ******    Copyright(C), 2010-2016, NULL Co.,Ltd     ******

 **********************************************************


 *@Tittle        :    通用延时函数

 *@Version        :    v1.1

 *@Author        :    Liy

 *@Dat            :    2016-08-10 15:03:15

 *@Desctription    :    延时函数库

 *@History        :

 *    #v1.1     2016-08-10 15:03:41

 *        1. 删除固定延时函数,仅保留X倍延时;

 *        2. 优化X倍延时函数的时间,所有X倍延时函数误差控制为5us。

 *    #v1.0    2016-08-03 16:44:18

 *        1. 完成12MHz、12T模式下常用延时函数


 **********************************************************

 **********************************************************

 */



#include "Delay.h"


/**

 * 晶振12MHz,12T模式下延时

 */

#ifdef FSOC_12M_MOD_12T

/**

 * 晶振12MHz,12T模式下延时10*X us,固定误差5us。

 * X最大值255

 */

void DelayX10us(UINT8 X)

{

    UINT8 i;

    do

    {

        _nop_();

        i = 3;

        while (--i);

    } while (--X);

}


/**

 * 晶振12MHz,12T模式下延时1*X ms,固定误差5us。

 * X最大值255

 */

void DelayX1ms(UINT8 X)

{

    UINT8 i, j;

    do

    {

        i = 2;

        j = 240;

        do

        {

            while (--j);

        } while (--i);

    } while (--X);

}


/**

 * 晶振12MHz,12T模式下延时10*X ms,固定误差5us。

 * 周期数N=((2+(114*2+2)+(256*2+2)*19+2))*X+5

 * X最大值255

 */

void DelayX10ms(UINT8 X)

{

    UINT8 i, j;

    do

    {

        i = 20;

        j = 114;

        do

        {

            while (--j);

        } while (--i);

    } while (--X);

}


/**

 * 晶振12MHz,12T模式下延时10*X s,固定误差5us。

 * 周期数N=(1+3+(((123*2+2)*1+(256*2+2)*153) +2 ) + ((256*2+2)*256+2)*7+2)*X+5

 * X最大值255

 */

void DelayX1s(UINT8 X)

{

    UINT8 i, j, k;


    do

    {

        NOP();

        i = 8;

        j = 154;

        k = 123;

        do

        {

            do

            {

                while (--k);

            } while (--j);

        } while (--i);

    } while (--X);

}


#endif


好了,简单了解了下汇编,软件延时也基本精确了,结束。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>