コード
以下のようなコードで試した。概要としては
- 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 でやりたくなってしまう。

