1 对C51中定时/计数器赋初值算法的补充
《力源电子工程》1999年第3期《C51语言应用编程的若干问题》一文的例3中,用下述方法为T0赋初值,使T0在启动后满1000个机器周期时产生中断申请:
TMOD = 0X01
/*T0工件在定时器方式1,为16位*/
TH0 = -(1000/256)
TL0 = -(1000%256)
1.1 赋初值算法问题的验证
这种算法(以下称其为新算法)比较新颖而且方便,不同于传统的算法。但经过验算,发现这种算法有一个漏洞,会引起定时或计数的严重错误,仍以本例进行验证如下:
新算法:
TH0
=-(1000/256)
=-(3)
= 0XFD
TL0
=-(1000%256)
=-(232)
= 0X18
传统算法:
2^16 – 1000 = 64536 = 0XFC18
即:
TH0 = 0XFC
TL0 = 0X18
两种算法得出的TH0值相差1。
若使T0在启动后1024个机器周期时产生中断申请。
新算法:
TH0
=-(1024/256)
=-(4)
= 0XFC
TL0
=-(1024%256)
=-(0)
= 0X00
传统算法:
2^16 – 1024 = 64512 = 0XFC00
即:
TH0 = 0XFC
TL0 = 0X00
则两种算法得出的值完全相同。
经观察发现,当TL0 = 0X00 时,新算法得出的结果是正确的,否则,必须将新算法得出的TH0的值减去1,才能得出正确的结果。
在所需机器周期数大于256时,这种错误将使中断提早到达256个机器周期;
在所需机器周期数小于256时,TH0应为0XFF,新算法得出TH0=0X00,将会使中断迟到(0XFF-0X00)* 256=65280个机器周期。
在所需机器周期数等于0时最甚(当然,在实际编程中不会出现这种情况,但为严谨起见,对其进行论述),中断将会迟到65536个机器周期。
证明:
为方便理解,仍以16位的定时/计数器为例,对13位的定时/计数器仍可以用下述方法进行证明(见1.3适用范围一节)。
设X表示定时/计数所需的机器周期数
设A = X/256(本文中X/256均指X除以256舍尾取整);B = X%256。
1. 当X>256时
新算法的计算过程中
TH0 =-(X/256),实际上是TH0 = 256-(X/256),因为TH0是8位长度,它的值不超过255。
TL0 =-(X%256)是同样道理。
则新算法的计算过程可表示为:
TH0TL0 =(256-X/256)* 256 +(256-X%256)
=(256-A)* 256 +(256-B)
= 65536-256A +(256-B) (1)
式(1)中(256 – B)即 TL0 的值。
传统算法的计算过程可表示为:
TH0 TL0 = 65536-X
= 65536-[(X/256)* 256 + X%256]
= 65536-256A-B (2)
可以看出,新算法确实比传统算法的正确值多了256,也就是说,TH0的值应减去1,只有当式(2)中B=0时,在新算法中则:TL0 =-(B)=0,即式(1)中的(256-B)=0,此时式(2)与式(1)才相等。
2. 当X<256时(A=0)
新算法的计算过程可表示为:
TH0TL0 =-(X/256) * 256 + 256-X%256
= 256-B (3)
传统算法的计算过程可表示为:
TH0TL0 = 65536-B (4)
新算法的结果比传统算法结果小65280,若将其TH0减去1变为0XFF,则为正确结果。
3. 特例,当X=0时(A=B=0)
新算法的计算结果为:
TH0TL0 = 0X0000 (5)
传统算法的计算结果为:
TH0TL0 = 0X10000(实际上不可能) (6)
两者相差65536。
1.2 产生问题的原因分析
(1) 分析产生这个问题的原因,可先从定时/计数器的工作过程入手。
新算法中
TH0 =-(X / 256)
TL0 =-(X % 256)
其主观意图是在TH0中装入0X100H(即256)减去X/256的整数部分得出的差ΔTH0,使TH0控制计数ΔTH0* 256个机器周期T(或输入ΔTH0* 256个计数脉冲),TL0也同理;然而当TL0≠0时,定时/计数器在启动后的(256-TL0)个机器周期T后即向TH0进位。按前面所说的主观意图来理解,则是在TH0中记录下“已计满256个机器周期T的时间或256个输入脉冲”的信息。也就是说:TL0所设定的(256-TL0)个机器周期T,错误地使TH0(应记录整数个256T)“跳数”(加1),将使定时/计数器产生非预期的中断申请。
所以当TL0≠0时新算法得出的TH0值应减去1,则可以避免此错误的发生。程序应以如下形式:
TH0 =-(X / 256)
TL0 =-(X % 256)
if (TL0<>0) TH0- -;
相反,当TL0=0时,TL0计数满后向TH0进位正是新算法的本意,所以不会发生定时/计数错误。
上面分析了当X>256时的情况(A>0),当X<256时,A=0,新算法得出TH0=0,显而易见应为TH0=0XFF。
(2) 简单地说:新算法是把传统算法的0X10000-hhll(hhll=X)当作(0X100-hh)赋给TH0,(0X100-ll)赋给TL0来计算,当ll=0时,(0X100-ll)=0X100,没用向0X100的最高位“1”即TH0借位;当ll≠0时,要发生借位,传统算法中有这一过程,而新算法则丢失了这个借位,所以会在TH0中多出1来。
之所以要从不同角度进行分析,是因为在实际编程中,我们可能会从不同角度出发确定算法,经过上述分析,可以避免类似失误的发生。
1.3 适用范围
当定时/计数器工作在模式0时,将算法中的256替换为32,使用如下语句:
TH0 =-(X/32)
TL0 =(-(X%32))& 0X1F
if(TL0<>0) TH0--;
则同样适用上述结论。文中以定时/计数器0 的定时器方式为例,但对定时/计数器1以及它们工作在计数器方式时也是适用的。
2 对C51条件编译语句#if…用法的订正
《力源电子工程》1999年3期《C51语言应用编程的若干问题》一文中有下述宏定义与条件编译例子(例1):
#define flag 1
#ifdef flag==1
#define fosc 6M
delay=10;
#elif flag==0
#define fosc 8M
delay=12;
#else
#define fosc 12M
delay=20;
#endif
main()
{
for(I=0;I
}
经实际运行,发现“#ifdef flag==1”一句似有不妥,为叙述方便起见,设有如下情形。
1. 情形1
#ifdef 标识符
该条件编译语句当并且仅当 “标识符” 曾被“#define”语句定义过(如果有“# undef 标识符”语句则在其前面)时为TRUE,即使如下形式也是如此:
#ifdef 标识符 == 常量 亦即:#ifdef 常量表达式。
2. 情形2
#if 标识符 == 常量 亦即:#if 常量表达式
该条件编译语句当并且仅当 “标识符 == 常
量”为TRUE时,为TRUE。
所以在上述例子中“#ifdef flag == 1”的值将永远为TRUE,不论“flag == 1”是否为TRUE。
根据程序目的可知,这一句应为:
“#if flag ==1”。
另外,在Franklin C51 V3.20编译系统目录OMF51\EXAMPLES\DCLOCK.C 中有如下一段:
#ifdef CPU==8051
#define SECOND (4000/(12/FREQ))
#else
#define SECOND (200/(12/FREQ))
#endif
在此段前没有出现对“CPU”的定义,实际上, #ifdef CPU==8051
一句是不合理的。其值等于 #ifdef CPU
而非
(#ifdef CPU) && (#if CPU==8051)
特别指出,在参考文献2第128页的例子中,也出现了
#ifdef CPU==8051,这一语句。
上述结论作者经过Franklin C51 V3.20编译验证。
参 考 文 献
1 张毅刚, 修林成, 胡振江. MCS-51单片机应用设计. 哈尔滨:哈尔滨工业大学出版社, 1990
2 徐爱钧, 彭秀华. 单片机高级语言C51应用程序设计. 北京:电子工业出版社, 1998