鳥なき里のマイコン屋(126) ラズパイPico、遅ればせながらビルド&デバッグ環境整える

Joseph Halfmoon

4月にようやくRaspberry Pi Picoを1枚購入後、ずっとMicroPythonで動かしてきました。でもま、ずっと気になっていたのが、ボードの短辺にDEBUGと書かれてある3端子。今回は重い腰を上げて、C/C++のビルドからGDBでデバッグまで一通り動かしてみました。ようやくDEBUG端子の活躍の場ができた?

MicroPythonで始めたときは、Pythonでは取り扱えないデバイスなどが多くて直ぐにC/C++に移行しないといけなくなるでしょ的な予想でおったのです。しかし、ラズパイPicoにはPIOという強力なステートマシンがあり、ハードよりの仕事を任せることができます。お陰でMicroPythonでも大抵のことはデキてしまう。。。ということでズルズルとC/C++環境の構築は先延ばしにしてしまいました。

しかし、MicroPythonでは無縁なハードもあるのです。ラズパイPicoボードの端っこにあるDEBUGとシルクが印刷された3端子です。2線式(GNDと合わせて3本)Serial Wire Debugの端子です。これによりデバッガを動かして、ブレークポイントで止めたり、メモリやレジスタの内容を吟味したりすることができるようになります。Cやアセンブラで書くときは必須な機能。ラズパイPicoでもそろそろアセンブラしたいので使えるようにしておく、というものでしょう。

注文してあった2枚目のRaspberry Pi Picoボードが到着、こちらはC/C++での開発用といたしました。MicroPythonの書き込み自体は数分あれば終るのですが、1枚を両方の環境で使っていると、PythonコードをCのオブジェクト載せた状態に書き込もうとしたりして混乱必至。使い分けるというもんでしょう。ボードはお手頃価格だし、最近は在庫もあるみたいだし。

まずはビルド環境の準備

開発母艦としてRaspberry Pi  4 model B+(お手頃な2GB版だけれども)も以前に購入してあったのでそれをクロス開発のホストとして使用します。インストール方法は、以下のご本家getting-startedを見れば「ほぼほぼ一発」。Raspberry Pi OS上であれば、便利なスクリプトなど準備されているので、書かれている通りの操作を数行打ち込めば大筋終ります。勿論、ダウンロードやらビルドやらに時間が掛かりますが。

getting-started-with-pico.pdf

実はRaspberry Pi 4が到着したときに時間のかかるダウンロードやビルドはやってありました。今回はその先です。実際にサンプルプログラムをビルドして、ラズパイ4からラズパイPicoに書き込んでみる。そしてデバッガの制御化でそのオブジェクトを動かしてみることであります。

ラズパイPicoとラズパイ4の間の接続

Getting Startedを読むと、Picoと母艦のラズパイ4の間の接続には3系統の経路があることが分かります。

  1. ラズパイ4のUSBポートにPicoのUSBを接続
  2. ラズパイ4の拡張端子のUART端子にPicoのUARTを接続
  3. ラズパイ4の拡張端子とPicoのDEBUGポートを接続

第1の経路は2つの方法で使うことができます。最初はラズパイ4からドライブに見せる方法。Pico上のBOOTSELボタンを押しながらUSBを接続すればラズパイ4からは、/dev/sda1 のようなリムーバブルディスクとして認識されます。これをファイルシステムの適当なところにマウントし、そこにラズパイ4上でクロスコンパイルした結果の .uf2 オブジェクトファイルを転送すればPicoのFlashに書き込まれる、という方法です。

第1の経路の2番目の方法はUSBを仮想シリアルとして使う方法です。ラズパイ4から /dev/serial0 のように認識されるので、仮想端末など使えばPico上のプログラムと通信できます。

第2の経路はUSBでなく、「本物」のUART同士を接続し(RXとTXをクロスさせるのを忘れずに)、シリアル通信するものです。

そして第3の経路はラズパイ4の拡張端子GPIO24と25をPicoのデバッグポートに接続するもの。デバッガ(OpenOCD)がこの経路を通じてPicoのデバッグインタフェースを制御してくれます。デバッガからはオブジェクトの書き込み機能があるので、デバッグ用にビルドされた .elf 形式のオブジェクトの書き込みができます。

さて、第1の経路については、Getting Startedの指示どおり操作して、ストレージとしてもシリアルポートとしても使えることを確かめました。この経路でシリアル使えるなら、急いで第2の経路は確かめなくてもよいね(もう一組ケーブル作るの面倒くさいし。)

