ESP-WROOM-32の外部フラッシュメモリにアクセスする（ESP32's spi_flash API）

#概要
　この記事はESP-WROOM-32の外部SPIフラッシュメモリ（4MB)にアクセスする方法を記述しています。
　今回はSPIフラッシュメモリにアクセスするAPIを使用しています。progmemやファイルシステム（SPIFFS）を使用していません。

＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞
<font color="Red">**2019/4/24追記**</font>
　ファイルシステム（SPIFFS）を使用したSPIフラッシュメモリアクセスのライブラリを作りました。
次の記事でサンプルコードを公開しています。
[「ESP32のSPIFFSサンプルコードをライブラリ化しました」](https://qiita.com/kmaepu/items/e4c20d2027db9a7df813)
＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞＞

　作成した[スケッチファイル](https://github.com/kmaepu/ESP32-SPI-flash)はGitHubで公開しています。ライセンスはフリーです。

　外部メモリにアクセスするので処理時間が気になりますが、今回は処理時間の計測を省いて動作のみ行っています。


#開発環境
　開発環境：Arduino-IDE（Arduino-ESP32）
　使用ボード：ESP-32-DevKit
　ライブラリ：esp_spi_flash.h　※１  
            
  ※１：Arduino-IDEでESP-WROOM-32の開発環境が整っていればデフォルトで用意されています。

# ESP-WROOM-32の外部SPIフラッシュメモリについて
　ESP-WROOM-32の外部フラッシュメモリは4MBあります。このメモリ空間は用途によって区切られています。（パーティションテーブル）
デフォルトのパーティションテーブル（default.csv）は次のように設定されています。

| Name | Type | SubType | Offset | Size |
|:-----------|------------:|:------------:|:------------:|:------------:|
| 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 |

　パーティションテーブルの詳しい説明をすると長くなるので、主観での簡単な説明にします。
システムが「nvs」、「otadata」、「app0」、「app1」を使用しています。
「eeprom」はスケッチの中でprogmemを使用すると使われる領域のようです。
「spiffs」はファイルシステムSPIFFSを利用するときの領域です。

　今回はパーティションテーブルをデフォルト（default.csv）とし、フラッシュメモリの「spiffs」領域にアクセスしています。

# ソースコード
　大まかな処理フローは次のとおりです。
1.<font color="Red">**SPIフラッシュからデータを4kB（１セクタ）読み出す**</font>
2.シリアルログに読み出したデータの先頭データを出力
3.<font color="Red">**シリアルフラッシュの指定アドレスから4kB（１セクタ）を消去**</font>
4.読み出したデータをインクリメントして、書き込み用の配列に格納
5.シリアルログに書き込むデータを出力
6.<font color="Red">**シリアルフラッシュに1byteデータを書き込む**</font>
7.1秒wait

```c:serialflash.ino
#include "esp_spi_flash.h"
#define SPIFFS_BASE_ADDR 0x291000 // SPIFFS領域のベースアドレス

uint32_t chip_size = 0;
uint8_t w_buf[SPI_FLASH_SEC_SIZE];
uint8_t r_buf[SPI_FLASH_SEC_SIZE];

void setup() { 
  Serial.begin(115200, SERIAL_8N1);
  w_buf[0] = 0x00;
}

void loop() {
  // SPI flashから読み込み
  spi_flash_read(SPIFFS_BASE_ADDR,r_buf,SPI_FLASH_SEC_SIZE);
  Serial.printf("Read SPI flash data = %x\n",r_buf[0]);

  w_buf[0] = r_buf[0] + 1;

  // SPI flashの指定アドレスから指定サイズだけデータを消去する
  spi_flash_erase_range(SPIFFS_BASE_ADDR,SPI_FLASH_SEC_SIZE);

  // SPI flashへ書き込み
  Serial.printf("Write to SPI flash = %x\n\n",w_buf[0]);
  spi_flash_write(SPIFFS_BASE_ADDR,w_buf,1);

  delay(1000);
}
```

　スケッチファイルをボードに書き込み、シリアルモニタを起動すると次のようなログが表示されます。
<img width="154" alt="serialflash_demo.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/183282/a1a39905-3bf9-a1e5-ae71-5910619ca856.png">

　読み込むたびにデータがインクリメントされてることが確認できます。
また、電源を落として起動しなおしてみると、前回の続きからデータがインクリメントされていきます。

以下spi_flash APIの説明です。
#### SPIフラッシュメモリから読み込み
```c
// SPI flashから読み込み
spi_flash_read(SPIFFS_BASE_ADDR,r_buf,SPI_FLASH_SEC_SIZE);
```
このAPIはSPIフラッシュメモリからRAMに読み込みます。
APIは次のように定義されています。

```c
esp_err_t spi_flash_read(size_t src_addr, void *dest, size_t size)
```
・__src_addr__
　フラッシュメモリ内の読み込みたいデータが格納されている先頭アドレスを指定します。
　今回はデフォルトのパーティションテーブルでSPIFFS領域の先頭アドレスを指定しています。
・__*dest__
　フラッシュメモリから読み込んだデータの格納先ポインタを指定します。
　今回はuint8_t型配列の先頭アドレスを指定しています。
・__size__
　フラッシュメモリから読み込むデータのサイズを指定します。
　今回は1セクタ（4kB)を指定しています。

####SPIフラッシュメモリの内容を削除(フラッシュ）する
```c
// SPI flashの指定アドレスから指定サイズだけデータを消去する
spi_flash_erase_range(SPIFFS_BASE_ADDR,SPI_FLASH_SEC_SIZE);
```
このAPIはフラッシュメモリの指定アドレスから、指定したサイズのデータを削除（フラッシュ）します。
APIは次のように定義されています。

```c
esp_err_t spi_flash_erase_range(size_t start_address, size_t size)
```
・__start_address__
　フラッシュメモリ内の削除したいデータが格納されている先頭アドレスを指定します。
　今回はspiffs領域の先頭アドレスを指定しています。
・__size__
　削除するデータサイズを指定します。
　今回は1セクタ（4kB)を指定しています。

####SPIフラッシュメモリに書き込む

```c
// SPI flashへ書き込み
spi_flash_write(SPIFFS_BASE_ADDR,w_buf,1);
```

このAPIはフラッシュメモリの指定したアドレスから、指定したサイズだけデータを書き込みます。
APIは次のように定義されています。

```c
esp_err_t spi_flash_write(size_t dest_addr, const void *src, size_t size)
```
・__dest_addr__
　フラッシュメモリの書き込み先アドレスを指定します。
　今回はspiff領域の先頭アドレスを指定しています。
・__*src__
　書き込むデータが格納されているポインタを指定します。
　今回はuint8_t型配列の先頭アドレスを指定しています。
・__size__
　書き込むデータのサイズを指定します。
　今回は1byteを指定しています。

#あとがき
　SPIフラッシュのアクセスはセクタ単位が便利そうです。spi flash APIの中に

```c
esp_err_t spi_flash_erase_sector(size_t sector)
```
といった、セクタ単位で処理するAPIを使う事ができるからです。
　spi_flash_mmap()というAPIでも外部SPIフラッシュにアクセスできそうです。試していないので動作は分かりませんが、SPIフラッシュメモリから領域を指定して内部メモリ(SRAM)上に展開するようです。
　その他にファイルシステム（FATやSPIFFS）を利用した方法もあります。こちらのほうが直感的に扱いやすいかと思いますが、こういった方法もある事が分かりました。

#参考
　ESPRESSIF ESP-IDE Programming Guide SPI Flash APIs  
 https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/storage/spi_flash.html