ESP32マイコンの電池駆動を考える(2)

マイコン/Arduino

以前、ニッケル水素電池でESP32-C3を駆動させる記事を書きました。

ESP32-C3のDeepSleepの機能をつかって間欠動作させて超低消費電力とし、電池による長時間駆動を目指す試みです。

今回はアップデートとして、バッテリーの見直しによって低消費電力化を目指してみました。

ESP32-C3

今回使用するマイコンはESP32-C3です。ESP32シリーズの中でも低消費電力をうたっている製品です。

秋月電子ではマイコン単体で400円以下で販売されていますし、使いやすいように2.54ミリ間隔でピンを引き出しているXIAO ESP32C3なども販売されています。

電源問題

データシートによると駆動電圧は3.0~3.6V、消費電流は500mAとなっています。

消費電流がかなり大きく見えますが、これはWifi通信をしたときなどに瞬間的に流れる値で、実際には10~20mA程度の時間がほとんどです。

これらの仕様が結構やっかいです。

まず上限値は3.6Vなので公称3.7Vのリチウムイオン電池は使用できません。0.1Vぐらいなら超えても大丈夫な気はしますが、リチウムイオン電池の電圧は充電直後には4Vを超えますのでそのまま使っちゃえとは言いにくいです。ニッケル水素電池×3というのも同様に公称値は1.2×3=3.6Vですが充電直後はもっと高い電圧です。

つまり、もっと高い電圧を用意したうえでレギュレータかDCDCコンバータでちょうどよい電圧を作り出す必要があるのです。この場合、これらの降圧素子の消費電流が無視できないほど大きいため、せっかくのESP32のDeepSleep時の消費電力の低さが生かせません。

また、瞬間的とは言え数百mAの電流が必要なので、電池の内部抵抗による電圧低下も無視できません。大電流が流れた瞬間の電圧低下でESP32にリセットがかかってしまいます。
ニッケル水素電池3本直列(≒3.6V)をマイコンに直結させて実験を行ったこともありますが、電池容量の大半を残したまま、通信開始でリセットがかかりループする状態になったことがあります。ニッケル水素電池は内部抵抗が小さい部類だと思いますが、それでも突入電流による電圧降下が問題になったわけですね。

レギュレーターの消費電力

先に述べたように、DCDCコンバーターやレギュレーターを使用する場合、これらの待機電力や変換効率も無視できません。

レギュレーターを使用した場合、ドロップ電圧×電流がまるごとロスになります。例えば5V電源から3.3Vに降圧した場合、変換効率は66%です。

一方、DCDCコンバーターでは80~90%以上の変換効率ですが待機電力が大きい傾向があるため、駆動時間の大半を占めるDeepSleep時の損失が馬鹿になりません。

ESP32のDeepSleepを使用して極限まで消費電力を切り詰めたい場合にはバッテリーを直接ESP32に接続して使用したいところです。

救世主-18650LFPバッテリーの登場

そんなことを考えていたころ、2024年末に突如としてオーム電機から18650型の単セルLFPバッテリーが販売されました。怪しげな製品ではなく、JBRC会員企業様の製品です。

LFPバッテリーの簡単で安全な充電方法を考える(2)
オーム電機から単セルのLFPバッテリーが発売されていたので購入して検証してみました。件の電池今回購入したのはこちら、オーム電機のセンサーライト専用の18650型リン酸鉄リチウムイオン電池(LS-BLFP18A-S)です。LFPバッテリーは充...
https://www.yodobashi.com/product/100000001008692265/

公称電圧は3.2V、充電電圧も3.6VなのでESP32-C3の駆動電圧にもぴったりと一致します。従来のリチウムイオンバッテリーと比べて発火・爆発しにくく安全性が高いのもメリットです。

電池容量は1800mAhです。18650型としては控えめではありますが、内部抵抗が小さく放電末期までの安定動作が期待できます。

今回はこの電池をつかってESP32-C3の長時間駆動を試してみました。

ESP32に限らず、3.3V動作のモジュール・素子は多いので好都合ですね。
ただし、本来はオーム電機から発売されている機器専用の電池です。本記事ではメーカー指定外の使い方をしている点はご留意ください。

仕様・構成

