|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
x
延迟函数恐怕是任何一个51程序中最经常用到的部分,可以用空循环来做,也可以使用基于
定时器的中断来做。两种方法各有优缺点,各自适应不同的应用场合。今天,我按照自己的
思路给出这个问题的解答。这个解答是基于空循环的方法,我讲解的顺序是先用汇编来实现,
然后想办法把这段汇编嵌入到C程序中。以下的时间单位假定为机器周期,而不是具体的多少
毫秒微秒之类的。
第一步:汇编实现。
我们需要用到3个寄存器和3个内存单元。寄存器用来循环计数,内存单元用来保存循环次数
的初始数值。假定寄存器使用r5、r6和r7,对应的内存单元用标号记为delayr5、delayr6和
delayr7。也许你已经有所领悟,我使用3个寄存器和3个内存单元就是要做3层嵌套循环,r5
和delayr5用于控制最外层,r6和delayr6用于控制中间层,r7和delayr7用于控制最内层。
在你的汇编程序最开始,你应该定义好delayr5、delayr6和delayr7,比如像下面这样:
示例代码1:
delayr5 data 30h
delayr6 data 31h
delayr7 data 32h
也就是说,delayr5占据了地址编号为30h的内部RAM,delayr6占据了地址编号为31h的内部RAM,
delayr7占据了地址编号为32h的内部RAM。具体的地址根据你的程序需要自行修改。注意不要
使得内存单元地址和寄存器冲突。好了,现在把我的汇编子程序给你:
示例代码2:
DELAY:
mov r5,delayr5 ;2个机器周期
DELAY1:
mov r6,delayr6 ;2个机器周期
DELAY2:
mov r7,delayr7 ;2个机器周期
DELAY3:
djnz r7,DELAY3 ;2个机器周期
djnz r6,DELAY2 ;2个机器周期
djnz r5,DELAY1 ;2个机器周期
ret ;RET指令本身要用2个机器周期才能返回
假定,你已经给delayr5、delayr6和delayr7赋予了正确的数值,当你调用DELAY的时候就会进
行一段时间的空循环,然后返回,达到延迟的目的。而且,根据我的设计,你应该遵守下面的
调用顺序:
示例代码3:
;其它代码
mov delayr5,#0XXh ;2个机器周期
mov delayr6,#0XXh ;2个机器周期
mov delayr7,#0XXh ;2个机器周期
lcall DELAY ;LCALL指令本身要用2个机器周期才能转
;到DELAY处去执行
;其它代码
在示例代码3中,#0XXh表示某些合适的数值(后面再说如何得到这些数值),那么前3行就是赋
值,第4行是调用。那么包含前3条赋值代码在内,上面的程序一共运行了多少个机器周期呢?
答案是:2{ [ ( delayr7 + 2 )delayr6 + 2 ]delayr5 } + 2 + 2 + 2 + 2 + 2 + 2 公式1
现在,假定你要延迟100个机器周期的时间,那么你就要想办法把循环初始数值算好,然后给
delayr5、delayr6和delayr7赋值就行了,也就是替换示例代码3中的那些#0XXh。这可不是一个
简单的活,你怎么知道你给的初始数值就一定能使公式1算出100个机器周期呢?所以,一个很
重要的工作就是要逆向计算,算出公式1中的那些delay常数。我给你准备了一个VC程序,用于
计算这些数值,你自己下载来使用。注意#0XXh不能是零。
第二步:如何得到初始数值。
仔细看好下面的说明,你才能学会如何得到初使数值,使得延迟误差收敛到1个机器周期之内。
程序界面如图所示。操作顺序如下:
1 设定晶体频率,单位Hz。不认识KHz、MHz这些单位,只能输入数字。
2 比如AT89C51这种芯片,一个机器周期包括12个时钟周期,那么要把12这个数字设置好。
3 但后点击设定。只要你修改上述两个数值之一时,必需要重新点设定按钮。
4 最大和最小的延迟时间,是根据你输入的晶体频率和分频系数自动计算出来的,你要延迟
的时间只能在这两者之间,超出无效。
5 设定好延迟时间,点计算,就可以得到delayr5、delayr6和delayr7。
以下几个问题需要解答。
A 是不是所有的延迟时间都能精确地达到?
答:不是。根据公式1,延迟的机器周期只能是偶数,不能是奇数。即便是偶数,也不见得能
找到精确的delayr5、delayr6和delayr7。
B 不能精确延迟怎么办?
答:你看见了,程序的界面有一个参数,实际延迟时间与预期值的误差。举例说明,假定12MHz
晶体,12分频(AT89C51),延迟1001微妙,可是实际算出来的量只能延迟1000微妙,差1微妙。
我把这个差值表示为以机器周期为单位的量显示出来,在本例是-1。那么你可以很简单的修改
代码,加上一个nop就行了,如果是-2,加2个nop,这个差值很小,不会让你加很多nop。那如
果差值是正的怎么办?例如你要延迟123458微妙,实际出来的是123460。你可以这样,把要延
迟的数值稍微改小一些,比如123456,那样的话,计算出来的差值是零,然后你人为加2个nop
就OK了。
C 太大或者太小的延迟时间怎么办?
答:如果你要延迟6个微妙,直接写6个nop不是更好吗?如果你要延迟100秒,可以每次延迟20秒,
共5次调用不就OK了,况且延迟100秒,好像也没有必要精确到1微妙。
D 如果差值是-10000,我岂不是要写10000个nop?
答:你好笨。汉字一写一划,二写二划,三写三划,这么说万字要写10000划了。如果差10000个,
你可以再搞一个延迟10000的调用不就行了,大拆小,小拆无。
第三步:嵌入C程序中。
上面是汇编的代码,用C直接写精确的延迟程序是不可能的,只能混合编成,把汇编程序嵌入进
去。当然要做一些小的变化以适应C语言程序。
1 程序需要6个内存空间用来保存变量,你必须有足够空间。像下面这样,在你的main之前:
示例代码4:
unsigned char data DelayConstantInner = 0; //内层延迟时间常数
unsigned char data DelayConstantMiddle = 0; //中层延迟时间常数
unsigned char data DelayConstantOuter = 0; //外层延迟时间常数
unsigned char data DelayCounterInner = 0; //内层延迟计数器
unsigned char data DelayCounterMiddle = 0; //中层延迟计数器
unsigned char data DelayCounterOuter = 0; //外层延迟计数器
2 声明延迟子程序,如果你把这个子程序放在main之前,那么可以声明与实现一起做,如下:
示例代码5:
void TimeDelay()
{
#pragma asm
DELAY000:
mov DelayCounterOuter, DelayConstantOuter
DELAY001:
mov DelayCounterMiddle,DelayConstantMiddle
DELAY002:
mov DelayCounterInner, DelayConstantInner
DELAY003:
djnz DelayCounterInner, DELAY003
djnz DelayCounterMiddle,DELAY002
djnz DelayCounterOuter, DELAY001
#pragma endasm
}
如果你要调用它,为了保持精确性和与上面汇编的兼容性,应该这样做:
示例代码6:
DelayConstantInner = XXX;
DelayConstantMiddle = XXX;
DelayConstantOuter = XXX;
TimeDelay()
这样,生成的汇编代码与上面完全一致。XXX的数值最小是1,最大是255,不能用0。
3 当然,在C程序中嵌入汇编,还要设置一些编译器选项,这个你自己找书看。这一步很重要哦,要
不你编译根本通不过。
原代码用VC2005打开。
DelayTime.rar
(1.33 KB, 下载次数: 3 )
原代码.rar
(41.8 KB, 下载次数: 3 )
|
|