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


概要

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

>>>>>>>>>>>>>>>>>>>>>>>>>>>
2019/4/24追記
 ファイルシステム(SPIFFS)を使用したSPIフラッシュメモリアクセスのライブラリを作りました。
次の記事でサンプルコードを公開しています。
「ESP32のSPIFFSサンプルコードをライブラリ化しました」
>>>>>>>>>>>>>>>>>>>>>>>>>>>

 作成したスケッチファイルはGitHubで公開しています。ライセンスはフリーです。

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

開発環境

 開発環境:Arduino-IDE(Arduino-ESP32)
 使用ボード:ESP-32-DevKit
 ライブラリ:esp_spi_flash.h ※1

※1: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.SPIフラッシュからデータを4kB(1セクタ)読み出す
2.シリアルログに読み出したデータの先頭データを出力
3.シリアルフラッシュの指定アドレスから4kB(1セクタ)を消去
4.読み出したデータをインクリメントして、書き込み用の配列に格納
5.シリアルログに書き込むデータを出力
6.シリアルフラッシュに1byteデータを書き込む
7.1秒wait

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);
}

 スケッチファイルをボードに書き込み、シリアルモニタを起動すると次のようなログが表示されます。

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

以下spi_flash APIの説明です。

SPIフラッシュメモリから読み込み

// SPI flashから読み込み
spi_flash_read(SPIFFS_BASE_ADDR,r_buf,SPI_FLASH_SEC_SIZE);

このAPIはSPIフラッシュメモリからRAMに読み込みます。
APIは次のように定義されています。

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フラッシュメモリの内容を削除(フラッシュ)する

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

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

esp_err_t spi_flash_erase_range(size_t start_address, size_t size)

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

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

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

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

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の中に

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