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


    前言

      最近自学STC公司的8051系列单片机,编程中如流水灯等非精确延时多用软件延时实现,写了几个类似DelayX10us(unsigned char x)的函数方便调用,函数内部的语句多是用STC官方延时程序再自己套一个for或者do..while循环改造而成,像这样:

    //非精确延时10*Xus
    //
    @12.000MHz 12T模式 void DelayX10us(unsigned char x) { unsigned char i; for (; x > 0; x--) { _nop_(); i = 2; while (--i); } }

      由于不懂汇编,所以对代码的实际延时时间一直没有深究,每次都是凭感觉摸索个大概。今天突然心血来潮在keil仿真中执行了一下以上代码,观察了一下延时时间,得到结果如下:

    X 延时目标(us) 实际延时(us) 误差
    1 10 24 140%
    10 100 150 50%
    100 1000 1410 41%

      

      OMG,100us误差达到50%,延时1000us误差也有41%,这还真是“非(常的)精确”啊。

      突然觉得有必要研究一下汇编代码,搞懂这个延时是怎么误差这么大的。学习嘛,就不该留盲点,也正好借此机会了解一下汇编语言,对理解单片机底层应该有一定帮助。如果编程人员对自己写的代码底层如何实现一清二楚,那溢出、内存泄漏什么的bug就绝不会存在了。当然,要达到这个理想情况是很难的,只能朝着这个方向多努力了。

      写了一段代码做研究用,如下:

    #include <reg52.h>
    #include <intrins.h>
    
    void DelayX10us(unsigned char x);
    void main()
    {
        DelayX10us(1);
        DelayX10us(10);
        DelayX10us(100);
        while (1);
    }
    
    //@12.000MHz 12T
    void DelayX10us(unsigned char x)
    {
        unsigned char i;
        for (; x > 0; x--)
        {
            _nop_();
            i = 2;
            while (--i);
        }
    }
    View Code

      


    反汇编代码

      顺便说一下,软件环境:Keil uvison 4。

      上述代码编译完后,点击"Start Debug"开始调试,Disassembly窗口中就显示出了相应的反汇编代码,还显示了C语言与汇编代码的对应关系,比在Linux环境下调试方便多了。

    main()函数:

    DelayX10us()函数

      查芯片手册中指令系统部分内容可知,上述代码中LCALL、SJMP、JC、DJNZ、RET这几个指令是2机器周期指令,其余是1机器周期指令。现在开始来计算延时时间:

      x=1: 

      main()中 for循环 返回 总  计
    机器周期    1+2 (1+1+1+2   +1+1+2*2   +1+2)*1 +1+1+1+2  2   24

      说明:1、main()中传值和跳转两个操作周期为1+2。

           2、0x0016  SUBB A,0x00 为执行借位减法,可以简单理解为将A-0x00-Cy(进位借位标识,也就是上一句中的C)的结果装入A,并判断如果够减(结果>=0),Cy=0(未产生借位);如果不够减(结果<0),Cy=1(产生借位)。所以当A>=1时,都够减,Cy=0,下一句JC不会跳转,直到A=0不够减时才跳转。(A就是X的值)

         3、for循环中,第一次从0x0014到0x0020执行完,周期数为1+1+1+2   +1+1+2*2   +1+2,此时R7寄存器中存储的x值为0;此时已跳转到0x0014继续执行,直到0x0018,跳转到0x0022,周期数为1+1+1+2。返回main()函数又花两个周期。所以main()中"DelayX10us(1);"共耗费24个,12M/12T模式下即为24us。

      同理,x=10:

      main()中 for循环 返回 总  计
    机器周期    1+2 (1+1+1+2   +1+1+2*2   +1+2)*10 +1+1+1+2  2   150

      x=100时同理1+2  +(1+1+1+2+1+1+2*2+1+2)*100  +1+1+1+2  +2 = 1410

    小结

        综上可看出,单纯的在官方延时函数基础上套for循环而得到的延时相当不精确。分析误差原因可知,main()中的3个周期、子函数返回的2个周期、for循环末尾的(1+1+1+2)个周期,这10个机器周期是固定误差值,最关键的在于涂黄部分共14个周期,超出了预期的10us倍增的延时。把这部分稍微改一下,使括号内涂黄部分变为10个机器周期,这样子就能使所有的x倍延时的误差值都为固定误差10us了。更改后的代码如下:

    //非精确延时10*X us,固定误差10us
    //@12.000MHz 12T模式
    void DelayX10us(unsigned char x)        
    {
        unsigned char i;
        for (; x > 0; x--)
        {
            _nop_();
            _nop_();
        }
    }

      更改后的延时机器周期数=1+2  +(1+1+1+2  +1+1 +1+2)*X  +1+1+1+2  +2 = 10*X+10。X在1~255取值范围内,误差均为固定10us。

    PS:本文所有延时都是在12MHz晶振、12T模式下计算,1个机器周期=1us。

       反汇编代码为Keil软件内代码优化等级level 8下编译后的反汇编。不同优化等级编译的代码反汇编后有稍许差别,再次不做论述。

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

  • 相关阅读:
    js捕获activex事件
    any cpu ×86 ×64
    android手机设备查看/data/data
    this android sdk requires android developer toolkit version
    oracle函数获取汉字拼音的首字母
    Go语言最佳实践——异常和错误
    Go语言最佳实践——通道和并发
    Go语言最佳实践——面向对象
    对闭包的理解
    Go 的垃圾回收机制在实践中有哪些需要注意的地方(转)
  • 原文地址:https://www.cnblogs.com/ToddleLobster/p/5741076.html
Copyright © 2020-2023  润新知