💡

IoT用マイコンにおけるDeepSleep復帰時間のばらつき

2024/06/25に公開

1. はじめに

IoT向けのマイコンで、タイマー復帰のDeepSleepは、狙った時間に復帰しないことがあります。これは、マイコンによって異なるように感じられます。そこで、実験をしてみました。

2. 結論

ESP32シリーズは、復帰のバラツキが1秒程度あります。復帰時間の精度を求める場合は、対策が必要でしょう。

3. 用いた実験系

3.1. 全体概要

対象となるマイコンとラズパイ4を用いました。

図 観測系のイメージ

  • GPIOからの500msのパルス出力と30分間のDeepSleepとを繰り返す
  • ラズパイはGPIOでパルスを受け、時刻をcsvファイルに記録
  • パルスの間隔が30分間隔になっているかを確認

図 マイコンとラズパイ接続した様子(写真のマイコンはラズピコW)

3.2. ESP32用 プログラム (arduino)

  • GPIOから500msのパルス出力
  • 30分間のDeepSleep
  • 復帰後はsetup()から開始
#define PIN_TIC 26
#define SLEEP_US  (30*60*1000*1000)

void setup() {
  pinMode(PIN_TIC, OUTPUT);
  digitalWrite(PIN_TIC, 1);
  delay(500);
  digitalWrite(PIN_TIC, 0);
  delay(1);
  esp_deep_sleep(SLEEP_US);
}
void loop() { }

3.3. ラズパイ用 観測プログラム (node-red)

図 node-redのフロー

  • GPIOのデバウンス設定は25ms
  • パルスの立ち上がりのみ検出
  • csvファイルに Date.now()値を記録
  • csvファイル名はinjectionノードを実行した時の日時

function1の内容 (fnamesには予め、日時が入っている)

msg.payload= Date.now()
msg.fnames= global.get("fnames")
return msg;

3.4. データ

ラズパイには次のようなテキストデータが保存されます。
下から上を引いて1000で割る(ミリ秒→秒)。
その値と1800(設定値)とを比較すれば、設定した時間と実際の時間の誤差になります。
(計算自体は、ngraph for winで(dif(y)/1000-1800)と入力し、プロット時に行っています。)

1717907502028
1717909302644
1717911104008
1717912903811
1717914704018

4. 結果

4.1. 結果

ESP32 , ESP32-c3 , seeed xiao , Arduino MKR Wi-Fi 1010 (mkr と記載), Sony Spresense , Raspberry pi Pico W (rpicowと記載) に対して、それぞれ24時間以上実施した結果です。

今回は平均値よりもバラツキ(std)に着目しています。
rpicowは、microPythonでコーディングしたものを使っています。(初めに試したのがrpicowだったのですが、時間の都合上、cでは試してません)

デバイス std[s] mean[s] 備考
ESP32 0.89 -8.16
ESP32-c3 0.77 0.104
xiao 0.0009 0.697
mkr 0.0005 0.97
spresense 0.0033 1.92
rpicow 0.0008 1.75 uPython

図 ESP32でのSleep間隔誤差の結果

図 ESP32C3でのDeepsleepの結果

図 Seeed XiaoでのSleepの結果

図 Arduino MKR Wifi-1010でのSleepの結果

図 SpresenseでのSleepの結果

図 Raspberry PI Pico WでのSleepの結果

4.2. ESP32

  • 概要
    • それまでのWi-Fiマイコンの価格を100分の1以上にした価格破壊の元祖
    • cpuコアはxtensa Lx6
  • 結果
    • DeepSleepの平均は8秒程度早い。
    • バラツキが上下で1秒以上

4.3. ESP32 c3

  • 概要
    • CPUコアがRISC-VになったESP32
  • 結果
    • ESP32無印にあった大きな遅れは無い
    • バラツキが上下で1秒以上

4.4. Seeed Xiao

  • 概要
    • SAM D21 マイコン(無線部なし)
    • 超小型 Arduino互換
  • 結果
    • バラツキは小さい
    • 平均0.7秒程度の遅れ

4.5. Arduino MKR Wifi-1010

  • 概要
    • Arduino正規品のWi-Fi搭載版
    • SAM D21 (Cortex-M0+) 搭載
    • 無線部:u-blox製NINA-W10 (中身はESP32)
  • 結果
    • バラツキは小さい
    • 平均1秒程度の遅れ

