M5StickCであそぶ 〜バッテリーを使う〜

今回は、気になっている人も結構多いのではないかと思われる、バッテリー関係のお話です。

M5StickCには80mAhのリチウムイオンポリマー充電池が初めから搭載されているので、電源周りを自分で考えなくて良く、大変扱いやすいデバイスと思います。ただ、言うて80mAhの容量です。この容量で無線通信やらディスプレイ表示やらを駆使したらバッテリーなんて瞬殺なのでは、というのが当初からの懸念でした。ということで、普通に使ったらどれぐらい電池がもつのか、また電池を長くもたせるためにできることはないかなど、自分が思いつく範囲で調べてみました。

 

まず、電力消費に関係しそうなところで、真っ先に思いついてかつ調整できそうなところとして、ディスプレイの明るさがあります。実際、M5.Axp.ScreenBreath()というメソッドで6段階で調整できます。

0〜12の値が設定できますが、6以下だと何も表示されません。

最大の12だとくっきりはっきり見えますが、

最小の7だとこんな感じで、ぎりぎりなんとか見える程度です。屋外だとかなり厳しいです。8とか9にするとだいぶマシになるので、ずっと画面表示をする必要がありかつ消費電力を抑えたいという方は、それぐらいの値にしておくとよいのではないでしょうか。

 

続いて、CPUの動作周波数です。ずっとArduino Pro mini (8MHz, 3.3V)をメインに使ってきた私からすると驚愕なのですが、デフォルトの動作周波数は240MHzらしいです。30倍…

「そこまでの速さはいらないよ!」という人であれば、動作周波数を下げれば消費電力を抑えることができそうな気がします。上記リファレンスによれば、setCpuFrequencyMhz()というメソッドで240MHz, 160MHz, 80MHz, 40MHz, 20MHz, 10MHzから選んで設定できるようです。最小値でもArduino Pro mini (8MHz, 3.3V)より速い…

 

最後に、スリープモードです。ESP32が搭載されているということで、多くの方がDeepSleepモードを使った省電力化を考えておられると思います。ただ、先でも紹介させて頂いたリファレンスによると、M5StickCでのDeepSleepモードはちょっと扱い方に癖がありそうです。

上記によると、「DeepSleepからのCPUの復帰はタイマーのみ」となっています。自分で試してみても、確かにそうでした。DeepSleep中に電源ボタンを押しても、ディスプレイが明るくなるだけです。つまり、言い換えると「何かボタンを押してDeepSleepからCPUを復帰させることはできない」ということになります。自分的にはそれができると嬉しいなあと思っていたのですが、どうやらできないようです。それがしたければ、通常のON/OFF操作をしてください、ということになりそうです(←電源ボタンを2秒長押しでON、6秒長押しでOFFが本体の仕様)。もちろん、「定期的に起きてセンシングして寝る」みたいな使い方をされる人にとっては、特に問題のない仕様です。

なお、DeepSleep()メソッドとは別にSetSleep()メソッドが用意されており、こちらはCPUは眠らないものの、とりあえずディスプレイ表示を消すことはできるようです。このSleep状態のときは、電源ボタンの短押しで復帰します。ディスプレイが消えているだけなので、例えばSleep中に何かボタンを押して中の変数をカウントアップさせたりした場合は、復帰時にはそのカウントアップが反映された状態になっています。

 

とりあえず自分が気になった電源周りのところは以上になります。次に、上記の内容をいくつか組み合わせて、実際どれぐらい電池が保つのかを試してみました。本当はこまめに値を見つつ、何回か測定した結果を載せるべきなのですが、家事&育児の合間を縫ってこういう作業をしているので、粗々な計測になってしまっていることを予めご了承頂けますと幸いです。

まずは、テスト用のプログラムの確認です。

#include <M5StickC.h>

#define BTN_A_PIN  37
#define BTN_ON  LOW
#define BTN_OFF HIGH
uint8_t prev_btn_a = BTN_OFF;
uint8_t btn_a      = BTN_OFF;

#define DISP_BRIGHTNESS_MIN  7 // 0〜6もセット可能だが、何も見えない
#define DISP_BRIGHTNESS_MAX 12 //
uint8_t disp_brightness = DISP_BRIGHTNESS_MIN;

#define PWR_BTN_STABLEL     0 // 押していない or 1秒以上押し続ける
#define PWR_BTN_LONG_PRESS  1 // 1秒の長押し発生
#define PWR_BTN_SHORT_PRESS 2 // 1秒以下のボタンクリック

boolean is_sleep = false;

double vbat = 0.0;
int8_t bat_charge_p = 0;

