| Timer0: Timer0 is a 8bit timer. In the Arduino world timer0 is been used for the timer functions, like delay() , millis() , micros(), PWM D5 and D6. Timer1: Timer1 is a 16bit timer. Tthe Servo library ,PWM D9 and D10 use timer1 Timer2: Timer2 is a 8bit timer like timer0. tone() , PWM D11 and D3 use timer2. |
| //while() //millis() unsigned long time1; //自訂delay函數ms void mdelayms(unsigned long dt) { unsigned long time0; time0=millis(); while((millis()-time0)<dt); } void setup(){ Serial.begin(9600); } void loop(){ time1 =millis(); mdelayms(2000); Serial.println(millis()-time1); delay(1000); } |
| //micros() unsigned long time1; void mdelayus(unsigned long dt) { unsigned long time0; time0=micros(); while((micros()-time0)<dt); } void setup(){ Serial.begin(9600); } void loop(){ time1 =micros(); mdelayus(2000); Serial.println(micros()-time1); delay(1000); } |
| //不讓Delay延誤Arduino程式 unsigned long time[2] = {0,0}; void setup() {} void loop() { time[0] = millis(); if(time[1] != time[0]){ //避免1毫秒運行2次 time[1] = time[0]; if(time[0] % 1000 == 0){ //每1秒執行一次 //運行程式碼; //運行完後還可做其他事情 } if(time[0] % 5000 == 0){ //每5秒執行一次 //運行程式碼; } } } |
| Arduino計時器(Timer)程式庫(web)(Github)(.zip) 介紹一個好用的Arduino計時器(Timer)程式庫,由Dr. Monk所開發,由Jack Christensen稍加修改後放在github上。 下載並解壓縮後,把目錄改名成Timer,搬移到Arduino軟體開發環境的sketchbook目錄的libraries子目錄下,你可以從File-Preferences-Sketchbook location:查到sketchbook目錄在哪,若裡面沒有libraries這個子目錄,請自行建立,然後把計時器程式庫整個Timer目錄搬進去。 需要重新開啟Arduino軟體開發環境,它才知道你新安裝的程式庫。
|
|
//_delay_ms 未使用timer #include <util/delay.h> #ifdef F_CPU } void loop() { delay.h delay.h.pdf 置於
|
| Arduino 的時間函式(web) Arduino 的時間函式有如下三個 : 一. millis() : 此函式會傳回從開機到現在的毫秒數, 無參數, 傳回值為 unsigned long, 故最大值為 4294967295 毫秒, 即 4294967.295 秒, 換算成天數為 4294967.295/86400=49.71027 天, 即約 50 天就會重新歸零. 測試程式如下 : unsigned long time; void setup() { Serial.begin(9600); } void loop() { time=millis(); Serial.println(time); } 執行後開啟串列埠監視視窗, 會看到數字飛快地往下跑, 觀察這些數字可發現差額約為 9~10 ms, 亦即 loop() 函式執行一圈大約為 0.01 秒 (100Hz), 一秒鐘可以跑 100 圈. 2054497 2054507 2054516 2054525 2054535 2054544 2054553 二. micros() : 此函式會傳回從開機到現在的微秒數, 也就是 millis() 的千分之一. 無參數, 傳回值為 unsigned long, 故最大值為 4294967295 微秒, 即 4294.967295 秒, 換算成分鐘數為 4294.967295/60=71.583 分, 即約 72 分鐘就會重新歸零. 測試程式如下 : unsigned long time; void setup() { Serial.begin(9600); } void loop() { time=micros(); Serial.println(time); } 擷取串列埠輸出資料, 差額為 9360, 即 9.36ms, 似乎較 millis() 精確些. 2429604 2438964 2448324 2457684 2467044 2476404 2485764 三. delay(ms) 暫停程式執行, 傳入參數是型態為 unsigned long 之毫秒數, 無傳回值. 測試程式 : nsigned long time; void setup() { Serial.begin(9600); } void loop() { time=millis(); delay(1000); Serial.println(time); } 我們在呼叫 millis() 後加了 delay(1000) 來延遲 1 秒再往下執行, 結果序列埠輸出如下 : 0 999 1999 3000 4000 5000 6000 7001 8001 9000 10001 11001 12002 13002 14003 15002 16003 可見迴圈所耗時間幾乎完全被 delay() 主宰了. 四. delayMicroseconds(us) 此函式是延遲指定之微秒數, 但是與 delay() 不同的是, 傳入參數類型為 unsigned int, 因此最大值為 16383, 即約 16 ms. 測試程式 : unsigned long time; void setup() { Serial.begin(9600); } void loop() { time=micros(); delayMicroseconds(16383); Serial.println(time); } 擷取序列埠視窗輸出如下, 可見差額約為 16ms : 0 16656 33424 50216 67008 83796 100592 117428 134276 151120 167972 184812 但若傳入超過 16383 之參數, 仍可編譯成功, 只是執行結果並非所預期者, 例如傳入 1000000 (延遲 1 秒) : unsigned long time; void setup() { Serial.begin(9600); } void loop() { time=micros(); delayMicroseconds(1000000); Serial.println(time); } 序列埠輸出卻是差額 700 左右 : 0 148 752 1508 2320 3132 3936 4740 5552 6364 7176 7972 delayMicroseconds() 是給延遲數千微秒用的, 秒級的應該使用 delay(). |
| (ref) (1)如何剛好 delay 1 micro second ? 注意 delayMicroseconds(1); 其實會 delay 大約 1.5 micro second(us), 如果要真的 delay 剛好 1 micro second, 可以如下做: #define ddtick __asm__("nop\n\t"); #define dd4tick __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); // the Macro dd4tick will delay 0.25us exactly on 16MHz Arduino #define delayOneUs() { dd4tick; dd4tick; dd4tick; dd4tick; } 然後就可用 delayOneUs(); 剛好 delay 一個 micro second, 注意這 delayOneUs( ) 是 Macro (巨集)不是 function, 所以沒有調用 function 的 Overhead, 剛好做 16個 NOP, 用掉 16 cycles, 在 16MHz 的 Arduino 剛好是 1 us 的時間 ! 那可不可以 delay 比 1 us 更短時間呢? 當然可以 ! 已經 #define 為 ddtick 了, 所以只要寫 ddtick; 就會剛好 delay 0.0625 micro second; 如果寫 dd4tick; 則剛好 delay 0.25 us, 注意是剛好0.25 us喔, 不多也不少 (2)來看看 delay( ) 的程序源碼: (注意 delay 是以 ms(millis second)為最小單位, 1 ms = 1000 us) void delay( unsigned long ms ){ unsigned long start = millis(); while (millis() - start < ms) { // do nothing here } // while( } // delay( 你會發現,這 delay( ) 的代碼很簡單, 它就是不斷的調用 millis( ) 看看時間到了沒? 時間沒到就不返回, 一點學問都沒吧 :-) 請注意,因為 millis( ) 的值是靠 timer0 的中斷幫忙每次加 1, 如果中斷請求被禁止,則每次調用 millis( ) 會得到一樣的值。 為什麼? 看過 millis( ) 以及它的相關函數就知道了 ! 補充更正: 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( 這種新寫法是 delay 的比較準確, 誤差應該在 7us 以內(舊版delay誤差可能高達1ms); 但是卻使得 delay( )前後各調用一次 millis( )相減可能不等於 delay( )的 ms 數! 因為 millis( ) 回傳的值其實會有 1ms 的誤差! 所以: 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( )中斷程序內使用! -------------------------------------------------------------------------------------------- (3)接著來看看 millis( ) 的代碼是怎麼寫的 ? 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 啥? 原來它只是先用 cli( ) 把中斷請求禁止, 然後讀取 timer0_millis; 放到臨時變量 m, 接著還原中斷狀態, 然後把 m 送回來 ! 請注意我是說"還原中斷狀態", 不是說"恢復中斷", Why? 因為原本在進入 millis( ) 之前有可能已經是禁止中斷的狀態, 是否禁止中斷被記錄在 SREG 中的一個 bit, 在送回答案之前做 SREG = oldSREG; 還原中斷狀態, 因為在剛進入 millis( ) 時有把 SREG 先複製到 oldSREG 這臨時變量中! 注意雖然你在 ISR( ) 內可以調用 millis( ), 但是在 ISR( ) 內因為中斷請求被禁止, 所以連續調用 millis( ) 得到的答案都不會變喔 ! 因此千萬不要在 ISR( ) 中斷程序內寫如下: while( millis( ) < timeUP ) { //.. do nothing 或 do something } 這樣這 while Loop 會陷入永不停止的 LOOP !!! 因為 millis( ) 都不會改變答案 ! (4)關於 timer0 的中斷與其處理程序 SIGNAL(TIMER0_OVF_vect) 是誰負責計算 timer0_millis 這個變數(Variable, 變量) ? 問題來了, 既然 millis( ) 的答案來自 timer0_millis 這個變數(Variable), 那 timer0_millis 這是啥東西呢? 原來它是一個全域變量(Global variable), 意思是可被各 function 存取(訪問)的 unsigned long 變量。 那又是誰負責計算這 timer0_millis 呢? 是一個中斷程序負責, 如下: 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++; } 看到這裡, 我們發現 millis( ) 答案來自 timer0_millis; 而 timer0_millis 必須系統發現 TIMER0_OVF_vect 中斷才會改變(稍後討論), 所以在 ISR( ) 內連續調用 millis( ) 其答案是不會變的 ! 因為在 ISR( ) 內中斷是被禁止的, 根本沒機會進入SIGNAL(TIMER0_OVF_vect), 所以在 ISR( ) 內連續調用 millis( ) 回傳值不會變 ! 所以千萬不要在 ISR( ) 內企圖用 millis( ) 判斷過了多久 ! 因為在 ISR( ) 內執行期間 millis( ) 在靜止狀態 !! (5)何時會執行上述的 SIGNAL(TIMER0_OVF_vect) 這 ISR( ) ? 為什麼 ? 好了, 剩下的問題是何時會執行上述中斷代碼 SIGNAL(TIMER0_OVF_vect) ? 這代碼的 TIMER0_OVF_vect 名稱就已經說明了是當 timer0 發生 Overflow的中斷, 也就是 timer0 的內部計數寄存器 TCNT0 算了一輪迴(0,1,2...254, 255, 0), 從 255 加 1 又變為 0 之時(這時稱 Overflow 溢位)會產生中斷進入這處理程序 ! 那麼 timer0 的 TCNT0 每隔多久會加 1 呢? 就是每當 timer0 被 "踢" 一下的時候啦! 被 "踢"一下就是 timer0 的時脈變化一下, 稱作一個 tick 或一個 clock cycle; 由於 timer0 的 Prescaler 是被Arduino設定為 64, Arduino 大都使用 16 MHz 的時脈,除頻 64 之後給 timer0 用, 則每個 clock cycle (或稱 tick) 時間為: 1 秒 / (16 000 000 / 64) = 1/250000 = 0.000004 sec = 0.004 ms 所以給 timer0 的 tick 是每個 tick 0.004ms = 4 us (micro second)。 意思是每隔 0.004 milli sec 計時器(定时器)的時脈電路會"踢" timer0 一下, 這使得 timer0 會自動把 TCNT0 加 1, (注意不是靠 CPU 喔!) 因為 TCNT0 只有 8 bit, 看作無符號整數 (unsigned char), 既然 TCNT0 每 0.004ms 會自動加 1, 總會加到 255, 然後 255 再加 1 變回 0 (即 Overflow), 共使用256 ticks, 共花了 0.004 ms * 256 = 1.024ms, 這時會對 CPU 產生中斷一次, 要求 CPU 進入上述的中斷處理程序SIGNAL(TIMER0_OVF_vect) 處理 。 *** 注意給 CPU 的 clock cycle 是 0.0625 us 喔(沒有除以 64) !! (6)每隔 1.024ms 把 millis 加 1 豈不是有誤差 0.024ms 那要如何修正 ? 我們看到了在中斷處理程序中主要是把 timer0_millis 加 1, 但請注意, 實際上這時是經過了 1.024 ms, 並不是 1ms, 也就是產生了誤差, 長此以往, 這誤差會越來越大 ! 還好, Arduino 的工程師很聰明, 另外用一個變數 timer0_fract 紀錄誤差, 就是 timer0_fract += 3; 然後你會發現在 (timer0_fract >= 125) 時會做調整: if (timer0_fract >= 125) { timer0_fract -= 125; timer0_millis += 1; } 這個動作跟閏年(Leap year)原理類似, 因為地球繞太陽一圈的回歸年其實是365.2421990741天, 不是 365天也不是 366天, 所以每四年要閏年一次多一天, 可是四年多一天等於算做一年是 365.25 天, 又不准了, 因此每一百年又把多算的一天取消(公元年/100整除不是閏年)做修正 !! 這裡的算法是因每次誤差 0.024 ms, 用 3 代表, 然後 125 就是代表 0.024ms * 125 = 1.000ms, 因此如果 (timer0_fract >= 125) 就要把 millis 加 1, 並且要做 timer0_fract -= 125; 注意不是設為 0 喔, 是減去 125, 因這時可能是125, 126, 127 這三個之一個, 多出來的誤差要累計到下次的計算內。 (7)接著來看看相關的 micros( ) 這 function 是如何寫的: 不過以下這程序已經被我簡化成比較容易看懂(依據16MHz clock), 它會用到前面提及的 timer0_overflow_count; unsigned long micros() { unsigned long m; uint8_t oldSREG = SREG; // 狀態寄存器(包括是否允許 Interrupt) uint8_t t; // 臨時變量 cli(); // 禁止 Interrupt m = timer0_overflow_count; // timer0 已經 overflow 幾次 ? t = TCNT0; // timer0 目前的值 if ((TIFR0 & _BV(TOV0)) && (t & 255)) m++; // timer0 目前的TCNT0值不是 0且欠一次中斷 SREG = oldSREG; // 恢復狀態寄存器(注意不一定恢復中斷喔 !) return ((m *256) + t) * 4; // 最大只能代表約 71.58分鐘 } // micros( 你可以看到它只是短暫禁止中斷, 然後讀取兩個整數到 m 和 t, 並在恢復中斷狀態後回傳 ((m *256) + t) * 4; 這答案。 (8)為何 micros( ) 回傳的值都是 4 的倍數 ? 其實從程序最後回傳值就知道一定是 4 的倍數 ! 前面說過因為 timer0 的 clock cycle 是每個 tick 0.004ms = 4 us, 在該函數內最後是回傳 ((m *256) + t) * 4; 所以你會發現 micros( ) 回傳的值都是 4 的倍數 ! 回傳的 ((m *256) + t) * 4 這答案用白話文說, 就是 ((TCNT0 已Overflow次數) * 256 + TCNT0 ) * 4 注意前面說過該 timer0 的 TCNT0 是每 4 us自動加 1, 這也是為何最後要乘以 4 獲得幾個 micro seconds的答案 ! 因為 micros( ) 答案是用 unsigned long 表示, 所以 micros( ) 大約開機後每70分鐘會Overflow 歸零, 4294967296 /1000/1000 /60 分鐘 = 71.58 分鐘 還有, 請注意, 在進入 micros( ) 之前可能已經禁止中斷, 所以結束 micros( ) 之前不是用 eni( ); 恢復中斷, 是用 SREG = oldSREG; 恢復原先的中斷狀態! 前面說過, 如果你在 ISR( ) 內連續調用 millis( ) 其答案是不會變的 ! 因為在 ISR( ) 內中斷是被禁止的, 根本沒機會進入SIGNAL(TIMER0_OVF_vect), 所以 millis( ) 回傳值不會變, 但是, 在 ISR( ) 內連續調用 micros( ) 則其值是會變的 ! (9)為什麼在 ISR( ) 內中斷已經被禁止但連續調用 micros( ) 仍會有變化 ? 因為從 micros( ) 的源代碼可以看到, 它的答案是 ((m *256) + t) * 4; 其中的 m 就是 timer0_overflow_count 中斷禁止時當然不會變, 可是其中的 t 是 TCNT0 是由 timer0 自己不斷的加 1, 不會受到 CPU 是否允許中斷或禁止中斷的影響 ! 只是如果中斷被禁止太久, 由於禁止中斷期間 timer0_overflow_count 都不會變, 這樣得到的 micros( ) 還是會受到禁止中斷的影響 ! 我這裡只是強調在 ISR( ) 內連續調用 micros( ) 會得到不同的值 ! (10)調用 micros( )要花掉多少時間 ? 別忘了調用 micros( ) 也要時間, 寫 unsigned long kk = micros( ); 意思是跑去 micros( ) 裡面, 然後回傳的答案要複製到 kk, 跳去 micros( ) 要四個 CPU Clock cycle, 把答案放入 kk 要 8 個 CPU Clock cycle, 在 micros( ) 裡面共會用掉 17 CPU clock cycle, 所以, kk = micros( ); 總共會花掉 4 + 17 + 8 = 29 個 CPU Clock cycle; 在 16MHz 狀況下, 每個 CPU Clock cycle (tick)是 0.0625 us 於是kk = micros( ); 總共會用到 29 * 0.0625 us = 1.8125 us 就是說調用 micros( ) 本身會花掉將近 2 us, *** 注意不要把 CPU 的 tick (clock cycle) 與 timer 的 tick 搞混了 ! ------------------ 關於 AVR 指令集以及各指令 clock 請看: http://www.atmel.com/images/doc0856.pdf (11)最後來看 delayMicroseconds( ) 這 function, 以下這是新版的, (在2010/01/29之前的版本執行 delayMicroseconds( ) 之時會禁止中斷 !!) 新版本已經不再禁止中斷, 這樣萬一在 delayMicroseconds( ); 中途產生中斷, 由於一個中斷處理程序即使甚麼事都不做就中斷返回也要42cycles大約 2.625us, ( 參看 http://www.gammon.com.au/forum/?id=11488 ) 於是實際 delay 的時間會比預期的更長一些 ! 以下就是新版的 delayMicroseconds( ) 的源代碼: void delayMicroseconds( unsigned long us ){ if (--us == 0) return; // 表示 us 是 1 us <<= 2; // us = us*4, 因等下每Loop一次是 0.25us us -= 2; // 修正這句以及前兩句耗掉約 0.5us __asm__ __volatile__ ( "1: sbiw %0,1" "\n\t" // 2 cycles "brne 1b" : "=w" (us) : "0" (us) // 2 cycles ); } // delayMicroseconds( 上面那兩句用 __asm__ __volatile__ ( ) 夾住看起來像天書的, 是組合語言寫的小 Loop, 共會依據參數 us 做 Loop (unsigned int)( 4 * (us-1) -2) 次, 每 Loop 一次是 0.25us; 還有, 由於進入 delayMicroseconds( ) 與離開 delayMicroseconds( ) 合起來就大約 1.125 us, 因此delayMicroseconds( )會有大約 1us 的誤差 ! 實際上 delayMicroseconds(1)會花掉約 1.4375 us; delayMicroseconds(2)則會花大約 2.25 us; 可是因為 micros( ) 回傳的值必定是 4 的倍數, 如果你用 micros( )檢查是看不出來的 !! 注意因為考慮準確度的問題, 雖然參數是 unsigned long, 但是官方網站建議 us 最大只可以到 16383 不然會很不準: (因為目前版本其實只用unsigned int, 且把參數乘以4) http://arduino.cc/en/Reference/delayMicroseconds ** 其實 16383 就是 65535 除以 4 的整數 (unsigned int 的最大就是 65535) ! ** 如果 us 超過 16383 則delayMicroseconds(us); 會很不準確, 主要原因是那會相當於 delayMicroseconds(us % 16384); 因為從delayMicroseconds( ) 源代碼可看到它做了 us = (us -1)*4 -2 然後只用us右邊兩個 bytes. 請注意, 在 delayMicroseconds( ) 的源代碼中所夾的組合語言, 由於它是使用 SBIW 指令對新的 us 做減一並檢查是否還沒減到 0 (brne)就繼續Loop, 可是注意 sbiw 是 16 bit 指令, 這表示雖然參數 us 是 unsigned long, 但其實目前版本的 delayMicroseconds( us ) 只用 us 變量(減去1後乘以四再減去2)右邊的 16 bits, 所以, 如果你的 us 大於 16383, 則其真正 delay 的時間將會變成延遲大約 (us % 16384) micro second. 換句話說, 寫 delayMicroseconds( 16483 ); 將會變成與 delayMicroseconds( 99 ); 幾乎相同 !!! 還有, 因代碼內立即對 us 減去 1才判斷是否為 0, 所以, delayMicroseconds(0); 反而是delayMicroseconds()延遲最久的, 將會比 delayMicroseconds(16383); 還久一點點(大約多 1 us) ! 關於 Arduino 所用 CPU 的指令可以看: http://en.wikipedia.org/wiki/Atmel_AVR_instruction_set 或看 AVR 指令集 (Instruction Set) http://www.atmel.com/images/doc0856.pdf 或是看 ATmega328 的 datasheet: http://www.atmel.com/Images/doc8161.pdf (P.427-) 或看 AVR 組合語言入門 http://www.avr-asm-tutorial.net/avr_cn/beginner/ 關於上述提及的 Source code 在這: https://code.google.com/p/arduin ... es/arduino/wiring.c (Sorry Google.com 可能被屏蔽) ================================================ (12)為何說在 ISR( ) 中斷程序內最好不要調用 millis( ) 和 micros( ) 呢? 事實上 micros( ) 與 millis( ) 都會利用 timer0 的中斷幫忙, 由於 timer0 的 Prescaler 是被Arduino設定為 64, Arduino 大都使用 16 MHz 的時脈, 除頻 64 之後給 timer0 用, 所以給 timer0 的 tick 是每個 tick 0.004ms = 4 us (micro second); 也就是說, TCNT0 每 0.004ms 會自動加 1, 每當 timer0 的 TCNT0 從 0 數到 255 再次加 1 變為 0 之時(Overflow), 會執行中斷處理程序 SIGNAL(TIMER0_OVF_vect) ! (如果中斷被禁止則暫時不執行 !) (13)為何說在 ISR( ) 中斷程序內不可以調用 delay( ) 做延遲呢? 如果你有認真看過 (2)中討論的 delay( ) 是如何寫的, 就知道 delay( )是不斷的調用 millis( ); 既然中斷被禁止後 millis( ) 都不會變, 那麼即使 delay(1); 也都回不來了, 你只能 RESET 或關機重開 !!! (14)那在 ISR( ) 內為何仍可用 delayMicroseconds( ) 做延遲呢 ? 如果你有認真看在 (11)中討論的 delayMicroseconds( )的源代碼, 就不會這麼問了 :-) 因為 delayMicroseconds( ) 與中斷一點關係也沒有, 它是算好該做幾個簡單的 Loop 拖延時間, 等該 Loop 的次數做完就回來, 所以即使中斷被禁止仍可以正常使用 !! 或者應該說, 如果中斷被禁止, 則 delayMicroseconds( ) 的延遲比較準確, 因為每次的中斷處理也會浪費掉一些時間, 這會使得實際得延遲稍微久一些些 !! ============ ============================ ,,。。,。,,,。。。,,,。。。,,,。。 ! ===================================================== (15)每次中斷處理要花掉多省時間呢? 你當然可以寫個程序用測的, 但通常測不準, 所以我們來用分析的 以下是 SIGNAL(TIMER0_OVF_vect) 與其對組譯後的組合語言 SIGNAL(TIMER0_OVF_vect) { timer0_millis += 1; timer0_fract += 3; if (timer0_fract >= 125) { timer0_fract -= 125; timer0_millis += 1; } timer0_overflow_count++; } 以下是對應的組合語言機器碼 : ------------------------------- ISR(TIMER0_OVF_vect) { be: 1f 92 push r1 ; // 2 clock c0: 0f 92 push r0 ; // 2 clock c2: 0f b6 in r0, 0x3f ; 63 // 1 clock c4: 0f 92 push r0 ; // 2 clock c6: 11 24 eor r1, r1 ; r1 xor r1 will get 0 // 1 clock c8: 2f 93 push r18 ; // 2 clock ca: 3f 93 push r19 ; // 2 clock cc: 8f 93 push r24 ; // 2 clock ce: 9f 93 push r25 ; // 2 clock d0: af 93 push r26 ; // 2 clock d2: bf 93 push r27 ; // 2 clock // copy these to local variables so they can be stored in registers // (volatile variables must be read from memory on every access) unsigned long m = timer0_millis; d4: 80 91 08 01 lds r24, 0x0108 d8: 90 91 09 01 lds r25, 0x0109 dc: a0 91 0a 01 lds r26, 0x010A e0: b0 91 0b 01 lds r27, 0x010B unsigned char f = timer0_fract; e4: 30 91 0c 01 lds r19, 0x010C m += 1; e8: 01 96 adiw r24, 0x01 ; 1 ea: a1 1d adc r26, r1 ec: b1 1d adc r27, r1 f += 3; ee: 23 2f mov r18, r19 f0: 2d 5f subi r18, 0xFD ; 253 // 減去253等於+3 if (f >= 125) { f2: 2d 37 cpi r18, 0x7D ; 125 f4: 20 f0 brcs .+8 ; 0xfe <__vector_16+0x40> f -= 125; f6: 2d 57 subi r18, 0x7D ; 125 m += 1; f8: 01 96 adiw r24, 0x01 ; 1 fa: a1 1d adc r26, r1 fc: b1 1d adc r27, r1 } timer0_fract = f; fe: 20 93 0c 01 sts 0x010C, r18 timer0_millis = m; 102: 80 93 08 01 sts 0x0108, r24 106: 90 93 09 01 sts 0x0109, r25 10a: a0 93 0a 01 sts 0x010A, r26 10e: b0 93 0b 01 sts 0x010B, r27 timer0_overflow_count++; 112: 80 91 04 01 lds r24, 0x0104 116: 90 91 05 01 lds r25, 0x0105 11a: a0 91 06 01 lds r26, 0x0106 11e: b0 91 07 01 lds r27, 0x0107 122: 01 96 adiw r24, 0x01 ; 1 124: a1 1d adc r26, r1 126: b1 1d adc r27, r1 128: 80 93 04 01 sts 0x0104, r24 12c: 90 93 05 01 sts 0x0105, r25 130: a0 93 06 01 sts 0x0106, r26 134: b0 93 07 01 sts 0x0107, r27 } 138: bf 91 pop r27 ; // 2 clock 13a: af 91 pop r26 ; // 2 clock 13c: 9f 91 pop r25 ; // 2 clock 13e: 8f 91 pop r24 ; // 2 clock 140: 3f 91 pop r19 ; // 2 clock 142: 2f 91 pop r18 ; // 2 clock 144: 0f 90 pop r0 ; // 2 clock 146: 0f be out 0x3f, r0 ; 63 // 1 clock 148: 0f 90 pop r0 ; // 2 clock 14a: 1f 90 pop r1 ; // 2 clock 14c: 18 95 reti ; // 4 clock ============================================= 從以上程序可以發現, 跳入 ISR( ) 會立即做備份寄存器的動作, 會花掉大約 16 clock左右(與裡面用到運算有關**, 這例子是 20colock), 離開 ISR( ) 之前會做回覆寄存器並 reti 返回的動作, 花掉約 19 clock(這例子是 23); 另外從產生中斷到跳入中斷處理ISR 要 7 clock, 共計 42 clock = 0.0625us*42 = 2.625 µs (這例子是 50 clock = 3.125 us) 意思是說即使中斷處理程序內任何事都不做也會花掉 2.625 micro second. 這 2.625 us 期間當然 CPU 都無法做正常的事 ! 參考: http://www.gammon.com.au/forum/?id=11488 關於 AVR 指令集以及各指令用多少 clock 請看: http://www.atmel.com/images/doc0856.pdf ** 與裡面用到運算有關是因為 ISR( )內的運算如果因為運算會毀掉的寄存器都要先備份到 Stack內(用 Push), 之後在離開 ISR( ) 之前要用 POP 從 Stack 取出放回寄存器。 (16)那麼寫 unsigned ans = millis( ); 會花掉多少時間呢? 以下是 millis( ) 的組合語言: unsigned long millis() { unsigned long m; uint8_t oldSREG = SREG; 14e: 8f b7 in r24, 0x3f ; 63 // 1 clock // 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(); 150: f8 94 cli ; // 1 clock m = timer0_millis; 152: 20 91 08 01 lds r18, 0x0108 ; // 2 clock 156: 30 91 09 01 lds r19, 0x0109 ; // 2 clock 15a: 40 91 0a 01 lds r20, 0x010A ; // 2 clock 15e: 50 91 0b 01 lds r21, 0x010B ; // 2 clock SREG = oldSREG; 162: 8f bf out 0x3f, r24 ; 63 // 1 clock return m; } 164: b9 01 movw r22, r18 ;// 1 clock 166: ca 01 movw r24, r20 ;// 1 clock 168: 08 95 ret ;// 4 clock ================================= === Total 17 clocks 從以上的指令, 發現 millis( ) 內部總共會用掉 17 clocks 假設已經定義 unsigned long ans; 寫 ans = millis( ); 因為 call 調用函數 4 clock, 回來後把答案放入 ans 用 8 clock, 所以會用掉共 4 + 17 + 8 = 29 clocks, 就是說 ans = millis( ); 要用 0.0625us*29 = 1.8125 micro seconds. ================================= AVR 指令集以及各指令 clock 請看: http://www.atmel.com/images/doc0856.pdf ============================================ void delayMicroseconds( unsigned long us ){ if (--us == 0) return; // 表示 us 是 1 us <<= 2; // us = us*4, 因等下每Loop一次是 0.25us us -= 2; // 修正這句以及前兩句耗掉約 0.5us __asm__ __volatile__ ( "1: sbiw %0,1" "\n\t" // 2 cycles "brne 1b" : "=w" (us) : "0" (us) // 2 cycles ); } // delayMicroseconds( 補充說明: if(--us == 0) return; 這句意思是先把 us 減去 1, 如果此時等於 0 就返回 ! 再提醒一次, 根據官方網站說明, 使用 delayMicroseconds( us ); 的 us 不要超過 16383, 否則 會被看做 delayMicroseconds( us % 16384); 且 delayMicroseconds(0); 是這函數延遲最久的 !! 16383us = 16.383 ms = 0.016383 秒 參考: http://arduino.cc/en/Reference/DelayMicroseconds *** 每一句 C 語言指令大約要 2 clock cycles 到 15 clock cycles, 當然比較複雜的C 語言指令可能多達數十clock甚至一百多 clock cycles; 以 16MHz 時脈則每 16 clock cycles 要 1 us (micro second). 注意 I/O (輸入/輸出)如 Serial.print( ) 則相對更慢, 在 9600 bps 的波特率下每發送一個 char 大約要 1ms = 1000us; 比加減乘除慢約 數百倍到 兩千倍 ! ! |
| (ref) 真的嗎? 據說 delayMicroseconds(1); 會 delay 大約 2 Microseconds ? 那可不可能只 delay 一個 micro second 呢? 當然可能, 不然我寫這廢文幹嘛 ?! 怎做? 就直接使用只花一個 tick (cycle)的機器指令(用組合語言)即可, 那樣會 delay 0.0625 us(micro second), 多寫幾個就可 delay 1 us, 其實說穿了也不難理解 :-) 大多數 Arduino 板子使用 16MHz 的時脈, 每個 tick (cycle) 就是 0.0000000625 秒, Arduino 使用的 ATmega328 CPU 平均每個指令要 2個到3個 ticks, 注意 ATmega328 是八位元 (8 bit) 的 CPU, 一次只能處理一個 Byte, 因此像 int a = b; 則因 int 用 2 Bytes, 大約要 8 個 ticks, 如果寫 long x = y; 則大約要 16個 ticks, 即大約 1 us (micro seconds); 由於進入函數(函式) function 和離開 function 要做不少指令會花時間, 所以在 16MHz時脈之下, 使用 delayMicroseconds() 至少要大約 1.5 micro seconds, 也就是說 delayMicroseconds(1); 會 delay 約 1.5 micro seconds, 這時和 delayMicroseconds(2); 會花掉約 2.125us 其實是很接近的 ! 換句話說, 使用 delayMicroseconds(1); 是不可能的任務 ! 還有, 在 delayMicroseconds( ); 期間任何中斷請求是被禁止的 !(註: 更正一下, Arduino 自 2010/01/29 的 0018版本之後, 其 delayMicroseconds 已經不再禁止中斷了 !) (參考: http://arduino.cc/en/Main/ReleaseNotes ) 因此, 原則上不要用 delayMicroseconds( ); 做相對比較久的延遲。 阿就是說應該是要 delay 很短時間才用 delayMicroseconds( ); 可是既然 delayMicroseconds(1); 其實會 delay 2 micro seconds, 那要如何只 delay 1 us 或是比 1us 更短呢? 很簡單 ! 可以用夾住組合語言的 NOP 指令( 1 個 cycle 或 tick)來達成, 例如: __asm__("nop\n\t"); // 0.0000000625 秒 = 62.5 ns(nanosecond)=0.0625us __asm__("nop\n\t""nop\n\t"); // 0.0000001250 秒 = 0.125us __asm__("nop\n\t""nop\n\t""nop\n\t"); // 0.0000001875 秒=0.1875us 或以下這可 delay 0.25 us (micro second): __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); // 0.0000002500 秒=0.25us 所以, 使用最後這內含 4 個 nop 的指令可延遲 0.25us, 那麼連續用四次這樣0.25us合起來就是 1 us (1 micro second): __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); // 0.25us __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); // 0.25us __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); // 0.25us __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); // 0.25us 千萬不要寫這樣: int i; for(i=0; i<4; ++i) { __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); // 0.25us } // for( 因為這樣並不是只有 0.25us *4, 這是因為做 i=0 以及 ++i 和檢查 i<4 都會花去不少時間 ! 可是寫那樣看起來很醜ㄟ !? 簡單, 先 #define 一個 macro 來用, 然後就可看起來比較簡潔: (總要先醜一次 ) #define x38 __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); //.. 然後之後可寫如下: x38; // delay 0.25 us x38; x38; // delay 0.5 us x38;x38;x38;x38; // delay 1 us 最後, 順便說說 millis( ) 這自帶的 function.. 請注意因為 millis( ) 是靠 timer0 的中斷來計算已經開機過了幾個 milli seconds, 既然 delayMicroseconds( ); 的期間中斷請求是被禁止的, 這時當然 millis( ) 也停止計數, 這表示 millis( ) 會變得稍微不準, 誤差就是大約被你 delayMicroseconds( ); 的時間 ! 雖然很小, 但一直累積之後也會誤差越來越大 ! 還有, 由於中斷處理程序(ISR 或一些庫 attachInterrupt的)內也是自動禁止中斷請求, 所以你在中斷處理程序內使用 millis( )是不會變的 ! 千萬不要在中斷處理程序內寫這樣: unsigned long upTime = 5 + millis( ); while( millis( ) < upTime) { // wait for times up } 這樣會陷入永無止境的循環, 因為每次叫用 mullis( ) 都得到一樣的值 !! 此時你只能按 RESET 或關電源重新開機囉 !!! 參考: http://playground.arduino.cc/Main/AVR 也可看看這 ATmega328 datasheet: http://www.atmel.com/Images/doc8161.pdf Q: delayMicroseconds(us) 的誤差有多少? A: 根據官方網站, delayMicroseconds(us) 如果參數 us 在 16383 以內(大約16秒內), 則誤差會在 3 us (micro second)以內, 但是如果參數 us 太大則可能更多的誤差! 事實上如果你寫參數 us 超過 16383, 則相當於 delayMicroseconds(us % 16384); 因為 delayMicroseconds(us) 是用一段簡單的 Loop 拖延時間, 但只用 (us-1)*4-2 之後右邊的16bits來做 Loop次數 !! 由於用Loop拖延與中斷無關, 所以在中斷被禁止的情況仍然可以正確的 "拖延" ! 因為Arduino 自2010/01/29 的 0018版本之後, 其 delayMicroseconds 已經不再禁止中斷, 那摽是在 Loop 拖延之時有可能被中斷打斷, 然而每次中斷的處理至少大約要 2.7 us, 這個在 delayMicroseconds( ) { } 內無法知道, 所以, 在 delayMicroseconds( ) 期間, 中斷處理會使得真正的延遲比 delayMicroseconds( us ); 要求的 us 還要久 ! 這包括系統本身維護 millis( ) 的每 1.024 ms 一次的中斷, 每多一次中斷就會多至少 2.7 us (micro second). Q: delay(ms) 的誤差有多少? A: 理論上 delay(ms) 的誤差最大就是 1ms, 但是如果在禁止中斷的情況下, 調用 delay(ms) 就可回不來了!! 例如: cli( ); // 禁止中斷 delay(3); 這樣就死了 !! 只能把 Arduino 做 RESET (重置, 複位) 或重新開機 ! 因為一旦中斷被禁止, 則調用 millis( ) 每次答案都相同, 可是 Arduino 自帶的 delay( ) 就是根據每次調用 millis( )決定是否已經 delay 夠多 ! Q:為何改變 timer0 的 Prescaler 會影響到 millis( ) 和 delay( ) ? A: 不止會影響 millis( ) 和 delay( ), 也會影響到 micros( ); 因為 millis( ) 的答案和 micros( ) 的答案都靠 timer0 的中斷處理維護, 預設(默認)是使用 Prescaler 64, 會使得 timer0 每 1.024 秒發出中斷請求! 改變 timer0 的 Prescaler 會使的中斷頻率不同, 導致 millis( ) 和 micros( ) 都不準確! 連帶使得依賴檢查 millis( ) 的 delay( ) 也變得不正確 ! 不過 delayMicroseconds( ) 不受影響, 因為前面說過, delayMicroseconds(us) 不是靠中斷, 它是靠執行預先算出來特定數量的指令拖延時間!! Q:到底 delay( ) 和 delayMicroseconds( ) 有何差別 ? 原則上建議 16秒以上用 delay( )即可 ! 要 delay 延遲的時間很短才用 delayMicroseconds( ) ; 通常是軟體在與硬體(硬件)溝通時因為硬件規定要延遲一小段時間才需要使用 delayMicroseconds( ) ; 想要搞清楚 delay( ) 和 delayMicroseconds( ) 的差別, 就來看看 delay( ) 和 delayMicroseconds( ) 的源代碼: 先看 delay( ) 這 function: void delay( unsigned long ms ){ unsigned long start = millis(); while (millis() - start < ms) ; // do nothing here in the Loop } // delay( 你會發現, 它就是不斷的調用 millis( ) 看看時間到了沒? 時間沒到就不返回, 一點學問都沒吧 :-) 請注意, 因為 millis( ) 的值是靠 timer0 的中斷幫忙每次加 1 , (每當 TCNT0 寄存器從0加到255又加1回到0之時產生中斷), 如果中斷請求被禁止, 則每次調用 millis( ) 會得到一樣的值 ! 所以才說在 ISR 中斷處理程序內調用 millis( ) 每次值會一樣!! 至於 Arduino 的 delayMicroseconds( ) 在 2010/01/29之後的版本 已經改為不會禁止中斷, 且也改為比較準確, 以下是新版的 delayMicroseconds( ) 的源代碼: void delayMicroseconds( unsigned long us ){ if (--us == 0) return; // 表示 us 是 1 us <<= 2; // us = us*4, 因等下每Loop一次是 0.25us us -= 2; // 修正這句以及前兩句耗掉約 0.5us __asm__ __volatile__ ( "1: sbiw %0,1" "\n\t" // 2 cycles "brne 1b" : "=w" (us) : "0" (us) // 2 cycles ); } // delayMicroseconds( 上面那兩句用 __asm__ __volatile__ ( ) 夾住看起來像無字天書的, 是組合語言寫的小 Loop, 會 Loop 當時 us 的次數(原先 us 乘以 4 又減去 2 的新 us 值), 每 Loop 一次是 0.25us; 還有, 由於進入 delayMicroseconds( ) 與離開 delayMicroseconds( ) 合起來就大約 1.125 us, 因此delayMicroseconds( )會有誤差 ! 實際上 delayMicroSeconds(1)會花掉大約 1.125 + 0.25 = 約 1.4 us; delayMicroSeconds(2)則會花大約 1.125+0.5+0.5=2.125 us; 可是因為 micros( ) 回傳的值是 4 的倍數, 如果你用 micros( )檢查是看不出來的 !! 注意因為考慮準確度的問題, 所以官方網站規定 us 最大隻可以 16383 : (還有會把參數 us 乘以四) http://arduino.cc/en/Reference/DelayMicroseconds 因為 delayMicroseconds( ) 目前版本寫法其實只使用參數的右邊 16 bits, 所以, 如參數 us 大於 16383, 會被看作 delayMicroseconds(us % 16384); 還有, 因為 delayMicroseconds(us) 程序內先把 us 減去 1 才判斷是否為 0, 所以 delayMicroseconds(0); 會 delayMicroseconds(16384); 也就是會延遲大約 16.384 ms == 16384 us (micro seconds) ! 但是, delayMicroseconds(16388); 則會與 delayMicroseconds(4); 幾乎相同 !!! 關於上述提及的 Source code 在這: https://code.google.com/p/arduin ... es/arduino/wiring.c (Sorry Google.com 可能被屏蔽) 這樣應該清楚了吧 !? 更多關於 timer0_overflow_count 的資訊請看: https://ucexperiment.wordpress.c ... no-millis-function/ |