单片机嵌入式产生精确延时的一种方法
扫描二维码
随时随地手机看文章
前段时间在编写延时程序时遇到了个定时器计数器回绕的问题,也就是计数器达到最大值后溢出,想找个简单的解决方案一直想不出来,函数如下:
void Delay(Uint16 ms)
{
Uint16 currcnt;
currcnt = TCNT; //get current cnt register val
while(TCNT < currcnt+ms);
}
TCNT为硬件的寄存器值,在做单片机程序时,精确的延时很常用,也很方便,可以用一个定时器来实现精确延时,上面为实现原理,但遇到一个问题就是上面简单的
处理后有个计数器溢出回绕的问题,利用变量可以解决但是感觉想找一个通用简单的方法一直没找到,突然看到Linux内核上关于时间比较的代码,激动呀,问题的答案
有了,内核的人还是很猛的,一句话搞定,我把小脑袋想破也没想出来,呵呵。
在linux编程中,经常使用jiffies作为时间的度量。通常都是使用unsigned long来保存jiffies的值,并用之比较时间的先后顺序。
但是即使是unsigned long的位数已经比较大了,jiffies仍然可能产生回绕问题。
比如unsigned long a = jiffies; sleep(some time)。这时jiffies回绕了,然后unsigned long b = jiffies。
由于jiffies回绕,b本来是发生在a之后的事件,可是b的值却小于a。
这种情况下,如果只是简单比较b>a来判断b是否发生在a之后,无疑是错误的。
Linux内核为了解决jiffies的回绕问题,提供了现成的宏,用于判断时间的先后。今天就以time_after为例,看看它为什么可以应对jiffies的回绕问题。
/*
* These inlines deal with timer wrapping correctly. You are
* strongly encouraged to use them
* 1. Because people otherwise forget
* 2. Because if the timer wrap changes in future you won't have to
* alter your driver code.
*
* time_after(a,b) returns true if the time a is after time b.
*
* Do this with "<0" and ">=0" to only test the sign of the result. A
* good compiler would generate better code (and a really good compiler
* wouldn't care). Gcc is currently neither.
*/
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(b) - (long)(a) < 0))
这个宏定义很简单,可以忽略typecheck,其就是用于检查参数的类型是否正确。如这里就是用于判断a和b是否为unsigned long类型。
最关键的就是((long)(b) - (long)(a) < 0)。
在理想的情况下,时间是可以不停增长的,后来的时间值一定比前面的值大。所以b-a一定小于0。然后计算机的世界不是一个理想的世界,
所有的值都有其位数限制的。在32位平台上,long的位数为32位。按照二进制补码的表示方式,从0到0x7fffffff的区间,值是逐渐递增的。
从0x80000000到0xFFFFFFFF这个区间,值是逐渐缩小的。
这就有4中情况:
1. a和b都在0到0x7FFFFFFF之间:
a若在b之后发生,则a的值大于b。那么(long)b-(long)a<0。
2. a和b都在0x80000000到0xFFFFFFFF之间:
a若在b之后发生,b为较大的负数,a为较小的负数,那么(long)b-(long)a<0。
3. b在0到0x7FFFFFFF之间,而a在0x80000000到0xFFFFFFFF之间:
a为负数。b-a,相当于b+(-a)。只要a与b之间的绝对差值小于或等于0x80000000,则b+(-a)仍然为负数。
4. b在0x80000000到0xFFFFFFFF之间,而a在0到0x7FFFFFFF之间:
b为负数,b-a等于b+(-a)。同样在a与b之间的绝对差值小于或等于0x80000000,则b+(-a)仍然为负数。
总结这四种情况,在a与b的绝对值相差不到0x80000000时,这个宏是正确的。而在利用jiffies作为时间度量和比较单位时,时间差并不会太大。
所以这个time_after可以有效的避免jiffies回绕问题。