コード
以下のようなコードで試した。概要としては
- 2.5秒に1度起動する (それ以外は deep sleep)
- 起動時にデータをRTCメモリに書きむ (センサーデータ取得を想定)
- 4回に1度 WiFi に接続し、UDP でセンサーデータを送信する
というもの
#include <Arduino.h> | |
#include <ESP8266WiFi.h> | |
#include <WiFiUdp.h> | |
#include "rtc_memory.hpp" | |
#include "config.h" | |
WiFiClient wifiClient; | |
struct { | |
char SSID[255] = WIFI_SSID; | |
char Password[255] = WIFI_PASS; | |
} wifi_config; | |
bool startWifi(int timeout) { | |
WiFi.disconnect(); | |
WiFi.mode(WIFI_STA); | |
Serial.println("Reading wifi_config"); | |
Serial.println("wifi_config:"); | |
Serial.print("SSID: "); | |
Serial.print(wifi_config.SSID); | |
Serial.print("\n"); | |
if (strlen(wifi_config.SSID) == 0) { | |
Serial.println("SSID is not configured"); | |
return false; | |
} | |
WiFi.begin(wifi_config.SSID, wifi_config.Password); | |
int time = 0; | |
for (;;) { | |
switch (WiFi.status()) { | |
case WL_CONNECTED: | |
Serial.println("connected!"); | |
WiFi.printDiag(Serial); | |
Serial.print("IPAddress: "); | |
Serial.println(WiFi.localIP()); | |
return true; | |
case WL_CONNECT_FAILED: | |
Serial.println("connect failed"); | |
return false; | |
} | |
delay(500); | |
Serial.print("."); | |
time++; | |
if (time >= timeout) { | |
break; | |
} | |
} | |
return false; | |
} | |
struct deep_sleep_data_t { | |
uint16_t count = 0; | |
uint8_t send = 0; | |
uint16_t data[12]; | |
void add_data(uint16_t n) { | |
data[count] = n; | |
} | |
template <class T> | |
void run_every_count(uint16_t n, T func) { | |
count++; | |
if (!send) { | |
send = count % (n - 1) == 0; | |
} else { | |
send = 0; | |
count = 0; | |
func(); | |
} | |
} | |
}; | |
rtc_memory<deep_sleep_data_t> deep_sleep_data; | |
void post_sensor_data(); | |
void setup() { | |
pinMode(13, OUTPUT); | |
Serial.begin(74880); | |
Serial.println("Initializing..."); | |
// データ読みこみ | |
if (!deep_sleep_data.read()) { | |
Serial.println("system_rtc_mem_read failed"); | |
} | |
Serial.print("deep_sleep_data->count = "); | |
Serial.println(deep_sleep_data->count); | |
// データの変更処理(任意) | |
deep_sleep_data->add_data(deep_sleep_data->count); | |
deep_sleep_data->run_every_count(4, [&]{ | |
Serial.println("send data"); | |
// なんか定期的に書きこみたい処理 | |
post_sensor_data(); | |
}); | |
if (!deep_sleep_data.write()) { | |
Serial.print("system_rtc_mem_write failed"); | |
} | |
if (deep_sleep_data->send) { | |
ESP.deepSleep(2.5e6, WAKE_RF_DEFAULT); | |
} else { | |
// sendしない場合は WIFI をオフで起動させる | |
ESP.deepSleep(2.5e6, WAKE_RF_DISABLED); | |
} | |
} | |
void loop() { | |
} | |
// dummy | |
void post_sensor_data() { | |
if (startWifi(30)) { | |
// do http etc... | |
WiFiUDP udp; | |
IPAddress serverIP(192, 168, 0, 5); | |
udp.beginPacket(serverIP, 5432); | |
udp.write((uint8_t*)deep_sleep_data->data, sizeof(deep_sleep_data->data)); | |
udp.endPacket(); | |
// wait for sending packet | |
delay(1000); | |
} else { | |
Serial.println("failed to start wifi"); | |
ESP.restart(); | |
} | |
} |
extern "C" { | |
#include <user_interface.h> | |
}; | |
template <class T> | |
class rtc_memory { | |
// 4 bytes aligned memory block address in rtc memory. | |
// user data must use 64 or larger block. | |
// but system_rtc_mem_read() is failed for 64 so use 65. | |
static constexpr uint32_t USER_DATA_ADDR = 65; | |
static constexpr uint32_t USER_DATA_SIZE = 512 - ((USER_DATA_ADDR - 64) * 4); | |
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; | |
} | |
uint32_t calc_hash(T& data) const { | |
return fnv_1_hash_32((uint8_t*)&data, sizeof(data)); | |
} | |
public: | |
uint32_t hash; | |
T data; | |
static_assert(sizeof(T) <= (USER_DATA_SIZE - sizeof(hash)), "sizeof(T) it too big"); | |
bool read() { | |
// Read memory to temporary variable to retain initial values in struct T. | |
rtc_memory<T> read; | |
// An initial rtc memory is random. | |
bool ok = system_rtc_mem_read(USER_DATA_ADDR, &read, sizeof(read)); | |
if (ok) { | |
// Only hashes are matched and copy to struct. | |
if (read.hash == calc_hash(read.data)) { | |
memcpy(this, &read, sizeof(read)); | |
} | |
} | |
return ok; | |
} | |
bool write() { | |
hash = calc_hash(data); | |
return system_rtc_mem_write(USER_DATA_ADDR, this, sizeof(*this)); | |
} | |
T* operator->() { | |
return &data; | |
} | |
}; |
実測
説明をいれると
約5秒でAPに接続できている。その後UDPで送信し、送信が終わるのをちょっと無駄に1秒待っている。
V: 31.72mA となっているのが画面内の平均だけど、WiFi 接続が2回起きているので、1サイクルの平均ではない。
1サイクル(約16秒)あたりは、WiFi に 80mA 6秒、スリープ 20uA 10秒とし平均30mA。
もし単三 NiMH 2400mAh 3本(公称1.2V * 3 = 3.6V)をLDOで3.3Vにして電源にしたとすると、バッテリ容量は2400mAh * 1.2V * 3 = 8640mWh。このケースのESP8266の平均消費電力は 3.6V * 30mA = 108mW (LDO損失こみ)。8640 / 108 = 80時間。
支配的なのは圧倒的にWiFi接続時間なので、とにかく周期を長くとれば、それだけ電池寿命は伸びる。実際のところ電池3本で実用的なのは5分以上かなあという気がする (5分間隔でも2ヶ月ぐらい)。
これよりも短い間隔でデータのやりとりをする場合、電池駆動は考えないほうがよさそう。どうしても電池駆動したい場合WiFi 使うのが間違っているので ZigBee とか使うべきなんだと思う。とはいえ ESP8266 が圧倒的に安すぎるので、なんでも WiFi でやりたくなってしまう。