Chinaunix首页 | 论坛 | 博客
  • 博客访问: 636089
  • 博文数量: 205
  • 博客积分: 7891
  • 博客等级: 少将
  • 技术积分: 2168
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-29 13:16
文章分类

全部博文(205)

文章存档

2015年(4)

2014年(5)

2013年(1)

2012年(4)

2011年(51)

2010年(86)

2009年(45)

2008年(9)

分类: C/C++

2009-08-07 11:49:47

 

在单片机应用中,经常会遇到需要短时间延时的情况,一般都是几十到几百μs,并且需要很高的精度(比如用单片机驱动DS18B20时,误差容许的范围在十几μs以内,不然很容易出错);而某些情况下延时时间较长,用计时器往往有点小题大做。另外在特殊情况下,计时器甚至已经全部用于其他方面的定时处理,此时就只能使用软件定时了[1]。
        1 C语言程序延时

  Keil C51的编程语言常用的有2种: 一种是汇编语言;另一种是C 语言。用汇编语言写单片机程序时,精确时间延时是相对容易解决的。比如,用的是晶振频率为12 MHz的AT89C51,打算延时20 μs,51单片机的指令周期是晶振频率的1/12,即一个机器周期为1 μs;“MOV R0,#X”需要2个机器周期,DJNZ也需要2个机器周期,单循环延时时间t=2X+3(X为装入寄存器R0的时间常数)[2]。这样,存入R0里的数初始化为8即可,其精度可以达到1 μs。用这种方法,可以非常方便地实现512 μs以下时间的延时。如果需要更长时间,可以使用两层或更多层的嵌套,当然其精度误差会随着嵌套层的增加而成倍增加。

  虽然汇编语言的机器代码生成效率很高,但可读性却并不强,复杂一点的程序就更难读懂;而C语言在大多数情况下,其机器代码生成效率和汇编语言相当,但可读性和可移植性却远远超过汇编语言,且C 语言还可以嵌入汇编程序来解决高时效性的代码编写问题。就开发周期而言,中大型软件的编写使用C 语言的开发周期通常要比汇编语言短很多,因此研究C语言程序的精确延时性能具有重要的意义[3]。

  C程序中可使用不同类型的变量来进行延时设计。经实验测试,使用unsigned char类型具有比unsigned int更优化的代码,在使用时应该使用unsigned char作为延时变量。

  2 单层循环延时精度分析

  下面是进行μs级延时的while程序代码。

  延时函数:

  void delay1(unsigned char i) {
    while(i );}
  主函数:
  void main() {
    while(1) {
      delay1(i);
    }
  }

  使用Keil C51的反汇编功能,延时函数的汇编代码如下:

  C:0x00E6AE07MOVR6,0x07
  C:0x00E81FDECR7
  C:0x00E9EEMOVA,R6
  C:0x00EA70FAJNZC:00E6
  C:0x00EC22RET

500)this.width=500" border=0>

图1 断点设置位置图

  通过对i赋值为10,在主程序中图1所示的位置设置断点。经过测试,第1次执行到断点处的时间为457 μs,再次执行到该处的时间为531 μs,第3次执行到断点处的时间为605 μs,10次while循环的时间为74 μs,整个测试结果如图2所示。

500)this.width=500" border=0>

图2 使用i--方式测试仿真结果图

  通过对汇编代码分析,时间延迟t=7X+4(其中X为i的取值)。测试表明,for循环方式虽然生成的代码与用while语句不大一样,但是这两种方法的效率几乎相同。C语言中的自减方式有两种,前面都使用的是i--的方式,能不能使用--i方式来获得不同的效果呢?将前面的主函数保持不变,delay1函数修改为下面的方式:

  void delay1(unsigned char i) {
    while(--i);}

  同样进行反汇编,得到如下结果:

  C:0x00E3DFFEDJNZR7,
  C:00E3C:0x00E522RET

  比较发现,--i的汇编代码效率明显高于i--方式。由于只有1条语句DJNZ,执行只需要2个时钟周期, 1个时钟周期按1 μs计算,其延时精度为2 μs;另外,RET需要2个时钟周期,能够达到汇编语言代码的效率。按前面的测试条件进行测试,第1次执行到断点处的时间为437 μs,再次执行到该处的时间为465 μs,第3次执行到断点处的时间为493 μs,10次while循环的时间为28 μs,整个测试结果如图3所示。

