Arduino
ファームウエア
ESP32
ESP-WROOM-32
ESP32-DevKitC,

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


概要

 この記事はESP-WROOM-32の外部SPIフラッシュメモリ(4MB)にアクセスする方法を記述しています。

 今回はSPIフラッシュメモリにアクセスするAPIを使用しています。progmemやファイルシステム(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);
}


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

serialflash_demo.PNG

 読み込むたびにデータがインクリメントされてることが確認できます。

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

以下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