fogota的个人空间 https://passport2.21ic.com/?158903 [收藏] [复制] [RSS]

日志

单片机心得(三)——聊聊C语言的延时程序(中级)

已有 469 次阅读2017-8-27 21:19 |个人分类:8051|系统分类:单片机| 延时程序, C语言教程, C51, 8051

       接着初级编继续说。上一回,刚刚说过了怎么用STC-ISP生成延时程度。

       你可以将你要用的延时程序全用这方式生成。那么就不用看下的的了。如果你也像我一样,想做成一个函数库,这好像更方便。

       这一回,我们来怎样做成简便的Delay(n)函数。
       第一,我们要用到#include <intrins.h>  (最好写到Delay.h里面吧。) 因为里面有定义 _NOP_();

       下面假设系统频率=11.0592MHz,延时程序的下限是2us。如果你定义的系统频率更小,那延时程序的下限要更大一些。一个_nop_()就是一定空指令呀,51已经到了极限了呀。
       第二,那么STC-Y1的2us到4us都是用nop组合的,因为子函数的压栈和出栈都要5us。那么这3个要用宏来实现。宏要写在H文档中,方便C文件调用。
#define Delay2us()        _nop_()
#define Delay3us()        _nop_(),_nop_()
#define Delay4us()        _nop_(),_nop_(),_nop_()

       第三,5us以上可以用子函数实现。我们用STC-ISP生成Delay5us()

       第四,我们来完成Delay6us()到Delay9us()。
       这不需要用STC-ISP生成了。我们写成宏。最好多用宏,因为延时程序不是每一个都用得到,没用到的函数,编译时会发出警告。虽然用不到的函数会被优化掉,不影响代码位积。出一些警告总是碍眼。
#define Delay6us()        Delay3us(),Delay3us()
#define Delay7us()        Delay5us(),Delay2us()
#define Delay8us()        Delay5us(),Delay3us()
#define Delay9us()        Delay5us(),Delay4us()
做到这一层已经足够,就止打住。

       第五,我们用STC-ISP生成Delay10us(),Delay20us(),Delay50us()

       第六,我们来完成Delay30us(),Delay40us(),Delay60us()到Delay90us()
#define Delay30us()        Delay20us(),Delay10us()
#define Delay40us()        Delay20us(),Delay20us()
#define Delay60us()        Delay50us(),Delay10us()
#define Delay70us()        Delay50us(),Delay20us()
#define Delay80us()        Delay50us(),Delay30us()
#define Delay90us()        Delay50us(),Delay40us()

       第七,我们要实现Delay100us(n),就不用STC-ISP生成。上面说的完全没有技术含量。同时上面的都不适合制成DelayXXus(n),留意我这里用上参数了。如果针对11.0592MHz来说,这Delay10us(n)也是能办到的,方法和下面要说的一样,只是数值不同。但是如果晶振再小一些的话,Delay10us(n)真没法做了。

有两点,我们要知道:
       (1)一个循环里面不能全是nop,一定要加点别的语句。否则Keil会将nop消了。所以不能写成while(--n){Delay4us();}或while(--n){_nop_(),_nop_(),_nop_();}
       (2)unsigned char i; 和 unsigned int i; 做加法,时间是不一样的;一个是8位,一个是16位;对于8位cpu的51来说,16类型的要做一次进位,不管0还是1。(就是因为这个原因,STC-ISP后面生成的延时代码宁用unsigned char两重循环,都不unsigned int。他们工程师对后者不确定。)

       接下来说技术点了。

       我们先看看用什么代码。
void Delay100us(unsigned char n)        //@11.059MHz
{
   unsigned char i ;
   while(n--)
    {
      for(i = 0 ; i < Y ; i++) ;
    }
}
里面这个Y,我也不知取多少,假定取100吧

我们要main里面写到
while(1)
{
Delay100us(50);
LED=!LED;
}

我们来编译,改好Bug。运行,测量LED频率 F 。
设 测量F = f0,按公式1: T=1/F/2/X     (注:按上面的程序,取X=50
得到 t0=1/f0/2/50

       我们假设这个结果 t0 就是100us,目前没调校好,所以明显不准备,但可以用来校正。我们可以无视压栈出栈的时间,实际上这个时间被平摊了。我们得到一个方程:
      方程1:      t0/Y=(100*10^-6)/Z
       这个t0/Y约等于每次for的运算的时间。所以我们得到公式2
公式2 :Z=(100*10^-6)*Y/t0

很明显,这个常数100*10^-6就是100us

       因为我这个代码测出来有一些误差,所以我建议用103*10^-6,代替100*10^-6
即公式3 :Z=(103*10^-6)*Y/t0

而计算结果 Z 则四舍五入,取整数。
再将Z值代替Y值,再编译一次,运行,测频率,得到 F = f1
再计算得到t1,t1代替t0再放到公式3,计算,这次Z 则四舍五入,取整。应该等于 Y 了,那就可以确定了。如果不等,都一次次的接近了。

我自己的经验是,做一次已经很准的了。这些计算可以放到Excel表上,制成公式来做。又方便又不出错。

       第八,我们要实现Delay1ms(n)。
       Keil新建一个工程,默认是8级的代码优化。因为C语言生成汇编后,因为程序员的原因,还有编译器本身死板的代码转化,会出现好多被认为是无用的代码,还有可以改良的代码。这些被分成9个层次的优化方式,可提升运行速度又能缩小代码。0级是什么都不优化。
       为了高效运行,我们要对C代码用高阶的代码优化,只要是基于实际测量结果就不担心了。所以,我们敢用unsigned int i;  
       这就不用多想了。将unsigned char i; 要换成 unsigned int i; 
void Delay1ms(unsigned int n)        //@11.059MHz
{
   unsigned int i ;
   while(n--)
    {
      for(i = 0 ; i < Y ; i++) ;
    }
}

方法跟第七步一样就行。注意,因为换了变量类型,所以Y值不是成比例了。要老老实实的再做一次。
常数取1.003*10^-3

即公式4:Z=(1.003*10^-3)*Y/t0

       另外参数n的类型也换了,延时参数可以更大了。哈哈。因为在ms这个段,误差更加不在呼了。

       关于延时程序话题就写到这里。要整得更好美的代码,更好用,就要靠自己了。

       维京猎人  2017-8-27

扩展阅读
《KEIL C51代码优化详细分析》——度娘"KEIL C51代码优化详细分析 - 十日十乞 - 博客园"


路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)