トップ 差分 一覧 ソース 検索 ヘルプ ログイン

Sleep

SLEEP命令の使い方

 sleep命令の基本-アイドルモード

たとえば、以下のようなプログラムについて、

// 1MHzクロックで動作。Timer1 プリスケーラ=1/8、8μs/count
// Timer1オーバーフロー割り込み周期 0.524288sec
// 割り込みのたび変数count++して、(count&15)==0 の時だけLED点灯
// →8.4秒に一度、0.5秒だけLEDを点灯させる 
#include <avr/io.h>
#include <avr/interrupt.h>

uint8_t count;
ISR(TIMER1_OVF_vect )
{
   count++;
   PORTB = (count&15)?1:0; //LEDはPB0に接続
}

int main(void)
{
   TCCR1B = 8; //prescaler 1/8
   TIMSK = _BV(TOIE1); //Timer1 Overflow Interrupt
   sei();
   for(;;);
}

これをたとえばATmega8の1MHz内蔵RC発振、3V動作で動作させるとします。消費電流は、LED点灯に15mA流すとして、

mega8自体の消費電流=1MHz動作なら2.6mA
LEDの消費電流=15mA、ただし1/16の時間だけ点灯するので、平均では0.94mA

となり、意外とAVRの消費電流が馬鹿にならないことになります。

このプログラムの動作を考えると、初期設定が終わった後、8μsecに一度Timer0が動かされ、0.5秒に一度割り込みルーチンが動かされる一方で、for(;;);ループでは2μsec毎にRJMP命令が実行されています。何とも無駄っぽいです。そこで、AVRには必要時以外寝る=sleepする機能が用意されています。上記のプログラムを下記のように変更すると、

#include <avr/io.h>
#include <avr/interrupt.h>

#include <avr/sleep.h>
uint8_t count;
ISR(TIMER1_OVF_vect )
{
   count++;
   PORTB = (count&15)?1:0; //LEDはPB0に接続
}

int main(void)
{
   TCCR1B = 8; //prescaler 1/8
   TIMSK = _BV(TOIE1); //Timer1 Overflow Interrupt
   set_sleep_mode(SLEEP_MODE_IDLE);
   sei();
   for(;;)
   {
       sleep_mode();
   }
}

このプログラムは、sleep_mode();でスリープ命令を実行するとアイドルモードという省エネモードに突入します。この間Timer1は動き続けますがプログラムはストップします。そしてTimer1オーバーフロー割り込みがかかるとプログラムは動きだし割り込みルーチンを実行します。割り込みが終わりメインルーチンに戻るとfor(;;)ループに従い再びsleep命令が実行されまた眠りに陥ります。

消費電力はどうなるでしょうか?

2313自体の消費電流  平均 0.3mA
  sleep中(アイドルモード)0.3mA
  割込動作中1.8mA*全体の0.01%以下≒平均では0.18μA
LEDの消費電流   平均 0.94mA

となり、かなりましになります。

 パワーダウンモード

スリープにも段階があります。ちょっとうたた寝しておいて起こされればすぐ仕事ができるモードか、ぐっすり熟睡してたたき起こされない限り寝続けるかの違いと思えばいいでしょう。熟睡しているモードの方がより省エネですが、ちょっとしたこと(割り込み)では起きてくれない場合もあります。

スリープモードには、もう1つ使い道があります。スイッチ入力の監視以外は完全に止めてしまい、実質スイッチオフと同じ状態にしてしまうことです。これにより電源スイッチが不要になり、操作スイッチを押せば電源が入るようなものが作れます。

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
uint8_t count;

EMPTY_INTERRUPT(INT0_vect);
//ただSleepから起こしてもらうだけのための割込ルーチン。
//SIGNAL(SIG_INTERRUPT0) {}よりサイズが小さい。

ISR(TIMER1_OVF_vect )
{
   count++;
   PORTB = ((count&15)==1) ? 0 : 1; //LEDはPB0に接続.
   if (count==0) {
       //16回点灯してcount==0に戻ったらパワーダウンモード、
       //それ以外ならアイドルモード
       PORTB = 0xFF;
       set_sleep_mode(SLEEP_MODE_PWR_DOWN);
   }
   else
   {
       set_sleep_mode(SLEEP_MODE_IDLE);
   }
}

int main(void)
{
   DDRB = 0xFF;
   PORTB= 0xFF;
   DDRD = 0b11111011; //PD3 Input
   PORTD= 0b11111111; //PD3 Pullup
   TCCR1B = 8; //prescaler 1/8
   GICR = _BV(INT0);
   TIMSK = _BV(TOIE1); //Timer1 Overflow Interrupt
   set_sleep_mode(SLEEP_PWR_DOWN); //パワーダウンモード+INT0はLowレベル割込
   sei();
   for(;;)
   {
       sleep_mode();
   }
}

