ESP8266を電池で使う

DeepSleepタイマーの精度


ESP8266を乾電池で使う場合、スリープは必須の要件となります。
ESP8266には3つのスリープモードがあります。
スリープモードの詳細はこちらで 紹介されていますが、乾電池で使う場合は、最も消費電流の小さいDeepSleepを使うことになります。
DeepSleep中は微弱電流で動き続けますが、DeepSleep中のタイマーの精度がどこにも見当たらなかったので調べてみました。

使用したスケッチは以下のスケッチです。
3600秒に1回の間欠動作で起動し、RTCのユーザエリアに起動した回数を保持し、その回数をMQTTでサーバーに送ります。
ESP.getResetReason()を使って、DeepSleepからの復帰かどうかを判定して います。

IO16ピンは通常はLOWですが、「Deep Sleep」中はHIGHとなります。
ESP.deepSleep() 関数の引数で指定した時間が経過すると、IO16ピンはLOWとなります、
IO16ピンをRESETピンに接続しておくと、リセットにより「Deep Sleep」から復帰します。
この時、ESP.getResetReason()は[Deep-Sleep Wake]となります。

RTCユーザエリアの使い方はこちらこちらを参考に させていただきました。
ありがとうございます。

RTCメモリーのサイズは768バイトですが、先頭の256バイトはシステムが使うので、ユーザが使えるのは512バイトです。
RTCメモリーサイズに関しては、以下に解説があります。
https://github.com/esp8266/Arduino/issues/2875

MQTTサーバーはローカルなサーバーを使いました。
/*
 DeepSleepタイマーの精度を確認する
*/

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

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

// 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);

//RTC memory(512Byte)の定義
struct {
  uint32_t counter;
  uint8_t data[508];
} rtcData;

// Connect AP
void setup_wifi() {
  // We start by connecting to a WiFi network
  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());
}

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

  sprintf(clientid,"ESP8266-%06x",ESP.getChipId());
  Serial.print("clientid=");
  Serial.println(clientid);
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect(clientid)) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}


void setup() {
  unsigned long startMillis = millis();
  delay(100);
  Serial.begin(115200);
  Serial.println();
  Serial.print("ESP.getResetReason()=");
  Serial.println(ESP.getResetReason());
  String resetReason = ESP.getResetReason();

  /*
  enum rst_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();
  /*
  struct rst_info{
      uint32 reason;
      uint32 exccause;
      uint32 epc1;
      uint32 epc2;
      uint32 epc3;
      uint32 excvaddr;
      uint32 depc;
  };
  */
  Serial.print("reset reason=");
  Serial.println(prst->reason);

  // RTC memoryからデータを読み込む
  if (ESP.rtcUserMemoryRead(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
    Serial.println("rtcUserMemoryRead Success");
    if (prst->reason != 5) { // Not REANSON_DEEP_SLEEP_AWAKE
      rtcData.counter = 0;
    } else  {
      rtcData.counter++;
    }
  } else {
    Serial.println("rtcUserMemoryRead Fail");
  }
 
  Serial.print("rtcData.counter=");
  Serial.println(rtcData.counter);

  // RTC memoryにデータを書き込む
  if (ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
    Serial.println("rtcUserMemoryWrite Success");
  } else {
    Serial.println("rtcUserMemoryWrite Fail");
  }
   
  setup_wifi();
  Serial.println("Starting MQTT Client");
  client.setServer(mqtt_server, 1883);
  server_connect();
  unsigned long connectedMillis = millis();
  Serial.print("startMillis=");
  Serial.print(startMillis);
  Serial.print(" conectedMillis=");
  Serial.print(connectedMillis);
 
  // 接続までに必要な時間(Mill Sec)
  unsigned long diffMillis = connectedMillis-startMillis;
  Serial.print(" diffMillis=");
  Serial.println(diffMillis);

  // 接続までに必要な時間を考慮したDeepSleep時間(Micro Sec)
  uint32_t time_us = SLEEP * 1000 * 1000;
  time_us = time_us - (diffMillis*1000);
  Serial.print("time_us=");
  Serial.println(time_us);

  // MQTT publish
  char topic[50];
  char msg[50];
  sprintf(topic,"DeepSleep/ESP8266-%06x",ESP.getChipId());
  sprintf(msg,"%02x %d %d",rtcData.counter, diffMillis, time_us);
  Serial.println(msg);
  client.publish(topic, msg);
  client.disconnect();
  WiFi.disconnect();
  
  // DEEP SLEEPモード突入
  Serial.println("DEEP SLEEP START!!");
  ESP.deepSleep(time_us , WAKE_RF_DEFAULT);
  delay(1000);
}

void loop() {
}

mosquittoクライアントを使って受信した日時とMQTTのメッセージを確認してみました。
APとMQTTサーバーに接続するのに大体4秒程度かかります。
それを差し引いた時間でDeepSleepしています。


1時間(60分)で2分の誤差(2/60=3.33%)なので、誤差は結構大きいです。
DeepSleepタイマーだけでは正確な間欠動作は不可能なことが分かりました。
2日間(48時間)動かしたら、1時間以上は狂う計算です。

同じスケッチをESP8285でも動かしてみました。
やはり1時間(60分)で90秒程度の誤差が有りました。


続く...