ESP8266のReset動作


ESP8266モジュールに搭載されているFlashメモリーのサイズを調べたときに、EspClassと 呼ばれる関数群があることが分かりました。
EspClass関数群の中に興味深い関数が有りました。
ESP.reset()
ESP.restart()

どうやら強制的に再起動を行う関数のようです。
そこで、これらの関数を実行したときに、RTCユーザエリアはクリアされるのかどうか確認してみました。
使用したスケッチは以下のスケッチです。
ESP.getResetReason()を使って、Resetからの復帰かどうかを判定しています。
/*
 ESP.reset()/ESP.resart()でRTC User Memoryがクリアされるかどうかのテスト
*/

#include <ESP8266WiFi.h>

//RTC memory(512Byte)の定義
struct {
  uint8_t data[512]; // User Data
} rtcData;

void setup() {
  delay(1000);
  Serial.begin(115200);
  Serial.println();
  Serial.print("ESP.getResetReason()=");
  Serial.println(ESP.getResetReason());
  String resetReason = ESP.getResetReason();

  /*
  enum rst_reason {
  REANSON_DEFAULT_RST = 0, // ノーマルスタート。電源オンなど。
  REANSON_WDT_RST = 1, // ハードウェアウォッチドッグによるリセット
  REANSON_EXCEPTION_RST = 2, // 例外によるリセット。GPIO状態は変化しない
  REANSON_SOFT_WDT_RST = 3, // ソフトウェアウォッチドッグによるリセット。GPIO状態は変化しない
  REANSON_SOFT_RESTART = 4, // ソフトウェアによるリセット。GPIO状態は変化しない
  REANSON_DEEP_SLEEP_AWAKE= 5, // ディープスリープ復帰
  REANSON_EXT_SYS_RST = 6, // 外部要因(RSTピン)によるリセット。
  };
  */

  rst_info *prst = ESP.getResetInfoPtr();
  /*
  struct rst_info{
      uint32 reason;
      uint32 exccause;
      uint32 epc1;
      uint32 epc2;
      uint32 epc3;
      uint32 excvaddr;
      uint32 depc;
  };
  */
  Serial.print("reset reason=");
  Serial.println(prst->reason);
  
  // RTC memoryからデータを読み込む
  if (ESP.rtcUserMemoryRead(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
    Serial.println("rtcUserMemoryRead Success");
    if (prst->reason != 4) { // Not Software/System restart
      for (int i = 0; i < sizeof(rtcData.data); i++) {
        rtcData.data[i] = 0;
      }
    }
    else {
      rtcData.data[0]++;
    }
  } else {
    Serial.println("rtcUserMemoryRead Fail");
  }

  // RTC memoryにデータを書き込む
  if (ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
    Serial.println("rtcUserMemoryWrite Success");
  } else {
    Serial.println("rtcUserMemoryWrite Fail");
  }

  Serial.print("rtcData.data[0]=");
  Serial.print(rtcData.data[0]);
  Serial.print(" millis=");
  Serial.println(millis());
  if ((rtcData.data[0] % 2) == 0) {
    Serial.println("Do ESP.reset");
    for (int i=20;i>0;i--) delay(1000);
    ESP.reset();
  } else {
    Serial.println("Do ESP.restart");
    for (int i=20;i>0;i--) delay(1000);
    ESP.restart();
  }
}

void loop() {
}

結果は以下の通りです。
ESP.getResetReason()=External System
reset reason=6
rtcUserMemoryRead Success
rtcUserMemoryWrite Success
rtcData.data[0]=0 millis=1066
Do ESP.reset

ESP.getResetReason()=Software/System restart
reset reason=4
rtcUserMemoryRead Success
rtcUserMemoryWrite Success
rtcData.data[0]=1 millis=1066
Do ESP.restart

ESP.getResetReason()=Software/System restart
reset reason=4
rtcUserMemoryRead Success
rtcUserMemoryWrite Success
rtcData.data[0]=2 millis=1066
Do ESP.reset

ESP.getResetReason()=Software/System restart
reset reason=4
rtcUserMemoryRead Success
rtcUserMemoryWrite Success
rtcData.data[0]=3 millis=1067
Do ESP.restart

ESP.reset()/ESP.restart() から復帰した時は、rtcData.data[0]の値はインクリメントされています。
ESP.reset()/ESP.restart() ともに、RTCユーザエリアはクリアされないことが分かりました。
millis()の値は初期化されています。

これ、非常に面白いです。
例えば、1つのスケッチの中で、WifiのSoftAPモードとStationモードを切り替えたり、
1つのスケッチの中で、UDPとTCPを使い分けることができます。



ESP.reset()とESP.restart()はどちらも同じように強制リセットの動作をしますが違いが有ります。
こ ちらにその違いが紹介されています。
Google翻訳君にお願いしました。

ESP.reset()はハードリセットであり、問​​題を引き起こす可能性があるいくつかのレジスタを古い状態のままにしておくことができま す。
これはPCのリセットボタンとほぼ同じです。
ESP.restart()はSDKに再起動を指示するので、よりクリーンな再起動を行います。可能であればこれを使用してください。



WeMosやNodeMCUの様に、スケッチの書き込み(ブートモードの変更)を自動的に行う機能が付いているボードでは、
リセットが掛かると、自動的にUART Download mode(書き込みモード)になってしまうボードが有ります
リセット動作を確認するときは、裸のモジュールを使った方が余計なトラブルが有りません。

続く....