RP2040(Raspberry Pi Pico) - boot2 と cortex-m-rt の詳細

Written on 2024-06-02

この冬のプロジェクトとして、手元にあった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-rttprint-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-halrp2040-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)だけオフセット。
  • 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が行う。

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

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によってリンカオプションが指定されている。

rustflagscargoから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-rtlink.xを提供する。
    • link.xがプロジェクト固有ののmemory.xを読み込む。
    • link.xrp2040-pacが提供するdevice.xも読み込み、チップのメモリマップに合わせてオブジェクトをアロケーションする。
    • link.xは、割り込みベクタのためのセクション(.vector_table)を提供する。
  • テンプレートはデバッグ出力にdefmtを使っている。defmtを使うためにはdefmt.xを使って必要なオブジェクトを必要なアドレスに配置しなければならない。

また、Cargo.toml の方には、release build時にLTOを含む最適化を実施するように設定されている。

このテンプレートは 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を保存しておけば、デバッガの変数ビューで、ペリフェラル・レジスタが表示される。

長くなりすぎたので分割する。

後編はこちら