最終更新日
記事公開日

ESP32を乾電池で動かす(DeepSleepで省電力&電池切れチェック)

単三電池2本で動くESP32開発ボード

今回は、ESP32開発ボードを乾電池(単三電池×2個)で動かす方法について紹介します。

「すぐに電池がなくなってしまわないか不安」

「乾電池を繋いでみたけど、うまく動かなかった」

という方は、ぜひ参考にしてみて下さい。

乾電池の基本

乾電池

ESP32の動作電圧は、2.2V~3.6V(3.3V推奨)です。

単三電池一本の電圧が約1.5V。

よって単三電池が2本あれば、ESP32を動かすことが可能となります。

ESP32と乾電池の配線図

DeepSleep機能(省電力モード)

DeepSleep(省電力モード)中のESP32

一般的なアルカリ乾電池(単三)の容量は、約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機能(省電力モード)のサンプル回路・配線図

電池を繋いだ直後は、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) の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)
  • 押しボタンスイッチ

配線・回路図

押しボタンスイッチを押すと、スマホに電池の電圧をLINE通知する装置

Wi-Fi接続が不安定になることがあったので、電源ラインに電解コンデンサを取付けています。

おそらく、Wi-Fi接続時に大きな電力を消費する為、一時的に電力不足に陥るのだと思います。

プログラム

以下の変数は各自書き換えて下さい↓

  1. ssid
  2. password
  3. token

※LINE通知を行うには、自分用のトークンを発行しておく必要があります。詳しくは、下記の記事をご覧ください↓

【ESP32】ボタンを押したら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を使う方法が一番スムーズだと判断しました。

この記事のURLをコピー

メールアドレスは公開されませんのでご安心ください。また、* が付いている欄は必須項目となります。

内容に問題なければ、下記の「コメントを送信する」ボタンを押してください。

関連情報

運営者プロフィール
コダマ

職業はIT系フリーランス。過去、電子配線業務の経験が10年ある為、はんだづけも得意です。宮崎県在住、30代・2児の父親。

カテゴリー