認識與實驗Arduino的睡眠模式

根據Nick Gammon這位澳洲老兄,在Power saving techniques for microprocessors(微處理器省電技術)文章,於Arduino UNO Rev 3控制板執行底下的程式碼:

void setup () {}
void loop () {}

所測量到的消耗電流量:

  • 採用9V電池,接電源插孔供電,約消耗55 mA
  • 用5V電源供電,約消耗46.6 mA

若用最精簡的準系統(barebone)形式,例如,在麵包板上直接用ATmega828處理器和石英震盪器等少數零件組裝的Arduino,僅消耗15.15 mA電流。

因為Arduino控制板上的USB序列埠轉換晶片以及電壓調節元件,都會消耗電力。

畢竟Arduino控制板是「原型開發板」,其用意是提供一個方便、好用的微電腦控制實驗工具。實驗成功之後,如果要長久保留作品或者需要節省電力,最好自製一個精簡的Arduino板,或購買類似Arduino Pro Mini這種沒有其他周邊零件的板子。

Arduino的睡眠模式

Arduino像電腦和手機一樣,也具備睡眠∕休眠∕待機功能。在睡眠狀態下,系統幾乎完全停止運作,只保留基本的偵測功能,因此只消耗少許電力。以電腦為例,在睡眠狀態下,可被鍵盤按鍵或者網路訊息喚醒。

底下的程式一開始就讓微控器進入睡眠狀態,下文將採用名叫”Enerlib”的程式庫,簡化睡眠設定程式:

#include <avr/sleep.h>

void setup () 
{
  // 設定採用“Power-down”睡眠模式
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
  // 啟用睡眠模式
  sleep_enable();
  // 進入睡眠模式
  sleep_cpu ();  
}

void loop () { }

這段程式在UNO R3控制板上,約消耗32.9 mA電流;但是在精簡的「準系統」Arduino板,僅僅消耗0.36mA (360μA)

ATMega328微控器具有六種睡眠模式,底下是依照「省電情況」排列的睡眠模式名稱,以及Enerlib(註:Energy和Library,即:「能源」和「程式庫」的縮寫)程式庫的五道函數指令對照表,排越後面越省電。「消耗電流」欄位指的是ATmega328處理器本身,而非整個控制板。

睡眠模式

Energy指令

中文直譯

消耗電流

Idle

Idle()

閒置

15mA

ADC Noise Reduction

SleepADC()

類比數位轉換器降低雜訊

6.5mA

Power-save

PowerSave()

省電

1.62mA

Standby

Standby()

待機

1.62mA

Extended Standby

延長待機

0.84mA

Power-down

PowerDown()

斷電

0.36mA

微控器內部除了中央處理器(CPU), 還有記憶體、類比數位轉換器、序列通訊…等模組。越省電的模式,仍在運作中的模組就越少。

例如,在”Power-Down”(電源關閉)睡眠模式之下,微控器僅剩下外部中斷看門狗計時器Watchdog Timer, 參閱下文說明)仍持續運作。而在Idle睡眠模式底下,SPI, UART(也就是序列埠)、計時器、類比數位轉換器等,仍持續運作,只有中央處理器和快閃記憶體(Flash)時脈訊號被停止。

時脈訊號就像心跳一樣,一旦停止時脈訊號,相關的元件也隨之暫停。各種睡眠模式的詳細說明,請參閱ATmega328微控器的資料手冊,第39頁,「Power Management and Sleep Modes(電源管理與睡眠模式)」單元。

採用Enerlib程式庫設定睡眠模式

Enerlib程式庫可簡化Arduino睡眠模式的程式設定,請先下載Enerlib程式庫並解壓縮到Arduino的libraries資料夾

Arduino的libraries資料夾

本實驗程式的行為如下:

  1. 啟動時,每隔0.5秒點、滅三次位於第13腳的LED。
  2. LED閃爍完畢後,進入“Power-down(斷電)”睡眠模式。
  3. 當中斷0(第2腳)的訊號改變時,喚醒Arduino,再次閃爍LED三次,接著再進入睡眠模式。

請先把Arduino的數位腳2接高電位(5V或3.3V插座):

Arduino的數位腳2接高電位

反覆閃爍LED的基本程式碼如下:

const byte ledPin = 13;  // LED腳位

void setup() {
  Serial.begin(9600);

  pinMode(ledPin, OUTPUT);
  
  Serial.println("Running...");
}

void loop() {
  digitalWrite(ledPin, !digitalRead(ledPin));
  delay(500);
}

負責閃爍LED的關鍵敘述是這一行:

閃爍LED的關鍵敘述

設定喚醒Arduino的中斷服務常式

修改上一節的程式,建立Energy程式物件,並加入中斷服務常式敘述(廣告一下,中斷服務常式的說明,請參閱《超圖解Arduino互動設計入門》的D-3頁):

設定喚醒Arduino的中斷服務常式

若Arduino處於睡眠狀態,只要中斷0腳位的訊號改變,它就會被喚醒。然而,同一個程式其他敘述,也有可能需要接收中斷0的訊息。為此,Energy提供一個用於判斷Arduino是否處於睡眠狀態的WasSleep()函數,若是,它將傳回true。

底下是修改後的wakeISR中斷處理常式,若Arduino之前處於睡眠狀態,則state變數值將是1,若是在執行過程發生中斷訊號,state值將是2:

wakeISR中斷處理常式

透過state值,主程式將能得知中斷的觸發時機。補充說明,WasSleep()函數只能寫在中斷處理常式裡面。

讓Arduino睡眠的主程式

主程式迴圈如下,它將在閃爍LED三次後進入最省電的「斷電」睡眠模式:

