【Arduino】sleepを利用して電力消費を抑えてみた


概要

Wikipediaによるとスリープとは・・

コンピュータにおけるスリープとは、省電力の待機電源モードのことである。通常の起動状態と比べると大きく電力を節約できる上に、起動中のプログラムなどを終了させることなく、素早いリブートが可能となるといった利点がある。多くの場合、電源LEDが点滅することによりスリープ状態であることが分かる。スリープ ~ WIKIPEDIA

Arduinoで開発可能なAVRマイコンでもスリープ機能を使用することができます。Arduinoではライブラリを利用することで簡単にスリープを実現できます。

Arduinoのスリープ機能を使って消費電力を抑える方法を検討してみました。

 

 

 


環境

 

 

 


Arduinoのスリープモード

 

Arduinoでスリープを利用する方法は、リファレンスに記載されています。
リファレンスによると、いくつかのスリープモードを指定でき、スリープモードにより機能や復帰条件が異なるようです。

 

AVRマイコンのデータシートを参照すると、より詳細なスリープの機能と復帰条件が記載されていました。

 

スリープモードの機能、復帰条件、効果をまとめると下表のようになります。

モード 説明 省電力効果

※数値が大きいほど効果大

SLEEP_MODE_PWR_IDLE
アイドル状態
システムクロックは停止しますが、内臓タイマーや外部割込み、シリアルポートなどの機能は動作します。復帰方法は外部割込み、ウォッチドッグタイマー、ADCの入力変化、リセットピンによる復帰が可能
SLEEP_MODE_PWR_ADC AD変換ノイズ低減用に利用されます(ADC対応のAVRでないと利用不可)
SLEEP_MODE_PWR_DOWN
パワーダウンモード
最低限の動作のみのもっとも消費電力が少なくなります。
外部割込み、ウォッチドッグタイマー、リセットピンによる復帰が可能。
SLEEP_MODE_PWR_SAVE
パワーセーブモード
タイマー用の外部発信器は動作しているので、外部割込み、ウォッチドッグタイマー、リセットピンによる復帰が可能。
SLEEP_MODE_PWR_STANDBY
スタンバイモード
メインクロックが動作している以外、パワーダウンモードと同じ。

 

 

 


ウォッチドックタイマを使用したスリープと復帰

最も省電力効果の高いSLEEP_MODE_PWR_DOWNモードをARDUINO内部のWATCHDOGタイマだけで使用する方法です。

ラジオペンチさんがブログで公開しているdelayWDT関数は、SLEEP_MODE_PWR_DOWNモードに入る直前でADCを停止、復帰直後にADC開始を行い消費電力を劇的に抑えています。ただし、このdelayWDT関数は、WATCHDOGタイマは最長8秒のため、最大でも8秒で復帰します。

8秒より長い時間スリープさせるために、指定時間に到達するまで何度もスリープを繰り返すようにします。

 

 


// 3GIM(V2) sample sketch -- httpGET
#include  
#include <avr/sleep.h>
#include <avr/wdt.h>
 
//スリープ・ウォッチドッグ用
int wakePin = 2; //割り込み番号指定(実際は0→1ピンを指定/1→2ピンを指定)
bool initFlg = true;
volatile int wdt_cycle = 0; 
volatile int wdt_counter = 0; 
  
/*****************************************/
/*
 * ウォッチドッグ処理の参考元:2014/11/17 ラジオペンチさん http://radiopench.blog96.fc2.com/
 */
/*****************************************/
void delayWDT_setup(unsigned int ii) { // ウォッチドッグタイマーをセット。
 // 引数はWDTCSRにセットするWDP0-WDP3の値。設定値と動作時間は概略下記
 // 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
 // 6=1sec, 7=2sec, 8=4sec, 9=8sec
 byte bb;
 if (ii > 9 ){ // 変な値を排除
 ii = 9;
 }
 bb =ii & 7; // 下位3ビットをbbに
 if (ii > 7){ // 7以上(7.8,9)なら
 bb |= (1 << 5); // bbの5ビット目(WDP3)を1にする
 }
 bb |= ( 1 << WDCE );
 
 MCUSR &= ~(1 << WDRF); // MCU Status Reg. Watchdog Reset Flag ->0
 // start timed sequence
 WDTCSR |= (1 << WDCE) | (1<<WDE); // ウォッチドッグ変更許可(WDCEは4サイクルで自動リセット)
 // set new watchdog timeout value
 WDTCSR = bb; // 制御レジスタを設定
 WDTCSR |= _BV(WDIE);
} 
 
ISR(WDT_vect) { // WDTがタイムアップした時に実行される処理
 // wdt_cycle++; // 必要ならコメントアウトを外す
}
 
 
void delayWDT(unsigned long t) { // パワーダウンモードでdelayを実行
 Serial.println("Goodnight!"); //動作中の表示
 delay(100);
 
 delayWDT_setup(t); // ウォッチドッグタイマー割り込み条件設定
 ADCSRA &= ~(1 << ADEN); // ADENビットをクリアしてADCを停止(120μA節約)
 set_sleep_mode(SLEEP_MODE_PWR_DOWN); // パワーダウンモード
 sleep_enable();
 
 sleep_mode(); // ここでスリープに入る
 
 sleep_disable(); // WDTがタイムアップでここから動作再開 
 ADCSRA |= (1 << ADEN); // ADCの電源をON (|=が!=になっていたバグを修正2014/11/17)
 
}

 
 
 
void setup()
{
 pinMode(wakePin, INPUT);
}
 