void show_battery_info(){
  // バッテリー電圧表示
  // GetVbatData()の戻り値はバッテリー電圧のステップ数で、
  // AXP192のデータシートによると1ステップは1.1mV
  vbat = M5.Axp.GetVbatData() * 1.1 / 1000;
  M5.Lcd.setCursor(0, 5);
  M5.Lcd.printf("Volt: %.2fV", vbat);

  // バッテリー残量表示
  // 簡易的に、線形で4.2Vで100%、3.0Vで0%とする
  bat_charge_p = int8_t((vbat - 3.0) / 1.2 * 100);
  if(bat_charge_p > 100){
    bat_charge_p = 100;
  }else if(bat_charge_p < 0){
    bat_charge_p = 0;
  }
  M5.Lcd.setCursor(0, 25);
  M5.Lcd.printf("Charge: %3d%%", bat_charge_p);

  // 液晶の明るさ表示
  M5.Lcd.setCursor(0, 45);
  M5.Lcd.printf("Brightness:%2d", disp_brightness);
}

///////////////////////////////////////////////////////////////

void setup() {
  // Initialize the M5StickC object
  M5.begin();
  pinMode(BTN_A_PIN,  INPUT_PULLUP);

  // CPU周波数設定。
  // デフォルトは240MHzで、240, 160, 80, 40, 20, 10から選択可
  while(!setCpuFrequencyMhz(10)){
    ;
  }

  // LCD display
  M5.Lcd.setRotation(1);  // ボタンBが上になる向き
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);
  M5.Axp.ScreenBreath(disp_brightness); // 液晶の明るさ設定
}

void loop() {
  // 情報表示
  show_battery_info();

  btn_a = digitalRead(BTN_A_PIN);

  if(prev_btn_a == BTN_OFF && btn_a == BTN_ON){
    // ボタンAが押されたとき。1回ごとにディスプレイの明るさを上げる。
    // 最小7、最大12。12を超えたら0に戻す
    disp_brightness += 1;
    if(disp_brightness > DISP_BRIGHTNESS_MAX){
      disp_brightness = DISP_BRIGHTNESS_MIN;
    }
    M5.Axp.ScreenBreath(disp_brightness);
    M5.Lcd.setCursor(0, 45);
    M5.Lcd.printf("Brightness:%2d", disp_brightness);
  }

  prev_btn_a = btn_a;

  if(M5.Axp.GetBtnPress() == PWR_BTN_SHORT_PRESS) {
    // 電源ボタンの短押し
    if(is_sleep){
      ; // 復帰するだけで、何もしない
    }else{
      M5.Axp.SetSleep(); // 画面が消えるだけのスリープに入る
    }
    is_sleep = !is_sleep;
  }

  delay(100);
}

先のリファレンスなどを見てみると、バッテリーが大体4.2VがMaxで、3.0Vを下回るとM5StickCがシャットダウンするようでしたので、バッテリーの電圧をGetVbatData()で取得し、線形的に見て4.2V〜3.0Vの間のどの位置にいるかでおよその残容量のパーセンテージを出すようにしています。実際の電池の減り方は線形ではないので、あくまで目安ということで。実際、このやり方で表示していると、20%過ぎたあたりからはみるみる残容量が減っていくことになります。

あと、電源ボタンの短押しで、Sleep状態への移行/復帰ができるようにしていますが、これはSetSleep()であってDeepSleep()ではありません。先に述べたとおり、DeepSleepに入ってしまうと、タイマーを設定しない限り復帰できなくなってしまうので。

このプログラムを使って、色々試してみます。

 

まずはフルフルで、「ディスプレイの明るさMax(=12)、CPU動作周波数Max(=240MHz)、SetSleepなし」だとどうなるか。

経過時間 バッテリー電圧表示 バッテリー残%表示
開始時点 4.07V 89%
30分後 3.69V 57%
1時間後

1時間後に様子を見にきたらすでにお亡くなりになっていました。。。うーん、1時間もたなかったか。。。

 

続いて、「ディスプレイの明るさMin(=7)、CPU動作周波数Max(=240MHz)、SetSleepなし」です。

経過時間 バッテリー電圧表示 バッテリー残%表示
開始時点 4.10V 92%
30分後 3.83V 68%
1時間後 3.69V 57%
1時間30分後 3.28V 22%

開始から1時間30分経過時点で確認した22%からみるみる電池残量が減っていき、その3分後にシャットダウンしました。シャットダウン直前の電圧表示は2.9Vとかだったので、およそ4.2V〜3.0Vの間で残量を100%換算するのはおよそ正しいとは思いますが、やっぱり線形で考えるのはやや辛いです。この残量表示だと、20%を切ると後数分もたないというレベルの危険領域です。

