こんにちは、shotaです。
このシリーズではESP32を搭載したオリジナルマイクロマウスEspecialを製作します。
前回の記事ではESP-IDFのバージョンをv3.3から安定版のv4.0に移行する方法について書きました。
今回からESP32のファイルシステムの使い方について書きます。
ただ、ファイルシステムを扱うために避けて通れないのが、フラッシュメモリのパーティションとアドレスの仕組みです。
そしてESP32には、フラッシュメモリ内部のアドレスと、ESP32全体のアドレスマッピングが存在するので、いきなりフラッシュメモリの話をすると混乱してしまいます。(私が混乱しました)
ということで、今回はESP32のアドレスマッピングについて私が理解した内容をまとめます。
そもそもESP32って何だっけ?
まずはそもそもESP32とは何だったのか、全体像から振り返りましょう。
私のオリジナルマイクロマウスEspecialに搭載しているESP32は、正確に言うとESP32-WROOM-32Dというモジュールです。
(これをESP32モジュールと呼ぶことにします)
ESP32モジュールの中にESP32-D0WDというICや、フラッシュメモリICが内蔵されています。
外からは全く見えませんが、モジュールの中に回路基板があり、そこにESP32-D0WDとフラッシュメモリICが実装されています。

ESP32-WROOM-32Dモジュール
引用元:https://docs.espressif.com/projects/esp-idf/en/release-v3.2/hw-reference/modules-and-boards.html#esp32-wroom-32d-esp32-wroom-32u
このメインのICであるESP32-D0WDこそがESP32の本体です。
(これをESP32チップと呼ぶことにします)
ESP32チップの中にはCPUやROM、RAM等のメモリ、GPIO、SPI等のペリフェラルが内蔵されています。
ESP32チップの中身は半導体の世界なので分解しても理解できません。
まとめるとこんな感じです。
ESP32チップは何を内蔵しているの?
ESP32チップを分解しても、半導体素人の私には何も理解できないのでデータシートを読みます。
データシートの1.6 Block DiagramにあるFunctional Block Diagramを見てましょう。
この図にはESP32チップに内蔵された機能の全体像が描かれています。
図の中央にはCPU(2個)と、ROMとSRAMがあります(Core and memory)。左側にはSPI、I2C、I2S等のペリフェラルが並んでいます。
他には無線機能(図上のBluetoothやWi-Fi)や、リアルタイムカウンタ(図下のRTC)などが見えます。
また、チップ内部にフラッシュメモリを持つESP32製品もあるようです(図左上のEmbedded flash)。
ちなみに、私が使用しているESP32-WROOM-32Dモジュールの中にあるESP32-D0WDチップには、フラッシュメモリが内蔵されていません。
※図の各機能の配置とチップ内の構造は関係していません。

ESP32チップ Functional Block Diagram
引用元:https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf
ESP32のアドレスマッピングを知る
データシートの3.1.2 Internal MemoryにはESP32チップに内蔵されたメモリの概要が書かれています。
448 KB of ROM for booting and core functions
520 KB of on-chip SRAM for data and instructions
ESP32チップには、448 KBのROMと、520 KBのRAMが内蔵されてるようです。
そして、繰り返し言いますが、ESP32チップには4MB以上の外部フラッシュメモリが接続されます。
CPUがこれらのメモリ内にあるデータへアクセスするためには、データが格納されているアドレスを指定しなければなりません。
また、ペリフェラルにアクセスするためにもアドレスが必要です。
ESP32ではこれらのアドレスを一括で管理するために、次の図で示された構造でアドレスをマッピングしています。
図の中央にある0x0000_0000 ~ 0xFFFF_FFFFが、ESP32で使われるアドレスマッピング(アドレス空間)です。
例えば0x3FF0_0000 ~ 0x3FF7_FFFFの領域にはペリフェラル(Peripheral)が割り当てられています。

Address Mapping Structure
引用元:https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf
アドレスマッピングを詳しく見る
アドレスマッピングの詳細は、データシートのTable 5: Memory and Peripheral Mappingに記載されています。

