M5StickCでできること 〜モバイルバッテリーの「AUTO POWER OFF」をキャンセルする

「M5StickC」などのデバイスは、

  • LCDディスプレイやバッテリーなどが搭載されている。
  • GROVEポートを使えば、ハンダ付けなどをしなくても外付けセンサを接続できる。

といった理由で、IoTデバイスをお気軽に開発するのにうってつけです。


ただ、M5StickCを普通に動かすと、常時100mA近くの電流を消費する上、たとえディープスリープさせても数mAの電流を消費してしまうなど、低電力化という視点で見ると、あまり適切なデバイスとは言えません。
また、例えば一定期間毎に温度を測定するというような用途ならばディープスリープも活用できますが、例えば振動を常時監視し、揺れを検知したら通知するというような用途の場合は、デバイスをディープスリープさせることはできず、常に100mA近くの電流を消費してしまうことになります。

そんな訳で、ここでは、M5StickCをできるだけ低電力で動作させてみたいと思います。

M5StickCの低消費電力化

例として、以下のようなデバイスをつくることにします。

  • M5StickC内蔵の加速度センサで、加速度データを常時監視する(加速度を常時監視するため、ディープスリープは使えません)。
  • 20分に1回の間隔で、その期間の加速度データの最大値・最小値を「ambient」に送信する。

M5StickCの低電力化ノウハウについては こちら の記事を参考にさせていただきました。

実施する方策は、以下のとおりです。

  • CPUの動作周波数を、デフォルトの「240MHz」から最低の「10MHz」に落とす。
  • LCDディスプレイの輝度を、デフォルトの「12」から目視できる最低レベルの「8」に落とす。
  • 5V出力用DCDCをOFFにする。

なお、Wi-Fi通信時にはCPUの動作周波数を「80MHz」以上にする必要があります。
今回は、データを「ambient」に送信する時(20分に1回)だけ動作周波数を「240MHz」にしてWi-Fi接続し、送信が完了したら速やかにWi-Fiを切断して「10MHz」に戻すことにしました。

作成したスケッチはこちらです。

#include <M5StickC.h>
#include "Ambient.h"

WiFiClient client;
Ambient ambient;

const char*   ssid         = "XXXXXXXX";
const char*   password     = "XXXXXXXX";
unsigned int channelId     = XXXXX;
const char*  writeKey      = "XXXXXXXX";

float         accX, accY, accZ;
float         maxX         = -99.9;
float         minX         = 99.9;

unsigned long measInterval = 10;
unsigned long sendInterval = 1200000;
unsigned long prevMeasTime = 0;
unsigned long prevSendTime = 0;

void setup() {
  M5.begin();
  M5.Axp.begin(false, false, false, false, true);
  setCpuFrequencyMhz(10);
  M5.Axp.ScreenBreath(8);
  M5.Imu.Init();
  M5.Lcd.setRotation(3);
}

void loop() {
  M5.update();
  unsigned long currentTime = millis();
  if(currentTime-prevMeasTime >= measInterval) {
    prevMeasTime = currentTime;
    // 振動データ採取
    M5.IMU.getAccelData(&accX, &accY, &accZ);
    if(accX > maxX) maxX = accX;
    if(accX < minX) minX = accX;

    if(currentTime-prevSendTime >= sendInterval) {
      prevSendTime = currentTime;
      // 動作周波数を上げる
      setCpuFrequencyMhz(240);
      // Wi-Fi接続
      M5.Lcd.fillScreen(BLACK);
      M5.Lcd.setCursor(0, 0, 1);
      M5.Lcd.printf("WIFI START : %s ", ssid);
      WiFi.begin(ssid, password);
      while(WiFi.status() != WL_CONNECTED) {
        delay(500);
        M5.Lcd.print(".");
      }
      M5.Lcd.printf(" WIFI READY\n");
      // ambientにデータ送信
      ambient.begin(channelId, writeKey, &client);
      ambient.set(1, maxX);
      ambient.set(2, minX);
      ambient.send();
      M5.Lcd.printf("[maxX] %.2f, [minX] %.2f\n", maxX, minX);
      maxX = -99.9;
      minX = 99.9;
      // Wi-Fi切断
      WiFi.disconnect(true);
      M5.Lcd.printf("WIFI DISCONNECTED\n");
      // 動作周波数を下げる
      setCpuFrequencyMhz(10);
    }
  }
}

