4月にようやくRaspberry Pi Picoを1枚購入後、ずっとMicroPythonで動かしてきました。でもま、ずっと気になっていたのが、ボードの短辺にDEBUGと書かれてある3端子。今回は重い腰を上げて、C/C++のビルドからGDBでデバッグまで一通り動かしてみました。ようやくDEBUG端子の活躍の場ができた?
※「鳥なき里のマイコン屋」投稿順Indexはこちら
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上であれば、便利なスクリプトなど準備されているので、書かれている通りの操作を数行打ち込めば大筋終ります。勿論、ダウンロードやらビルドやらに時間が掛かりますが。
実はRaspberry Pi 4が到着したときに時間のかかるダウンロードやビルドはやってありました。今回はその先です。実際にサンプルプログラムをビルドして、ラズパイ4からラズパイPicoに書き込んでみる。そしてデバッガの制御化でそのオブジェクトを動かしてみることであります。
ラズパイPicoとラズパイ4の間の接続
Getting Startedを読むと、Picoと母艦のラズパイ4の間の接続には3系統の経路があることが分かります。
-
- ラズパイ4のUSBポートにPicoのUSBを接続
- ラズパイ4の拡張端子のUART端子にPicoのUARTを接続
- ラズパイ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みたい。詳しくはこちら)
ビルドとデバッグ
さてデバッグ動作の確認テストの対象は、吉例の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とか出力されるんでしょうか?シングルステップ実行は正常に行われているように見えますが。
ま、手作業にてビルドとデバッグはできるみたいなので、次回からはもう少し環境をカッコよくして行きたいと思います。