500)this.width=500" border=0>

图3 使用--i方式测试仿真结果图

  调整i的取值,i取8时延时时间为24 μs,i取9时延时时间为26 μs。通过分析得出,10次循环为28 μs是由于外层循环造成的,其精度可以达到2 μs。在设计时应该考虑参数传递和RET语句执行所需要的时间周期。实验分析发现,for语句使用--i方式,同样能够达到与汇编代码相同的精度。i取不同值时延时仿真结果如图4所示。

500)this.width=500" border=0>

图4 i取不同值时延时仿真结果图

  3 多重嵌套下的C程序延时

  在某些情况下,延时较长,仅使用单层循环方式是不能完成的。此时,只能使用多层循环方式,那么多重循环条件下,C程序的精度如何呢?下面是一个使用for语句实现1 s延时的函数。

  延时函数

  void delay1s(void) {
    for(k=100;k>0;k--) //定时1 s
    for(i=20;i>0;i--)
    for(j=248;j>0;j--);
  }

  主函数调用延时函数代码段:

  while(1) {
    delay1s();
    scond+=1;
  }

  为了直接衡量这段代码的效果,利用Keil C找出这段代码产生的汇编代码:

  C:0x00B37002JNZ
  C:00B7C:0x00B5150CDEC0x0C
  C:0x00B7E50DMOVA,0x0D
  C:0x00B9450CORLA,0x0C
  C:0x00BB70DEJNZC:009B
  C:0x00BDE50BMOVA,0x0B
  C:0x00BF150BDEC0x0B
  C:0x00C17002JNZC:00C5
  C:0x00C3150ADEC0x0A
  C:0x00C5E50BMOVA,0x0B
  C:0x00C7450AORLA,0x0A
  C:0x00C970CAJNZC:0095
  C:0x00CB22RET

  分析汇编代码,其他汇编代码使用的不是DJNZ跳转方式,而是DEC和JNZ语句来实现循环判断。1条JNZ指令要花费2个时钟周期,3条指令就需要6个机器周期,MOV指令和DEC指令各需要1小时钟周期,1个时钟周期按1 μs算,其精度最多达到8 μs,最后加上一条LCALL和一条RET语句,所以整个延时精度较差[4]。

  利用Keil C的测试工具,在一处设置一个断点。第1次执行到中断处的时间为0.000 513 s,第2次执行到中断处的时间为1.000 922 s,时间延迟为1.000 409 s,测试结果如图5所示。对于上面的3种循环嵌套,循环次数为100×20×248=496 000,每次循环的时间约为2 μs。

500)this.width=500" border=0>

图5 三重嵌套循环1 s实现时间测试结果

  为获取与汇编语言延时的差距,同样进行1 s的延时,程序代码段如下:

    LCALL DELY1S
    INC Second
  DELY1S:MOV R5,#100
  D2:MOV R6,#20
  D1:MOV R7,#248
    DJNZ R7,$
    DJNZ R6,D1
    DJNZ R5,D2
    RET

  通过Keil C51测试,其实际延迟时间为0.997 943 s。虽然C语言实现延时方式的汇编代码复杂度增加,但是与汇编语言实现的方式性能差距并不大。

  4 总结

  汇编语言在实时性方面具有较大的优越性,虽然使用Keil C51可以在C语言程序中嵌入汇编代码,但是复杂度明显提高。实验证明,只要合理地运用C语言,在延时编程方面就可以达到与汇编语言相近的精度。为了获得精确的时间延迟,可通过Keil C工具的仿真功能,调整延迟量,从而得到较理想的结果。