Memory and Peripheral Mapping Table
引用元:https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf
まずはROMについて見てみましょう。
表のEmbedded Memory(組み込みメモリ、内部メモリ)カテゴリにある、Internal ROM 0には0x4000_0000 ~ 0x4005_FFFFのアドレスが割り当てられています。
Internal ROM 1には0x3FF9_0000 ~ 0x3FF9_FFFのアドレスが割り当てられています。
それぞれのアドレス領域のデータサイズは384 KBと64 KBです。足し合わせると448 KBです。先程の説明で出てきた数値と同じですね。
448 KB of ROM for booting and core functions
続いて外部フラッシュメモリについて見てみましょう。
外部フラッシュメモリ(Externa Flash)には、4 MBと11 MB+248 KBの2つの領域が割り当てられています。
4 MBの領域には0x3F40_0000 ~ 0x3F7F_FFFFのアドレスが割り当てられています。
0x3F7F_FFFF ~ 0x3F40_0000には、0x3F7F_FFFF – 0x3F40_0000 + 1より、0x0040_0000個のデータを格納できます。10進数で表すと4194304個です。
一般的に、1つのアドレスには1バイトのデータが格納されるので、この領域には4194303バイトのデータを格納できます。
コンピュータの世界では1024 (2の10乗)バイトを1 KB(または1 KiB。1 kBではない)、1048576(2の20乗)バイトを1 MB(または1 MiB)で表現する習慣があるので、
4194303 B = 4 * 1048576 B = 4 * 1 MB = 4 MBとなります。4 MBと書かれていてもピッタリ4000000バイトとなるのではありません。
ROMやRAM、外部フラッシュメモリのデータ領域には決められたアドレスが与えられている、ということが分かりました。
GPIOのアドレスマッピングとレジスタの関係を知る
このブログ記事の目的は「ESP32のファイルシステムを扱う」なので、メモリのアドレスマッピングについて何となく理解できればOKです。
とはいえ、せっかくアドレスマッピングの表を開いているので、ついでにペリフェラルも見てみましょう。
注目するのはGPIOです。
先程の表Memory and Peripheral Mappingを見ると、GPIOには0x3FF4_4000 ~ 0x3FF4_4FFFのアドレスが割り当てられていることが分かります。
このアドレスをどのように使えばGPIOを扱えるのでしょうか・・・もうちょっと詳しく見てみましょう。

Memory and Peripheral Mapping: GPIO
引用元:https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf
ペリフェラルの使い方については、ESP32のテクニカルリファレンスマニュアルに詳細が書かれています。
5. IO_MUX and GPIO Matrixにある、5.12 Register Summaryを見てみましょう。
表の一番上にあるGPIO_OUT_REGは、GPIO 0~31の出力レジスタで、0x3FF4_4004というアドレスが割り当てられています。
その次にはGPIO_OUT_W1TS_REGというレジスタがあり、アドレス0x3FF4_4008が割り当てられています。
よく見ると、アドレスが4つ飛ばし(4バイト)で増えてることがわかります。

GPIO Register Summary
引用元:https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
GPIO_OUT_REGレジスタの詳細を見てみましょう。5.13 Registersに各レジスタの詳細が書かれています。
GPIO_OUT_REGレジスタには4バイト(32ビット)のデータ領域が割り当てられていることが分かります。
つまり、アドレス0x3FF4_4004に格納されている1バイトデータには、GPIO0 ~ GPIO7の出力値が、
アドレス0x3FF4_4005に格納されている1バイトデータにはGPIO8 ~ GPIO15の出力値が格納されているということです。
同様に、GPIOの出力(1/0)をセットするGPIO_OUT_W1TS_REGレジスタや、入力値が格納されるGPIO_IN_REGレジスタ等もあるので、気になる方はマニュアルを読んでみてください。

GPIO_OUT_REG register
引用元:https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
かなり前の話になりますが、このブログシリーズでGPIOを使ってLEDを点灯させる記事を書きました。
次のようなコードで、GPIO22の出力値を切り替え、LEDを点滅させていました。
今思うと、あれはGPIO_OUT_W1TS_REGレジスタの22ビット目に1と0を書き込んでただけなんですね〜。
#define GPIO_OUTPUT_IO_0 22 gpio_set_level(GPIO_OUTPUT_IO_0, 0); gpio_set_level(GPIO_OUTPUT_IO_0, 1);
まとめ
長くなりましたが、次のことを覚えておけばESP32のフラッシュメモリが扱いやすくなると思います。
- ESP32モジュールはESP32チップとフラッシュメモリを内蔵する
- フラッシュメモリとは別に、ESP32チップはROMとRAMを内蔵する
- アドレスマッピングにより、ROM、RAM、フラッシュメモリにアクセスするためのアドレスが決まっている
次回の記事では、フラッシュメモリを使ってデータの読み書きをします。