- 最終更新日
- 記事公開日
ESP32を乾電池で動かす(DeepSleepで省電力&電池切れチェック)
今回は、ESP32開発ボードを乾電池(単三電池×2個)で動かす方法について紹介します。
「すぐに電池がなくなってしまわないか不安」
「乾電池を繋いでみたけど、うまく動かなかった」
という方は、ぜひ参考にしてみて下さい。
乾電池の基本
ESP32の動作電圧は、2.2V~3.6V(3.3V推奨)です。
単三電池一本の電圧が約1.5V。
よって単三電池が2本あれば、ESP32を動かすことが可能となります。
DeepSleep機能(省電力モード)
一般的なアルカリ乾電池(単三)の容量は、約1,000mAh。
1,000mAhは、1000mAの電流を一時間流すことができるという意味です。
500mAなら2時間です。
ESP32の消費電流は、約100mA。
単三電池1個で、ESP32を約10時間使えることになります(理論上)
電池が2個なら、約20時間です。
もちろん、大きな負荷のかかる処理を行えば、その分だけ消費電流は増え、電池の消耗も早くなります。
例えばWi-Fiに接続した場合、瞬間的に500mAもの電流を消費してしまいます。
そこで、できるだけ電池の持ちを良くするために『DeepSleep』という機能を使用します。
DeepSleepとは、マイコンのほとんどの動作を停止して、消費電力を抑えた状態で待機するモードです。
DeepSleepを使えば、消費電流を0.1mA以下まで抑えることができます。
void setup() {
// ディープスリープ突入
esp_deep_sleep_start();
}
DeepSleepからの復帰方法は、タイマーを使用したり、タッチパネルを触れた瞬間、GPIOへの信号をトリガーにするなど、さまざまな指定が可能です。
例えば、押しボタンスイッチで(GPIO34がHIGHになった瞬間)ディープスリープモードを解除するには、以下のようなコードとなります。
// ディープスリープ時にも保持できる変数
RTC_DATA_ATTR int bootCount = 0;
void setup() {
// GPIO34ピンの押しボタンを押すとDeep Sleepから復帰
// GPIO34 → 34^2 = 17179869184
// 17179869184(10進数) → 400000000(16進数)
esp_sleep_enable_ext1_wakeup(0x400000000, ESP_EXT1_WAKEUP_ANY_HIGH);
// 起動回数(ディープスリープの回数)をカウント
bootCount++;
// 初回起動時は実行しない
if (bootCount > 1) {
// メイン処理
pinMode(25,OUTPUT);
digitalWrite(25,HIGH);
delay(1000);
}
// ディープスリープ突入
esp_deep_sleep_start();
}
void loop() {
}
電池を繋いだ直後は、DeepSleepは起動していません。
よって、esp_sleep_enable_ext1_wakeup以降のプログラムが実行されてしまいます。
そこで初回起動時には、メイン処理が実行されないようにカウントを行っています。
押しボタンスイッチを押すと、1秒間緑色のLEDが点灯したあとに、DeepSleepに突入します。
また押しボタンスイッチを押すと、DeepSleepから復帰して、1秒間緑色のLEDを点灯後、すぐにDeepSleepに突入します。
電池切れチェック機能(自己電圧計測)
電池が完全になくなってしまうと、ESP32が起動できなくなってしまいます。
また、Wi-Fi接続などは大きな電力を必要とします。
電池が消耗していると、ESP32自体の起動はギリギリできても、Wi-Fiに接続するための電力が足りない状態に陥る可能性があります。
そうなると、ESP32がフリーズしたり、故障の原因となってしまいます。
そこで、ESP32の自己電圧を測定し、電池切れを起こす前に通知する機能が必要です。
電池切れチェックには、ADC(アナログ-デジタル変換回路)を使います。
ESP本体のGPIOピンからHIGHのデジタル信号を出力。
そのまま、ESP本体の別のGPIOピンで電圧を読み取ります(ADC)
もし、ADCで読み取った電池の電圧値が低くなっていたら、LEDを点灯させるなどの処理を行います。
以下、サンプルコードです。
// LED Pin
int red_pin = 32;
// ADC Pin
int out_pin = 2;
int in_pin = 15;
void setup() {
// LED Pin設定
pinMode(red_pin,OUTPUT);
// ADC Pin設定
pinMode(out_pin,OUTPUT);
pinMode(in_pin,ANALOG);
// 自己電圧を測定するための信号を出力
digitalWrite(out_pin,HIGH);
// 自己電圧の読み取り
analogSetAttenuation(ADC_0db);
float adc = analogRead(in_pin);
float volt = adc / 4096 * 0.95;
// 電池の電圧が低いときの処理
if(volt < 0.6) {
digitalWrite(red_pin, HIGH);
delay(1000);
esp_deep_sleep_start();
}
}
void loop() {
}
GPIOピンから出力された電圧を計測するために、抵抗器で分圧しています。
プルダウン抵抗を10kΩ、入力抵抗を30kΩとしました。
分圧抵抗値は、ADCの「基準電圧」と「減衰率」をベースに算出しています。
基準電圧とは?減衰率とは?
例えば、新品電池の電圧が3.3Vを示していたとします。
何日か使用したところ3.0Vまで降下しました。
このときの基準電圧は、新品電池の電圧「3.3V」となります。
しかし、ESP32の基準電圧をこちらで決めることはできません。
予め用意された関数(analogSetAttenuation)を用いて、処理する必要があります。
Analog to Digital Converter (ADC) – ESP32
※上記は「ESP-IDF」開発環境の資料です(ESP32の開発メーカーEspressifがリリースしている開発ソフト)今回は「Arduino IDE」開発環境を使用しているので、この資料とは設定コマンドが異なります。ご注意ください。
要約すると、
- ESP32でのADC基準電圧は、約1.1V
- 1.1Vより大きい電圧を入力したときは、減衰して測定される
- 減衰率は4つのオプションから選択可能
という感じです。
デフォルト設定では「ADC_11db」となっています。
ADC_11dbの場合、測定可能範囲は0.15V ~ 2.45V。
よって、ADCピンに入る電圧を2.45V以内に収まるように分圧する必要があります。
もし2.45Vを超えるような電圧(例えば3.0V)を入力しても、ADCの値は2.45Vを示します。
今回のサンプルでは、減衰なしの「ADC_0db」に設定しています。
測定可能範囲は0.1V ~ 0.95V。
10kΩと30kΩで分圧すれば、ADC入力ピンに入る電圧は最大0.825Vと範囲内に収まります。
(10kΩ /10kΩ + 30kΩ) * 3.3V = 0.825V
減衰率が少ない分、誤差も少なくなるはずです。
ADCデータの読み取り
ADCのデータ取得は「analogRead」で行います。
取得したデータは12bitの2進数(最大:111111111111)で構成されており、10進数(最大:4095)に変換されて、変数に格納されます。
// GPIO15ピンでADCデータの読み取り
float adc = analogRead(15);
例えば、ADCピンに0.95Vが入力されると、adcには「4095」が格納されます。
ADCピンに0.8Vが入力された場合は、「3448」が格納されます。
0.8 / 0.95 * 4096 = 3448
※4096 = 0を含む4095段階
この値を電圧に変換する数式は以下となります。
float volt = adc / 4096 * 0.95;
実際に電池(3.15V)で計測してみた結果、以下のようになりました。
理論値
(10kΩ /10kΩ + 30kΩ) * 3.15V = 0.78V
テスターでの実測値
0.76V
ADC後のvolt値
0.72V
0.1V以下の誤差なので、悪くないと思います。
実際の使用例
押しボタンスイッチを押すと、LINE通知する装置です。
Wi-Fi接続の際に大きな電流を消費するので、電池の電圧が下がると、あっという間に動かなくなってしまいます。
できるだけ無駄な電池の消耗を防ぐ必要がありました。
そこで、DeepSleep(省電力モード)を使用しています。
この装置では、ボタンが押されると、DeepSleepから復帰。
Wi-Fiに接続し、Line通知が終わると、またDeepSleepに突入します。
その際、乾電池からESP32に供給される自己電圧を測定しています。
もし、電圧が基準ライン以下まで下がっていた場合は、電池切れと判定。
警告灯(赤色LED)を1秒点灯し、再びDeepSleepに突入します。
LINE通知するメッセージの内容は、分圧した電圧値です。
使用パーツ
- WayinTop ESP32(ESP-WROOM-32)- Amazon
- 抵抗(1kΩ)
- 抵抗(10kΩ)
- 抵抗(30kΩ)・・・ 10kΩを3つ直列接続
- LED(赤・緑・黄)
- 電解コンデンサ(470μF/16V)
- 押しボタンスイッチ
配線・回路図
Wi-Fi接続が不安定になることがあったので、電源ラインに電解コンデンサを取付けています。
おそらく、Wi-Fi接続時に大きな電力を消費する為、一時的に電力不足に陥るのだと思います。
プログラム
以下の変数は各自書き換えて下さい↓
- ssid
- password
- token
※LINE通知を行うには、自分用のトークンを発行しておく必要があります。詳しくは、下記の記事をご覧ください↓
#include <WiFi.h>
#include <WiFiClientSecure.h>
// WiFi設定
const char* ssid = "your-ssid";
const char* password = "your-password";
// LINE Notify設定
const char* host = "notify-api.line.me";
const char* token = "your-token";
// デバック用LEDに使用するPinの設定
int red_pin = 32;
int yellow_pin = 27;
int green_pin = 25;
// ADCに使用するPinの設定
int out_pin = 2;
int in_pin = 15;
// ディープスリープ時にも保持できる変数
RTC_DATA_ATTR int bootCount = 0;
void setup()
{
// Deep Sleepからの復帰
esp_sleep_enable_ext1_wakeup(0x400000000, ESP_EXT1_WAKEUP_ANY_HIGH);
// シリアルモニタの通信速度
Serial.begin(115200);
// Pin設定
pinMode(red_pin,OUTPUT);
pinMode(yellow_pin,OUTPUT);
pinMode(green_pin,OUTPUT);
pinMode(out_pin,OUTPUT);
pinMode(in_pin,ANALOG);
// 自己電圧を測定するための信号を出力
digitalWrite(out_pin,HIGH);
// 自己電圧の読み取り
analogSetAttenuation(ADC_0db);
float adc = analogRead(in_pin);
float volt = adc / 4096 * 0.95;
// 自己電圧をシリアルモニタに表示
String prt_volt = String("\n") + volt + String("V(") + int(adc) + String(")");
Serial.println(prt_volt);
// 電池の電圧が低いときは、警告LEDを表示&DeepSleepに突入
if(volt < 0.7) {
digitalWrite(red_pin, HIGH);
digitalWrite(green_pin, LOW);
delay(1000);
esp_deep_sleep_start();
}
// 正常に起動したことを示す緑色のLEDを点灯
digitalWrite(green_pin, HIGH);
// 起動回数(ディープスリープの回数)をカウント
bootCount++;
String prt_cnt = String("起動回数: ") + int(bootCount) + String("\n");
Serial.println(prt_cnt);
// 最初に起動したときは実行しない(押しボタンを押さなくても動いてしまう)
if (bootCount > 1) {
// Wi-Fi
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
// WiFi接続
WiFi.begin(ssid, password);
// WiFiの接続状態を確認
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Lineにリクエストを送信する
send_line(volt);
// LED点灯
digitalWrite(yellow_pin, HIGH);
delay(1000);
}
// LED消灯
digitalWrite(red_pin, LOW);
digitalWrite(yellow_pin, LOW);
digitalWrite(green_pin, LOW);
// ディープスリープ突入
esp_deep_sleep_start();
}
// line通知
void send_line(float get_message) {
// HTTPSへアクセス(SSL通信)するためのライブラリ
WiFiClientSecure client;
// サーバー証明書の検証を行わずに接続する場合に必要
client.setInsecure();
Serial.println("Try");
//LineのAPIサーバにSSL接続(ポート443:https)
if (!client.connect(host, 443)) {
Serial.println("Connection failed");
return;
}
Serial.println("Connected");
// リクエスト送信
String send_message = String(get_message) + String("V");
String query = String("message=") + String(send_message);
String request = String("") +
"POST /api/notify HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Authorization: Bearer " + token + "\r\n" +
"Content-Length: " + String(query.length()) + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
query + "\r\n";
client.print(request);
// 受信完了まで待機
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line == "\r") {
break;
}
}
String line = client.readStringUntil('\n');
Serial.println(line);
}
void loop(){
}
シリアルモニタに出力された結果
試作機
編集後記
押しボタンで制御するシステムの場合、電池を節約する方法は他にも色々あります。
- モーメンタリスイッチではなく、オルタネイトボタンを使用する
- トランジスタでラッチ回路を作る
- 汎用ロジックIC(74HC74/フリップフロップ)で回路を作る
- ラッチングリレーを利用する
- セルフロックモジュールを利用する(Amazon)
しかし、「Wi-Fi使用時に流れる500mA以上の電流に耐えうるトランジスタやICの選定が大変」「部品点数が多くなる」「定格電圧が合わない」「コストが高くなる」など、さまざまな面から今回のDeepSleepを使う方法が一番スムーズだと判断しました。