方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

【ESP32】スリープモード(Deep / Light sleep)選択基準。消費電力の最適化と実装

この記事は約9分で読めます。

様々な方言を話すおしゃべり猫型ロボット『ミーア』を開発中。

ミーア
おしゃべり猫型ロボット「ミーア」は、100以上の種類の豊かな表情で、悲しみや喜びを共有します。様々な性格(皮肉・おせっかい・ロマンチスト・天然)や方言(大阪弁・博多弁・鹿児島弁・広島弁)も話してくれます。ミーアとの暮らしで、毎日の生活をもっ...

今回は、ミーアの基板で使用しているESP32の消費電力を最適化するために、スリープモードをどのように選択し、実装していくかについて見ていく。

前回の下記記事で、Lipoによる給電にも対応できるようにしたので、USB-C接続で常に電源供給されている状態とは異なり、ESP32の消費電力も考慮する必要が出てきた。

ESP32のスリープモード概要

ESP32は高度な省電力機能を備えており、その中心となるのがスリープモード。主にDeep-sleep modeとLight-sleep modeの2つのスリープモードがある。

実際には、細かく分けると5つのモードがあるが、今回は割愛。

公式詳細記事はこちら

Sleep Modes - ESP32 - — ESP-IDF Programming Guide latest documentation

Light-sleep mode

  • デジタル周辺機器、ほとんどのRAM、CPUは一時停止状態(clock-gated)になる。しかし、完全オフではなく一時停止状態のため、復帰時には、プログラムはスリープ前の状態から再開されるため、迅速な復帰が可能
  • Light-sleepは、比較的短時間のアイドル状態や、迅速なレスポンスが求められる場合に適している。

Deep-sleep mode

  • デジタル周辺機器、ほとんどのRAM、CPUは一時停止ではなく電源オフになる。そのおかげで低い電力消費を実現する。
  • チップの中で電源が維持されるのは、RTCコントローラ(時刻やアラームの維持)、ULPコプロセッサ(簡単な計算やセンサーデータの監視)、RTC FASTメモリ、RTC SLOWメモリ(データの保存)のみ。
  • メモリの内容は保持されず、復帰時にはリセットが行われ、プログラムは最初から再開される。
  • Deep-sleepは長期間のアイドル状態や、非常に限られた機能しか必要としないアプリケーションに適している

ちなみに各modeでの消費電力はこちら

  • アクティブモード(通常時):95-240mA
  • Light-sleep mode:〜0.8mA(800μA)
  • Deep-sleep mode:10〜150μA

特筆すべきは、Light-sleep modeとDeep-sleep modeで消費電力がアクティブモートの時と比較すると、ほとんど誤差みたいな差であること。

また、Wi-Fi/Bluetooth機能は、Light/DeepいずれのSleep modeでも、デフォルトでOffになり、Wi-fi/Bluetooth機能でワイヤレスにESP32と接続されている機器は通信を絶たれる。ちなみに、例外的にONに保っておくこともコードを書けばできる。

Deep sleep modeでの実装はこちら

ねこ型ロボット「ミーア」の特性と要件

このプロジェクトでは、ESP32ベースの猫型ロボットを開発している。このロボットは、ST7735S LCDディスプレイを目として使用し、MP3ファイルの再生機能と、TTP223タッチセンサーによるインタラクション機能(目が動き、音声を再生する)を持っている。また、Wi-Fiを通じてMP3ファイルをアプリからダウンロードする機能もある。

なぜLight-sleep modeが適切か

今回は、下記理由から、Deep-sleep modeよりLight-sleep modeの方が適していると判断。

迅速なレスポンス

  • ユーザーがTTP223タッチセンサーを操作した際、すぐにロボットが反応し、目を動かし、音声を再生する必要がある。Light-sleep modeでは、Deeep-sleepmodeより復帰が迅速で、ユーザーのインタラクションに対するレスポンスが速い。

外部割り込みによる復帰

  • TTP223タッチセンサーからの割り込みにより、Light-sleepから迅速に復帰することができる。
  • Deep-sleep modeでは、ESP32はGPIO割り込みを利用して復帰することができるが、この機能は限られたピンにのみ対応。また、復帰後、ESP32は完全にリセットされ、プログラムの実行は最初から開始される。今回だと、目のLCDディスプレイが電源オフの状態から電源ONになるのがUX的に再起動的な印象を与えて良くないかなと

Light-sleep modeの実装

Light-sleep modeへの移行条件と復帰要件

Light-sleep modeへの移行条件:

  • ユーザーの最後のアクション(頭に取り付けたTTP223タッチセンサーを触れる / アプリからの操作するなど)から一定時刻過ぎた場合、ESP32はライトスリープモードに移行する。
  • この移行前に、ロボットが「おやすみ」といったフレーズを発して、お休みの目の画像を表示することで、ユーザーにスリープモードへの移行を知らせる。