4.6. Sony Spresense

  • 概要
    • Cortex-M4F (6コア)
    • Arduino-IDEでも開発可能
    • 無線部は無いが、有線LANボードなどオプションが結構ある
    • 本体I/Oは1.8Vだが、拡張ボードI/Oはジャンパで5Vか3.3Vを選択可能
    • 給電は本体USBか本体の電源供給部分にコネクタをハンダ付けして行う
  • 結果
    • バラツキは小さい
    • 平均2秒程度の遅れ(起動自体に2秒程度かかる)

4.7. Raspberry PI Pico W

  • 概要
    • 無線部搭載ラズピコ
    • Arm Cortex M0+
    • 無線部: infineon製 CYW43439
  • 結果
    • バラツキは小さい
    • 平均1.7秒程度の遅れ
    • (コードはmicroPython)

5. まとめ

  • ESP32シリーズのタイマーは30分に1秒近く乱れる
    • xtensa版だけでなくRISC-V版も乱れる
  • ESP32で時刻管理する場合は、NTP や BLEのCurrent Time Serviceの利用が無難
  • ARM衆は優秀
    • (ラズピコwは安価なのに無線部付きで優秀)

A. 付録: 利用したコード

ESP32c3 (m5stamp c3)用

#define SLEEP_US  (30*60*1000*1000)
#define PIN_TIC 10
#define PIN_BOARD_BUTTON 3

void setup() {
  pinMode(PIN_TIC,OUTPUT);
  pinMode(PIN_BOARD_BUTTON,INPUT);

  digitalWrite(PIN_TIC,1);
  delay(450);
  delay(50);
  digitalWrite(PIN_TIC,0);
  delay(1);

  if ( digitalRead(PIN_BOARD_BUTTON)==0 ){
    esp_deep_sleep(SLEEP_US);
  }
  Serial.begin(115200);
}
void loop() {
  Serial.println("please reset");
  delay(1000);
}

arduino MKR wi-fi 1010用

#include "ArduinoLowPower.h"

#define PIN_LED 6
#define PIN_TIC 14
#define SLEEP_MS  (30*60*1000)

void setup() {
  pinMode(PIN_LED,OUTPUT);
  pinMode(PIN_TIC,OUTPUT);
  digitalWrite(PIN_LED,LOW);

  digitalWrite(PIN_TIC,HIGH);
  delay(450);
  digitalWrite(PIN_LED,HIGH);
  delay(50);
  digitalWrite(PIN_LED,LOW);
  digitalWrite(PIN_TIC,LOW);
  delay(1);

  LowPower.deepsleep(SLEEP_MS);
}

void loop() {}

Raspberry pi pico w用(upython)

import machine
import time

SLEEP_MS = 30*60*1000
PIN_TIC = 15

bdled = machine.Pin('LED', machine.Pin.OUT)
pintic = machine.Pin(PIN_TIC, machine.Pin.OUT)

pintic.on()
bdled.on()
time.sleep(0.5)
pintic.off()
bdled.off()
time.sleep(0.5)

machine.deepsleep(SLEEP_MS)

seeed xiao用

#include "ArduinoLowPower.h"

#define PIN_LED 13
#define PIN_TIC 10
#define SLEEP_MS  (30*60*1000)

void setup() {
  pinMode(PIN_LED,OUTPUT);
  pinMode(PIN_TIC,OUTPUT);
  digitalWrite(PIN_LED,LOW);

  digitalWrite(PIN_TIC,HIGH);
  delay(450);
  digitalWrite(PIN_LED,HIGH);
  delay(50);
  digitalWrite(PIN_LED,LOW);
  digitalWrite(PIN_TIC,LOW);
  delay(1);

  LowPower.deepsleep(SLEEP_MS);
}
void loop() {}

spresense用
15番ピンをGNDに接続しておく

#include <LowPower.h>

#define PIN_TIC 13
#define PIN_SKIPDS 15
#define TIME_SLEEP_SEC  (30*60)

void setup() {
  LowPower.begin();                      
  pinMode(PIN_TIC,OUTPUT);
  pinMode(PIN_SKIPDS,INPUT);
  pinMode(LED0, OUTPUT);
  
  digitalWrite(PIN_TIC,HIGH);
  delay(450);
  digitalWrite(LED0,HIGH);
  delay(50);
  digitalWrite(LED0,LOW);
  digitalWrite(PIN_TIC,LOW);
  if (digitalRead(PIN_SKIPDS) == 0){
    LowPower.deepSleep(TIME_SLEEP_SEC);
  }
}
void loop() { delay(1000); }

Discussion