これにより、M5StickCの消費電流値は30mA程度、Wi-Fi通信時(20分のうち数秒間)のみ100mA超という低電力化を実現できました。

このスケッチをM5StickCに書き込み、内蔵バッテリーで動かしたところ、2時間40分にわたり連続稼働させることができました。

モバイルバッテリーを使ってM5StickCを長期稼働させる

ただ、M5StickCの内蔵バッテリーは容量が80mAhのため、ここまでの低電力化を行っても、前述のようにせいぜい2時間程度しか駆動できません。
電源のない場所で、より長期間動かしたい場合は、外付けバッテリーなどを使うことになります。

外付けバッテリーとして真っ先に思い浮かぶのは、スマホなどに充電するための「モバイルバッテリー」です。
さまざまな製品が販売されており、一般的にも広く使われているため、価格面でもこなれています。

ただ、現在販売されている大部分のモバイルバッテリーには「AUTO POWER OFF」という機能が搭載されています。
これは、モバイルバッテリーが給電していない時に、自動的にモバイルバッテリーの電源をオフにする機能で、一度オフになってしまうと、スイッチを押したりしない限り再度オンにすることはできません。
「AUTO POWER OFF」するかどうかは、モバイルバッテリーから給電している電流値で判定され、機種にもよると思いますが、概ね数十mAがしきい値になっているようです。
とある期間中、モバイルバッテリーから給電する電流値が数十mAを超えなかったら、モバイルバッテリーがオフになるという仕様です。

つまり、前述のスケッチを書き込んだM5StickCにモバイルバッテリーをつないでも、電流消費が30mA程度しかないため、バッテリーからは給電していないと見なされ、モバイルバッテリーはすぐにオフになってしまいます。
これではモバイルバッテリーからM5StickCに給電し続けることはできません。

モバイルバッテリーの「AUTO POWER OFF」をキャンセルするために、例えば こちら のような製品が販売されています。
モバイルバッテリーに定期的に負荷電流を流すことで、モバイルバッテリーの電源がオフにならないように制御するものです。

今回は、これと同様の機能を、M5StickCのスケッチで実現しようと思います。

使用するモバイルバッテリーは、Ankerの「PowerCore 10000」です。


このバッテリーは、120秒以内に一瞬90mA程度の電流を流すようにすれば、オフになりませんでした。
よって今回は、余裕をもって60秒毎に0.6秒間だけ、90mA程度の消費電流となるようなスケッチをつくります。

消費電流を増やすための方策は、以下のとおりです。

  • CPUの動作周波数を最大の「240MHz」に上げる。
  • LCDディスプレイの輝度を最大の「12」に上げる。
  • CPUに負荷をかけるために「ハノイの塔」という処理を実行する(参考にさせていただいた記事は こちら)。

60秒毎に0.6秒間だけこれらを実施し、0.6秒が経過したら速やかに元の状態に戻します。

作成したスケッチはこちらです。

#include <M5StickC.h>
#include "Ambient.h"

WiFiClient client;
Ambient ambient;

const char*   ssid         = "XXXXXXXX";
const char*   password     = "XXXXXXXX";
unsigned int channelId     = XXXXX;
const char*  writeKey      = "XXXXXXXX";

float         accX, accY, accZ;
float         maxX         = -99.9;
float         minX         = 99.9;

boolean       loadFlag     = false;
unsigned long measInterval = 10;
unsigned long sendInterval = 1200000;
unsigned long loadInterval = 60000;
unsigned long loadPeriod   = 600;
unsigned long prevMeasTime = 0;
unsigned long prevSendTime = 0;
unsigned long prevLoadTime = 0;