以下のような仕様・構成を考えます。

  • 数十秒~数十分ごとの間欠動作
  • 電源レギュレータなどは使用せず、ESP32C3マイコン(生)にLFPバッテリー(単セル)を直結
  • 環境センサー(BME280)からのデータを収集し、Wifi経由でSQLサーバーにデータを送信
  • 送信データには、I2C接続による気温湿度気圧センサーの他、バッテリー電圧の情報も含む

I2Cセンサー

I2Cバスに接続するセンサーの電源はESP32C3のGPIOから供給しました。電源ラインに直結してしまうと無駄に電力を消費してしまうからです。

電源電圧の測定

電源電圧を470kΩの抵抗を2つ使って半分に分圧したうえでESP32内蔵のADC(-11dbのアッテネータ)を使用して測定します。

実はここが消費電流のネックになっていた部分です。以前の実験では10kΩで分圧していたため、この抵抗だけで160~170μAも消費してしまっていました。(待機時の電流の大部分が電源測定用の抵抗で消費されてしまっていました)。

ESP32のADCの入力インピーダンスは公開されていませんが、実際測定をした方のブログ記事によると数M~10数MΩ程度とのことでした。シャント抵抗を470kΩ×2≒1MΩとすると少々誤差が大きくなりそうですが、ここは電池交換のための概ねの値がわかればよいこととして、この値に設定しました。

3.3V÷1MΩ=3.3μAなのでESP32C3単体のDeepSleep中の値と同程度ではありますが、この水準まで切り詰めると動作中の電流消費の方が支配的になってきます。
また、動作中のみシャント抵抗に電流が流れるような方法も色々と試してみたのですがうまくいかなかったため、今回は電源にシャント抵抗を直結しています。ここは今後の課題ですね。

実験してみた

回路図

実験のために作成した回路は以下のようなものです。LFPバッテリーとESP32を直結しています。本来であれば最低限ヒューズは入れるべきかと思います。再現される場合は各自安全策をお願いします。

なお、回路図ではBME280を描いていませんが、I2CバスでESP32C3と接続し、電源はGPIO5から取っています。

スケッチ

WiFiへの接続は100msecごとにリトライし、6秒以内に接続が確立しない場合は強制的にDeepSleepに移行するようにしています。
リトライ回数は、大半の場合で5回以下で済みますが、10回以上リトライがかかる場合もあります。また、数十~数百回に1回ぐらいの割合で接続できずにデータ送信失敗になる場合があります。この辺りは電波状況等にもよると思いますが、電力消費が厳しくなる要因ですね。

#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include "php2sql.h"

#define SCL 7
#define SDA 6
#define ADCPIN 1
#define I2CPIN 5
#define LEDPIN 8

#define WDT 6000

#define interval_NRM 30 //unit:sec
#define interval_LV1 30
#define interval_LV2 3600
#define LOWVOLTAGE_1 3000 //unit:milliVolt
#define LOWVOLTAGE_2 2800
unsigned long interval = interval_NRM;

//WiFi
WiFiClient client;
const char* ssid = "your-ssid";
const char* password = "your-password";

//Ambient
PHP2SQL p2s;
String url = "http://192.168.0.111/";
String php = "insdata.php";
String tb = "tb1111";

//SENSORS
Adafruit_BME280 bme;

//コア0に割り当てるスレッド
TaskHandle_t thp[1];

void OffTimer(void *args) {
  delay(WDT);
  goDeepSleep();
}

void goDeepSleep()
{
  digitalWrite(LEDPIN, LOW);
  WiFi.disconnect(true);
  esp_deep_sleep_start();
  delay(100);
}

void sendAmbient()
{
  int v ;
  float temp, pressure, humid;
  float lux=0;

  int loop = 0;

  //電圧測定
  v = analogRead(ADCPIN); delay(1);
  v += analogRead(ADCPIN); delay(1);
  v += analogRead(ADCPIN); delay(1);
  float mv = (float)v * 1.41334 / 3;
  if (mv < LOWVOLTAGE_2)
  {
    interval = interval_LV2;
    esp_sleep_enable_timer_wakeup(interval * 1000000ULL);
    goDeepSleep();
  }
  else if (mv < LOWVOLTAGE_1)
  {
    interval = interval_LV1;
    esp_sleep_enable_timer_wakeup(interval * 1000000ULL);
  }

  //気圧気温湿度
  digitalWrite(I2CPIN, HIGH); delay(10);
  if (!bme.begin(0x76))
  {
    goDeepSleep();
  }

bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // temperature
Adafruit_BME280::SAMPLING_X1, // pressure
Adafruit_BME280::SAMPLING_X1, // humidity
Adafruit_BME280::FILTER_OFF);
bme.takeForcedMeasurement();

