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

前言

  最近自学51单片机,编程中如流水灯等非精确延时多用软件延时实现,写了几个类似DelayX10us(unsigned char x)的函数方便调用,函数内部的语句多是用官方延时程序再自己套一个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,延时10us经验误差达到了140%,100us误差也有50%,这还真是“非精确”啊。

  突然觉得有必要研究一下汇编代码,搞懂这个延时是怎么误差这么大的。学习嘛,就不该留盲点,也正好借此机会了解一下汇编语言,对理解单片机底层应该有一定帮助。如果编程人员对自己写的代码底层如何实现一清二楚,那溢出、内存泄漏什么的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);
    }
}

  


反汇编代码

  顺便说一下,软件环境: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

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

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

时间: 2024-10-09 10:23:27

8051单片机软件精确延时研究(一)的相关文章

对MSP430单片机__delay_cycles精确延时的说明及改正

在这里, 我来讨论一下关于MSP430单片机使用__delay_cycles延时的问题. IAR for MSP430编译器提供了一个编译器内联的精确延时函数(并非真正的函数)以提供用户精确延时使用, 该函数原型是: __intrinsic void __delay_cycles(unsigned long __cycles);该内部函数实现__cycles个CPU周期的延时,但对于该参数的设置,我要陈述一下: __cycles需要我们传递的是CPU运行的周期个数 网上普遍的用法是: #defi

Keil C51程序设计中几种精确延时方法

单片机因具有体积小.功能强.成本低以及便于实现分布式控制而有非常广泛的应用领域[1].单片机开发者在编制各种应用程序时经常会遇到实现精确延时的问题,比如按键去抖.数据传输等操作都要在程序中插入一段或几段延时,时间从几十微秒到几秒.有时还要求有很高的精度,如使用单总线芯片DS18B20时,允许误差范围在十几微秒以内[2],否则,芯片无法工作.用51汇编语言写程序时,这种问题很容易得到解决,而目前开发嵌入式系统软件的主流工具为C语言,用C51写延时程序时需要一些技巧[3].因此,在多年单片机开发经验

单片机C语言延时需注意的问题

标准的C语言中没有空语句.但在单片机的C语言编程中,经常需要用几个空指令产生短延时的效果.这在汇编语言中很容易实现,写几个nop就行了. 在keil C51中,直接调用库函数: #include // 声明了void _nop_(void); _nop_(); // 产生一条NOP指令 作用:对于延时很短的,要求在us级的,采用“_nop_”函数,这个函数相当汇编NOP指令,延时几微秒.NOP指令为单周期指令,可由晶振频率算出延时时间,对于12M晶振,延时1uS.对于延时比较长的,要求在大于10

Primace 5.0软件与KEIL单片机软件联合在线仿真步骤

Primace 软件是CME(京微雅格)公司的FPGA芯片开发专用软件.因为CME的FPGA,如M5.M7等内嵌有8051核,所以可以和MCU联合在线仿真,虽然FPGA内的程序不可控,不能一步一步的仿真,但是因为MCU程序可控,可以一步一步的运行,所以可以利用此特性,用MCU给FPGA发送命令,然后控制FPGA的运行与操作. 所以此FPGA开发板也可以做8051单片机开发板. 这里主要介绍FPGA与keiLl联合仿真的时候的关于KEIL的工程的建立与设置.主要是做的比较简单的仿真,比较粗俗.简单

关于51精确延时及keil仿真延时时间

转自:http://blog.sina.com.cn/s/blog_980e19e00101b5dh.html 有时候需要精确的延时,比如18B20温度传感器对时序要求非常严格,必须精确到微秒级别 一.用NOP函数 在keil C51中,直接调用库函数: #include // 声明了void _nop_(void); _nop_(); // 产生一条NOP指令 作用:对于延时很短的,要求在us级的,采用“_nop_”函数,这个函数相当汇编NOP指令,延时几微秒.NOP指令为单周期指令,可由晶振

织女星开发板RISC-V内核实现微秒级精确延时

前言 收到VEGA织女星开发板也有一段时间了,好久没玩了,想驱动个OLED屏,但是首先要实现IIC协议,而实现IIC协议,最基本的就是需要一个精确的延时函数,所以研究了一下如何来写一个精确的延时函数.众所周知,ARM Cortex-M内核都有一个24位的SysTick系统节拍定时器,它是一个简易的周期定时器,用于提供时基,多为操作系统所使用.RV32M1的RISC-V内核也有一个SysTick定时器,只不过它不属于内核,而是使用的一个外部通用定时器,即LPIT0( low power perio

使用系统定时器SysTick实现精确延时微秒和毫秒函数

SysTick定时器简介 SysTick定时器是存在于系统内核的一个滴答定时器,只要是ARM Cortex-M0/M3/M4/M7内核的MCU都包含这个定时器,它是一个24位的递减定时器,当计数到 0 时,将从RELOAD 寄存器中自动重装载定时初值,开始新一轮计数.使用内核的SysTick定时器来实现延时,可以不占用系统定时器,由于和MCU外设无关,所以代码的移植,在不同厂家的Cortex-M内核MCU之间,可以很方便的实现.而东芝的这款TT_M3HQ开发板使用的TMPM3HQFDFG芯片,正

【转】STM32 不占用定时器(包括SysTick)实现精确延时(巧用DWT)

/** ****************************************************************** * file core_delay.c * author fire * version V1.0 * date 2018-xx-xx * [url=home.php?mod=space&uid=247401]@brief[/url] 使用内核寄存器精确延时 **************************************************

x264编码延时研究

研究了一下x264编码延时. 方法是加log在x264.c static int encode( x264_param_t *param, cli_opt_t *opt ) { ... i_frame_size = encode_frame( h, opt->hout, &pic, &last_dts ); if( i_frame_size == 0)   // delay frames fprintf( stderr, "output zero %d\n", i