ESP32 の Power Management 機能
この記事では,ESP32をバッテリー駆動するためのヒントをお届けします.
はじめに
ESP32とは?
ESP32とは,WiFi/BLEを搭載したIoTマイコンです.
CPUが240MHzのデュアルコアで,RAMも520kBと, マイコンにしてはスペックが高いところが魅力です.
シリアル通信などのペリフェラルも充実していて, UART, I2C, SPIなどのピンを好きなGPIOピンに割り当てることができて超便利です!!
また,公式のSDKにFreeRTOSというRTOSが組み込まれていて, 容易にマルチタスクのアプリケーションを動作させることができます.
ESP32についての記事一覧はこちら
電流消費ハンパないって!!
そんなESP32は,省エネ仕様を謳っています.
バッテリー駆動も普通にできるのかと思いきや, スリープなどを考えずに使用すると, CPUだけでも常時50mA程度消費し, WiFiと同時に起動すると100mAを超えるほどになります.
これでは,どんなに大きなバッテリーを用意しても数日しか持ちません.
そこで今回は,ESP32をバッテリー駆動させるため,Power Management 機能について紹介します.
Power Management
概要
ESP32 の Power Management は,ESP32公式でサポートされている機能です.
以下の3つを制御して,省エネを図ります.
- CPUの周波数
- APB (ペリフェラルのクロック) の周波数
- Light-Sleep
実際には,Power Management 機能を有効化した状態で,
// FreeRTOS の delay 関数
// 1000 [ms]
vTaskDelay(1000 / portTICK_PERIOD_MS);
などを実行すると,その間はCPUのクロックを止めてくれるというものです.
参考
- ESP-IDF Programming Guide - Power Management
動作環境は ESP-IDF
PowerManagementのLight-Sleepを使用するためには,
make menuconfig
を編集する必要があるので,
ESP-IDF環境で実行する必要があります.
ESP-IDFの導入はこちら
Power Management 機能の使い方
make menuconfig
の設定
Power Management における Light-Sleep を有効化するためには,
make menuconfig
の
Component config/FreeRTOS/Tickless idle support
にチェックを入れます.
これで,FreeRTOSのdelay中にクロックを止めることができます.(FreeRTOSにそういう機能がちゃんとある)
Light-Sleepを使用しない場合,Arduino環境でも一応動作するようですが,240MHzが40MHzに落ちるだけなので,あまり効果がないかもしれません.
インクルードファイル
#include "esp_pm.h"
初期化
以下のコードを実行すると,PowerManagement機能がスタートします.
#include "esp_pm.h"
void pm_init() {
esp_pm_config_esp32_t esp_pm_config_esp32;
esp_pm_config_esp32.max_cpu_freq = RTC_CPU_FREQ_240M; //< make menuconfigで設定したCPU周波数
esp_pm_config_esp32.min_cpu_freq = RTC_CPU_FREQ_XTAL;
esp_pm_config_esp32.light_sleep_enable = true;
esp_pm_configure(&esp_pm_config_esp32);
}
省エネ動作
例えば,以下のような関数を呼べば,その間スリープとなります.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void sleep_ms(uint32_t ms) {
vTaskDelay(ms / portTICK_PERIOD_MS);
}
スリープの禁止
シリアル通信をするときや,何かを駆動するときなど, delayはするけどCPUのクロックを変えたくない場合があります.
そんなときは,Power Management Locks を使います.
PowerManagementの各機能
- CPUの周波数
- APB (ペリフェラルのクロック) の周波数
- Light-Sleep
に対して,ロックハンドルを生成し, ロックの獲得,解放を行います.
実行例は以下の通りです.
#include "esp_pm.h"
// CPU の周波数固定ハンドル
esp_pm_lock_handle_t lock_handle_cpu;
// APB の周波数固定ハンドル
esp_pm_lock_handle_t lock_handle_apb;
// Light-Sleepの禁止ハンドル
esp_pm_lock_handle_t lock_handle_light;
// ハンドルの初期化
void pm_lock_init() {
// CPU の周波数固定ハンドル
esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "cpu", &lock_handle_cpu);
// APB の周波数固定ハンドル
esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "apb", &lock_handle_apb);
// Light-Sleepの禁止ハンドル
esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "light", &lock_handle_light);
}
// ロックの獲得
void acquire(esp_pm_lock_handle_t lock_handle) {
esp_pm_lock_acquire(lock_handle);
}
// ロックの解放
void release(esp_pm_lock_handle_t lock_handle) {
esp_pm_lock_release(out_handle);
}
// 実行例
void pm_lock_example(){
pm_lock_init(); //< ロックハンドルを初期化
acquire(lock_handle_apb); //< APB周波数を固定
// シリアル通信などを行う
release(lock_handle_apb); //< 解放
}
C++ ラッパ
C言語はわかりづらいので,C++のラッパを作ってみました.
GitHub上のコードが最新です.以下の説明は古い可能性があります.
PowerManagement クラス
直観的に使えるようになっています.
class PowerManagement {
public:
static void init();
static void printLockStatus();
class Lock {
public:
Lock(esp_pm_lock_type_t lock_type, const char *name);
void acquire();
void release();
private:
esp_pm_lock_handle_t lock_handle;
};
};
実行例
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "PowerManagement.h"
extern "C" void app_main() {
// PowerManagement機能の有効化
PowerManagement::init();
// ロックハンドルの生成
PowerManagement::Lock cpu_clock_lock(ESP_PM_CPU_FREQ_MAX, "my cpu lock");
PowerManagement::Lock apb_clock_lock(ESP_PM_APB_FREQ_MAX, "my apb lock");
PowerManagement::Lock light_sleep_lock(ESP_PM_NO_LIGHT_SLEEP, "my light-sleep lock");
cpu_clock_lock.acquire(); //< ロック
// write tasks under normal CPU clock frequency
cpu_clock_lock.release(); //< 解放
vTaskDelay(3000 / portTICK_PERIOD_MS); //< Sleep
apb_clock_lock.acquire(); //< ロック
// write tasks under normal ABP clock frequency
apb_clock_lock.release(); //< 解放
vTaskDelay(3000 / portTICK_PERIOD_MS); //< Sleep
light_sleep_lock.acquire(); //< ロック
// write tasks under no light-sleep
light_sleep_lock.release(); //< 解放
vTaskDelay(portMAX_DELAY); //< eternal sleep
}
サンプルプロジェクト
上記のサンプルコードにログ出力のコードを追加したものです.
一応,そのまま動くはずです.
実行例
unzip PowerManagement.zip
cd PowerManagement
make menuconfig # シリアルポートの設定
make flash monitor
注意
Light-Sleepの注意
Light-Sleepの間は,普通のGPIOはオープン状態になります.
例えば,LEDを繋いでいた場合は,消えてしまいます.
代わりに,RTC_GPIOを使うことで,Light-Sleep中もキープさせることができます.
例えば,Light-Sleep中でもLEDを光らせたければ,以下のようにします.
#include "driver/rtc_io.h"
void rtc_gpio_test() {
// rtc_gpio init
gpio_num_t pin_led = GPIO_NUM_27; //< LEDをつけたピン
rtc_gpio_init(pin_led); //< 初期化
rtc_gpio_set_direction(pin_led, RTC_GPIO_MODE_OUTPUT_ONLY); //< 出力に設定
// Light-Sleep中でも持続するLED
rtc_gpio_set_level(pin_led, 1); //< LED点灯
rtc_gpio_hold_en(pin_led); //< 持続を有効化
vTaskDelay(1000 / portTICK_PERIOD_MS); //< Light-Sleep
rtc_gpio_hold_dis(pin_led); //< 持続を無効化
}
詳しくは,公式リファレンス - GPIO をご覧ください.
まとめ
今回の内容でESP32のバッテリー駆動への足掛かりになりました.
さらに,Modem-Sleepという,Wi-Fiの省エネを図るスリープもあるので,後日紹介したいと思います.
では,みなさんも良きESP32ライフを!