ちなみに、M5StickCにはGetWarningLeve()という、バッテリーが低電圧になってしまっているかどうかを確認するメソッドがあるのですが、このときのバッテリー電圧の基準が約3.4Vというところで、上記の%換算のやり方だと33%ぐらいの位置になります。よって、例えば3.4Vぐらいで残10%とかになるように計算した方が、実用上は困らないかもしれません。

ともあれ、ディスプレイの明るさを最小にすることで、最大にしていたときよりも30分以上、稼働時間を延ばすことができました。それでも、およそ1時間半です。

 

続いて、「ディスプレイの明るさMin(=7)、CPU動作周波数Max(=240MHz)、SetSleepあり」です。さらにスリープを足してみましょう。ディスプレイの表示がなされるのは残量確認のために30分に1回Sleepから復帰するときのみなので、さらに稼働時間が延びることが期待されます。

経過時間 バッテリー電圧表示 バッテリー残%表示
開始時点 4.09V 91%
30分後 3.72V 60%
1時間後

予想に反して、1時間待たずにお亡くなりになってしまいました。。。うーん、これはさすがに理屈に合わないような。。。再計測してみます。

経過時間 バッテリー電圧表示 バッテリー残%表示
開始時点 4.10V 91%
30分後 3.71V 59%
50分後 3.51V 42%

こちらもはやり、1時間後には死んでしまっていました。うーん、なんでだろう。何か見落としがあるのかもしれませんが、ちょっとわかりません。

 

ここからは、CPUの動作周波数を変更してみます。個人的にはArduino Pro mini (8MHz, 3.3V)程度の処理速度があれば充分ですので、「ディスプレイの明るさMax(=12)、CPU動作周波数Min(=10MHz)、SetSleepなし」で試してみます。

経過時間 バッテリー電圧表示 バッテリー残%表示
開始時点 4.06V 88%
30分後 3.79V 65%
1時間後 3.66V 55%
1時間30分後 2.90V 0%

丁度1時間30分になるところぐらいでシャットダウンしました。CPU動作周波数が最大だったときは1時間もたなかったので、動作周波数を落とせばだいぶ延命はできそうです。

 

続いて、「ディスプレイの明るさMin(=7)、CPU動作周波数Min(=10MHz)、SetSleepなし」で試してみます。

経過時間 バッテリー電圧表示 バッテリー残%表示
開始時点 4.10V 91%
30分後 3.99V 82%
1時間後 3.92V 76%
1時間30分後 3.85V 70%
2時間後 3.79V 65%
2時間30分後 3.76V 63%
3時間後 3.74V 61%
3時間30分後 3.69V 57%
4時間後 3.58V 48%
4時間30分後 3.44V 36%
5時間後

…なんだか今までの計測全部どこかやり方間違っていたんじゃないかと思うぐらい、一気に保ち時間が長くなりました。初の4時間半越えです。

 

最後に、「ディスプレイの明るさMin(=7)、CPU動作周波数Min(=10MHz)、SetSleepあり」を試して終わります。先の計測でSetSleepをありにすると悪化傾向にありましたが。。。

経過時間 バッテリー電圧表示 バッテリー残%表示
開始時点 4.11V 92%
30分後 3.88V 73%
1時間後 3.75V 62%
1時間30分後 3.63V 52%
2時間後

今回も悪化傾向で、それもSleepなしに比べて3時間近く動作時間が短くなってしまいました。。。

 

ということで、今回の評価結果を見てみると、「ディスプレイの明るさMin(=7)、CPU動作周波数Min(=10MHz)、SetSleepなし」という条件が圧倒的に長寿命でした。もちろん、実際はここからいろんなセンサを使ったり無線通信をしたりでさらに寿命は短くなってしまうかと思うのですが、一つの目安としてご参考にして頂けますと幸いです。もちろん、「DeepSleepをタイマーで定期的に起こす」というような用途がはまるアプリケーションであれば、それを積極的に使えばもっと長寿命化できると思います。今回はDeepSleepが使えない状況での評価、ということでお願いします。

 

今回の結果を簡単にまとめると、

  • ディスプレイの明るさを下げるのはバッテリーの長寿命化に有効
  • CPUの動作周波数を下げるのはバッテリーの長寿命化にとても有効
  • SetSleepは現状全く役に立っていないどころかむしろバッテリーの長寿命化に悪影響

ということになるのですが、気になるのは3つめのSetSleepです。本体実装を疑うのを一番最後にするとすれば、自分がSetSleepを有効に使うための条件を何か見落としてしまっているのかもしれません。もし何かご存知の方がおられましたら、ご教授頂けますと幸いです。