プルアップされたINT0(PD3)ピンとGND間にスイッチをつけておけば、スイッチを押すと16回LEDが点滅した後パワーダウンモードに入ります。このときの消費電流は0.3μAと見積もられます。15mAのLED点灯が8時間できる電池なら、パワーダウンのままでも46年は持つ計算になります。(実際には電池が自己放電しちゃうけどね)

 スリープのモード

たとえばmega8には以下のようなものがあります。

SM2-0 種別 <avr/sleep.h>での定義 スリープ解除に使える割り込み 消費電力*1
  (通常動作)     1.8mA
000 アイドル SLEEP_MODE_IDLE 最低限割込+Timer2/EEPROM/ADC/Timer/INT割込 0.3mA
001 AD変換ノイズ低減 SLEEP_MODE_ADC 最低限割込+Timer2/EEPROM/ADC/  
010 パワーダウン SLEEP_MODE_PWR_DOWN 最低限割込のみ*2 0.3μA(*3)
011 パワーセーブ SLEEP_MODE_PWR_SAVE 最低限割込+非同期Timer2割込 9μA
110 スタンバイ SLEEP_MODE_STANDBY パワーダウンモードと同じ、メインクロック動作維持(復帰が速い) 37μA(*4)

  • 1:3V,内蔵RC発振1MHz動作
  • 2:最低限割込=リセット、BODリセット、ウォッチドッグリセット、INT割り込みのうちLowレベル割り込み、TWI割り込みのうちアドレス一致割込
  • 3:WDT停止時。WDT許可時は20μA
  • 4:内蔵RC発振では不可。1MHzセラミック振動子接続時の値

各モードは、MCUCRレジスタのビットSM2,SM1,SM0に値を設定し、SEを1にセットすることで設定されますが、後述するset_sleep_mode()マクロで簡単に設定できます。

  • mega48/88 ではパワーセーブ時に同期 Timer2 とその割り込みを動かすことができるようになった。I/O クロックが生きていることになるが、この I/O クロックは Timer2 にしか供給されないことに注意。Timer2 で比較出力をポートに出すようにしても寝ている間は出力ポートは変化しないはず。
  • _SLEEP_TYPE = 2 のもの (AVR-LibC 参照) は <avr/sleep.h>での定義名と実装の関係が崩れていることがある。tiny2313 は SLEEP_MODE_ADC を指定するとパワーダウン動作になるし、tiny26 で SLEEP_MODE_PWR_SAVE を指定するとスタンバイ動作する等。
  • 説明が消えたのは対応関係がアレなことがあるからデータシート見て直接書けてことのような気がしてきた。
  • tiny2313はADCないのでまあ SLEEP_MODE_ADC指定がありえねーってことでいいとしても、tiny26は困りますね。STANDBYがないわけで。代わりにPWR_SAVE使えという注釈がいるか。

スリープモード設定マクロ

<avr/sleep.h>に定義されているマクロ関数と定義名が利用できます。

set_sleep_mode(スリープモード名)
上の表にある定義名を入れるだけで、デバイスにあったスリープ設定を行います。ただしスリープ許可(SEビットのセット)は行わないようです。スリープに入るときは下記のsleep_mode()を用いてください。sleep命令実行前にSEをセットします
sleep_mode()
単なるasm("sleep"::);だけでなく、、直前にSEビットをセットしてスリープ可にして、スリープを抜けた直後にSEビットをクリアするコードを生成します。

  • なんでこんな仕掛けにしたんでしょう?set_sleep_modeでSEもセットして、sleep_mode()はsleep命令だけでも支障ないように思えるのですが・・・?SLEEP_TYPE4,5で問題が起きるのかな?
  • データシートの SE ビットの項によれば、目的外スリープ動作移行を避けるため SLEEP 命令実行直前に SE ビットをセットし、起動後ただちにクリアすることが推奨されます ── なのだそうです。

avrgcc sleep関連の注意点(WinAVR 20050214)

  • tiny2313/tiny26で、設定モードと実際のモードの対応がずれています。STANDBY動作はset_sleep_mode(SLEEP_MODE_PWR_SAVE)で設定されます。SLEEP_MODE_STANDBYの設定はありません。あとでトラブルの元なので、対応されるまではこの2デバイスではset_sleep_mode()ではIDLEとPWR_DOWNだけを使った方がようでしょう。
  • いくつかのデバイスで、設定できるけど動作しないモードがあります。特にSLEEP_MODE_ADC。ADCがあって、コンパイルエラーにならないけどサポートされていないものもありますので注意してください。必ずデータシートで動作モードの有無をチェックしてください。