SDカードを使ってESP32のファームウェアをアップデートしてみた

# 概要
ESP32のファームウェアをSDカードに入れて、ESP32のアプリケーション上でファームウェアをアップデートします。※ArduinoIDEを使用しました

[espressif公式のサンプルコード](https://github.com/espressif/arduino-esp32/blob/master/libraries/Update/examples/SD_Update/SD_Update.ino)をベースにして以下の変更を加えました。
・変更１：SDカードの制御（SD_MMC.hではなくSD.hのライブラリを使用する）
・変更２：動作しているファームのパーティションアドレスをシリアル出力する

# 使用したもの
* [ESPr® Developer 32](https://www.switch-science.com/catalog/3210/)　 ※これ以降は「ESP32］と呼称
* [HiLetgo® 2.8 "TFT LCDディスプレイ タッチパネル](https://www.amazon.co.jp/HiLetgo%C2%AE-LCD%E3%83%87%E3%82%A3%E3%82%B9%E3%83%97%E3%83%AC%E3%82%A4-%E3%82%BF%E3%83%83%E3%83%81%E3%83%91%E3%83%8D%E3%83%AB-SPI%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB240-ILI9341/dp/B072N551V3)（SDカードスロットとして使用）
* Arduino IDE（バージョン1.8.5を使用）
* SDカード

# 結線

以下の通りにESP32とSDカードを結線します

### SD
|ESP32  |SD  |備考  |
|---|---|---|
|VOUT  |VCC  |
|GND  |GND  |
|IO14  |SD_SCK  |10kの抵抗を使用して3.3Vでプルアップする（[参考記事](https://github.com/espressif/esp-idf/tree/master/examples/storage/sd_card)）|
|IO2  |SD_MISO  |10kの抵抗を使用して3.3Vでプルアップする（[参考記事](https://github.com/espressif/esp-idf/tree/master/examples/storage/sd_card)）|
|IO15  |SD_MOSI  |10kの抵抗を使用して3.3Vでプルアップする（[参考記事](https://github.com/espressif/esp-idf/tree/master/examples/storage/sd_card)）|
|IO13  |SD_CS  |10kの抵抗を使用して3.3Vでプルアップする（[参考記事](https://github.com/espressif/esp-idf/tree/master/examples/storage/sd_card)）|

# 動作手順

### スケッチ

スケッチの動作は以下の流れです。

１．現在動作中のファームウェアの領域アドレスをシリアル出力する
２－１．新しいファームウェアがある場合、ファームウェアを書き込んで、再起動する
２－２．新しいファームウェアがない場合、再起動する

```simple_sdcard_firmware_update.ino
/*

 Arduino board manager:
  https://dl.espressif.com/dl/package_esp32_index.json

 Arduino Settings(use Arduino 1.8.5):    
  Board ESP32 Dev Module
  Flash Mode  QIO
  Flash Frequency 40MHz
  CPU Frequency 240MHz
  Flash Size  4M (32Mb)
  Partition Scheme  Default 4MB with spiffs(1.2MB APP / 1.5MB SPIFFS)
  Upload Speed  115200
  Core Debug Level non

 The circuit:
 * SD card attached to SPI bus as follows:
 ** CLK - pin 14
 ** MISO - pin 2
 ** MOSI - pin 15
 ** SS - pin 13 

 */

#include <Update.h>

#include "esp_partition.h"
#include "esp_ota_ops.h"

#include "FS.h"
#include "SD.h"
#include "SPI.h"
SPIClass spiSD(HSPI);
#define SD_CLK 14
#define SD_MISO 2
#define SD_MOSI 15
#define SD_SS 13
#define SDSPEED 27000000

#define UPDATE_FILE_NAME "/update.bin"

const esp_partition_t *running;

// perform the update from a given stream
void performUpdate(Stream &updateSource, size_t updateSize) {
   if (Update.begin(updateSize)) {      
      size_t written = Update.writeStream(updateSource);
      if (written == updateSize) {
         Serial.println("Written : " + String(written) + " successfully");
      }
      else {
         Serial.println("Written only : " + String(written) + "/" + String(updateSize) + ". Retry?");
      }
      if (Update.end()) {
         Serial.println("done!");
         if (Update.isFinished()) {
            Serial.println("Update successfully completed. Rebooting.");
         }
         else {
            Serial.println("Update not finished? Something went wrong!");
         }
      }
      else {
         Serial.println("Error Occurred. Error #: " + String(Update.getError()));
      }
   }
   else
   {
      Serial.println("Not enough space to begin OTA");
   }
}

// check update file is available
boolean updateFileIsAvailable(fs::FS &fs) {
  boolean ret = false;
  
  File updateBin = fs.open(UPDATE_FILE_NAME);
  if (updateBin) {
    if(updateBin.isDirectory()){
       Serial.println("Error, new firmware is not a file");
    }
    else {
      size_t updateSize = updateBin.size();
      if (updateSize > 0) {
         ret = true;
      }
      else {
         Serial.println("Error, file is empty");
      }
    }
    updateBin.close();
  }
  else {
    Serial.println("Could not load new firmware file from sd root");
  }

  return ret;
}

// check given FS for valid update.bin and perform update if available
void updateFromFS(fs::FS &fs) {
  File updateBin = fs.open(UPDATE_FILE_NAME);

  size_t updateSize = updateBin.size();
  performUpdate(updateBin, updateSize);  
  updateBin.close();

  // remove the update file from sd card to indicate end of the process
  fs.remove(UPDATE_FILE_NAME);

  reboot();
}

void setup() {
  Serial.begin(115200);

  running = esp_ota_get_running_partition();
  Serial.printf("running partition address:0x%08x", running->address);
  Serial.println("");

  Serial.print("Checking new firmware...");
  spiSD.begin(SD_CLK, SD_MISO, SD_MOSI, SD_SS);
  boolean newFirmIsOK;
  if(!SD.begin( SD_SS, spiSD, SDSPEED)){
    newFirmIsOK = false;
  }
  else {
    if (updateFileIsAvailable(SD)) {
      newFirmIsOK = true;
    }
    else {
      newFirmIsOK = false;
    }
  }
  if (newFirmIsOK) {
    Serial.println("OK");
    Serial.println("Start firm update");
    updateFromFS(SD);
  }
  else {
    Serial.println("FAILED");
    reboot();
  }

}

void reboot(){
    Serial.println("Reboot");
    delay(3000);
    ESP.restart();
}

void loop() {
  ; //will not be reached  
}

```

### 動作手順

１． ファームウェアを入れたSDカードを挿入する
　※ファームウェアはArduinoIDEの「スケッチ」→「コンパイルしたバイナリを出力」で作成する（スケッチと同じフォルダに生成される）
　※生成されたファームウェアのファイル名をupdate.binに変更する

２．PCとESP32を接続しているUSBケーブルを抜いてIO2を(プルアップ抵抗から)切り離す

![プルアップ説明2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155449/1b3ac195-acd5-16a2-bb5b-6cfedf7ff569.png)

３．USBを繋いでスケッチをESP32に書き込む[(スケッチはgithubに置きました)](https://github.com/bigw00d/esp32_ili9341_2.8_tft_touch_display/blob/master/examples/simple_display_sd_data/simple_display_sd_data.ino)
※Arduinoの設定は以下の通りにしました
![スケッチ設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155449/5525a27d-58f6-d080-f702-0ee97725de39.png)


４．USBケーブルを抜いてIO2を(プルアップ抵抗と)接続する

５．USBケーブルを接続する

６．ESP32のアプリが立ち上がり、アップデートが行われる

７．アップデートが終わると再起動してアップデートされたファームウェアが立ち上がる
　　※SDカード上のファームウェアは削除されているので再びアップデートを行わない
  

# アップデートについて

今回アップデートでは[Update.h](https://github.com/espressif/arduino-esp32/blob/master/libraries/Update/src/Update.h)というespressif公式のライブラリを使用しています。これはOTA(Wi-Fiを使用するファームアップデート）にも使用されるライブラリです。

そして、このライブラリでは以下のメモリマップ上のapp0かapp1の使用していない方のファームウェア用の領域に新しいファームウェアを書き込みます。

そして、アップデートした時に起動する領域を切り替えることで、再起動時に新しいファームウェアで動作する、ということのようです。（[参考記事](https://lang-ship.com/blog/?p=625)）

```
 # Name,  Type, SubType, Offset,   Size, Flags
 nvs,     data, nvs,     0x9000,   0x5000,
 otadata, data, ota,     0xe000,   0x2000,
 app0,    app,  ota_0,   0x10000,  0x140000,
 app1,    app,  ota_1,   0x150000, 0x140000,
 eeprom,  data, 0x99,    0x290000, 0x1000,
 spiffs,  data, spiffs,  0x291000, 0x16F000,
```


実際に動作中の領域のアドレスをシリアル出力させたところ、以下のように0x010000→0x150000と変わっていることが分かります。

```
running partition address:0x00010000
Checking new firmware...OK
Start firm update
Written : 302896 successfully
done!
Update successfully completed. Rebooting.
Reboot
　：
running partition address:0x00150000
```


# 終わりに

最初はOTAのサンプルコードの一部を流用するつもりでしたが、SDカードをアップデートに使用するピンポイントなサンプルコードが見つかって幸運でした。

アップデートに要する時間はそこまで長くないので、LCDなどの表示でその待ち時間を「上手く」扱えれば、ゲーム機のソフトのようにSDカードを使うことができて、面白そうです。

見ていただいてありがとうございました。
тнайк чoμ_〆(・ω・｡)			
			
			
# 参考記事

 + espressif公式のデータシート
  + https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf

 + M5StickCのPartition Tablesを調べる
  + https://lang-ship.com/blog/?p=625

 + ESP32 でやってみたいこと(すｚのAVR研究)
  + http://suz-avr.sblo.jp/category/4396940-1.html

 + BLE環境センサー・ゲートウェイ(ESP32編)
  + https://ambidata.io/samples/m5stack/ble_gw/


			
# 更新履歴
* 2019-10-16：新規作成