void loop()
{
 wdt_counter++;
 //初回を除き、カウンターが10以下なら再度スリープ状態へ
 if(!initFlg && wdt_counter < 10 ){ delayWDT(9); // 8sec return; } if(wdt_counter >= 10 ){
 wdt_counter = 0;
 }
 initFlg = false;
 
 //************************************
 //ここに処理を書く
 //************************************
 }
 // END

 

 


RTCモジュールと外部割込みを使用したスリープと復帰

 

ウォッチドックタイマを使用して数秒ごとにスリープモードから復帰していてはせっかくのスリープ機能の効果が薄れます。そこで、今回は外部のタイマーを用意して、そのタイマーが定期的に(毎秒、毎分、毎時間、毎日、毎週等の単位)Arduinoの外部割込みを発生させることで、Arduinoをスリープから復帰させることにします。

 

タイマーにはネットで購入したHiLetgo製 ZS-042というリアルタイムモジュールを使用します。このモジュールはリアルタイムクロック(RTC)のDS3231とEEPROMのAT24C32が搭載されていて、アラーム機能により設定した間隔で信号を送信します。

HiLetgo製 ZS-042 リアルタイムクロック

このモジュールはリアルタイムクロック(RTC)のDS3231とEEPROMのAT24C32が搭載されていて、アラーム機能により設定した間隔(毎秒、毎分、毎時間、毎日、毎週等の単位)で出力可能です。
時計機能以外にも温度センサーや設定可能な方形波出力信号出力、指定日時に割り込みで起こしてくれる2つのアラーム機能、32KbitのEEPROMが付いているなど単に時計機能だけでなく便利な機能も搭載されています。
通信方式:I2C

※ライブラリは下記ライブラリを使用します。
https://github.com/JChristensen/DS3232RTC

 

Arduino Unoとこのリアルタイムクロック(RTC)モジュールは次のように接続します。

ArduinoUnoとリアルタイムクロックの接続

I2Cで通信を行うため、Vcc/Gnd/SCL/SDAを接続します。

下記サンプルコードは「もしくはねふぁの覚え書き」の記事「ArduinoでDS3231 RTCモジュールを使った定期処理」から引用させていただいたものです。

※毎分00秒にRTCから割り込みを発生させ、シリアルポートに時刻を出力後電力セーブモードに入るコード

//Reference :
//https://github.com/JChristensen/DS3232RTC
//http://forum.arduino.cc/index.php?topic=109062.0
//http://donalmorrissey.blogspot.jp/2010/04/sleeping-arduino-part-5-wake-up-via.html
//http://easylabo.com/2015/04/arduino/8357/
 
#include     //http://github.com/JChristensen/DS3232RTC
#include          //http://www.arduino.cc/playground/Code/Time
#include          //http://arduino.cc/en/Reference/Wire (included with Arduino IDE)
#include <avr/sleep.h>
#include <avr/power.h>
 
bool rtcint = false;
 
void setup(void)
{
  //clear all
  RTC.alarmInterrupt(1, false);
  RTC.alarmInterrupt(2, false);
  RTC.oscStopped(true);
 
  //sync time with RTC module
  Serial.begin(9600);
  setSyncProvider(RTC.get);   // the function to get the time from the RTC
  if (timeStatus() != timeSet)
    Serial.println("Unable to sync with the RTC");
  else
    Serial.println("RTC has set the system time");
  Serial.flush();
 
  //show current time, sync with 0 second
  digitalClockDisplay();
  synctozero();
 
  //set alarm to fire every minute
  RTC.alarm(2);
  attachInterrupt(4, alcall, FALLING);
  RTC.setAlarm(ALM2_EVERY_MINUTE , 0, 0, 0);
  RTC.alarmInterrupt(2, true);
  digitalClockDisplay();
}
 
void loop(void)
{
  //process clock display and clear interrupt flag as needed
  if (rtcint) {
    rtcint = false;
    digitalClockDisplay();
    RTC.alarm(2);
  }
 
  //go to power save mode
  enterSleep();
}
 
void synctozero() {
  //wait until second reaches 0
  while (second() != 0) {
    delay(100);
  }
}
 
void alcall() {
  //per minute interrupt call
  rtcint = true;
}
 
void digitalClockDisplay(void)
{
  // digital clock display of the time
  setSyncProvider(RTC.get); //sync time with RTC
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(' ');
  Serial.print(day());
  Serial.print(' ');
  Serial.print(month());
  Serial.print(' ');
  Serial.print(year());
  Serial.println();
  Serial.flush();
}
 
void printDigits(int digits)
{
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(':');
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}
 
void enterSleep(void)
{
  //enter sleep mode to save power
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_mode();
 
  sleep_disable();
  power_all_enable();
}

 

 

 


まとめ

 

 

 

 


参考