ESP8266 の deep sleep モードは CPU などの電源を全て落として、RTC (時刻を持ってるわけではないっぽいけど内蔵RTCがあるらしい) だけ生かして設定時刻になったら RTC にリセットさせるという挙動をする。

そういうわけで、ディープスリープ後はリセット直後と同じ状態になり、メモリなどは引き継げない。

もちろん deep sleep 時には wifi の接続も切れてしまうので、毎回繋ぎなおしになる。自分の環境だとAPに接続するまで5秒ぐらいかかるし、接続中は電流が80mA(データシート的には最大で180mA)ぐらい流れる。高い頻度で起動すると初期化のコストがかなり大きい。

ユースケース

センサーデータは数秒に一度取得したいが、データ送信はさらに少ない頻度でどこかに送信したい、というような場合。

system_rtc_mem_read / system_rtc_mem_write

ESP8266 自体にこういうAPIがあり、RTC にあるメモリ(揮発性)を読み書きすることができる。RTC は deep sleep でも有効なので、これは電源が接続されている限り保持されるメモリのようだ (電源を切ると初期化される)。ユーザ領域として最大512bytes使えることになっている。

ということで、これを使って起動回数を記録しつつ、何回かに一度 WiFi 接続するようなサンプルを書いてみた (実際接続はしてないけど)

未初期化の system_rtc_mem_read は不定値がかえってくるみたいなので、CRC なりなんなりでハッシュをとって明示的に初期化をしたほうがよさそう。

#include <Arduino.h>
extern "C" {
#include <user_interface.h>
};
	
// system_rtc_mem_write() 先のブロックアドレス。
// 4 bytes で align されており、先頭256bytes はシステム予約領域
// 64 から書けるはずだが、65 以降でないとうまくいかなかった。。
static const uint32_t USER_DATA_ADDR = 66;

// ハッシュ関数 (FNV) CRC でいいけどコード的に短いのでFNV
static uint32_t fnv_1_hash_32(uint8_t *bytes, size_t length) {
	static const uint32_t FNV_OFFSET_BASIS_32 = 2166136261U;
	static const uint32_t FNV_PRIME_32 = 16777619U;
	uint32_t hash = FNV_OFFSET_BASIS_32;;
	for(size_t i = 0 ; i < length ; ++i) hash = (FNV_PRIME_32 * hash) ^ (bytes[i]);
	return hash;
}

// struct の hash (先頭にあることを想定) を除くデータ部分のハッシュを計算する
template <class T>
uint32_t calc_hash(T& data) {
	return fnv_1_hash_32(((uint8_t*)&data) + sizeof(data.hash), sizeof(T) - sizeof(data.hash));
}

struct {
	// retain data
	uint32_t hash;
	uint16_t count;
	uint8_t  send;
	uint16_t etc2;
} retain_data;

void post_sensor_data();

void setup() {
	pinMode(13, OUTPUT);

	Serial.begin(9600);
	Serial.println("Initializing...");

	// データ読みこみ
	bool ok;
	ok = system_rtc_mem_read(USER_DATA_ADDR, &retain_data, sizeof(retain_data));
	if (!ok) {
		Serial.println("system_rtc_mem_read failed");
	}
	Serial.print("retain_data.count = ");
	Serial.println(retain_data.count);

	// ハッシュが一致していない場合、初期化されていないとみなし、初期化処理を行う
	uint32_t hash = calc_hash(retain_data);
	if (retain_data.hash != hash) {
		Serial.println("retain_data may be uninitialized");
		retain_data.count = 0;
		retain_data.send = 0;
	}

	// データの変更処理(任意)
	retain_data.count++;
	if (!retain_data.send) {
		// 4回に1度送信する
		retain_data.send = retain_data.count % 4 == 0;
	} else {
		Serial.println("send data");
		retain_data.send = 0;
		// なんか定期的に書きこみたい処理
		post_sensor_data();
	}

	// 書きこみ処理。hash を計算していれておく
	retain_data.hash = hash = calc_hash(retain_data);
	ok = system_rtc_mem_write(USER_DATA_ADDR, &retain_data, sizeof(retain_data));
	if (!ok) {
		Serial.println("system_rtc_mem_write failed");
	}

	// 動作確認用のダミー
	digitalWrite(13, HIGH);
	delay(1000);
	digitalWrite(13, LOW);

	if (retain_data.send) {
		ESP.deepSleep(1e6, WAKE_RF_DEFAULT);
	} else {
		// sendしない場合は WIFI をオフで起動させる
		ESP.deepSleep(1e6, WAKE_RF_DISABLED);
	}
}

void loop() {
}

// dummy
void post_sensor_data() {
	for (uint i = 0; i < 5; i++) {
		digitalWrite(13, HIGH);
		delay(300);
		digitalWrite(13, LOW);
		delay(300);
	}
}
  1. トップ
  2. tech
  3. ESP8266 (ESP-WROOM-02) の Deep Sleep でデータを引き継ぐ