Light-sleep modeからの復帰:

  • ユーザーが猫の頭を撫でる(TTP223タッチセンサーを触れる)と、ロボットはライトスリープモードから復帰。
  • 復帰時には、「もう起こしたの?」などのフレーズを発することで、自然なインタラクションの演出を試みる。

実際のコード

こちらのgithubに、light-sleep modeの実装例が記載してある。

esp-idf/examples/system/light_sleep at ece73357caa6c766770136b82639964870e340ba · espressif/esp-idf
Espressif IoT Development Framework. Official development framework for Espressif SoCs. - espressif/esp-idf

wake upの方法には、Timer, Touchpad, GPIOピン, UARTなどがあるが、今回はGPIO wake upで行う。現状、ttp223外部センサーをGPIO 14に接続しているため。

実際のコードがこちら(main.cpp)。espressifのスリープモード周りのライブラリが充実しているので、シンプル。

C++
#include "esp_sleep.h”

#define HEAD_BUTTON_PIN 14

void setup(){
	// GPIOウェイクアップ機能を有効にする
	gpio_wakeup_enable((gpio_num_t)HEAD_BUTTON_PIN, GPIO_INTR_HIGH_LEVEL);
}

unsigned long lastActionTime = 0; // 前回のアクション実行時刻を記録

void loop(){

// 現在時刻
  const unsigned long currentTime = millis();
	// スリープモードへの移行判定
  if ((currentTime - lastActionTime) > 15 * 1000)
  {
    Serial.println("スリープモードに入ります");
    delay(1000);
    // TODO:
    esp_sleep_enable_gpio_wakeup(); // GPIOウェイクアップを有効にする
	  esp_light_sleep_start();        // ライトスリープモードに入る
    
		
		Serial.println("Active modeになります。");
	// TODO:
    eyeMovement();
    lastActionTime = millis();
  }
  // 音声が再生中ではない場合
  if (!mp3->isRunning())
  {
    eyeMovement();
  }
}

実行ファイルで、#include “esp_sleep.h” が必要。

esp_sleep_enable_gpio_wakeup() 関数

まず、esp_sleep_enable_gpio_wakeup() 関数を使用して、GPIOによるウェイクアップを有効にしておく。

今回の外部タッチセンサーも、esp32と接続している周辺機器に該当し、ライトスリープモードに入ると、CPUから周辺機器への通信接続が一時停止になるので、デフォルトだと、その後外部タッチセンサーをタッチしても命令を受け付けない。なので、あらかじめ、このGPIOピンは、ライトスリープモードに入ったとしても、例外的に通信接続を保ちますよと宣言しておくためのもの。

esp_light_sleep_start() 関数

至ってシンプルで、この関数を呼び出せば、ライトスリープモードに入る。

今回は、現在時刻をmillis()で取得し、テスト的に最後のアクションから15秒以上経過した場合にスリープモードに入るようにした。

gpio_wakeup_enable() 関数

最後に、gpio_wakeup_enable() 関数で、ttp223センサーの状態変化を検出して、ライトスリープモードからデバイスをウェイクアップする。この関数は下記の2つの引数を持つ。

  1. gpio_num_t gpio_num: ウェイクアップに使用するGPIOピン番号。gpio_num_t型なので、int型でGPIOピンを宣言している場合にはキャストする必要がある。もしくは、#define HEAD_BUTTON_PIN GPIO_NUM_14 などと、gpio_num_t型で宣言するか。
  2. gpio_int_type_t intr_type: ウェイクアップのトリガーとなる条件を指定。指定できる条件はGPIO_INTR_LOW_LEVELGPIO_INTR_HIGH_LEVELの2つのみ。GPIO_INTR_LOW_LEVELを指定すると、ピンが低電圧レベルになった時にウェイクアップし、GPIO_INTR_HIGH_LEVELを指定すると、ピンが高電圧レベルになった時にウェイクアップする。

TTP223センサーはデフォルトでactive HIGHなので(下記記事参照)、今回は、gpio_wakeup_enable(HEAD_BUTTON_PIN, GPIO_INTR_HIGH_LEVEL); となる。

ちなみにeyeMovement()関数は、st7735s TFT LCDディスプレイで目を動かし続けるための関数。

完成!

静止画なので分かりづらいが、無事最後のタッチ操作から15秒が経過すると、その時の最後の目の状態で一時停止状態になってスリープモードに入り

スリープモード状態で、タッチセンサーを押すと、Active modeに戻る。

残タスク:よりインタラクティブにするために

Light-sleep modeに入る前に、スリープ用のアニメーション画像と音声「おやすみ〜 / 眠くなってきたにゃぁ など」を表示する。

また、スリープ中から、ユーザーに頭をタッチされてawakeになる時に、eyeMovementではなく、寝起きのアニメーション画像と音声「起こしたん? / 寝とったのに、もう など」を表示することで、よりインタラクティブな要素を演出できるようにする。

コメント

タイトルとURLをコピーしました