大家好,欢迎来到IT知识分享网。
那当 millis()
溢出(Overflow)归零时到底有没有问题?
答案是原则上millis()
溢出归零本身没有问题, 但是如果程序码没写好则有问题!
micros( )
也是使用 unsigned long, 所以类似的问题也会发生在 micros( )
(差别只是millis( )
要 49.71 天才归零, 但 micros( )
则大约 71.58分钟归零)
millis( ) 何时会溢出?
因为 millis( )
是用 unsigned long (32 bits)表示开机到现在为止多少
milli second, 用 32 bit 表示一个无符号整数, 32bit都是 1就是最大数, 等于 2 的 32 次方
-1 = 从 0 数到 共要 milli seconds,等于
/1000 /60 /60 小时; 每天有24小时,所以等于
/1000 /60 /60 / 24 天 = 49.710 天`
也就是说, 开机后大约快要 50 天之时 millis( )会归零!那到底有没问题 ?!
为何说原则上millis()
溢出归零是没问题,
但是如果程序码没写好则有问题! 因为问题并不是出在 millis( )
归零本身!
1. 正确–Arduino 的 delay()
代码:
void delay( unsigned long dtms ){
unsigned long start = millis(); while (millis() - start < dtms);; } // delay(
以上这程序完全正确, 且即使 millis( )
溢出归零也不会有问题!!
2. 错误–Arduino 的 delay( )
代码:
void delay( unsigned long dtms ){
unsigned long start = millis(); while (millis() < start + dtms);; // 有问题! } // delay(
3. 原因分析
从数学观点看起来似乎相同,因为 millis() - start < dtms
,应该就相当于 millis()< start + dtms
这只在没有 Overflow 之时成立, 因为把左边的 减项移到右边变加项, 应该对的。
如果millis()
前进到 Overflow 之时就不相同!!其实这不是 millis()
本身 Overflow 或 RollOver 归零(变成 0)之时出问题!
严格说是当 millis()
前进到 unsigned long
最大数的一半时大约24天半之后就会开始出问题!!
因为它的问题是出在 start + dtms 这运算! 并不是出在 millis()
本身是否 Overflow!!!
3.1 用c语言测试
// yc.c --- to test millis( ) Rollover (Overflow); by #include <stdio.h> int main( ) {
unsigned long k5 = -5; // , 再+5就归零 unsigned long every = 7; unsigned long milli = k5; // printf("every=%lu\n", every); printf("milli=k=%lu\n", milli); /// unsigned long start = milli; printf("First method.. milli = %lu, ", milli); printf("start=%lu\n", start); printf(" milli - start=%lu\n", milli - start); while(milli - start < every){
printf("milli=%lu,", milli); printf(" milli - start=%lu\n", milli - start); ++milli; } printf("===1st method, NOW milli = %lu ==========\n", milli); milli = k5; printf("\nSecond method.. milli = %lu, ", milli); printf("start=%lu\n", start); printf(" start + every=%lu\n", start + every); while( milli < start + every){
printf("milli=%lu\n", milli); ++milli; } ++milli; printf(" after ++milli, check milli=%lu\n", milli); while( milli < start + every){
printf("milli=%lu\n", milli); ++milli; } printf("===2nd method, NOW milli = %lu ======\n", milli); printf("===start + every = %lu\n", start + every); }
/* Result: ====================== D:\Users\tsaiwn\test>gcc yc.c D:\Users\tsaiwn\test>a every=7 milli=k= First method.. milli = , start= milli - start=0 milli=, milli - start=0 milli=, milli - start=1 milli=, milli - start=2 milli=, milli - start=3 milli=, milli - start=4 milli=0, milli - start=5 milli=1, milli - start=6 ===1st method, NOW milli = 2 ========== Second method.. milli = , start= start + every=2 after ++milli, check milli= ===2nd method, NOW milli = ====== ===start + every = 2 D:\Users\tsaiwn\test>
3.2 用arduino测试
要测 50 天吗? 当然不是,其实, millis()
的答案是可以偷改的(请看这里), 可以只测试几十秒。
只要声明 extern unsigned long timer0_millis
;然后在程序码中把 timer0_millis
改掉, 瞬间 millis( )
就变成你要的值了!
以下就这样来测试前面说的两种 delay( )
的写法,
test1( )
使用正确写法的 delayAA( )
;
test2()
则使用错误写法的 delayBB( )
;
不相信的朋友就测试一下吧:
extern unsigned long timer0_millis; // millis( )用的 void setup() {
Serial.begin(9600); Serial.print(" -1 === "); Serial.println((unsigned long)-1); cli(); // 禁止中断 timer0_millis = -2988; // 让 millis( ) 在 2.988 秒后 Overflow sei(); // 允许中断 test1(); cli(); // 禁止中断 timer0_millis = -2988; // 让 millis( ) 在 3.388 秒后 Overflow sei(); // 允许中断 test2(); Serial.println("=== 2nd Run for test2"); cli(); // 禁止中断 timer0_millis = -3388; // 让 millis( ) 在 3.388 秒后 Overflow sei(); // 允许中断 test2(); } void loop() {
//... } void test1() {
Serial.print("test1: time ="); Serial.println(millis()); unsigned long start = millis(); delayAA(2000); unsigned long tm2 = millis(); delayAA(3000); unsigned long tm3 = millis(); delay(2500); // library unsigned long endt = millis(); Serial.print("AA: start ="); Serial.print(start); Serial.print("=="); Serial.println((long)start); // 有正负 sign Serial.print("after 2000, now="); Serial.println(tm2); Serial.print("after total 5000 delay, now="); Serial.println(tm3); Serial.print("After delay(2500) endt="); Serial.print(endt); Serial.print(", run time = "); Serial.print(endt - start); Serial.println(", should be 7500"); } void test2() {
Serial.print("test2: time ="); Serial.println(millis()); unsigned long start = millis(); delayBB(2000); unsigned long tm2 = millis(); delayBB(3000); unsigned long tm3 = millis(); delay(2500); // library unsigned long endt = millis(); Serial.print("BB: start ="); Serial.print(start); Serial.print("=="); Serial.println((long)start); // 有正负 sign Serial.print("after 2000, now="); Serial.println(tm2); Serial.print("after total 5000 delay, now="); Serial.println(tm3); Serial.print("After delay(2500) endt="); Serial.print(endt); Serial.print(", run time = "); Serial.print(endt - start); Serial.println(", should be 7500"); } void delayAA(unsigned long dtms) {
unsigned long start = millis(); while (millis() - start < dtms) ; } void delayBB(unsigned long dtms) {
unsigned long start = millis(); while (millis() < start + dtms) ; }
输出结果:
-1 === test1: time = AA: start ===-2987 after 2000, now= after total 5000 delay, now=2013 After delay(2500) endt=4512, run time = 7499, should be 7500 test2: time = BB: start ===-2961 after 2000, now= after total 5000 delay, now= After delay(2500) endt=1539, run time = 4500, should be 7500 === 2nd Run for test2 test2: time = BB: start ===-3363 after 2000, now= after total 5000 delay, now= After delay(2500) endt=1137, run time = 4500, should be 7500
Q: 到底第二种条件式写法在何时会出问题 ?
A: 就是当millis()得到的数表示成有正负号的正数时, start = millis( )
超过 unsigned long
最大数的一半, 且该 start +dtms
结果是正数之时就会出问题!请注意这里的 dtms 是指范例中你想要 delay 的几个 ms (毫秒)!
例如假设 millis( )
得到的数看作有正负数,比如start = millis() = ;
start是signed类型,start就是 -800, 但你要 delay 805 ms,
则 millis() < -800 + 805
也就是< 5
是不成立的!
因为unsigned long
的 -800 其实是一个很大的正数, 怎会小于 5 呢?!
(不过, 如果这时你是要 delay 799 秒
或更少则仍不会有问题! Why ? 留给自己想 😃)
详细的解释原因:
虽然我们声明millis( )
、 start 以及function的参数为 unsigned long
, 但其实在计算机来说,它们还是一个有正负的数(严格说过一半之后就是负数了),
例如, 其实就是 -1, (都是二进制 32 个 1)
那
其实就是 -2,就是
-6
;
假设这时millis( )
是也就是
-6
;
写 unsigned long
start = millis( ); 得到
假设要 delay 的 dtms 是 8 表示要 delay 8 ms,
(a)先考虑第一种写法: (正确写法)
unsigned long start = millis( ); while( millis( ) - start < dtms );;
(b)接着来考虑第二种写法: (错误写法)
unsigned long
start = millis( );
while( millis( ) < start + dtms );;
同样假设开始时 millis( ) 与 start 都是 也就是 -6;
第一次进入 while 条件式为:
-6 < -6 +8 也就是 -6 < 2
学数学的会认为 -6 < 2 当然成立, 应该继续做 while LOOP等待,
问题是因为我们已经宣告 unsigned long,
所以 -6 不是 -6, 是 ,
那 怎会小于 2 呢 ?!
于是一开始 while( )
条件就不成立,
立即结束 while LOOP, 等于都没有 delay 就结束了!!!
所以, 第二种写法问题出在 start + dtms 这个地方,
由于 dtms 是正的数,
而 start = millis( )
虽然是 unsigned long
,
但当 millis( )
达到unsigned long
接近最大数之时其实就是负很少的负数,
这时millis( )
或说 start 看作signed如为负数且其绝对值如果小于 dtms 则会使start + dtms
变成正数就会出问题!
这使得 millis( ) < start + dtms
就会立即不成立, 于是 while LOOP 会立即结束!!
结论:
第二种写法 while( millis( ) < start + dtms );;
的出问题其实是在 millis( ) 超过 unsigned long
最大数一半之后,
且看作 signed 有正负的 millis( ) + dtms 结果是正的数, 这时就会出问题!!!
为了方便大家容易理解millis( )
的使用,
我把写在另一篇的相关信息 copy 过来补充如下:
Arduino 的 millis( ) 源代码(Source code):
unsigned long millis( ) {
unsigned long m; uint8_t oldSREG = SREG; //状态寄存器(包括是否允许 Interrupt); 1clock // disable interrupts while we read timer0_millis or we might get an // inconsistent value (e.g. in the middle of a write to timer0_millis) cli( ); // 禁止中断; 1 clock m = timer0_millis; // 读取内存的全局变量 timer0_millis;8 clock SREG = oldSREG; // 恢复状态寄存器(注意不一定恢复中断喔!);1 clock return m; // 6 clocks } // millis( // total 17 clock cycles
其中的 timer0_millis; 由以下 ISR 中断程序负责更新:
unsigned long timer0_millis=0; // 开机到现在几个 millis ? unsigned char timer0_fract=0; // 调整误差用 unsigned long timer0_overflow_count; // 给 micros( ) 用 SIGNAL(TIMER0_OVF_vect) {
timer0_millis += 1; timer0_fract += 3; if (timer0_fract >= 125) {
timer0_fract -= 125; timer0_millis += 1; } timer0_overflow_count++; }
程序中timer0_overflow_count
是给 micros( )
使用的! 以上这SIGNAL(TIMER0_OVF_vect)
的意思是: timer0_millis
必须系统发现TIMER0_OVF_vect
中断才会改变,所以在ISR( )
内连续调用millis( )
其答案是不会变的!
因为在ISR( )
内中断是被禁止的, 根本没机会进入SIGNAL(TIMER0_OVF_vect)
,所以在ISR( )
内连续调用millis( )
回传值不会变!所以千万不要在ISR( )
内企图用millis( )
判断过了多久!因为在ISR( )
内执行期间millis( )
在静止状态!
关于何时会执行上述的SIGNAL(TIMER0_OVF_vect)
这ISR( )
以及更多与以上相关的说明请看以下这:
关于delay()
,millis()
, micros()
,delayMicroseconds
与定时器(教程)
请注意第二种写法delay不够我们要的时间
delay()新旧版本比较
旧版本
虽然原先 Arduino 的 delay( )
是正确的, 不受 unsigned long
溢出或 Rollover 归零影响:
请看以下这 Arduino 的 delay( )
代码: (正确 ) (2010年9月以前 Arduino 版本用的)
void delay( unsigned long dtms ){
unsigned long start = millis(); while (millis() - start < dtms);; } // delay(
以上这程序完全正确, 且即使millis( )
溢出归零也不会有问题!!
但是, 因为这种不断调用millis( )
有两个缺点:
- 误差最多会大约 1ms
- 不可以在 ISR( ) 中断程序内使用, 否则因为
millis( )
不会变就回不来了!
所以, Arduino后来把 delay 改为使用检查micros( )
拖延所需的延迟时间!
2. 新版本
Arduino 自 2010/09/03 开始的版本已经把 delay( )
改写如下:
void delay(unsigned long ms) {
uint16_t start = (uint16_t)micros(); while (ms > 0) {
if (((uint16_t)micros() - start) >= 1000){
ms--; start += 1000; } // if }//while( }// delay(
unsigned long bb = millis( ); delay(1000); unsigned long ee = millis( );
则在新版本的 delay( )
, 可能出现 ee -bb 只有 999 不是期望的 1000 的情形!!
因为假设 delay前抓到 millis 是 5801,在新版本的 delay 延迟 1000 之后,
很有可能是刚好在 6800快要变 6801 之时,
于是你delay后的 ee = millis( )
; 很可能抓到 6800; 变很奇怪的只差999;
如果是以前的 delay( )
版本就不会有这种问题,
但是以前写法的 delay( )
本身会有最多 1ms 的误差!
还有, 新版的 delay( )
因为不是靠 millis( ),改用与中断无关的 micros( )
, 所以
新版本的 delay( )
也可以在ISR( )中断程序内使用!
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/132667.html