讓Arduino睡眠的主程式

完整的範例程式碼如下:

#include <Enerlib.h>

Energy energy;             // 宣告"Energy"程式物件

const byte swPin = 2;      // 開關腳位
const byte ledPin = 13;    // LED腳位
byte times = 0;            // 記錄執行次數
volatile byte state = 0;   // 暫存執行狀態

void wakeISR() {
   if (energy.WasSleeping()) {
    state = 1;
  } else {
    state = 2;
  }
}

void setup() {
  Serial.begin(9600);
 
  pinMode(ledPin, OUTPUT);
  pinMode(swPin, INPUT);
  digitalWrite(swPin, HIGH);
 
  attachInterrupt(0, wakeISR, CHANGE);  // 附加中斷服務常式
  
  Serial.println("Running...");
}

void loop()
{
  if (state == 1) {
    Serial.println("Was sleeping...");
  } else if (state == 2) {
    Serial.println("Was awake...");
  }
  state = 0;
  
  digitalWrite(ledPin, !digitalRead(ledPin));
  delay(500);
  times ++;
  Serial.println(times);

  if (times > 5) {
    times = 0;
    Serial.println("Go to sleep...");
    energy.PowerDown();
  }
}

編譯並上傳程式到Arduino板之後,開啟「序列埠監控視窗」,它將顯示:

序列埠監控視窗

接著,把連接中斷0的導線接到低電位(GND)

中斷0接到低電位(GND)

Arduino將被喚醒,並再次閃爍LED;筆者在LED閃爍的過程中,反覆將中斷0接高、低電位,「序列埠監控視窗」因而呈現如下的內容:

序列埠監控視窗

看門狗計時器簡介

看門狗計時器(Watchdog Timer, 簡稱WDT)是微控器內部的「當機」監控器,若微控器當掉了,它會自動重新啟動微控器。其運作原理是,看門狗內部有個計時器,微處理器必須每隔一段時間,向看門狗發出一個訊號,重設計時器值。

看門狗計時器喚醒微處理器

若看門狗遲遲沒有收到微處理器的訊號,計時器仍將繼續倒數,直到計時值變成零,它就會認定微處理器已經當掉了,進而重新啟動微處理器。

主程式可設定看門狗的計時器值,最短16ms,最長8s。Donal Morrissey寫了一篇看門狗程式的介紹,以及沈睡8秒之後,切換LED狀態的範例:Sleeping Arduino – Part 5 Wake Up Via The Watchdog Timer

Posts created 467

