ESP8266を電池で使う

LiPoバッテリーで動かしてみる


ESP8266には3つのスリープモードがあります。
スリープモードの詳細はこちらで 紹介されています。
そこで、DeepSleepモード+バッテリーでどの程度の期間、動くことができるのか実験してみることにしました。
使用したバッテリーは1500mAh 3.2VのLiFePO4バッテリーで、直列に2本使いました。
単三電池4本とほぼ同じ容量です。


3.2Vを2本直列で使うので、レギュレータが必要になります。
レギュレータにはQuiescent Current(無信号時電流)というのがあって、何もしていなくても電気を使います。

そこで、レギュレータの種類を変えて、どの程度動くのか調べてみました。
使用したスケッチは以下のスケッチです。
10分に1回の間欠動作で起動し、RTCのユーザエリアに起動した回数を保持し、その回数をMQTTでサーバーに送ります。
RTCのユーザエリアは512バイト(正確には4バイト領域が128個)ですが、先頭4バイトはCRCとして使って、初回起動時か DeepSleepからの復帰かを判断しています。
その後ろの4バイトを32ビット符号なしのカウンターとして使っています。
MQTTサーバーはローカルなサーバーを使いました。
/*
 Basic ESP8266 MQTT example
 RTC User Memoryを使用するDeelSleepモードのテスト
 Build for WeMos D1 R2 & Mini
*/

ADC_MODE(ADC_VCC);

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Ticker.h>

//#define SLEEP 5       // 5秒
//#define SLEEP 60    // 1分
#define SLEEP 600   // 10分
//#define SLEEP 3600  // 1時間

#define LED_PIN 4

// Update these with values suitable for your network.
const char* ssid = "アクセスポイントのSSID";
const char* password = "アクセスポイントのパスワード";
const char* mqtt_server = "MQTTブローカーのIPアドレス";

WiFiClient espClient;
PubSubClient client(espClient);
Ticker ticker1;

char topic[50];
char payload[50];

//RTC memory(512Byte)の定義
struct {
  uint32_t crc32; // CRC
  uint32_t counter;
  byte data[504]; // User Data
} rtcData;

// Calculate CRC
uint32_t calculateCRC32(const uint8_t *data, size_t length)
{
  uint32_t crc = 0xffffffff;
  while (length--) {
    uint8_t c = *data++;
    for (uint32_t i = 0x80; i > 0; i >>= 1) {
      bool bit = crc & 0x80000000;
      if (c & i) {
        bit = !bit;
      }
      crc <<= 1;
      if (bit) {
        crc ^= 0x04c11db7;
      }
    }
  }
  return crc;
}

// Print RTC memory
void printMemory(int sz) {
  char buf[3];
//  for (int i = 0; i < sizeof(rtcData); i++) {
  for (int i = 0; i < sz; i++) {
    sprintf(buf, "%02X", rtcData.data[i]);
    Serial.print(buf);
    if ((i + 1) % 32 == 0) {
      Serial.println();
    }
    else {
      Serial.print(" ");
    }
  }
  Serial.println();
}