temp = bme.readTemperature();
pressure = bme.readPressure() / 100.0F;
humid = bme.readHumidity();

digitalWrite(I2CPIN, LOW);
digitalWrite(LEDPIN, HIGH);

// Wi-Fi接続
  WiFi.begin(ssid, password);
  delay(200);
  while (WiFi.status() != WL_CONNECTED) {
    digitalWrite(LEDPIN, LOW);
    loop++;
    delay(50);
    digitalWrite(LEDPIN, HIGH);
    delay(50);
  }

  p2s.set(0, mv);
  p2s.set(1, temp);
  p2s.set(2, humid);
  p2s.set(3, pressure);
  p2s.set(4, loop);
  p2s.set(5, lux);
  p2s.send();

  goDeepSleep();
}

void setup()
{
  //ディープスリープ時間の設定
  esp_sleep_enable_timer_wakeup(interval * 1000000ULL);

  //WDTスタート
  xTaskCreatePinnedToCore(OffTimer, "OffTimer", 4096, NULL, 3, &thp[0], 0);

  //Pin設定
  Wire.begin(SDA, SCL);
  pinMode(I2CPIN, OUTPUT);
  pinMode(ADCPIN, ANALOG);
  pinMode(LEDPIN, OUTPUT);
  digitalWrite(LEDPIN, HIGH);
  analogSetAttenuation(ADC_11db);

  p2s.begin(url, php, tb);
  sendAmbient();
}

void loop()
{
}

<php2sql.h>

#ifndef php2sql_h
#define php2sql_h
#define dataN 10

class PHP2SQL {
  private:
    float D[dataN];
    bool eD[dataN];
    String URL;
    String PHP;
    String TB;
  public:
    PHP2SQL();
    void set(int i, float data);
    void send();
    void begin(String url, String php, String tb);
};

#endif

<php2sql.cpp>

#include <HTTPClient.h>
#include "php2sql.h"

//PHP経由でSQLにデータを書き込むライブラリ
PHP2SQL::PHP2SQL()
{
}

void PHP2SQL::begin(String url, String php, String tb)
{
  URL = url;
  PHP = php;
  TB = tb;
}

void PHP2SQL::set(int i, float data)
{
  D[i] = data;
  eD[i] = true;
}

void PHP2SQL::send()
{
  HTTPClient http;
  String targetURL = URL + PHP + "?table=" + TB;
  for (int i = 0; i < dataN; i++)
  {
    if (eD[i])
    {
      targetURL += "&d" + String(i) + "=" + String(D[i]);
      eD[i] = false;
    }
  }
  http.begin(targetURL);
  int httpCode = http.GET();
  http.end();
}

SQLサーバにデータを登録するPHPについては過去の記事を参照してください。

結果

(1)30秒間隔でデータの測定とデータ送信を行った結果、489時間の駆動を確認できました。平均消費電流は約3.7mAです。

(2)120秒間隔でデータの測定とデータ送信を行った結果、1841時間の駆動を確認できました。平均消費電流は約1mAです。

以前の検証で、単三型ニッケル水素電池×4本でXIAO ESP32C3を駆動させた場合よりも長時間駆動させることができました。(60日→75日)

ESP32を乾電池で動かすための消費電力測定
ESP32を乾電池で長時間駆動させるための調査、実験を行いました。必要とされる3.3V電源の生成方法とESP32の開発ボードの組み合わせでいくつかのパターンを検証しました。

次のグラフは120秒間隔で動作させたときの電圧ログの結果です。

まとめ

今回はLFPバッテリーを使用してESP32C3マイコンを駆動させてみました。

動作電圧と電池電圧がぴったりとかみ合ったことでレギュレーター等消費電力の増大を招く素子を使う必要がなくなり、長時間駆動が実現できました。

なお、リチウムイオン電池の取り扱いには各自十分に注意してください。LFPバッテリーは従来のリチウムイオン電池よりは安全性が高いとは言われていますが、ショートさせれば火花ぐらい簡単に飛びますし、爆発・発火の可能性は0ではありません。

コメント