別シリーズのMicroPythonの方でマルチコアやっていたので、C/C++SDKでもやった気になっていました。仕切り直し。C/C++SDK環境(VS Codeでリモート接続)でマルチコアを動かして、デバッガを使ってみます。お手軽にデバッグできるんだが、腑に落ちない挙動が一つ。概ねOKだから、また今度ね。いい加減な。
※「鳥なき里のマイコン屋」投稿順Indexはこちら
(末尾に実験で使用したコード全文を掲げました。)
ラズパイPicoは、Armコアを2個搭載のマルチコア・マイコンです。といってもラズパイ4のようなマルチコアでの並列コアでプログラム処理性能UP狙いとは違い、より低いレベルでの組み込み向けに2個のコアで仕事を分担、という感じでしょうか。
Core0と呼ばれるコアがまず走っていてmain()関数を実行、Core0は必要に応じてCore1を起動して一部の仕事をお任せするようなスタイルかと。別シリーズのMicroPythonネタでマルチコアが簡単に使えることは分かっておりましたが、C/C++SDKではどうでしょうか。
SDKの中では、High Level APIに位置付けられる以下のライブラリを使用することでマルチコア、簡単に使うことができます。
とりあえずCore1を起動して処理すべき関数を渡すだけであれば、以下の関数でことたります。
multicore_launch_core1()
コア間のやり取り不要であれば究極的には上記でたりますが、さすがにそういうことはないでしょう。リソースの使用や同期のためにlock、mutex、semaphoreといった一般的な排他制御の仕組みがSDKに存在します。しかし、ラズパイPicoの特徴として、コア間通信のためのハードウエアFIFOが存在するのです。これを使えば、「軽く」コア間通信が可能です。基本はブロッキングですが、FIFOのステータス確認用の関数があるので、実質ノンブロキングで使うことも可能です。
ラズパイPicoはOSなしですが、SDKのサポートは充実、面倒なことは皆SDKに任せてしまって簡単コーディングが可能と思われます。そこでコア0からコア1に一部のお仕事をオフロードするような雰囲気(実際になにもしやしないですが)のサンプルを書いて動かしてみました。
そこでちょっと気になるのがデバッガの使い易さです。当方でのC/C++SDKによるラズパイPico向け開発の道具だては以下の構成です。
-
- Raspberry Pi 4 を開発母艦としている。ツールチェーン、ビルドツリーはPi 4上にある。
- Pi 4とラズパイPicoは SWDで接続(他にUSBでも接続、UARTも接続可)
- Pi 4の開発環境には、PC上のVS Codeからリモート接続している
他のマイコンでは、マルチコアのデバッグが少し難儀なものもあるので、ラズパイPicoの場合はどんなんでしょうか?結論から言えば、
マルチコアだからといってデバッグに特に面倒なことはない
と思います。ほとんど何も考えずにリモートのVS Code上のGUIでマルチコアのデバッグが可能です。デバッグを開始したところが以下に。
起動するとmain()関数の冒頭でブレークがかかります。main()関数のローカルスコープの中の変数が列挙されていますが、まだ有効になる前です。ここでコールスタックを見てみると以下のような感じ。core0側はmain()内のブレークポイントで止まっており、core1(まだ処理を起動していない)も停止していることが分かります。
ここで、core1側で実行するコード部分にブレークポイントを設定して、実行継続してみます。core0側からcore1側のコードを起動し、core1の関数の途中でブレークにかかって止まったときのコールスタックはこんな感じ。
こんどは、core1側はブレークポイントでとまり、それと「連動」してcore0側も停止していることが分かります。コールスタックをみれば、core0はcore1側を待つところで止まっています。ここは予定どおり。
core1側のブレークポイントなので、ローカル変数はcore1側の関数の変数が見えてます。これまた期待通り。
ちょっとしたトラブル?
マルチコアの起動も、FIFOを使ったコア間通信も簡単で、特に問題もなくマニュアル通りに使えたのですが、1点不可解な現象が発生。Raspberry Pi 4とラズパイPicoをUSB接続しているのですがRaspberry Pi OS上に
/dev/ttyACM0 が見当たりません。
/dev/ttyACM0は、USBシリアルのデバイスです。ラズパイPicoのオブジェクトコードのSTDIOをUSBシリアルに向けていたら見えるはずなのに見当たりません。以前からラズパイPicoのSTDIOはUSBシリアルに向けて使っており、printfの出力は/dev/ttyACM0で拾っていたので困りました。
以前生成した別なプログラムの uf2 ファイルをBOOTSELモードで書き込むと /dev/ttyACM0 が現れます。勿論printfから送られてくる文字列を受信可能です。何が問題?ちゃんとCMakeList.txtはSTDIOをUSBに向けるようにしてあるのだけれど。。。
ちょっと調べて分からなかったので、「また後で」ということで調査はペンディング、STDIOを通常のUART0(GP0/GP1)に接続するように変更してしまいました(根性無。)
元々Raspberry Pi 4のUART0(GPIO14/15)にはシリアルケーブルを取り付けてあります。RX/TXの「クロス」に気をつけてラズパイPicoと接続しました。
そのためのCMakeList.txtの変更を以下にしめします。なお、target_link_libraries()で、pico_multicoreを設定しておかないと、今回使用のAPIは見えないようです。
pico_enable_stdio_uart(multTST0 1) pico_enable_stdio_usb(multTST0 0) target_link_libraries(multTST0 pico_stdlib pico_multicore)
末尾のプログラムを走らせたところがこちら。やっているのは、
-
- Core0からCore1を起動する
- Core1は「起動したよ」とメッセージをCore0に送る(以下すべてFIFOを使った通信)
- Core0は上記の起動メッセージをもらったら仕事を開始
- Core0からは仕事のトリガとして1を送る
- Core1はトリガの1を受け取ったら、受信したこと、受信の積算回数を報告(本当はここで何か仕事をする)
- Core0から仕事終わりの指令として2を送ると、Core1はそれに反応して「仕事あがり」のメッセージをCore0に送り返す
- Core0は上記の「あがり」メッセージを受け取ったら終了
CORE0: start. CORE0: Send 1 CORE1: Received 1 (1) CORE0: Send 1 CORE1: Received 1 (2) CORE0: Send 1 CORE1: Received 1 (3) CORE0: Send 1 CORE1: Received 1 (4) CORE0: Send 1 CORE1: Received 1 (5) CORE0: Send 1 CORE1: Received 1 (6) CORE0: Send 1 CORE1: Received 1 (7) CORE0: Send 1 CORE1: Received 1 (8) CORE0: Send 1 CORE1: Received 1 (9) CORE1: IDLE. End of multicoreTST0.
ま、マルチコアのサンプル動いたね。でもシリアルの件、気になるのだけれど。。。
鳥なき里のマイコン屋(132) ラズパイPico、何気に便利なpicotool に戻る
鳥なき里のマイコン屋(134) ラズパイPico、C/C++SDKでプログラマブルIO に進む
実験コード全文
#include <stdio.h> #include "pico/stdlib.h" #include "pico/multicore.h" #define HELLO_MSG (99999) #define EXIT_MSG (99998) #define MAX_LOOP (10) #define DO_COMMAND (1) #define EXIT_LOOP (2) void core1_start() { uint32_t p1Counter = 0; multicore_fifo_push_blocking(HELLO_MSG); while (true) { uint32_t rcvDat = multicore_fifo_pop_blocking(); if (rcvDat == EXIT_LOOP) break; printf("CORE1: Received %d (%d)\r\n", rcvDat, ++p1Counter); } printf("CORE1: IDLE.\r\n"); multicore_fifo_push_blocking(EXIT_MSG); while (true) { tight_loop_contents(); } } int main() { uint32_t p0Counter = 0; uint32_t command = DO_COMMAND; stdio_init_all(); printf("CORE0: start.\r\n"); multicore_launch_core1(core1_start); uint32_t hello = multicore_fifo_pop_blocking(); if (hello != HELLO_MSG) { printf("Unexpected CORE1 HELLO MESSAGE.\r\n"); return 1; } while(p0Counter++ < MAX_LOOP) { while (!multicore_fifo_wready()) { printf("Waiting CORE1.\r\n"); sleep_ms(100); } if (p0Counter == MAX_LOOP) { command = EXIT_LOOP; } printf("CORE0: Send %d\n\r",command); multicore_fifo_push_blocking(command); sleep_ms(2000); } hello = multicore_fifo_pop_blocking(); if (hello != EXIT_MSG) { printf("Unexpected CORE1 EXIT MESSAGE.\r\n"); return 1; } //multicore_reset_core1(); puts("End of multicoreTST0."); return 0; }