Raspberry Pi Pico の Flashを実用で使いたいよね!!
使いたんですけれど、細かーいところで引っかかったのでメモ。
なんでこの記事を書くに至ったか。
Raspberry Pi Pico 無印 / Wでは、搭載マイコンのRP2040に対して直接アクセスが可能なAPIを提供しています。
そのAPIの1つを用いて、RP2040を経由してFlashへアクセスが可能です。これを用いると、Flashの空き領域を利用することで電源を切ってもデータを保持することができます。USBフラッシュメモリと同じですね。
さて、公式のサンプルコードをみていきましょう。
最も単純なサンプル"program"では、flash_range_eraseでセクタ削除とflash_range_programでページ書き込みを行っています。
書込み単位と削除単位が異なり、書込み単位のほうが小さいという点に注意が必要です。
Flash Memory (フラッシュメモリ全体)
└── Block (ブロック: フラッシュメモリの消去単位)
├── Sector (セクタ: ブロック内の消去サブユニット)
│ ├── Page (ページ: データの読み書き最小単位)
│ │ ├── Data (データ: 実際に記録される情報)
│ │ └── ECC (エラー訂正コード: データ整合性保持情報)
確かに、このサンプルコード単体では問題なく動作しますが、実使用時には #define FLASH_TARGET_OFFSET (256 * 1024) の設定を適切に行う必要があります。この点について公式のAPI referenceに記述がなかったので、記事を書こうと思いました。
Flash利用時にメモリ破壊が起こりうる
Raspberry Pi Picoには、2MBのFlashが搭載されています。この2MBの内、利用可能でかつ他で利用されていない領域をFLASH_TARGET_OFFSETで指定する必要があります。
万が一適切に設定されていない場合、未定義動作を起こす場合があります。例えば、他で使用しているメモリ領域の破壊、Flash領域以外の参照など色々ですね。手元の事例では、Tcpサーバーと組合せたサンプルを作成した際に*[cyw43] do_ioctl(2, 263, 15): timeout*というログが出てフリーズしました。未定義動作らしい、意味不明なログですね。
有効なFLASH_TARGET_OFFSETの確認方法
適切なFLASH_TARGET_OFFSETの設定には、以下の3つの条件をすべて満たす値を設定する必要があります。
- Flashの物理アドレスの範囲内か
Flashの物理アドレスはデータシートで確認できる。
Flashの物理アドレス(XIP)は0x1000_0000~との記載あり[1]。
最初の8ビットがセグメントデコードとのことなので、標準的な用途であれば0x10セグメントを用いるはずですね。
以上より、0x1000_0000以上、0x1100_0000未満を用いるのが良さそうです。
- リンカスクリプトで割り当てられた範囲内か
リンカスクリプト(hoge.ld)とは、プログラムのメモリ配置を制御するためのスクリプトです。
pico-sdk\src\rp2_common\pico_standard_link\memmap_default.ld
物理アドレスの指定と、そのシンボリックが以下のように定義されています。
MEMORY
{
FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k
SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
}
0x1000_0000 + 0x0020_0000(2048k) = 0x1020_0000ですので、
0x1000_0000以上、0x1020_0000未満を用いるのが良さそうです。
- マップファイルで他の用途で使用されていない範囲か
マップファイル(hoge.map)とは、プログラムのビルド時に生成されるメモリ配置が詳細に記録されたファイルです。要するにリンカスクリプトが計画書、マップファイルが結果報告書です。
先ほどのサンプルファイルflash_program.cでは、#define FLASH_TARGET_OFFSET (256 * 1024)とあり、XIP_BASE + 256k = 0x10040000の部分を利用しています。
このファイルを手元の環境でビルドしてflash_program.elf.mapを覗いてみてところ、0x10040000付近は利用されていないことが分かりました。サンプルコードが適切に動作することの裏打ちですね。
*(.binary_info.*)
.binary_info.__bi_ptr33
0x100053e0 0x4 flash/program/CMakeFiles/flash_program.dir/C_/Program_Files/Raspberry_Pi/Pico_SDK_v1.5.1/pico-sdk/src/rp2_common/pico_stdio_uart/stdio_uart.c.obj
.binary_info.__bi_ptr34
0x100053e4 0x4 flash/program/CMakeFiles/flash_program.dir/C_/Program_Files/Raspberry_Pi/Pico_SDK_v1.5.1/pico-sdk/src/rp2_common/pico_stdio_uart/stdio_uart.c.obj
0x100053e8 __binary_info_end = .
.rel.dyn 0x100053e8 0x0
.rel.iplt 0x100053e8 0x0 c:/progra~1/raspbe~1/picosd~1.1/gcc-ar~1/bin/../lib/gcc/arm-none-eabi/10.3.1/thumb/v6-m/nofp/crtbegin.o
0x100053e8 . = ALIGN (0x4)
.ram_vector_table
0x20000000 0xc0
*(.ram_vector_table)
.ram_vector_table
ちなみに手元で別途開発していたTcpサーバーと組合せたサンプルのマップファイルでは、0x10040000付近がRaspberry Pi Pico Wの無線通信モジュール用のライブラリに割り当てられており、適切に動作しないことが分かります(動作させるとメモリ破壊が起こります。)。なぜcyw43がXIP領域を使用しているのか。。
.rodata.w43439A0_7_95_49_00_combined
0x100155f4 0x36fd8 CMakeFiles/pico_w-mqtt.dir/C_/Program_Files/Raspberry_Pi/Pico_SDK_v1.5.1/pico-sdk/lib/cyw43-driver/src/cyw43_ll.c.obj
.rodata.wifi_nvram_4343
0x1004c5cc 0x2e7 CMakeFiles/pico_w-mqtt.dir/C_/Program_Files/Raspberry_Pi/Pico_SDK_v1.5.1/pico-sdk/lib/cyw43-driver/src/cyw43_ll.c.obj
*fill* 0x1004c8b3 0x1
.rodata.cyw43_netif_output.str1.4
0x1004c8b4 0x22 CMakeFiles/pico_w-mqtt.dir/C_/Program_Files/Raspberry_Pi/Pico_SDK_v1.5.1/pico-sdk/lib/cyw43-driver/src/cyw43_lwip.c.obj
*fill* 0x1004c8d6 0x2
.rodata.cyw43_cb_tcpip_init.str1.4
0x1004c8d8 0x6 CMakeFiles/pico_w-mqtt.dir/C_/Program_Files/Raspberry_Pi/Pico_SDK_v1.5.1/pico-sdk/lib/cyw43-driver/src/cyw43_lwip.c.obj
*fill* 0x1004c8de 0x2
3つの条件をすべて満たす値を設定する値を設定することで、安全なFlash利用が可能になります。
この他にもリンカスクリプトを修正し、あらかじめ必要領域を確保するという方策も考えられます。
-
XIPとはexcute in placeの略称で、外部ストレージのメモリマップドIOシステムです。Raspberry Pi PicoのFlashはRP2040の外側にあるので外部ストレージ扱いになります。 ↩︎
Discussion