ATtiny1616を試す 省電力編

前回までで、ATtiny1616をコイン電池やボタン電池で動かせるようにはなりました。これで最低限の検証は済んで、私が工作する上では特に困ることはないのですが、せっかくなのでもう少し検証してみたいと思います。

世の中には、電源ON/OFFスイッチがついていないものが結構あります。代表的なのは、テレビやエアコンとかのリモコンですね。仮面ライダーの玩具も、電源スイッチがついていないものが結構多いです。ガイアメモリ、ガシャット、ライドウォッチ、プログライズキー、ワンダーライドブック、バイスタンプ等々。

これらは電源スイッチなしで電池を入れっぱなしでも年単位で電池がもったりしますので、当然、非操作時は超低消費電力になっているはずです。ATtiny1616でもその設定はできるはず、ということで、やってみます。

前回のLEDの点滅コードをベースにしつつ、以下の仕様を追加します:

  • 動作開始から20秒が経過したら、スリープさせる
  • ボタンを押すと、スリープから復帰する

ChatGPTに質問しつつ、とりあえず動作するように書き換えたコードが以下になります:

#include <avr/sleep.h>
#define PIN_LED 1
#define PIN_SW  2

#define ON  LOW
#define OFF HIGH

#define ON_OFF_MS         3000
#define SLEEP_TIMEOUT_MS 20000

bool is_lighting = false;
unsigned long last_led_change_point_ms = 0;
unsigned long start_point_ms = 0;

// 割り込み関数(attachInterrupt用)
void wakeup(){
  ;
}


void go_to_sleep(){
  digitalWrite(PIN_LED, 0);

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // 最も省電力なモード。CPU, クロック, ほぼ全ペリフェラル停止
  sleep_enable(); // スリープ許可。次のsleep_cpu()を有効化するフラグ
  sleep_cpu(); // CPU停止。復帰後は、次の行から再開

  // 復帰後
  sleep_disable();
}


void setup(){  
  _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm); // 動作周波数を1MHzに落とす
  ADC0.CTRLA &= ~ADC_ENABLE_bm; // ADC(アナログデジタル変換)を停止
  SPI0.CTRLA &= ~SPI_ENABLE_bm; // SPI停止
  TWI0.MCTRLA = 0;   // I2C マスター停止
  TWI0.SCTRLA = 0;   // I2C スレーブ停止

  Serial.begin(57600);
  pinMode(PIN_SW, INPUT_PULLUP);
  pinMode(PIN_LED, OUTPUT);

  attachInterrupt(digitalPinToInterrupt(PIN_SW), wakeup, FALLING); // HIGH -> LOW変化をトリガーにする
  start_point_ms = millis();
}

void loop(){
  unsigned long now_ms = millis();

  if(now_ms - last_led_change_point_ms > ON_OFF_MS){
    digitalWrite(PIN_LED, is_lighting ? 0 : 1);
    is_lighting = !is_lighting;
    last_led_change_point_ms = now_ms;
  }

  if(now_ms - start_point_ms > SLEEP_TIMEOUT_MS){
    go_to_sleep();

    // 復帰後リセット
    last_led_change_point_ms = millis();
    start_point_ms = millis();
  }

  delay(10);
}

前回のコードとピン配列が変わっていることに注意です。ボタン操作でスリープから復帰するには、非同期割り込みに対応したピンにボタンに割り当てないといけないので、2番(A6)をボタン用に割り当てています。

で、上記プログラムを書き込んでみたところ、無事、意図通りには動いたのですが、スリープに入った後の電流値がこちら。

1.74mA。うーん、前回動作時の3.37mAと比べれば半分には落ちていますが、思ったほど落ちていない、という印象。。。

ということで、もうちょっとChatGPTに聞いたり調べたりした結果、未使用ピンをプルアップすることで、そもそもの消費電流を抑えられるとのこと。やってみます。

#include <avr/sleep.h>
#define PIN_LED PIN_PA5
#define PIN_SW  PIN_PA6

#define ON  LOW
#define OFF HIGH

#define ON_OFF_MS         3000
#define SLEEP_TIMEOUT_MS 20000

bool is_lighting = false;
unsigned long last_led_change_point_ms = 0;
unsigned long start_point_ms = 0;

//volatile bool wakeup_flag = false;
// 割り込み関数(attachInterrupt用)
void wakeup(){
  //wakeup_flag = true;
}


