😁

Raspberry Pi Pico の Flashを実用で使いたいよね!!

2024/09/24に公開

使いたんですけれど、細かーいところで引っかかったのでメモ。

なんでこの記事を書くに至ったか。

Raspberry Pi Pico 無印 / Wでは、搭載マイコンのRP2040に対して直接アクセスが可能なAPIを提供しています。
https://www.raspberrypi.com/documentation/pico-sdk/hardware.html

そのAPIの1つを用いて、RP2040を経由してFlashへアクセスが可能です。これを用いると、Flashの空き領域を利用することで電源を切ってもデータを保持することができます。USBフラッシュメモリと同じですね。

さて、公式のサンプルコードをみていきましょう。
https://github.com/raspberrypi/pico-examples?tab=readme-ov-file#flash

最も単純なサンプル"program"では、flash_range_eraseでセクタ削除とflash_range_programでページ書き込みを行っています。
https://github.com/raspberrypi/pico-examples/blob/master/flash/program/flash_program.c

書込み単位と削除単位が異なり、書込み単位のほうが小さいという点に注意が必要です。

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つの条件をすべて満たす値を設定する必要があります。

  1. Flashの物理アドレスの範囲内か
    Flashの物理アドレスはデータシートで確認できる。

Flashの物理アドレス(XIP)は0x1000_0000~との記載あり[1]

https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf

最初の8ビットがセグメントデコードとのことなので、標準的な用途であれば0x10セグメントを用いるはずですね。

以上より、0x1000_0000以上、0x1100_0000未満を用いるのが良さそうです。

  1. リンカスクリプトで割り当てられた範囲内か
    リンカスクリプト(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未満を用いるのが良さそうです。

  1. マップファイルで他の用途で使用されていない範囲か
    マップファイル(hoge.map)とは、プログラムのビルド時に生成されるメモリ配置が詳細に記録されたファイルです。要するにリンカスクリプトが計画書、マップファイルが結果報告書です。

先ほどのサンプルファイルflash_program.cでは、#define FLASH_TARGET_OFFSET (256 * 1024)とあり、XIP_BASE + 256k = 0x10040000の部分を利用しています。

https://github.com/raspberrypi/pico-examples/blob/master/flash/program/flash_program.c#L15

このファイルを手元の環境でビルドして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利用が可能になります。

この他にもリンカスクリプトを修正し、あらかじめ必要領域を確保するという方策も考えられます。

脚注
  1. XIPとはexcute in placeの略称で、外部ストレージのメモリマップドIOシステムです。Raspberry Pi PicoのFlashはRP2040の外側にあるので外部ストレージ扱いになります。 ↩︎

ヘッドウォータース

Discussion