M5Paperの動作中の消費電力を削減する

M5Paper を動作させっぱなしにすると残像が残る?

M5Paper は、静電容量タッチスクリーン付きの 540 x 960 の4.7インチ電子ペーパー(EPD)を備えた、ESP32搭載のデバイスです。
無線LANやBT通信などと組み合わせて、色々なモノを作ることができます。*1


1150 mAhのLipoバッテリーを持っているため、これと電子ペーパーの電源を落としても表示が消えない特性を生かして、長期間ディープスリープさせながら定期的に画像取得するような作例がよく見られます。
しかし、タッチセンサーを利用した動作をする場合、タッチ状態を監視して反応(画面を再描画したり通信したり)したいため、内部状態がリセットされてしまうディープスリープは使えず、USB電源等に繋いで常時稼働させる必要があります。


さて、M5Paperでタッチ操作が可能なスケジューラを作り、常時通電で稼働させていたのですが、時計を1分に1度更新していたところ、画面のUSB-Cポート側の端にだけ、白黒反転した残像のようなものが残るようになってきました。
※配線の都合上、上下反対(つまりUSB-Cポートの位置が上になるよう)にして使っています。
f:id:NeoCat:20210725112051j:plain:w400
最初は電子ペーパーの劣化かと思ったのですが、電源を落としてしばらく放置すると何事もなかったように元に戻ります。
そして、残像の残るあたりをタッチすると、なんだか暖かいことに気がつきました。
どうやらこの高温が残像の原因らしく、電源を落として冷ますと元に戻っていたようです。
温度を測ってみるとこんなかんじでした。ピンポイントで測ると40℃くらいになっています。。
f:id:NeoCat:20210725133040p:plain:w400

そういえば、ESP32は最大消費電力が大きいので発熱もそれなりにあり、常時稼働させていると気温センサ等で正しい値がとれなくなったりします。
というわけで、消費電力を抑えることを考えてみることにしました。

M5Paper稼働中の電流計測

まずは簡単にM5Paperの稼働中にどれくらいの電流が流れているか計測してみました。
フル充電後にUSB電源で動作させてUSBチェッカーで1秒ごとに計測した値なので、ピーク値ではなくラフな平均的な電流値です。

状態電流値
WiFi通信中180 - 200mA
WiFi待機中150 mA

WiFi待機中」は、WiFiアクセスポイントへの接続はしたまま通信はせずにloop()をぐるぐる回っている状態です。
普通のESP32と比べても大きい値となっています。
調べてみたところ、電子ペーパー用のコントローラ IT8951 (裏面にも描いてあります) が60-80msくらい電流を使っているようで、この分大きいということでした。

IT8951 の動作電流を抑える

EPD周りの電源は、まとめて以下の関数で落とすことができるようになっています。

M5.disableEPDPower()

しかしこれをやってしまうと、電源を再投入しても描画がノイズだらけになったり掠れたりしてしまい、リセットしない限り正常に復帰できないようです。
ディープスリープする場合は復帰時はどのみちリセットになるので構いませんが、タッチ操作に応答して画面を部分書き換えしたい場合にはリセットされては困ります。

IT8951 のデータシートを読んでみると、IT8951 には電源モードとして Active / StandBy / Sleep の3つがあり、StandBy / Sleep では一部の機能を抑止して消費電力を抑えられるとあります。
これを使うにはSPI等でコマンドを送る必要があるため、M5PaperのArduino用ライブラリに関数を足して、電源モードを切り替えられるようにしました。
Pull Request を送ってみました。)
これを使って待機中にはIT8951をSleepに入れてやり、再描画が必要になったらActiveに戻すようにしてやると、見事に60mA程度の電流をカットすることができました。
(StandByでも同じくらい電流が減ります。細かい違いは今回は測定できませんでした)

  M5.EPD.Sleep();
  // ... タッチ操作を待機する ...
  M5.EPD.Active();
  // ... タッチ操作に反応して再描画 ...

ESP32のライトスリープとの組み合わせ

残りの消費電力の削減はESP32のライトスリープによって行います。
まず、通信しない時はWiFiをOFFにしてしまいます。

  delay(1000); // 通信完了直後に OFF にしてしまうとコネクション切断処理が途中のまま残ってしまうため少し待機
  WiFi.mode(WIFI_OFF); // または   WiFi.disconnect(true);

再度通信する際は WiFi.mode(WIFI_STA); WiFi.begin(...) で再接続が必要なため、少し時間がかかります(固定IPアドレスにするといくらか時間を短縮できます)。


その上で、待機中に ESP32 をライトスリープに入れます。
ライトスリープなら、ディープスリープと違ってメモリ内容は維持され、復帰時もスリープに入ったところから処理が再開されるので、簡単に使えます*2

ライトスリープはタイマーやGPIO、UPCなど、様々な復帰要因に対応しています。
(→ 参考: ESP32のライトスリープを調べる | Lang-ship
今回は時計の画面更新のため、およびタッチスクリーン操作で復帰させたいので、以下のようにしました。

void loop() {
...
    Serial.flush();  // Serialをflushさせておく
    esp_sleep_enable_timer_wakeup(500000);  // 0.5s後に起床
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_36, LOW);  // タッチで GPIO36 が LOW になるのでこの時も起床
    esp_light_sleep_start();  // ライトスリープ開始

    // 起床後の処理
}

ほとんど delay() を入れるくらいの感覚で使えますね。

改善後の動作電流

さて、これらの対応でどのくらい動作電流は変わったか計測してみました。

状態電流値
WiFi通信中180 - 200mA
WiFi OFF時120 - 140mA
IT8951 Sleep時60mA
Light Sleep時? - 20mA

WiFi OFF + IT8951 Sleep + Light Sleep時には最低値で1桁mA程度まで減少させることができました(精度不足で細かい値は取れず)。
0.5s 毎の起床時と平均すると10mA前後といったところでしょうか。
元と比べると平均消費電力は10分の1程度に大きく節減でき、バッテリー動作でも1日以上は動作する計算になります。

発熱も大きく抑えられ、残像が出る問題も解決することができました。
f:id:NeoCat:20210725133105p:plain:w400


かなりの効果もあったことだし、Pull Request 取り込まれるといいなあ。

*1:もうどこにも在庫がなくて入手できなくなっているような…

*2:ただしメインCPUはpause状態となり処理が止まってしまうので、WiFiなど定期的な維持処理が必要な機能は切っておく必要があります.