写得不错,他是用while(--i);产生DJNZ 来实现精确延时,后来有人说如果while里面不能放其它语句,否则也不行,do-while就可以,具体怎样我没有去试.所有这些都没有给出具体的实例程序来.还看到一些延时的例子多多少少总有点延时差.为此我用for循环写了几个延时的子程序贴上来,希望能对初学者有所帮助.(晶振12MHz,一个机器周期1us.)

    . 500ms延时子程序

程序:

     void delay500ms(void)

       {

       unsigned char i,j,k;

         for(i=15;i>0;i--)

         for(j=202;j>0;j--)

         for(k=81;k>0;k--);

       }

产生的汇编:

     C:0x0800     7F0F     MOV       R7,#0x0F

     C:0x0802     7ECA     MOV       R6,#0xCA

     C:0x0804     7D51     MOV       R5,#0x51

     C:0x0806     DDFE     DJNZ     R5,C:0806

     C:0x0808     DEFA     DJNZ     R6,C:0804

     C:0x080A     DFF6     DJNZ     R7,C:0802

     C:0x080C     22       RET      

计算分析:

    程序共有三层循环

    一层循环n:R5*2 = 81*2 = 162us                   DJNZ   2us

    二层循环m:R6*(n+3) = 202*165 = 33330us           DJNZ   2us + R5赋值 1us = 3us

    三层循环: R7*(m+3) = 15*33333 = 499995us         DJNZ   2us + R6赋值 1us = 3us

    循环外:   5us            子程序调用 2us + 子程序返回 2us + R7赋值 1us   = 5us

    延时总时间 = 三层循环 + 循环外 = 499995+5 = 500000us =500ms

计算公式:延时时间=[(2*R5+3)*R6+3]*R7+5

    . 200ms延时子程序

程序:

void delay200ms(void)

{

       unsigned char i,j,k;

         for(i=5;i>0;i--)

         for(j=132;j>0;j--)

         for(k=150;k>0;k--);

}

产生的汇编

C:0x0800     7F05     MOV       R7,#0x05

C:0x0802     7E84     MOV       R6,#0x84

C:0x0804     7D96     MOV       R5,#0x96

C:0x0806     DDFE     DJNZ     R5,C:0806

C:0x0808     DEFA     DJNZ     R6,C:0804

C:0x080A     DFF6     DJNZ     R7,C:0802

C:0x080C     22       RET

    . 10ms延时子程序

程序:

void delay10ms(void)

{

       unsigned char i,j,k;

         for(i=5;i>0;i--)

         for(j=4;j>0;j--)

         for(k=248;k>0;k--);

}

产生的汇编

C:0x0800     7F05     MOV       R7,#0x05

C:0x0802     7E04     MOV       R6,#0x04

C:0x0804     7DF8     MOV       R5,#0xF8

C:0x0806     DDFE     DJNZ     R5,C:0806

C:0x0808     DEFA     DJNZ     R6,C:0804

C:0x080A     DFF6     DJNZ     R7,C:0802

C:0x080C     22       RET      

    . 1s延时子程序

程序:

void delay1s(void)

{

       unsigned char h,i,j,k;

         for(h=5;h>0;h--)

         for(i=4;i>0;i--)

         for(j=116;j>0;j--)

         for(k=214;k>0;k--);

}

产生的汇编

C:0x0800     7F05     MOV       R7,#0x05

C:0x0802     7E04     MOV       R6,#0x04

C:0x0804     7D74     MOV       R5,#0x74

C:0x0806     7CD6     MOV       R4,#0xD6

C:0x0808     DCFE     DJNZ     R4,C:0808

C:0x080A     DDFA     DJNZ     R5,C:0806

C:0x080C     DEF6     DJNZ     R6,C:0804

C:0x080E     DFF2     DJNZ     R7,C:0802

C:0x0810     22       RET

在精确延时的计算当中,最容易让人忽略的是计算循环外的那部分延时,在对时间要求不高的场合,这部分对程序不会造成影响.

阅读(798) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~