102 thoughts on “認識與實驗Arduino的睡眠模式

  1. 請問一下,這裡介紹的休眠模式在UNO運作都正常,但使用Mega2560板子時,就會一進入Power Down模式後就叫不醒了,請問有解決方式嗎?
    我單純測是外部中斷功能是OK,但就是Power Down叫不醒,謝謝

    1. hi claude:

      問了Google,除了上文提到的Energy程式庫,還有narcoleptic以及LowPower程式庫,用於處理Arduino的睡眠模式。我尚未測試narcoleptic。

      LowPower 1.3版新增支援ATMega168, ATMega2560, ATMega1280以及ATMega32U4。詳細的睡眠模式與消耗電流對照表,請參閱程式庫作者Rocket Scream的”Lightweight Low Power Arduino Library“這篇貼文。

      下載並解壓縮LowPower程式庫,將程式庫資料夾命名成LowPower,存入Arduino的libraries資料夾,即可測試底下的程式碼:

      #include "LowPower.h"
      
      const byte swPin = 2;      // 開關腳位
      const byte ledPin = 13;    // LED腳位
      byte times = 0;            // 記錄執行次數
      volatile byte state = 0;   // 暫存執行狀態
      
      void wakeISR() {
          state = 1;
      }
      
      void setup() {
        Serial.begin(9600);
       
        pinMode(ledPin, OUTPUT);
        pinMode(swPin, INPUT);
        digitalWrite(swPin, HIGH);
       
        // 附加中斷服務常式,當中斷0為低電位時,喚醒處理器。
        attachInterrupt(0, wakeISR, LOW);
      }
      
      void loop()
      {
        if (state) {
          Serial.println("Running...");
          state = 0;
        }
        
        // 閃爍5次LED之後進入睡眠
        digitalWrite(ledPin, !digitalRead(ledPin));
        delay(500);
        times ++;
        Serial.println(times);
      
        if (times > 5) {
          times = 0;
          
          // 用最低耗電模式進入睡眠
          // SLEEP_FOREVER代表停用「看門狗」,約可節省 4µA 電流消耗。
          // 選擇性的ADC_OFF,代表關閉「類比數位轉換器(ADC)」
          // 選擇性的BOD_OFF,代表關閉「電源供應電壓準位偵測器(BOD)」,約可節約 17µA。
          LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); 
        }
      }
      

      thanks,
      jeffrey

  2. 冒昧請問一下:
    是否可以透過TX、RX腳位來喚醒呢?
    假設平常都沒在使用,所以讓它休眠省電,
    因為是透過Bluetooth來做控制,
    所以這時可以透過Bluetooth喚醒嗎?
    Ths!

    1. hi dick:

      根據ATmega328的規格書第39頁的表9-1,唯有省最少電力的”idle”模式,能被任何I/O腳位喚醒。

      不過,你可以嘗試使用上文的”power-down”睡眠模式,並且在數位0(RxD)和數位2(中斷0)腳位之間,串接一個1K~10K電阻。

      在中斷與序列埠接收腳之間串連一個電阻

      如此,即使處於睡眠模式,每當序列埠發生變化時,也能喚醒Arduino。

      thanks,
      jeffrey

  3. 請問我想要做pro mini 的睡眠模式
    我要怎麼實驗還有接腳怎麼接
    可以大概敘述一下或是有個小範例參考嗎
    謝謝!!!

    1. hi henry:

      pro mini板採用的處理器和UNO板一樣,都是ATmega328系列,所以按照本文連接數位2腳(亦即「中斷0」)即可。

      thanks,
      jeffrey

  4. 請教 :
    最近看到一篇談到 “超過微控制器所能負載之電流量”
    內容提到:
    如果有10組 I/O 針腳或更多在運作,
    並從每個消耗 20mA (例如 10盞 LED 的照明)。
    這樣就已超過了微控制器所能承仔的總供給額定電流,它將會損壞。
    這是真的嗎?
    那LCD 1602 的耗電量大約是多少呢?

    1. 可能會損壞。ATmega32p微控器的技術文件指出,所有I/O腳位的電流輸出同時不應該超過200mA。但是,多數導致控制板燒壞的原因應該是電源短路,例如,連接麵包板電路時,不小心把正電源和接地碰在一起。多數情況下,就LED的例子而言,只是LED的亮度會降低,或者週邊裝置可能會因為電流量不足而導致誤動作。

      電子零件的耗電量,可查閱技術文件,或者搜尋”supply current”關鍵字,例如,1602 LCD本體的消耗電流約1.5mA,模組上的背光LED則約120mA。

      thanks,
      jeffrey

  5. 上次的實驗很ok~~謝謝
    不過我想做內部中斷而不是外部中斷呢?
    就是我今天想要6分鐘跑一次,其他時間在睡眠
    我查了資料說看門狗最長是8秒,我可以讓他8秒醒一次
    然後跑10毫秒再繼續睡眠,等到6分鐘到再跑主程式嗎?
    感謝~~

  6. 有關外部中斷的問題想請教你:
    2 (interrupt 0), 3 (interrupt 1),18 (interrupt 5), 19 (interrupt 4), 20 (interrupt 3),21 (interrupt 2)。
    以上中斷腳位不知道與括號數字的關係為何?
    除了LOW模式會觸發為何其他模式無法觸發?

    自己實際測試過,不管程式宣告幾個中斷都是於第二隻腳位偵測:
    是否還有其他腳位可以做偵測?
    宣告的多個中斷該怎做第一個偵測腳位及第二個偵測腳位的區別?

    問題苦無解答,請大大幫忙,感謝

  7. 補充:
    2(中斷0),我有實際測試過.
    當有送訊號跟無送訊號給第二隻腳位第0支腳位都為5V,
    對於中斷腳位的對應關係無法理解,麻煩大大能為我解答
    非常的感謝!!!!!

    1. hi tommy:

      關於:2 (interrupt 0), 3 (interrupt 1),18 (interrupt 5), 19 (interrupt 4), 20 (interrupt 3),21 (interrupt 2)。

      第一個數字是腳位編號,括號裡面的數字是中斷編號,也就是呼叫attachInterrupt()函式的第一個參數,你可用底下的程式來測試中斷:

      int pin = 13;
      volatile int state = LOW;
      
      void setup()
      {
        pinMode(pin, OUTPUT);
        attachInterrupt(0, blink, CHANGE);
      }
      
      void loop()
      {
        digitalWrite(pin, state);
      }
      
      void blink()
      {
        state = !state;
      }
      

      thanks,
      jeffrey

  8. 非常感謝你及時為我解答
    可以在跟你釐清一個問題,
    有關觸發後進去是個無窮迴圈等待下次觸發才跳出來?還是執行一次就跳出來?

    最後讓我最困擾的問題,我現在外部中斷遇到的問題就是觸發模式部分
    我只有LOW模式會觸發,其他模式都無法觸發,試很多方式都無法
    可以再請你幫我解答嗎?

    在此非常感謝你!!!!

    1. 就像書本D-6頁提到的,中斷函式會在處理完畢後返回主程式;我測試了D-4頁的前四種中斷模式,並沒有遇到問題。

      thanks,
      jeffrey

    2. 觸發的敏感度問題
      我要抓瞬間訊號,為什麻CHANGE,RISING,FALLING此三種模式敏感度低很多,容易沒抓到訊號機台當掉.
      LOW模式卻是敏感度特高能瞬間反應,做下個動作且無抓不到訊號機台當掉的問題.

      是在硬體 軟體上有那些需要加強我沒做注意到的嗎?
      如有的話再請老師分享,非常感謝你!!!!

  9. 請教老師:

    在特殊的狀況下我須要Arduino能自己從新開機,
    是否有命令可以讓Arduino自行從新啟動呢!

    謝謝.

    1. Arduino並沒有重置處理器的指令,但原廠的技術文件第46頁,Reset Sources(重置來源)單元提到四種令ATmega328p處理器重置的情況,最常見的是外部重置,也就是在處理器的第1腳(Arduino板子上的Reset腳)輸入低電位。

      你可以將某個數位腳與Reset腳相連,再從該數位腳輸出低電位試試看。

      另一種是透過看門狗(Watchdog),這或許可行,請自行搜尋Arduino watchdog reset之類的關鍵字看看。

      thanks,
      jeffrey

    2. 剛剛搜尋時,看到3個透過軟體重置Arduino控制板的方案,詳細請參閱這篇貼文

      其中第二個方法是在程式開頭宣告如下的函式:

      void(* resetFunc) (void) = 0;  // 在位址0定義一個重置函式
      

      然後,在需要執行reset的程式區塊內,呼叫重置函式:

      resetFunc(); 
      

      但請注意,這種重置方法並不是真正的reset,因為處理器內部的暫存器(register)仍維持重置之前的紀錄。舉例來說,在程式中將某個腳位設置成OUTPUT,暫存器就會紀錄這個腳位是「輸出」狀態。

      另外,你可以嘗試SoftReset程式庫,根據該程式庫的說明,只要呼叫soft_restart()即可重置Arduino。

      thanks,
      jeffrey

  10. 非常謝謝老師的回答.
    想請問有關老師中斷問題,中斷在任何時候都會觸發
    那麻多個中斷觸發的先後順序寫?
    同時觸發怎辦?

    1. 原廠的技術文件第65頁,列舉了ATmega328p的重置與中斷向量(Interrupt Vector),向量編號數字越低代表越優先處理。

      第14頁的「重置和中斷處理」單元也有提到,ATmega處理器有兩種處理中斷的模式,在第一種處置模式中,若處理中斷程式期間,發生其他中斷事件,中斷旗標將被設置,並且會在當前的中斷處理完畢後,依照優先處理順序其他中斷。

      thanks,
      jeffrey

    2. 非常謝謝老師的解答.
      除了中斷還有其他方式可以做瞬間訊號抓取嗎?

    3. 我覺得可以參考頻率計數器(frequency counter)的軟、硬體設計,以及紅外線遙控接收器的程式庫設計,煩請搜尋相關的關鍵字。

      thanks,
      jeffrey

  11. 老師你好:
    可以發一篇有關AUTO RESET的問題嗎?
    怎樣的情況下ARDUINO Leonardo會自動重啟?
    如何分辨有無開啟serial port與AUTO RESET的狀態?
    求解!萬分感謝!

    1. hi tommy:

      官網”Disabling Auto Reset On Serial Connection“這篇文章,第一段有提到,Leonardo板因為USB序列埠架構跟其他Arduino板不同,不具備自動重置(auto-reset)功能。

      最後一段提到讓Leonardo板自動重置的解決方案:使用1200bps速率開啟序列埠連線,然後關閉序列埠;這也就是最後一段下方,Python程式碼的作用:

      #!/usr/bin/python
      # usage: reset_arduino 
      # where  is typically some /dev/ttyfoobar
      import sys
      import serial
      ser = serial.Serial()
      ser.port=sys.argv[1]
      ser.baudrate=1200
      ser.open(); ser.close()
      

      thanks,
      jeffrey

  12. 非常謝謝老師花時間給我資訊回答我問題.
    但我的ARDUINO Leonardo利用5V串變壓器(轉3V給雷射)串開關做雷射的開關動作,於一段時間會有偶發性的重開狀況,再重開時OUTPUT會送訊號 不送訊號反覆幾次過程會發出異音然後重開機.
    我有自己試試將5V串220電阻(材料有限)到reset接腳就沒有這個重開問題,這與網路的資訊有出路,才上來請問老師幫我解答.

    到底是板子問題?(當把5V串reset的電阻拿掉,重開現象也不見了)
    還是雷射變壓器問題?(雷射變壓器在切換瞬間會有短路現象)
    還是有什麻動作沒有做到造成他自動重啟現象?

    1. 了解,所以問題不在Leonardo控制板。

      如果你採用的雷射模組,只有雷射二極體,那你應該要依據它的順向導通電流來設計電晶體開關電路;若仍舊會影響到Arduino控制板,請加上光耦隔離開關電路。

      thanks,
      jeffrey

  13. 老師高明!
    但我判別是板子瞬間過載造成此現象.(老師是否同意板子過載會有此現象)
    目前使用繼電器的方式解決此現象,跟老師的光藕隔離開關電路解決方法雷同.
    非常謝謝老師無私的分享的解答,感恩~

    1. 嗯,我的想法和你一樣,因為電源系統不穩定而導致Arduino重新啟動。

      thanks,
      jeffrey

  14. 請問老師: 我裝一顆雷射當觸發訊號, 另外裝一顆雷射當檢測訊號;當觸發訊號(H–>L觸發), 開始走迴圈做物品檢測, 如果TURE就PASS, FASLE就INTERLOCK. 但我只要在剛觸發時做檢測, TURE的話就不中斷機台開始製程, 但是開始製程時, 觸發訊號會一直維持在L, 要等到製程完, 要做下一個物品檢測前, 觸發訊號才會回到H. 也就是說在第一個物品檢測PASS後, 我就要停止迴圈, 開始製程, 但ARDUINO的 VOID LOOP()會重覆一直執行, 會在我開始製程中一直偵測觸發訊號(L), 造成誤判..我要如何運用中斷函式來處理?

    1. hi canditor:

      loop()函式就像生物的生命週期,除非死掉,它一直都在運作。你應該是要在它的生命週期之內,妥善安排它的作業時間和流程,例如,加上判斷程式或延時程式過濾不必要的訊號。

      thanks,
      jeffrey

  15. Hi, 原來是趙大, 書寫得真好~~ 我一直當寶典, 也順著書上附錄兜 ATmega328 當作 Arduino.
    會找到這篇是我用 ATmega328+ESP8266+PIR sensors 上傳資料給 ThingSpeak… 其中會了供應 ESP8266 的 3.3V, 我外加了一個 LD1117 Regulator.
    整個系統結果很耗電…
    當沒有上傳時耗電量 ~132mA, 傳輸時 ~ 185mA.

    看來要來去找內部中斷控制的方法, 用您所說的arduino watchdog或avr wdt.h還去搜尋了~~

    真的謝謝您的書, 受益良多~~

    Thanks,
    Patrick

  16. 請問老師: 我想用一塊arduino 接收類比訊號, 再經由max232 (ttl –>rs232) 到另一塊arduino接lcd將類比數值print出來,要怎麼實現?

    1. hi canditor:

      你可以參考書本10-10頁「透過序列埠調整燈光亮度」的範例,加以改寫。如果是單純序列串連兩個Arduino,並不需要用到MAX232,請參閱5-5頁說明。你也可以參閱11-12,採用I2C介面串連兩個Arduino板。

      thanks,
      jeffrey

  17. 老師: 因為要擷取機台的類比訊號, 但機台屬高電壓區, 所以才會想用兩個arduino板子透過RS232光纖耦合器(TRP-C39)將序列資料由光纖輸出再轉回TTL給另一塊Arduino接收並print 數值. 整個流程是: 第一塊Arduino作類比訊號接收處理—>將TTL訊號傳給MAX232轉成RS232訊號—>將此RS232訊號丟給TRP-C39轉成光纖訊號輸出—–> 之後輸入給第二顆(TRP-C39)轉回RS232訊號—–> 再將轉回的RS232訊號丟給第二顆MAX232轉回TTL訊號—-> 再輸入給第二塊Arduino做lcd資料輸出..但是想說先把光纖耦合器(TRP-C39)這一段先拿掉, 單純嚐試先做訊號傳遞實驗Arduino –> max232—>max232 –>Arduino,但 一直失敗, 老師我的觀念是不是有錯誤?

    1. 原來如此,我相信你的規劃沒問題,建議先忽略MAX232部份,先直接串連測試程式。

      thanks,
      jeffrey

  18. 老師您好, 真的要麻煩您解惑了.. (有附上source code)
    我試過用您寫10Kohm電阻那一篇, 做實驗, 結果如下:
    1. 單一 arduino + lcd –> lcd 輸出值ok (輸出變數值採lcd.print (i); )
    2. (T)arduino 串 (R)ARDUINO –> Serial monitor 輸出fail / lcd 輸出 fail (R端ARDUINO 輸出變數採Serial.print (i); & lcd.print(i);)
    3. (T)arduino 串 (R)ARDUINO –> Serial monitor 輸出ok / lcd 輸出 fail (R端ARDUINO 輸出變數採Serial.write (i); & lcd.print(i);)
    4. (T)arduino 串 (R)ARDUINO –> Serial monitor 輸出ok / lcd 雖輸出ok但是數字會在同一格重疊覆蓋且數字輸出完畢後會再有兩個亂碼覆蓋 (R端ARDUINO 輸出變數採Serial.write (i); & lcd.write(i);)
    我是不是哪裡有寫錯??? 我的原碼如下:
    傳送端:
    const int portpin = A0;
    int val;
    void setup() {
    Serial.begin(9600); // put your setup code here, to run once:
    }

    void loop() {
    val=analogRead(portpin); // put your main code here, to run repeatedly:
    Serial.println(val);
    delay(2500);
    }
    ======================================================================================
    接收端:
    #include // Arduino IDE 內建
    #include
    // addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
    LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
    int i=0;
    void setup() {
    Serial.begin(9600);
    lcd.begin(16, 2);
    // 閃爍三次
    for(int i = 0; i 0){
    i=Serial.read();
    Serial.write(i);
    lcd.setCursor(10, 1);
    lcd.write(i);
    delay(400);
    }
    }

    感覺好像接收端一定要用serial.write (); 輸出才行???? 但lcd方面卻沒辦法輸出整數串, 例如1023, 他不會show 1023 , 他會先show 1 –>之後被0覆蓋 –> 之後被2覆蓋 –> 之後被3覆蓋 –> 之後被亂碼覆蓋兩次, 難後又開始一樣的循環… 單一顆arduino 輸出到 lcd 都很正常, 也不需要用lcd.write(); , lcd.print()就可搞定…到底哪邊出問題??? 老師幫幫我, 不難沒辦法結案了!! 拜託了!!

    1. 從你的程式邏輯看來,問題不是出在serial.write ();敘述。

      如果要序列傳送一連串字元,傳送端通常會在字串的結尾加上”\n”字元,明確表示「這段字串資料結束了」。

      而接收端的程式可以改寫成「收到完整的字串」之後,一次送給LCD顯示。而非收到一個字元,就顯示一個字元…雖然這樣也行,但是你的LCD顯示程式必須每次把游標移到下個字元位置,才不會覆蓋前一個字。

      接收端的程式可參考10-13頁,例如:

      void loop() {
        char data[11];  // 假設字串長度為10(再加上結尾的\0)
         :
        中間省略
         :
      
        if (Serial.available()) {
          while ((char = Serial.read()) != '\n')  {
              :
            讀取字元,直到收到'\n'為止…
              :
          }
        }
       
      }
      

      thanks,
      jeffrey

  19. 老師, 不好意思,
    1. 類比收到的應該是數字格式不是字元, 那也要加 /’n’字元,表示”字元資料結束”嗎?
    2. 我用獨立一片Arduino直接接收類比, 採lcd.print() 輸出到給lcd, 沒加 /’n’字元, 傳輸數值都正常. 為何兩片ARDUINO序列傳遞數值資料, 再由LCD輸出, 就不行???
    3. 獨立一片Arduino傳遞數值資料給LCD , 和兩片ARDUINO傳遞數值資料給LCD, 不都是序列傳輸嗎? 會有不同嗎?
    麻煩老師解或….感謝!!!

    1. analogRead()傳回的是整數格式資料,Serial和LCD物件的print()函式可接受數字類型,但是它傳出的數據是字串格式。

      在資料後面加上”\n”字元,跟資料是數字或字串無關,純粹只是為了讓接收端知道一段資料結束。假設發送端的Arduino傳出3筆資料,分別是128, 25和46,若每一筆資料都沒有結尾(或者說「分隔」),那接收端將收到:1282546。

      thanks,
      jeffrey

  20. 老師:
    感謝您, 我按照你說的10-10頁, 字串轉換, 並加”/N”來判斷資料傳遞結束, 果然成功了~~謝謝!! 但我還是對PRINT();有一點CONFUSE, …..
    1. 老師說的”analogRead()傳回的是整數格式資料, Serial和LCD物件的print()函式可接受數字類型,但是它傳出的數據是字串格式”, 意思是如果類比傳回是1024整數格式資料, Serial和LCD 輸出的是’1′ , ‘0’ , ‘2’ , ‘4’ 的ASCII字元所組成的字串嗎? 還是傳給Serial和LCD就已經轉成ASCII字元所組成的字串?
    2. 書上說Serial.print(‘1’) 輸出給人看的也是1, 但他其實是ACSII字元1 , 也就是DEC 49, 那我試過Serial.print(49), 他輸出給人看的也是49, 這個輸出49是ASCII字元的’4’和’9’所組成的嗎? 還是ASCII DEC的49?還是我寫入的Serial.print(49)的這個49才是ASCII字元的’4’和’9’所組成, 其實代表ASCII DEC的52 和 57所組成?

    1. 是的,兩者都是字串。就像10-10頁的例子,送出168,另一方接收到的是’1′, ‘6’, ‘8’。

      thanks,
      jeffrey

  21. 老師我想請問您,我是大學的專題生。
    我們是用Arduino Yun mini 來做實驗 但我測試睡眠程式成功後
    下次測試卻說無法上傳到板子上? 請問是神麼問題呢?

    1. hi leo:

      在之前的Arduino MEGA2560板子上,有人遇過「字串中包含連續三個驚嘆號」會導致程式無法上傳的bug。

      至於Arduino Yun,請改用arduino.org網站的IDE測試看看(因為Yun是arduino.org的產品),或者,根據這個老外的經驗,把Yun回覆到原廠狀態,再升級韌體試試看。

      good luck!
      jeffrey

  22. 老師我想請問您,我想要用arduino 計時1分鐘 用中斷通知CPU 的想法
    可以用這篇文章的中斷當參考嗎? 大致上該如何撰寫?

  23. 請問我使用Arduino UNO R3 IDE V1.6.7, 在上傳時找不到avr/sleep.h, 而出現錯誤訊息[ error: #error “No SLEEP mode defined for this device.”], 是什麼原因? 是因為我沒有安裝avr/sleep.h函式庫嗎? Thnkas

    1. 請問你的控制板有選擇正確嗎?我剛剛在IDE 1.6.5上測試編譯Enerlib程式庫的範例,並沒有問題。

      thanks,
      jeffrey

  24. 老師您好我是高中專題生
    我想請問您有無辦法使用MQ系列酒精感測器
    使Arduino休眠但唯有此感測器正常運作
    若偵測不到酒精,便從睡眠狀態喚醒?

    1. hi neverleave:

      你的問題應該是:透過感測器的訊號變化,觸發Arduino的中斷腳位,喚醒Arduino。所以,只要設計好電路,都可以用這種方式喚醒Arduino。

      thanks,
      jeffrey

  25. 老師不好意思打擾了
    我想請問如果進入了斷電或是待機狀態
    計時程式庫 是否還會繼續運作
    我自己測試是沒有動了
    請問是正常的嗎??
    謝謝老師

    1. hi juice:

      ATmega微控器內部有三個計時器,其中的Timer0和Timer1可在閒置(idle)模式運作,Timer2可在省電(Power-save)模式運作。在待機(Standby)和斷電(Power-down)模式之下,只能用看門狗計時器(Watchdog Timer)。因此,在睡眠模式下,某些計時程式庫無法運作是正常的。

      或者,你可以採用外部計時晶片(如:DS1307),定時喚醒ATmega微控器。

      thanks,
      jeffrey

  26. 老師抱歉打擾了
    請問遇到的狀況是我為我的arduino pro mini版寫入休眠的程式
    但是之後要寫新的程式時,卻發現都進不去

    (我為pro mini寫入的工具是用uno版reset腳位接地 TX、RX均互接)
    請問老師要怎麼解決
    謝謝老師 不好意思麻煩了!
    謝謝

    1. 請嘗試底下的接線,先用一字起子撬下Uno板的ATmega328微控器,再透過它上傳程式碼:

      使用Arduino Uno燒錄Mini板的程式

      上圖的小板是Arduino Mini,實際的連接腳位也許和你的控制板不同,請參閱控制板的技術文件或說明書。

      thanks,
      jeffrey

  27. 老師 您好:
    我用了光敏電阻來設計了一個很基本的 光感應燈的電路,就是利用類比輸入腳去抓光敏電阻上的類比電壓,
    控制LED的明滅,但我想更進階,使用ATtiny85來做成一個感應燈,關燈之後,只亮30秒,自動關閉,並且
    使用鋰電池供電,上述的功能我都已經完成…
    但我發現ATtiny85其實也有些耗電,所以我查到可以使用睡眠模式sleep,目前預設想使用Power_down模式,
    然後使用外部中斷睡眠的方式來做,當然這個”外部”訊號,就是想利用光敏電阻上的類比訊號來做,
    這樣亮完30秒之後,讓ATtiny85進入睡眠,直到下一次開燈,光敏電阻阻值產生變化到達一定的數值,
    然後中斷睡眠模式,可是中斷指令detachInterrupt好像只能使用數位訊號(HIGH;LOW;CHANGE;RISING
    ;FALLING),不知道有沒有可能用別種指令能夠達成,或者不然我應該嘗試使用光電晶體或是光二極體來達成
    數位訊號並取代光敏電阻的功能?
    因為拜讀您的超圖解書籍,似乎沒有比較深入提到關於Sleep的部分,所以在此請教,還請見諒,謝謝。

    1. hi kk:

      偵測中斷的腳位只接受數位訊號,所以你需要自行設計電路,讓類比輸出達到某個準位時,發出脈衝訊號引發中斷。

      thanks,
      jeffrey

  28. Hello 老師,不知道這個時間回覆文章您還看不看的到
    我的問題和1F一樣是想在Mega2560上實作睡眠
    但一樣的library能在uno上執行但無法在mega2560上編譯成功
    沒辦法測試3F您提供的程式碼

  29. 另外我想請問老師的是
    如果我的目的是想透過DS1307的RTC並藉由I2C的方式
    讓Mega2560能夠在”特定”的時間內醒來工作是可行的嗎?

    1. 我尚未在Mega2560測試過,若無法順利執行,你可能要換用其他程式庫。

      透過DS1307定時喚醒Arduino當然可行;DS1307會在設定時間到時,自動發送一個脈衝訊號給Arduino中斷腳,進而喚醒Arduino。你可以參考這個老外編寫的範例程式

      thanks,
      jeffrey

  30. 感謝老師指點
    另外想請問的是在睡眠期間
    若有部分腳位是根據Sensor的條件來輸出high/low
    當進入睡眠時使否會影響到腳位的輸出,如high因睡眠而變low等

    1. 這個問題你只要在Arduino上執行程式,實驗一下就知道了,留給你自行探索~

      have fun!
      jeffrey

  31. 老師您好,若我是想讓睡眠成為常態
    等到RTC回傳的數字符合條件時再喚醒
    這時候”Change”就無法發揮作用了吧?
    那我需要用哪種方式來”喚醒”呢

    1. 從你的需求看來,你需要採用具備「鬧鐘」功能的即時鐘。DS1307只具備時鐘功能,而有些即時鐘晶片,像DS3234,具備一天兩組鬧鐘功能。如此,只要先透過程式設置好鬧鐘,就能在指定時間喚醒Arduino了。

      thanks,
      jeffrey

  32. 老師您好,但這樣的鬧鐘是預先設定為固定參數
    是否就無法實現”每隔2分鐘”輸出一次方波來喚醒呢?

    1. 這樣的話,你可以用其他計時電路,例如555這個IC來製作定時脈衝產生器,請搜尋「555定時器」之類的關鍵字,即可找到相關電路。

      thanks,
      jeffrey

  33. 老師不好意思再次打擾
    那假如是以目前power save 或power down的狀態下
    時間確定是可以被keep住 透過外部喚醒醒來後也能正常讀
    有辦法已timer的方式來達到 不用外部中斷的方式喚醒嗎?

    1. 請參閱Donal Morrissey撰寫的這篇”Wake Up Via The Watchdog Timer“文章,透過看門狗最長每8秒喚醒Arduino。

      基本上,你的需求就像讓人睡眠,節省體力,卻要求腦袋保持清醒來數羊,有點強人所難。

      thanks,
      jeffrey

  34. 感謝老師指導!最後還是以DS3232用Alarm的方式外部觸發喚醒
    目前看來是有按照設定的時間周期的睡眠及喚醒
    但不太知道要如何測量實際耗電量是否有下降
    這部分能否提供方向呢? 板子是UNO
    再次感謝老師!

    1. 請參閱第二章,使用三用電錶測量電流的說明(2-25頁),用串連方式測量電源輸入端的電流。

      thanks,
      jeffrey

  35. 老師您好:
    我想請問一下,
    如果Arduino有接WIFI晶片(esp8266或winc1500之類的)當server時,
    若Arduino進入睡眠狀態時WIFI晶片是否就不能接收並處理外來的連線?

    我的想法是想讓平時Arduino保持睡眠,
    當有連線進來時才喚醒,但不知道這樣是不是可行。

    謝謝老師!

    1. Idle睡眠模式(POWER_MODE_IDLE)有支援透過序列埠喚醒,ESP8266與Arduino的連線透過序列埠,所以這個方案可行。

      thanks,
      jeffrey

  36. 老師不好意思想再問另一個問題,
    是關於1樓對mega2560睡眠的問題。
    我試了您的程式碼,可是在compile就過不了

    我下載的LowPower library是1.6版,IDE是1.8.1版
    我的IDE中開發板選擇 : Arduino/Genuino Mega or Mega2560時,
    compile會出現error: ‘power_usart3_enable’ was not declared in this scope
    但開發板換成Arduino/Genuino Uno卻可以編譯成功,
    想請問有辦法解決mega2560的問題嗎?
    謝謝老師。

    1. 剛剛看了一下LowPower程式庫的idleWakePeriodic範例程式,裡面有提到不同處理器要使用不同的參數初始化。例如:

       // ATmega2560
       LowPower.idle(SLEEP_8S, ADC_OFF, TIMER5_OFF, TIMER4_OFF, TIMER3_OFF, 
       		  TIMER2_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART3_OFF, 
       		  USART2_OFF, USART1_OFF, USART0_OFF, TWI_OFF);
      

      thanks,
      jeffrey

  37. 老師想請教你
    如果我的A程式是不停地在計算 ,但需要每隔1小時叫出B程式,watch dog也可以適用嗎

  38. 老師您好
    我想請問使用RC522可以喚醒Arduino嗎?
    是跟上面一樣在pin0和pin2之間串接一個電阻?

    1. 不,RC522上頭有個IRQ中斷接腳,理論上可以透過它來中斷睡眠,可是你也許需要修改程式庫。

      thanks,
      jeffrey

  39. 不好意思 我剛接觸arduino
    不太懂他的中斷是怎麼樣
    如果不用IRQ還有其他方法嗎?
    像是用ldle或是接電組之類的

    1. 如果是Idle模式的話,那應該沒問題(不用接電阻),請自行實驗看看。

      thanks,
      jeffrey

  40. 老師不好意思,如果我要改用botton的方式喚醒他,請問要改什麼嗎?
    我剛入門,改了滿多東西都成功不了…

    1. 睡眠模式並不是入門主題…請參閱4-11頁的按鍵(button)接法,取代導線即可。

      thanks,
      jeffrey

  41. 老師您好
    想請問Atmega328p在power-down的模式下
    是否還能讓他使用看門狗定時做出外部觸發呢
    我想盡可能省電又想週期的喚醒周邊設備

  42. 感謝老師分享
    想請教是否能使用Enerlib進入休眠
    再搭配看門狗定時發送外部中斷來喚醒自己呢
    目的是想週期的定期換醒自己

  43. 老師您好,
    想請教一下,使用webdurino使用power-down的時候是否還能維持有網路連線?
    因為我在架設的webdurino太耗電,想研究省電模式,但如果power-down會關閉網路電源,那我就不能遠端透過中斷喚醒了@@
    謝謝老師

  44. 請問Arduino 101 是否也可以如此操作呢?
    另外,進入sleep mode 後我該怎麼確認他真的進入睡眠呢?

    1. 我手邊沒有Arduino 101這個板子,再麻煩自行嘗試。確認睡眠最直接的方法是測量、比較控制板在一般運作和睡眠狀態的消耗電流。

      thanks,
      jeffrey

  45. 老師,請教您,我使用LORA模組,打算使用TX/RX來喚醒。
    因此我將板子的第二隻腳(RX)接10K電阻,並同時與LORA的TX串接

    打算讓LORA模組接收到訊號後喚醒MCU
    運行底下程式後發現,第一次可以成功,但進入睡眠後想再叫,就沒有反應了。

    #include
    #include
    #define ledPin 6
    String incomingString;
    SoftwareSerial LoRa(2, 3); //Rx,Tx
    unsigned long lastTransmission;
    const int interval = 1000;
    void setup() {
    LoRa.println(\”AT+RESET\”);
    delay(3000);
    Serial.begin(9600);
    delay(100);
    pinMode(2, INPUT);
    delay(100);
    pinMode(3, OUTPUT);
    delay(100);
    pinMode(ledPin, OUTPUT);
    LoRa.begin(9600);
    delay(100);
    LoRa.println(\”AT+IPR=9600\”);
    delay(100);
    LoRa.println(\”AT+NETWORKID=5\”);
    delay(100);
    LoRa.println(\”AT+MODE=0\”); //1:sleep mode,0:work mode
    delay(100);
    LoRa.println(\”AT+PARAMETER=10,7,1,7\”);
    delay(100);
    LoRa.println(\”AT+ADDRESS=30\”);
    delay(100);
    LoRa.println(\”AT+NETWORKID?\”);
    delay(100);
    LoRa.println(\”AT+ADDRESS?\”);
    delay(100);
    LoRa.println(\”AT+IPR?\”);
    delay(100);
    LoRa.println(\”AT+BAND?\”);
    delay(3000);
    LoRa.println(\”AT+PARAMETER?\”);
    while (LoRa.available())
    Serial.print(LoRa.readString());
    attachInterrupt(5,wakeup,CHANGE);
    }

    void wakeup(){
    PM.wakeFromDoze();
    LoRa.println(\”AT+SEND=20,6,Hello!\”);
    delay(100);
    digitalWrite(ledPin, HIGH);
    delay(1000);
    digitalWrite(ledPin, LOW);
    lastTransmission = millis();
    loop();
    }

    void loop() {
    if (LoRa.available())
    Serial.print(LoRa.readString());
    if (Serial.available())
    LoRa.print(Serial.readString());

    if (LoRa.available()) {
    incomingString = LoRa.readString();
    if (incomingString.indexOf(\”Hi\”) > 0) {
    if (millis() > lastTransmission + interval) {
    LoRa.println(\”AT+SEND=20,6,Hello!\”);
    delay(100);
    digitalWrite(ledPin, HIGH);
    delay(1000);
    digitalWrite(ledPin, LOW);
    lastTransmission = millis();
    }
    }
    }
    PM.sleep();
    }

  46. 老師好,我使用avr/sleep.h和avr/wdt.h讓arduino-nano進行休眠(PowerDown),預期每10分鐘喚醒一次(8秒*75),但是開始休眠的時間一般是通電或是上傳完成的時間,該如何讓開始休眠的時間可以依照RTC(DS3231)的時間執行?例如在10:00開始休眠,在10:10、10:20、10:30會喚醒。
    另外想要搭配TimeAlarms.h的鬧鐘功能進行排程工作,是不是必需在非休眠時才有辦法執行?例如鬧鐘設定10:05,但是10:10才會喚醒。
    剛入手老師的超圖解第三版,才發現已經有第四版了@@

    1. DS3231可設定兩組鬧鐘,假設你讓Arduino每天10:00開始休眠,然後每隔10分鐘喚醒一次,直到23:00之後持續動作。

      你可以設定一個鬧鐘(alarm 1)在10:00動作,並且令它啟動另一組每10鐘喚醒一次的鬧鐘(alarm 2)。每喚醒一次,Arduino就向DS3231讀取時間,直到23:00,停止鬧鐘 alarm 2。

      thanks,
      jeffrey

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top