sleep

 いつもUSBで電源を確保していたのですが、ADCをいじっていたら「連流流れすぎ」とMacに怒られました...危ないですね。たぶん100mAは流れていないハズなんですけども...。
 余談はこれくらいにして、ADCの前に今回はsleepをやっておきます。

sleep概要

 sleepは文字通りavrをsleepさせて消費電力を抑えたり、あるいは電源スイッチを省略したり、さらにADC変換中にsleepにさせてADCの精度を上げる*1などなど出来ます。sleepからの復帰は割り込みを使い、sleepの種類によって使える割り込みが違います。


sleep mode一覧

mode INT割り込み TWIアドレス一致割り込み Timer2割り込み SPM/EEPROM Ready割り込み ADC割り込み WDT割り込み OtherIO (PCINT?)
SLEEP_MODE_IDLE O O O O O O O
SLEEP_MODE_ADC Low Level O O O O O -
SLEEP_MODE_PWR_DOWN Low Level O - - - O -

 他にもSLEEP_MODE_PWR_SAVEとSLEEP_MODE_STANDBYがありますが、個人的に使わないので省略します。SLEEP_MODE_ADCはADCが搭載されたAVRでないとありません。割り込みの違い意外にクロック動作が変わってきてますが、プログラムを書く面から直接関係はしないと判断したのでこれも省略しています。
 INT割り込みの「Low Level」というのは状態変化・立ち上がり・立ち下がり割り込みでは復帰できないので注意です。


 sleepでは必要なレジスタはありますが、直接レジスタをいじることは無く、関数を呼び出して使います。使う関数も

  • set_sleep_mode(mode)
  • sleep_mode()

の2種類だけです。set_sleep_modeで使用するスリープのモードを選び、sleep_modeで実際にスリープさせます。これらはavr/sleep.hに書かれているので、これをインクルードする必要はあります。復帰は割り込みを使うので、割り込みハンドラ無いにsleep_mode()を書くのは避けた方が良いです、割り込みフラグピットが落ちないままスリープして次回の割り込みがかからなくなる可能性があるので。

サンプルコード


 SLEEP_MODE_IDLE
 SLEEP_MODE_IDLEではTimerは停止しないので、動作自体はタイマ0の時のサンプルと変わりません。
タイマ0カウンタモード - hijouguchiの日記

/*
 * ATTiny2313
 * タイマを使ってPD6を反転させる
 * 8MHzで1024分周ならおよそ110ms周期で点滅
 */
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

/* 割り込みでスリープから復帰 */
ISR(TIMER0_OVF_vect)
{
  PORTD ^= (1<<PD6); // PD6反転
}

int main(void)
{
  /* PD6を出力L設定 */
  DDRD  =  (1<<PD6);
  PORTD = ~(1<<PD6);

  TIMSK  = (1<<TOIE0); // タイマ0のオーバーフロー割り込み許可
  TCCR0B = 0x05;       // 1024分周でカウントスタート

  set_sleep_mode(SLEEP_MODE_IDLE); // アイドルモードに設定

  sei();               // 全体の割り込み許可

  while(1) sleep_mode(); // 適宜スリープさせる
  return 0;
}


 SLEEP_MODE_PWR_DOWN
 こちらの場合、タイマ自体が停止するためスリープ中は止まります。

/*
 * ATTiny2313
 * タイマを使ってPD6を反転させる
 * 8MHzで1024分周ならおよそ110ms周期で点滅
 */
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

/* STATEが0でスリープ禁止、1でスリープ許可(とする) */
volatile unsigned char STATE;

ISR(TIMER0_OVF_vect)
{
  PORTD ^= (1<<PD6); // PD6反転
}

/* 割り込みでスリープから復帰 */
ISR(INT0_vect)
{
  STATE = 0; // スリープ禁止
}

int main(void)
{
  STATE = 0;

  /* PD6を出力L, その他をプルアップ有り入力に設定 */
  DDRD  =  (1<<PD6);
  PORTD = ~(1<<PD6);

  GIMSK = (1<<INT0)|(1<<INT1);  // INT0, 1の割り込み許可

  TIMSK  = (1<<TOIE0); // タイマ0のオーバーフロー割り込み許可
  TCCR0B = 0x05;       // 1024分周でカウントスタート

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // アイドルモードに設定

  sei();               // 全体の割り込み許可

  while(1) {
    /* スリープ許可されてたら(PD6を消灯してから)スリープ */
    if(STATE == 1) {
      PORTD &= ~(1<<PD6);
      sleep_mode();
    }   

    /* PD5がLならスリープを許可 */
    if((~PIND & (1<<PD5)) == (1<<PD5)) STATE = 1;
  }
  return 0;
}

*1:クロックやピン変化自体がADCのノイズになるため、sleepさせてこれを抑制させる