// Connect AP
void setup_wifi() {
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Wait for WiFi...");
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

// Receive MQTT topic
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

// Connect MQTT server
int server_connect() {
  char clientid[20];

  sprintf(clientid,"ESP8266-%6x",ESP.getChipId());
  Serial.print("clientid=");
  Serial.println(clientid);

  Serial.print("Attempting MQTT connection...");
  // Attempt to connect
  if (client.connect(clientid)) {
    Serial.println("connected");
    // ... and resubscribe
    client.subscribe("inTopic");
    return 1;
  } else {
    Serial.print("failed, rc=");
    Serial.print(client.state());
    return 0;
  }
}

void flush_led() {
  static bool flag = true;
  if (flag) digitalWrite(LED_PIN,LOW);
  if (!flag) digitalWrite(LED_PIN,HIGH);
  flag=!flag;
}

void display_Running_Sketch (void){
  String the_path = __FILE__;
  int slash_loc = the_path.lastIndexOf('/');
  String the_cpp_name = the_path.substring(slash_loc+1);
  int dot_loc = the_cpp_name.lastIndexOf('.');
  String the_sketchname = the_cpp_name.substring(0, dot_loc);

  Serial.print("\nArduino is running Sketch: ");
  Serial.println(the_sketchname);
  Serial.print("Compiled on: ");
  Serial.print(__DATE__);
  Serial.print(" at ");
  Serial.print(__TIME__);
  Serial.print("\n");
}

void setup() {
  delay(1000);
  Serial.begin(115200);
  display_Running_Sketch();

  //RTC memoryからデータを読み込む
  if (ESP.rtcUserMemoryRead(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
    Serial.println("Read: ");
    printMemory(10);
    //読み込んだデータでCRC32を計算する
    uint32_t crcOfData = calculateCRC32(((uint8_t*) &rtcData) + 4, sizeof(rtcData) - 4);
    Serial.print("CRC32 of data: ");
    Serial.println(crcOfData, HEX);
    Serial.print("CRC32 read from RTC: ");
    Serial.println(rtcData.crc32, HEX);
    //CRC32が一致しないので初回起動時
    if (crcOfData != rtcData.crc32) {
      Serial.println("CRC32 in RTC memory doesn't match CRC32 of data. Data is probably invalid!");
      rtcData.counter = 0;
      for (int i = 0; i < sizeof(rtcData); i++) {
        rtcData.data[i] = 0;
      }
    }
   
    //CRC32が一致したのでDeelSleepからの復帰
    else {
      Serial.println("CRC32 check ok, data is probably valid.");
      Serial.print("counter=");
      Serial.println(rtcData.counter);
      rtcData.counter++;
    }
  }

  //CRC32を再計算
  rtcData.crc32 = calculateCRC32(((uint8_t*) &rtcData) + 4, sizeof(rtcData) - 4);
  //RTC memoryにデータを書き込む
  //電源不足でAPに接続できないときはカウンターが+1以上になる
  if (ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
    Serial.println("Write: ");
    printMemory(10);
  }

  //初回は起動を確認するためLEDをFlush
  if (rtcData.counter == 0) {
    pinMode(LED_PIN,OUTPUT);
    digitalWrite(LED_PIN,LOW);
    ticker1.attach_ms(50, flush_led);
  }

  //APに接続
  setup_wifi();
  if (rtcData.counter == 0) {
    ticker1.detach();
    digitalWrite(LED_PIN,LOW);
  }

  //mqtt brokerに接続
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  if (server_connect()) {
    sprintf(topic,"Battery/ESP8266-%6x",ESP.getChipId());
    sprintf(payload,"%08x %d %d",rtcData.counter,ESP.getVcc(),SLEEP);
//  sprintf(payload,"%02x%02x%02x%02x",
//  rtcData.data[3],rtcData.data[2],rtcData.data[1],rtcData.data[0]);
    Serial.print(topic);
    Serial.print(":");
    Serial.println(payload);
    client.publish(topic, payload);
    client.disconnect();
  }

  delay(1000);
  WiFi.disconnect();

  //DEEP SLEEPモード突入命令
  Serial.println("DEEP SLEEP START!!");
  uint32_t time_us = SLEEP * 1000 * 1000;
  ESP.deepSleep(time_us , WAKE_RF_DEFAULT);
}

void loop() {
  client.loop();
}


<補足>
その後の調査で、ESP.getResetInfoPtr()を使えば、起動理由が取れることが分かりました。
ESP.getResetInfoPtr()では以下のrst_info構造体のポインターが戻ります。
  struct rst_info{
      uint32 reason;
      uint32 exccause;
      uint32 epc1;
      uint32 epc2;
      uint32 epc3;
      uint32 excvaddr;
      uint32 depc;
  };

この構造体のreasonメンバーが起動理由で、以下のいずれかの値が設定されています。

  REANSON_DEFAULT_RST = 0, // ノーマルスタート。電源オンなど。
  REANSON_WDT_RST = 1, // ハードウェアウォッチドッグによるリセット
  REANSON_EXCEPTION_RST = 2, // 例外によるリセット。GPIO状態は変化しない
  REANSON_SOFT_WDT_RST = 3, // ソフトウェアウォッチドッグによるリセット。GPIO状態は変化しない
  REANSON_SOFT_RESTART = 4, // ソフトウェアによるリセット。GPIO状態は変化しない
  REANSON_DEEP_SLEEP_AWAKE= 5, // ディープスリープ復帰
  REANSON_EXT_SYS_RST = 6, // 外部要因(RSTピン)によるリセット。

  rst_info *prst = ESP.getResetInfoPtr();
  Serial.print("reset reason=");
  Serial.println(prst->reason);

WeMosやNodeMCUなど、GPIO0の自動切り替え回路が含まれているボードでは、ファーム書き換え直後は
REANSON_EXT_SYS_RST = 6
で起動します。
</補足>

OrangePiでMQTTのSubscriberを作成し、受信した日時とMQTTのメッセージをファイルに記録するようにしました。
全てのレギュレータはミニ基盤にはんだ付けして使っています。

AMS1117(1000mA)


TA48M033(500mA)


LP38690(1000mA)


XC6203(400mA)

400mAでは安定して起動できず

LT1763(500mA)



DeepSleep中のタイマーはかなり誤差が有ります。
600秒のSleep時間を指定した場合、DeepSleepから復帰する間隔は大体9分50秒(=590秒)でした。
Quiescent Currentとの関係を一覧にすると以下のようになります
レギュレータ 接続回数 駆動時間 Quiescent Current
AMS1117 0x450=1104回 181時間 2mA
TA48M033 0x63e=1598回 262時間 0.8mA
LP38690 0xd26=3366回 552時間 0.055mA
LT1763 0xb9b=2971回 487時間 0.030mA

AMS1117は安価で出力も大きいので、開発ボードではよく使われるレギュレータですが、電池駆動には全く向かないことが分かります。
レギュレータの違いだけで3倍も結果が変わります。
こちらに XC6202P332と 2500μFのコンデンサーを使って、単4乾電池3本で駆動する記事が紹介されています。
メチャクチャ時間をかけた大作です。

続く...