Raspberry Pi Picoでマルチコアプログラミング

2024/12/21に公開

これは自作OS Advent Calendar 202421日目の記事です。

Raspberry Pi Picoについて

Raspberry Pi PicoはRaspberry Pi Foundationが開発したマイコンボードです。

https://www.raspberrypi.com/products/raspberry-pi-pico/

RP2040という組込み向けのマイコンを搭載しており、通常のRaspberry PiのようにLinuxは動作しませんが、Arduino IDEやMicroPythonなどでプログラムを書くことができます。
値段も安価で、1,000円程度で購入することができます。

去年のAdvent CalendarではRustでのベアメタルプログラミングを紹介しました

https://zenn.dev/garasubo/articles/rasp-pico-rust

RP2040はデュアルコアのARM Cortex-M0+を搭載しており、マルチコアプログラミングが可能です。
今年はマルチコアプログラミングについて紹介します。言語は特に限定せずに一般的な話をします。

なお、Raspberry Pi Pico2という新しいボードも発売されています。こちらはRP2350という新しいマイコンを搭載しているため、今回の記事の方法とは異なるかもしれない点には注意してください

2つ目のコアを起動させる

RP2040が起動したときデフォルトで動くのはコア0です。コア1を起動させるためにはコア0からメッセージを送ることで起動させます。
詳しい起動方法はRP2040のデータシートの2.8.2. Launching Code On Processor Core 1に書かれています。
C言語での実装も書いてあるので、そちらを引用します(コメント部分は著者が翻訳・加筆)。

// FIFOを使い、コア0からコア1にコマンドを送ることでコア1を起動させる
//
// vector_tableはVTOR registerの値、つまりリセットベクターのアドレス、
// spはスタックポインタの初期値を指定します。コア0が使うメモリ領域とかぶらないように注意してください。
// entryはプログラムカウンタの(PC)値です。thumb命令であることを表すthumb bitが立っている必要があります(つまり最下位ビットが1である必要があります)。
const uint32_t cmd_sequence[] =
    {0, 0, 1, (uintptr_t) vector_table, (uintptr_t) sp, (uintptr_t) entry};
uint seq = 0;
do {
    uint cmd = cmd_sequence[seq];
    // 0を送る前はcore1から送られて来たREAD FIFOに溜まっているデータを一旦捨てる
    if (!cmd) {
        // FIFOが空になるまでデータを捨てる
        multicore_fifo_drain();
        // SEV命令を実行してコア1がFIFO待ちにできるようにする
        __sev();
    }
    // 32bitの値をwrite FIFOに書き込むことでコア1にコマンドを送る
    multicore_fifo_push_blocking(cmd);
    // read FIFOからコア1のレスポンスをブロッキングで読み込む
    uint32_t response = multicore_fifo_pop_blocking();
    // 正しいレスポンスが帰ってきた場合は次のコマンドを送り、そうでない場合は最初から再送信する
    seq = cmd == response ? seq + 1 : 0;
} while (seq < count_of(cmd_sequence));

FIFOになっているMailboxを使ってコア0からメッセージを送ることでコア1を起動させることができます。
entryにC言語形式の関数ポインタの最下位ビットを立てたものを指定することで、その関数から実行を開始します。

FIFOはSingle-cycle IO block(SIO)と言われるペリフェラル群に属しており、データシートの2.3.1.4. Inter-processor FIFOs (Mailboxes)に詳しい説明があります。

排他制御

マルチコアプログラミングでは2つのコアが同時にメモリアクセスする可能性があるため、排他制御の仕組みが必要です。
しかし、RP2040のArm Cortex-M0+はARMv6-Mアーキテクチャを採用しており、デフォルトではメモリアクセスを排他制御するための仕組みがありません。
そのため、排他制御用のペリフェラルを使う必要があります。
1つの方法としてはコア1を立ち上げるのにも使ったFIFOを使うことが考えられます。これはコア0とコア1の間で読み込み・書き込みが独立になっているためそれぞれのコアで自由に使いつつ通信することができます。
もう1つの方法がハードウェアスピンロックです。これも同じくSIOの一部でMMIOの読み込みでロックを取得して書き込みでロックを開放することができます。こちらを使えば素直にMutexのようなデータ構造を実装できそうです。
しかし、本数が32本と限られているため、気軽にMutexを使うということは難しいかもしれません。

実践的には普通のマルチコアプログラミングのようにコアに自由にプロセスを割り当てるようなことはせずに、それぞれのコアに独立した処理を割り当て、排他制御が必要な共有ステートは最小限にするようにアプリケーションやランタイムを設計をすることになりそうです。

割り込み

Systickはコアごとに存在するため、各コアで独立に割り込みを設定することができます。
GPIOはコアで共通ではあるようですが、割り込み信号自体はコアごとが独立に受け取るようです。詳しくはデータシートの2.3.2を参照してください。

まとめ

Raspberry Pi Picoのマルチコアプログラミングについて紹介しました。
Pi Picoは安価で手に入りやすいため、試しやすいのが嬉しいです。普通のマルチコアプログラミングとは異なる考慮事項があるところが面白いので、興味がある方はぜひ試してみてください。

Discussion