当然、今回のターゲットは第3の経路です。Picoにピンヘッダを半田付けする際に何も考えずDEBUGポートにもピンヘッダ(勿論上向きにですが)を半田付けしています。ラズパイ4の拡張ポートもピンヘッダなので、接続するためにはピンソケット~ピンソケットのメスメス型のワイヤが3本必要です。Getting Startedには『長いケーブルはエラーを起こしやすいぜ』的なことが書かれています。そのお教えから、ちょっと気が引けるのですが、2枚のボードを置く場所の関係で 約60cm くらいの長さのケーブル(AWG22、より線)を使ってしまいました。以下の写真。ま、これでとりあえず正常に動作しているみたいので 60cmで当分良いか。(2021年7月12日追記:結局この60cmケーブルは信号品質悪くてダメとの結論にいたりました。15cmに短くしたらOKみたい。詳しくはこちら

Pico_RPi4

ビルドとデバッグ

さてデバッグ動作の確認テストの対象は、吉例のLチカ(Blink)です。pico_examplesの中のblinkを使わせていただきました。ビルドするときのキモはcmakeでMakefileを作るときにちゃんとDEBUG用のビルドタイプを指定しておくことです。デバッグ用にビルドしたつもりがちゃんとDEBUG情報を含むオブジェクトができていなくて(BUILDとつづるところをBULDとしていた)、私はしばらく悩みました。

$ cmake -DCMAKE_BUILD_TYPE=Debug ..

make自体は わざわざ -j4 など指定しなくとも、大した時間もかからずに終わると思います。複数できるオブジェクトのうち、.uf2ならばストレージ経由でPicoに書き込み、.elfならばデバッガ経由で書き込みです。

後でVS CodeなどでCUIコマンドの動作をGUIの裏側に「隠ぺい」したいと考えておりますが、今回は勉強のためにも手動で各コマンドを動かしてみたいと思います。

最初に OpenOCDを立ち上げておかないとなりません。これがデバッグ用のサーバーになり、これにGDBを接続してデバッグを行うという感じ。Getting Startedに書かれているとおり(意味もわからず)以下のコマンドを打ち込むと起動いたしました。

$ openocd -f interface/raspberrypi-swd.cfg -f target/rp2040.cfg

例によってお間抜けな失敗ですが、最初 openocdがうまく立ち上がらず DAP init failed というエラーが発生しました。しかしそんなことは Getting Started の著者はお見通しでした。「接続を確かめよ」とあります。ラズパイ4のピンヘッダに刺した筈のケーブルが、ピンとピンの間にハマって「固定」されていました。老眼の目で見ずらい場所のボードに刺したときに「手が滑った」みたいです。接続しなおせば動きました。

うまく行くと、ずらずらとメッセージが出た最後に、以下のようにgdbでポート3333に接続してね、というメッセージが現れます。

Info : Listening on port 3333 for gdb connections

これがでたら、OpenOCDが動作している(動作中のウインドウにはOpenOCDからメッセージが出力されてくる)ので、別ウインドウを開けgdbでデバッグセッションを始めます。

$ gdb-multiarch blink.elf
GNU gdb (Raspbian 8.2.1-2) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.

~以下略~

先ほどOpenOCDが言っていたように、最初にOpenOCDがリッスンしている3333ポートに接続です(後でGDBの初期化ファイルに書いておけよ、と。)

(gdb) target remote localhost:3333
Remote debugging using localhost:3333

~以下略~

オブジェクトファイル(.elf)のロード

(gdb) load
Loading section .boot2, size 0x100 lma 0x10000000
Loading section .text, size 0x3f68 lma 0x10000100
Loading section .rodata, size 0xd8c lma 0x10004068
Loading section .binary_info, size 0x20 lma 0x10004df4
Loading section .data, size 0x1cc lma 0x10004e14
Start address 0x100001e8, load size 20448
Transfer rate: 32 KB/sec, 3408 bytes/write.

念のため初期化し、ロードされているファイルのソースを眺めます

(gdb) monitor reset init
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x000000ee msp: 0x20041f00
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x000000ee msp: 0x20041f00
(gdb) list

~以下ソース~

2か所にブレークポイントを仕掛けて走らせるとこんな感じ

(gdb) b main
Breakpoint 1 at 0x1000035c: file /home/pi/pico/pico-examples/blink/blink.c, line 9.
(gdb) b 20
Breakpoint 2 at 0x10000380: file /home/pi/pico/pico-examples/blink/blink.c, line 20.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x1000035c in main at /home/pi/pico/pico-examples/blink/blink.c:9
2 breakpoint keep y 0x10000380 in main at /home/pi/pico/pico-examples/blink/blink.c:20
(gdb) continue
Continuing.
Note: automatically using hardware breakpoints for read-only addresses.
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x00000178 msp: 0x20041f00

Thread 1 hit Breakpoint 1, main () at /home/pi/pico/pico-examples/blink/blink.c:9
9 int main() {

もちろんArmのレジスタの内容のダンプもできます。

(gdb) info r
r0             0x3d54d             251213
r1             0x0                 0
r2             0x0                 0
r3             0x3d598             251288
r4             0xd0000000          -805306368
r5             0x2000000           33554432
r6             0x18000000          402653184
r7             0x0                 0
r8             0xffffffff          -1
r9             0xffffffff          -1
r10            0xffffffff          -1
r11            0xffffffff          -1
r12            0x200003f4          536871924
sp             0x20041ff0          0x20041ff0
lr             0x10000d0f          268438799
pc             0x10000380          0x10000380 <main+36>
xPSR           0x21000000          553648128
msp            0x20041ff0          0x20041ff0
psp            0xfffffffc          0xfffffffc
primask        0x0                 0
basepri        0x0                 0
faultmask      0x0                 0
control        0x0                 0

シングルステップ実行はこんな感じ

(gdb) next
JTAG failure 7
SWD DPIDR 0x0bc12477
SWD DLPIDR 0x10000001
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x00000178 msp: 0x20041f00
17              gpio_put(LED_PIN, 1);

なんで JTAG failure 7とか出力されるんでしょうか?シングルステップ実行は正常に行われているように見えますが。

ま、手作業にてビルドとデバッグはできるみたいなので、次回からはもう少し環境をカッコよくして行きたいと思います。

鳥なき里のマイコン屋(125) Nucleo+MCP2562でCANバスにフレーム送信 へ戻る

鳥なき里のマイコン屋(127) Firmataでラズパイのお供にArduino へ進む