void go_to_sleep(){
  digitalWrite(PIN_LED, 0);

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // 最も省電力なモード。CPU, クロック, ほぼ全ペリフェラル停止
  sleep_enable(); // スリープ許可。次のsleep_cpu()を有効化するフラグ
  sleep_cpu(); // CPU停止。復帰後は、次の行から再開

  // 復帰後
  sleep_disable();
}


void setup(){  
  _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm); // 動作周波数を1MHzに落とす
  ADC0.CTRLA &= ~ADC_ENABLE_bm; // ADC(アナログデジタル変換)を停止
  SPI0.CTRLA &= ~SPI_ENABLE_bm; // SPI停止
  TWI0.MCTRLA = 0;   // I2C マスター停止
  TWI0.SCTRLA = 0;   // I2C スレーブ停止

  Serial.begin(57600);
  pinMode(PIN_SW, INPUT_PULLUP);
  pinMode(PIN_LED, OUTPUT);

  // 省電力化のため、未使用ピンはすべてプルアップする
  pinMode(PIN_PA1, INPUT_PULLUP);
  pinMode(PIN_PA2, INPUT_PULLUP);
  pinMode(PIN_PA3, INPUT_PULLUP);
  pinMode(PIN_PA4, INPUT_PULLUP);
  pinMode(PIN_PA7, INPUT_PULLUP);
  pinMode(PIN_PB0, INPUT_PULLUP);
  pinMode(PIN_PB1, INPUT_PULLUP);
  pinMode(PIN_PB2, INPUT_PULLUP);
  pinMode(PIN_PB3, INPUT_PULLUP);
  pinMode(PIN_PB4, INPUT_PULLUP);
  pinMode(PIN_PB5, INPUT_PULLUP);
  pinMode(PIN_PC0, INPUT_PULLUP);
  pinMode(PIN_PC1, INPUT_PULLUP);
  pinMode(PIN_PC2, INPUT_PULLUP);
  pinMode(PIN_PC3, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(PIN_SW), wakeup, FALLING); // HIGH -> LOW変化をトリガーにする
  start_point_ms = millis();
}

void loop(){
  unsigned long now_ms = millis();

  if(now_ms - last_led_change_point_ms > ON_OFF_MS){
    digitalWrite(PIN_LED, is_lighting ? 0 : 1);
    is_lighting = !is_lighting;
    last_led_change_point_ms = now_ms;
  }

  if(now_ms - start_point_ms > SLEEP_TIMEOUT_MS){
    go_to_sleep();

    // 復帰後リセット
    last_led_change_point_ms = millis();
    start_point_ms = millis();
  }

  delay(10);
}

書き換えついでに、ピン番号の指定方法を、0〜17の整数値を直接書くやり方から、”PIN_PXX”で書く方法に変更しました。DIP化ボードを見ながら開発する場合は、こっちの方がピン指定がわかりやすくはなると思います。ただ、今回のように、未使用ピンをまとめてプルアップしたい場合は、数字指定でfor文回した方がスッキリしたかもしれません。

では、いざ計測。まずは動作中、LEDのOFF時。

1.61mA。わ、この時点で前回の3.37mAから半分ぐらい減りました。

続いてLEDのON時。こちらも順当に減って、4.74mA。

そしてスリープ時ですが。。。

なんと0.1μA。すごい。これなら電源入れっぱなしでもだいぶもちそうです。

ボタンを押すと、ちゃんと復帰もしてくれました。

ということで、超シンプルな用途であれば、スリープ時にはめちゃめちゃ省エネにできる、ということは確認できました。とはいえ、いざ工作を始めると、ピンはめちゃめちゃ使うと思うので、実作品でもここまで省エネにできるかは不明です。まあ、そのときは電源ON/OFFスイッチをつければ済む話ではあるので、あわよくば実装を狙う、ぐらいで良いのかなと思います。

(★以下、記事作成後の追記★)

前回記事でも同じ追記をしていますが、こちらでも念のため。

ここまで、動作周波数を落とすのに、setup()の中で

_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm);

と記述していたのですが、プログラムの中でこれを書くよりは、Arduino IDE側の設定で”Clock”を”1MHz”にしておくほうが良さそうです。

というのも、UART通信を使用していて、setup()の方で動作周波数を落としたときにはうまくいかなったのが、上記のやり方で動作周波数を落とすとうまく動作する、ということがあったためです。ということで、フレキシブルに動作周波数を変えたりすることがなければ、基本的にはこちらのやり方で動作周波数を変えたほうが良いと思います。

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次