RP2040(Raspberry Pi Pico) - boot2 と cortex-m-rt の詳細
この冬のプロジェクトとして、手元にあったRaspberry Pi Pico(RasPico)のボード上で動くブートローダをRustで作ってみた。RasPicoで使われているRP2040というMCU特有の事情もあるし、低レイヤーRustで役にたつ知識も得ることができる、楽しいプロジェクトだった。GitHubのレポジトリ(https://github.com/nkon/boot-k)を参照しつつ、知見をまとめておこう。
長くなりすぎたので分割する。
この記事ではRasPico特有の事情や起動方法、そこで使われるboot2と呼ばれるブートローダの構造について解説する。
後半の記事として、自作のブートローダを実装するときの工夫した点について。
また、豆知識的な項目を個別記事としてまとめた。
実質的はリポジトリにある開発メモ(NOTE.md)の要約である。開発メモなのでまとまっておらず読みにくいが、細かい点はそちらも参照してほしい。
ハードウエア
ハードウエア詳細、開発環境は以前の記事にもまとめてある。
- ボード名称はRaspberry Pi Pico、ボード上にはMCU、発信器、USB Microコネクタ、電源(3.3V)、スイッチ、LED、2MBのQSPIフラッシュメモリ(W25Q16JV)が搭載されている。
- MCUはRP2040。Cortex M0+ 133MHz Dual Core。内蔵SRAMは265kB。
- ユーザプログラムは、外付けのFlashメモリに格納される。外付けFlashはメモリアドレス上にマップされ、そのアドレスで実行される(XIP:eXecute In Place)。
- 2xSPI, 2xI2C, 3xUART, 12bit ADC, 16ch PWM。
- 8本のGPIOがある。カスタマイズ可能なステートマシンとして動作でき、独自のアセンブラでプログラムする。
- SWDのデバッグインターフェイスあり。
普通にUSBに接続すると”RP2 Boot”というUSBメモリに見える。中には”INDEX.HTM”というファイルと”INFO_UF2.TXT”というファイルがある。これらはRP2040に内蔵されたROM(書き換え不可)のブートローダによるもの。
“INDEX.HTM”を開くと、公式サイトの製品ページに飛ぶ。データシートなどがダウンロード可能。
できあがったファームウエアをUF2形式に変換して、このドライブに書き込み、USBを繋ぎなおすと、そのファームウエアが実行される。再度、書き込みモードにしたいときは、基板上のBOOTSELスイッチを押しながらUSB接続すれば、初期と同様にドライブとして認識される。
この状態で、次のような組み込み開発環境を構築することができる。
- Rustでプロジェクトを作成。必要なライブラリを組み込む。
- Cargoでビルドする。
cargo run
で、プロジェクトをUF2に変換してUSBメモリに書き込む。- リセットすれば、書き込んだプログラムが実行される。
- USB CDCが有効になっているので、書き込んだプログラムとホストの間でシリアル通信が可能。
JTAG(SWD)からの書き込み
より低レイヤー寄りの開発の場合、JTAGで接続できるほうが便利だ。
- デバッガ接続。ステップ実行、レジスタ値の読み取りなどが可能。拡張によりVS Codeからもデバッグできる
- RTTを使ってログ出力が可能
もう一枚RasPicoボードを用意して、そちらをデバッガにすることができる。
こちらも以前の記事に詳しい。
- Bootボタンを押しながらUSBケーブルを接続。USB Mass storageにpicoprobe.uf2ファームウエアを書き込む。
- 接続も上記ページに書かれている。JTAGデバッガだけでなくUARTの機能も持っている。双方をターゲットボードに接続する。
- プロジェクトテンプレートからプロジェクトを作成する。
- JTAGを使ってターゲットに書き込む時は、
probe-run
を使う。cargo run
すると、probe-rs
がバイナリをSWD経由でターゲットボードに書き込んで実行される。
rp2040-project-template の解説
How Toとして、上記の情報は広く知られているし、これだけ知っていれば、RustでRasPico上で動作するプログラムを開発することができる。この環境がデファクトで、選択肢に迷いがなく、情報入手も容易。これがRasPico + Rustで組み込みプログラムに入門することの大きなメリット。旧来の評価ボード + C クロスコンパイラだと、環境固有の事情が多すぎて、自分のやりたい環境に適用していくことに時間を取られる。
ここからが本記事の本番。rp2040-project-templateが何をやっているのかを詳しく掘っていこう。
MCUの仕組みやライブラリが複数絡み合っていて、非常に興味深い。
プロジェクト・テンプレートのCargo.toml
を見ると、いくつかのライブラリを読み込んでいる。
cortex-m
: cortex-m プロセッサの低レベルアクセスを提供(MAC: Micro Architecture Crate)。rust-embedded プロジェクトより。cortex-m-rt
: cortex-m の起動を面倒見る。rust-embedded プロジェクトより。embedded-hal
: rust-embedded プロジェクトの提供するHAL。チップ依存部分はrp2040-hal
として分離され、このレイヤーはアーキテクチャ非依存のラッパー。defmt
: ログライブラリdefmt-rtt
: defmtのRTTを使う実装。RTTはSWDの上でのUARTより高速な文字出力方法panic-probe
:probe-rs
が提供するパニックハンドラ。パニック時にスタックトレースが出力されるのがデバッグに便利。メッセージ出力インターフェイスとしてprint-rtt
かprint-defmt
を選べる。テンプレート・プロジェクトではprint-defmt
を使うようになっている。rp-pico
: Raspberry Pi PicoというボードをサポートするBSP(Board Support Package)。これをインポートしておけば、RP2040というチップをサポートするのHAL(Hardware Abstraction Layer)であるrp2040-hal
も、チップ依存のブートローダであるrp2040-boot2
もインポートされる。rp2040-hal
: rp-picoをインポートしない場合はHALであるrp2040-hal
とrp2040-boot2
を手動でインポートする。rp2040-boot2
: RP2040用のboot2 ブートローダ。RP2040特有の起動の仕組みをサポートする(後述)。
RP2040特有の部分、Cortex-M + Rust特有の部分があり、混乱しやすい。常に、どれについて話題にしているのかを意識する必要がある。
おおまかには次の順序で説明していく。
- boot2(RP2040固有のブートストラップ)
- cotrex-m-rt(rust-embeddedプロジェクトが提供するCortex-Mのスタートアップ)
- flip-link(rust-embeddedプロジェクトが提供するスタックオーバーフローをぼうしするメモリレイアウト)
後半では、ブートローダを作成する場合の諸々について説明していく。
- Cargoでのマルチターゲットプロジェクトの構成
- boot2-ram-memcpy(Flashメモリから内蔵SRAMにコピーして動作するboot2プログラム)
- probe-rs(probe-rsプロジェクトが提供するJTAG/SWDインターフェイス)
- イメージ操作ツール(自作のイメージ操作ツール、それをブートローダ、ブートされる組み込みアプリケーション、イメージ操作用のコマンドラインツールで兼用する)
- UART出力の方法(とくに引数の構成方法)
- RP2040のROM内蔵ユーティリティの使い方
boot2を使ったユーザーアプリケーションの起動(RP2040固有)
RP2040には16KBの内蔵ROMがあり次の機能が格納されている。
- Initial startup routine: スタートアップ・ルーチン
- Flash boot sequence: QSPIフラッシュから起動するための仕組み
- Flash programming routines: QSPIフラッシュに書き込むためのサブルーチン
- USB mass storage device with UF2 support: USBマスストレージを提供しUF2ファームウエアからブートする
- Utility libraries such as fast floating point: ユーティリティ関数を提供(浮動小数点演算など)
起動の流れは、POR(Power On Reset)→内蔵ROMの Inital start up routine→外付けFlash有効にする(この段階ではSPIモード)→外付けFlashの先頭にあるboot2領域を読み込み実行する→(通常は)boo2のプログラムは、フラッシュのチップ仕様にしたがってQSPIを有効にして(SPIモードより高速)、XIPを有効にして、外付けFlashの後続領域にあるユーザアプリケーションを実行する。
boot2のコードはhttps://github.com/rp-rs/rp2040-boot2にある。
- boot2は、外付けフラッシュの先頭領域に格納される。後続のユーザアプリケーションを起動するためのブートローダ。
- RasPicoは外付けFlashとしてW25Q16JVが搭載されているので、boot2_w25q080を使う。boot2はRustのライブラリだが、本体はアセンブラで書かれている。
- それ以外にもboot2_ram_memcpyというものがあり、これは、コードを外付けFlashから内蔵RAMにコピーして、その上で実行する。そうすると 外付けFlashがXIPモードではなく、書き込み可能 にすることができる。
- Rustのライブラリsrc/lib.rsはプレビルドしたバイナリを配列に読み込むようになっている。ソースを変更した場合は
UPDATE_PRECOMPILED_BINARIES=true
とすることでbuild.rs
にもとづいて再ビルドが行われる(arm-none-eabi-gccが必要、features = ["assemble"]
を指定しておく)。
Macの場合、brew install arm-none-eabi-gcc
でインストールした場合、ビルドしようとするとcannot read spec file 'nosys.specs'
というエラーが出る。調べてみたら、パッケージが壊れているようだ。
https://github.com/raspberrypi/pico-feedback/issues/355
このページにあるように、パッケージの干渉を防ぐために、いったんbrew uninstall arm-none-eabi-gcc arm-none-eabi-gdb arm-none-eabi-bintool
して、さらにbrew autoremove
してからbrew install --cask gcc-arm-embedded
する。
boot2からユーザアプリケーションを起動する方法(RP2040固有)
boot2からユーザアプリケーションを起動するところが興味深いので詳細を見ておこう。
vector_into_flash:
ldr r0, =(XIP_BASE + 0x100)
ldr r1, =(PPB_BASE + M0PLUS_VTOR_OFFSET)
str r0, [r1]
ldmia r0, {r0, r1}
msr msp, r0
bx r1
ldr r0, =(XIP_BASE + 0x100)
:ldr
はロード命令。=
の後の続く、=0x12345678
は即値。r0
にユーザアプリケーションの先頭アドレスを格納。Flashの先頭アドレス(XIP_BASE
=0x10000000)からboot2のサイズ(=0x100)だけオフセット。- プロジェクト・テンプレートのリンカファイルによって、ユーザアプリケーションの先頭にboot2のバイナリ(=0x100)が埋め込まれるので、ユーザアプリケーション本体(
.text
)は0x100オフセットしたところから始まる。
- プロジェクト・テンプレートのリンカファイルによって、ユーザアプリケーションの先頭にboot2のバイナリ(=0x100)が埋め込まれるので、ユーザアプリケーション本体(
ldr r1, =(PPB_BASE + M0PLUS_VTOR_OFFSET)
:r1
にVTOR(Vector Table Offset Register)のアドレスをセット。PPBはコアの基本設定をするレジスタ群。VTORのアドレスはPPB_BASE
=0xe000_0000からのオフセット(=0xed08)で得られる。RP2040のデータシートのPPB関連記述を参照。str r0, [r1]
: 間接参照なので、r1
がポイントするアドレスにr0
の内容を書き込む。インテル形式とは逆向き。つまり、VTORにユーザアプリケーションの先頭アドレスを格納する。ここには、cortex-m-rtのリンカスクリプトによって.vector_table
が割り当てられている(後述)。ldmia r0, {r0, r1}
:ldmia
はレジスタ復元命令。POP
のようなもの。r0
が指し示すアドレスから始まるメモリの内容を、{r0, r1}
のレジスタ配列に格納する(この場合は2個)。つまり、r0
にはr0
が指す.vector_table[0]
の内容(=SP初期値=0x2003_fbb8
)が、r1
には.vector_table[1]
の内容(=コードの先頭アドレス=0x1000_01c1
)が格納される。これらの値は、ユーザアプリケーションがリンクするライブラリcortex-m-rt
(と、そこから呼ばれるflip-link
)のコンパイラやリンカスクリプトがセットする。msr msp, r0
:msr
はスタックポインタを更新する専用命令。r0
の内容(=SP初期値=0x2003_ffb8
)がmsp
にセットされる。bx r1
:r1
の指すアドレス(=0x1000_01c1
)にジャンプする。ジャンプの場合、アドレス末尾のビットが1
だと、それを0
に変更して、little endian モードで実行する。
メモリ構造を整理しながら起動の手順をたどると、このような感じになる。
- アプリケーション・イメージ=0x1000_0000にロードされる。内蔵ROMのInital startup routineはこのアドレスにジャンプしてくる。
- 先頭 0x100バイトはboot2のイメージが格納される。RasPicoの場合は
boot2_w25q080
。この配置はプロジェクト・テンプレートのリンカスクリプトが行う。boot2が実行され、アプリケーションにジャンプする(.vector_table[1]=0x100001c1
)。 - 0x100より後ろにアプリケーション本体が格納される。
- アプリケーション本体の先頭には
.vector_table
が格納される。この配置はcortex-m-rt
のリンカスクリプトが行う。.vector_table[0]
にはスタックポインタ、.vector_table[1]
にはアプリケーションの実行開始アドレスが格納される。この配置はcortex-m-rt
とそれが読み込んでいるflip-link
が行う。 - 実行開始アドレスは、アプリケーション本体からリセットベクタ(=0xc0)だけオフセットしたところから始まる。この配置は
cortex-m-rt
とそれが読み込んでいるflip-link
が行う。
- アプリケーション本体の先頭には
- 先頭 0x100バイトはboot2のイメージが格納される。RasPicoの場合は
cargo-binutils(Cortex-M + Rust)
rust-embedded プロジェクトが出している cargo-binutils を入れて置けば、ほぼ gnu binutils 互換で、バイナリの情報を調べることができる。--
より前のオプションはcargo
に対するもの、--
より後ろのオプションはobjdump
に対するもの。ターゲットのバイナリのパスは cargo の情報から適切に選択される。
ディスアセンブルによる確認、(Cortex-M + Rust, RP2040)
プロジェクトの先頭付近をデイスアセンブルしてみる。順序が異なるが、セクタ、サイズ、開始アドレス(LMA)が上の説明と対応していることがわかる。
❯ cargo objdump -v -- --headers
rp2040-project-template: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA Type
0 00000000 00000000 00000000
1 .vector_table 000000c0 10000100 10000100 DATA # 2. 0x1000_0000+0x100から.vector_tableが配置される(サイズ=0xc0)
2 .boot2 00000100 10000000 10000000 DATA # 1. 0x1000_0000からbootが配置される(サイズ=0x100)
3 .text 000024ec 100001c0 100001c0 TEXT # 3. 0x1000_0000+0x100+0xc0からアプリケーションコードが配置される
4 .rodata 00000700 100026b0 100026b0 DATA
5 .data 00000038 2003fbb8 10002db0 DATA # 4. .dataセクションが0x2003_fbb8から始まる。flip-linkを使っているので、その上がスタックとなる
6 .gnu.sgstubs 00000000 10002e00 10002e00 TEXT
7 .bss 0000000c 2003fbf0 2003fbf0 BSS
8 .uninit 00000400 2003fbfc 2003fbfc BSS
❯ cargo objdump -v -- --disassemble-all > asm.S
RP2040はCortex-M0+コアでありThumb2命令を使う。Thumb2命令は基本2バイトなので、32ビット即値を格納できない。即値はメモリの別の場所に格納され、PC相対で指定される。たとえば、ldr r0, =0x1000_0100
という命令は、0x1000_0100という即値をどこか(コードのシーケンシャル実行の区切りがいいところ、たとえばjmp
命令などで実行が途切れるところ、この例では0x48離れたところ)に格納して、ldr r0, [pc, #0x48]
という命令となる。これなら0x4813
で16ビットに収まる。この操作はリンカによって行われる。
rp2040-project-template: file format elf32-littlearm
Disassembly of section .vector_table: # .vector_tableは0x1000_0100から始まる
10000100 <__vector_table>:
10000100: fbb8 2003 <unknown> # .vector_table[0]はスタックポインタの初期値=0x2003_fbb8
10000104 <__RESET_VECTOR>:
10000104: 01c1 lsls r1, r0, #0x7 # .vector_table[1]はアプリケーションの実行開始アドレス=0x1000_01c0
10000106: 1000 asrs r0, r0, #0x20 # ここはvector_tableなのでディスアセンブル・ニモニックは誤変換
10000108 <__reset_vector>:
10000108: 0c91 lsrs r1, r2, #0x12
1000010a: 1000 asrs r0, r0, #0x20
1000010c: 2695 movs r6, #0x95
1000010e: 1000 asrs r0, r0, #0x20
... # 中略
Disassembly of section .boot2: # boot2は0x1000_0000から始まる
10000000 <BOOT2_FIRMWARE>:
10000000: b500 push {lr}
10000002: 4b32 ldr r3, [pc, #0xc8] @ 0x100000cc <BOOT2_FIRMWARE+0xcc>
10000004: 2021 movs r0, #0x21
10000006: 6058 str r0, [r3, #0x4]
10000008: 6898 ldr r0, [r3, #0x8]
1000000a: 2102 movs r1, #0x2
1000000c: 4388 bics r0, r1
1000000e: 6098 str r0, [r3, #0x8]
10000010: 60d8 str r0, [r3, #0xc]
10000012: 6118 str r0, [r3, #0x10]
10000014: 6158 str r0, [r3, #0x14]
中略
1000009c: 4812 ldr r0, [pc, #0x48] @ 0x100000e8 <BOOT2_FIRMWARE+0xe8>
1000009e: 4913 ldr r1, [pc, #0x4c] @ 0x100000ec <BOOT2_FIRMWARE+0xec>
100000a0: 6008 str r0, [r1]
100000a2: c803 ldm r0, {r0, r1}
100000a4: f380 8808 msr msp, r0 # アプリケーションに実行を移す。`msr`命令が特徴的なので見つけやすい
100000a8: 4708 bx r1
100000aa: b503 push {r0, r1, lr}
100000ac: 6a99 ldr r1, [r3, #0x28]
100000ae: 2004 movs r0, #0x4
100000b0: 4201 tst r1, r0
100000b2: d0fb beq 0x100000ac <BOOT2_FIRMWARE+0xac> @ imm = #-0xa
100000b4: 2001 movs r0, #0x1
100000b6: 4201 tst r1, r0
100000b8: d1f8 bne 0x100000ac <BOOT2_FIRMWARE+0xac> @ imm = #-0x10
100000ba: bd03 pop {r0, r1, pc}
100000bc: b502 push {r1, lr}
100000be: 6618 str r0, [r3, #0x60]
100000c0: 6618 str r0, [r3, #0x60]
100000c2: f7ff fff2 bl 0x100000aa <BOOT2_FIRMWARE+0xaa> @ imm = #-0x1c
100000c6: 6e18 ldr r0, [r3, #0x60]
100000c8: 6e18 ldr r0, [r3, #0x60]
100000ca: bd02 pop {r1, pc}
100000cc: 0000 movs r0, r0
100000ce: 4002 ands r2, r0
100000d0: 0000 movs r0, r0
100000d2: 1800 adds r0, r0, r0
100000d4: 0000 movs r0, r0
100000d6: 0007 movs r7, r0
100000d8: 0300 lsls r0, r0, #0xc
100000da: 005f lsls r7, r3, #0x1
100000dc: 2221 movs r2, #0x21
100000de: 0000 movs r0, r0
100000e0: 00f4 lsls r4, r6, #0x3
100000e2: 1800 adds r0, r0, r0
100000e4: 2022 movs r0, #0x22
100000e6: a000 adr r0, #0 <BOOT2_FIRMWARE+0xea>
100000e8: 0100 lsls r0, r0, #0x4 # 即値 0x1000_0100
100000ea: 1000 asrs r0, r0, #0x20
100000ec: ed08 e000 <unknown> # 即値 0xe000_ed08
中略
Disassembly of section .text: # アプリケーションコードの始まり
100001c0 <__stext>:
100001c0: f000 fd67 bl 0x10000c92 <__pre_init> @ imm = #0xace
100001c4: 4808 ldr r0, [pc, #0x20] @ 0x100001e8 <__stext+0x28>
100001c6: 4909 ldr r1, [pc, #0x24] @ 0x100001ec <__stext+0x2c>
100001c8: 2200 movs r2, #0x0
100001ca: 4281 cmp r1, r0
100001cc: d001 beq 0x100001d2 <__stext+0x12> @ imm = #0x2
100001ce: c004 stm r0!, {r2}
100001d0: e7fb b 0x100001ca <__stext+0xa> @ imm = #-0xa
100001d2: 4807 ldr r0, [pc, #0x1c] @ 0x100001f0 <__stext+0x30>
100001d4: 4907 ldr r1, [pc, #0x1c] @ 0x100001f4 <__stext+0x34>
100001d6: 4a08 ldr r2, [pc, #0x20] @ 0x100001f8 <__stext+0x38>
100001d8: 4281 cmp r1, r0
100001da: d002 beq 0x100001e2 <__stext+0x22> @ imm = #0x4
100001dc: ca08 ldm r2!, {r3}
100001de: c008 stm r0!, {r3}
100001e0: e7fa b 0x100001d8 <__stext+0x18> @ imm = #-0xc
100001e2: f000 f80b bl 0x100001fc <main> @ imm = #0x16 # cortex_m_rt の main にジャンプ
100001e6: de00 udf #0x0
100001e8 <$d.10>:
100001e8: f0 fb 03 20 .word 0x2003fbf0
100001ec: fc fb 03 20 .word 0x2003fbfc
100001f0: b8 fb 03 20 .word 0x2003fbb8
100001f4: f0 fb 03 20 .word 0x2003fbf0
100001f8: b0 2d 00 10 .word 0x10002db0
100001fc <main>: # cortex_m_rt が提供する main 関数
100001fc: b580 push {r7, lr}
100001fe: af00 add r7, sp, #0x0
10000200: f000 f800 bl 0x10000204 <rp2040_project_template::__cortex_m_rt_main::h9efed5a09b41a788> @ imm = #0x0
# ユーザコードの main にジャンプ
10000204 <rp2040_project_template::__cortex_m_rt_main::h9efed5a09b41a788>: # ユーザコードのmainはcortex_m_rt_mainと、マクロ展開される
10000204: b580 push {r7, lr}
10000206: af00 add r7, sp, #0x0
10000208: b090 sub sp, #0x40
1000020a: 484e ldr r0, [pc, #0x138] @ 0x10000344 <rp2040_project_template::__cortex_m_rt_main::h9efed5a09b41a788+0x140>
1000020c: 2501 movs r5, #0x1
1000020e: 6005 str r5, [r0]
略
cortex-m-rtとflip-link(embedded-rustプロジェクトが提供するCortex-Mのスタートアップ)
Rustはシステムプログラミング言語であり、高速で堅牢なプログラミングが要求される組込み分野に非常に適している。それをサポートするためにrust-embeddedプロジェクトがあり、そのプロジェウトが提供している方法に従うことがデファクトとなる。歴史的な事情によりCでの組み込みは亜種が多岐にわたっていて、適切な情報を得ることが難しいが、Rustでの組み込みの場合はrust-embeddedというデファクトに沿っていけばよいので、むしろ簡単である。
Rust-embeddedプロジェクトが想定するライブラリの構成は次のようになっている。Raspberry Pi Pico以外のボード、RP2040以外のチップについても同様の構成となる。
- BSP(board support package) = rp-pico
- HAL(hardware abstruction layer) = rp2040-hal
- PAC(peripheral access crate) = rp2040-pac
- MAC(micro architecture crate) = cortex-m
- HAL(hardware abstruction layer) = rp2040-hal
Micro architecture crete(MAC) がコアそのものをサポートし、PAC(Peripheral Access Crate)がペリフェラルへのレジスタアクセスをサポートする。PACはSVD2RUST でSVDから自動生成されたものがベースとなる。SVD(System View Description) はCMSIS-SVDで定められているインターフェイスで、ペリフェラルのレジスタをXMLベースで記述したもの。チップベンダから提供される。
MACとPACの上にHAL(Hardware Abstraction Layer)があり、チップの機能レベル(GPIOなど)のAPIを提供している。
さらにその上にBSPがボードレベルの機能(LEDやスイッチなど)を提供している。
それとは別にCortex-Mの場合はスタートアップのためにcortex-m-rt
が必要となる。cortex-m-rt
は次のものを提供する。
- いくつかのリンカスクリプト・フレームワークを提供し、コードを適切なアドレスに配置する。
- リセット・ベクタ、割り込みベクタの割当も cortex-m-rt が担当する。
- スタートアップ・ルーチンを提供し、ユーザ・アプリが
#[entry]
と指定した関数を呼び出す。
リンカ・スクリプト
このテンプレートでは .cargo/Config.toml
によってリンカオプションが指定されている。
rustflags
はcargo
からrustc
に渡される引数。-C
はリンカに関連する引数。link-arg
はそのままリンカに渡される引数。-Txxxx
はリンカスクリプトの指定。
rustflags = [
"-C", "linker=flip-link",
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "inline-threshold=5",
"-C", "no-vectorize-loops",
]
cortex-m-rt
がlink.x
を提供する。link.x
がプロジェクト固有ののmemory.x
を読み込む。link.x
はrp2040-pac
が提供するdevice.x
も読み込み、チップのメモリマップに合わせてオブジェクトをアロケーションする。link.x
は、割り込みベクタのためのセクション(.vector_table
)を提供する。
- テンプレートはデバッグ出力にdefmtを使っている。defmtを使うためには
defmt.x
を使って必要なオブジェクトを必要なアドレスに配置しなければならない。
また、Cargo.toml
の方には、release build時にLTOを含む最適化を実施するように設定されている。
flip-link
このテンプレートは flip-link
を使ってスタックオーバーフローをしないようにしている。
通常はRAMアドレスの低位側にBSS、ヒープが配置され、RAMアドレスの最高位側からスタックが消費されていく。この場合、スタック・オーバーフローすればヒープ領域が破壊される。flip-link
の場合は、RAMアドレスの高位側にBSSとヒープが予め配置され、その下からスタックが消費されていく。そうすれば、スタック・オーバーフローした場合はRAMの最低位アドレスに到達するだけでヒープが壊れることはない。
公式ページの図がわかりやすい。この図ではアドレス高位が上に書かれている(インテル方式)ことに注意。
実際には~/.cargo/bin/flip-link
コマンドがlld
のラッパーとして働く。
VS Code debugger
プロジェクト・テンプレートには .vscode/launch.json
も付属している。Cortex-Debug
など必要な拡張がインストールされていれば、VS CodeからGUIでデバッグが可能となる。今回はワークスペースのサブプロジェクトとして構成している。ビルドされたバイナリのパスが、ワークスペースのtarget
を指すように修正しなければならない。
"configurations": [
{
"coreConfigs": [
{
"programBinary": "../target/thumbv6m-none-eabi/debug/rp2040-project-template",
また、launch.json中にコメントされているが、rp2040.svdを保存しておけば、デバッガの変数ビューで、ペリフェラル・レジスタが表示される。
長くなりすぎたので分割する。