接着初级编继续说。
上一回,刚刚说过了怎么用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代码优化详细分析 - 十日十乞 - 博客园"