
別シリーズの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;
}