void hanoi(int n, char a, char b, char c) { // 消費電流値UPのための処理
  if(n > 0) {
    hanoi(n-1, a, c, b);
    hanoi(n-1, c, b, a);
  }
}

void task0(void* param) {
  while(1) {
    if(loadFlag) hanoi(20, 'a', 'b', 'c'); // loadFlag=trueの時、消費電流値UPのための処理を実行
    vTaskDelay(1);
  }
}

void setup() {
  M5.begin();
  M5.Axp.begin(false, false, false, false, true);
  setCpuFrequencyMhz(10);
  M5.Axp.ScreenBreath(8);
  xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 0);
  M5.Imu.Init();
  M5.Lcd.setRotation(3);
}

void loop() {
  M5.update();
  unsigned long currentTime = millis();
  if(currentTime-prevMeasTime >= measInterval) {
    prevMeasTime = currentTime;
    // 振動データ採取
    M5.IMU.getAccelData(&accX, &accY, &accZ);
    if(accX > maxX) maxX = accX;
    if(accX < minX) minX = accX;

    if(currentTime-prevLoadTime >= loadInterval) { // 消費電流値UPのための処理を開始
      prevLoadTime = currentTime;
      loadFlag = true;
      setCpuFrequencyMhz(240);
      M5.Axp.ScreenBreath(12);
      M5.Lcd.fillRect(108, 69, 50, 10, RED);
    }
    if(loadFlag && (currentTime-prevLoadTime >= loadPeriod)) { // 消費電流値UPのための処理を終了
      loadFlag = false;
      setCpuFrequencyMhz(10);
      M5.Axp.ScreenBreath(8);
      M5.Lcd.fillRect(0, 69, 160, 10, BLACK);
    }
    if(currentTime-prevSendTime >= sendInterval) {
      prevSendTime = currentTime;
      // 動作周波数を上げる
      setCpuFrequencyMhz(240);
      // Wi-Fi接続
      M5.Lcd.fillRect(0, 0, 160, 68, BLACK);
      M5.Lcd.setCursor(0, 0, 1);
      M5.Lcd.printf("WIFI START : %s ", ssid);
      WiFi.begin(ssid, password);
      while(WiFi.status() != WL_CONNECTED) {
        delay(500);
        M5.Lcd.print(".");
      }
      M5.Lcd.printf(" WIFI READY\n");
      // ambientにデータ送信
      ambient.begin(channelId, writeKey, &client);
      ambient.set(1, maxX);
      ambient.set(2, minX);
      ambient.send();
      M5.Lcd.printf("[maxX] %.2f, [minX] %.2f\n", maxX, minX);
      maxX = -99.9;
      minX = 99.9;
      // Wi-Fi切断
      WiFi.disconnect(true);
      M5.Lcd.printf("WIFI DISCONNECTED\n");
      // 動作周波数を下げる
      setCpuFrequencyMhz(10);
    }
  }
}

このスケッチをM5StickCに書き込み、容量10000mAhのモバイルバッテリー(前述のAnker PowerCore 10000)で動かしたところ、約5日にわたり連続稼働させることができました。

5日という稼働期間が微妙ではありますが、例えば「屋外で電源のない場所」ではあるが、「定期的にバッテリー交換に行くことは可能」というようなケースであれば、このような構成でIoTデバイスをつくるのも「あり」ではないかな?と思います。


なお、私がM5Stack、M5StickCの使い方を習得するのにあたっては、以下の書籍を参考にさせていただきました。


ごく基本的なところから、かなり複雑なスケッチや、ネットワーク接続など、比較的高度なものまで、つまづかずに読み進めていけるような構成になっており、大変わかりやすい本です。


このサイトで書いている、M5Stackシリーズ(M5Stack、M5StickCなど)に関するブログ記事を、「さとやまノート」という別のブログページに、あらためて整理してまとめました。

他のM5Stackシリーズの記事にも興味のある方は「さとやまノート」をご覧ください。