ESP-13を使ってみる

DeepSleepタイマーの精度


ESP-13には3つのスリープモードがあります。
スリープモードの詳細はこちらで 紹介されています。
DeepSleep中は微弱電流で動き続けますが、DeepSleep中のタイマーの精度がどこにも見当たらなかったので調べてみました。

使用したスケッチは以下のスケッチです。
3600秒に1回の間欠動作で起動し、RTCのユーザエリアに起動した回数を保持し、その回数をMQTTでサーバーに送ります。
RTCのユーザエリアは512バイトですが、先頭4バイトはCRCとして使って、初回起動時かDeepSleepからの復帰かを判断しています。
その後ろの1バイトカウンターとして使っています。
MQTTサーバーはローカルなサーバーを使いました。
/*
 Basic ESP8266 MQTT example
 RTC User Memoryを使用するDeelSleepモードのテスト
*/

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

#define SLEEP 3600  // 1時間

// Update these with values suitable for your network.
const char* ssid = "アクセスポイントのSSID";
const char* password = "アクセスポイントのパスワード";
const char* mqtt_server = "192.168.10.40";
//const char* mqtt_server = "broker.hivemq.com";

WiFiClient espClient;
PubSubClient client(espClient);

char topic[50];
char msg[50];

//RTC memory(512Byte)の定義
struct {
  uint32_t crc32; // CRC
  uint8_t data[508]; // 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
void server_connect() {
  char clientid[20];

  sprintf(clientid,"ESP8266-%6x",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");
      // ... and resubscribe
      client.subscribe("inTopic");
    } 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() {
 
  delay(100);
  Serial.begin(9600);
  Serial.println();

  //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が一致しないので初回起動時 RTC memoryをオール0で初期化
    if (crcOfData != rtcData.crc32) {
      Serial.println("CRC32 in RTC memory doesn't match CRC32 of data. Data is probably invalid!");
      for (int i = 0; i < sizeof(rtcData); i++) {
        rtcData.data[i] = 0;
      }
    }
  //CRC32が一致したのでDeelSleepからの復帰 RTC memoryの起動回数をインクリメント
    else {
      Serial.println("CRC32 check ok, data is probably valid.");
      rtcData.data[0]++; // 60分*0xFF=255時間なのでオーバーフローは考えない
    }
  }

  //CRC32を再計算
  rtcData.crc32 = calculateCRC32(((uint8_t*) &rtcData) + 4, sizeof(rtcData) - 4);
  //RTC memoryにデータを書き込む
  if (ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
    Serial.println("Write: ");
    printMemory(10);
  }

  setup_wifi();
  Serial.println("Starting MQTT Client");
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  server_connect();
  client.loop();

  sprintf(topic,"DeepSleep/ESP8266-%6x",ESP.getChipId());
  sprintf(msg,"%02x",rtcData.data[0]);
  Serial.println(msg);
  client.publish(topic, msg);
  client.disconnect();
  WiFi.disconnect();

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

void loop() {
}

OrangePi-PCでMQTTのSubscriberを作成し、受信した日時とMQTTのメッセージをファイルに記録するようにしました。



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

次回は、NTPを利用することで、毎時0分にDeepSleep から復帰する方法を紹介します。