pico_multicoreの用途と主要なメソッド

raspberry pi pico

用途

RP2040のデュアルコア機能(Core0 / Core1)を扱うためのライブラリ

処理を2つのCPUコアに分けることで、
処理を並行して実行したいときに使用する

core0のmain()で延々と実行したい処理があったとして、
データの準備をcore1で延々とする。とか、そんな感じ。

主要なメソッド

multicore_launch_core1(func)
引数で渡した関数をcore1で開始する

multicore_reset_core1()
core1をリセットし、停止状態に戻す

multicore_fifo_push_blocking(data)
core間通信用FIFOにデータを送信(空くまで待つ)
※ FIFOについては後述

multicore_fifo_pop_blocking()
FIFOからデータを受信(データが来るまで待つ)

multicore_fifo_rvalid()
FIFOに受信可能なデータがあるか確認する

補足

マルチコアの概要
RP2040にはCPUコアが2つあって、通常はCore0だけが動作してる
pico_multicoreを使えば、Core1を起動して別の処理を同時に実行できる。

core0とcore1の分担を上手く振り分ければ、
普通に処理能力が倍になる感じ。
(ラズパイピコ買ってから3年くらい知らなかった)

core0とcore1の役割
core0:通常の main() が動くコア
core1:multicore_launch_core1()で起動するコア
どちらのコアも性能は同じ

core0でメインの処理をして、core1でサブ的な処理をする設計思想が一般的らしい

core間通信(FIFO)について
もしもcore0とcore1とで同時に1つの変数にアクセスしてしまったらめちゃくちゃになるから、
core間では直接変数を共有しない。

代わりに、FIFOを使ってデータをやり取りする
お互いに送信、受信がはっきりしてるからめちゃくちゃにならないのがいい
※やりとりできるのは32bitの整数のみだから、ポインタとかIDを送る設計にすることが多いよ

実装のイメージ

ラズパイピコで動画再生をしたときのソース

core0からcore1を呼び出す

core1でmicroSDから映像と音声をちょっと読み込んでバッファに置いとく
↓↑高速で繰り返し
core0で映像の出力、音声の出力

※core0/core1の両方でグローバルな変数を触ってるのは無視してね
※下記の例ではcore1_entryを動かしたらずっと動きっぱなしだけど、
 必要なときにだけ何度も動かしたっていいよ。

// 使うライブラリとか変数とか宣言の記述を省略

// ---------- Core1: データの読み込み、データの展開 ----------
// core0のmain()から呼び出して使う。
static void core1_entry() {
    // 起動待ち
    while (!g_core1_should_run) {
        tight_loop_contents();
    }

    // 起動後はずっとここのループ
    while (true){
        // 動画再生可能になるまで待つ
        while(!g_video_playable || g_file_end_reached) {
            tight_loop_contents();
        }

        // ファイルを読み取り専用で開く
        fr = f_open(&fil, (root_path + filepath).c_str(), FA_READ);
        if (fr != FR_OK) {
            while (true);
        }
        // ファイルからデータを読み取る
        UINT br = 0;

        while (true) {
            // 空きバッファ番号を受け取る(空きが無ければここで待つ)
            int idx;
            queue_remove_blocking(&q_free, &idx);

            // SDから1フレーム読む(圧縮データ 16,320バイト)
            FRESULT fr = f_read(&fil, comp_buf, sizeof(comp_buf), &br);
            if (fr != FR_OK) {
            } else if (br != sizeof(comp_buf)) {
                g_file_end_reached = true;
                break; // ファイルの終端に到達したらループを抜ける
            }

            // DXT1 → RGB565に展開
            uint8_t* dst = lcd_buf[idx];
            const int blocks_per_row = LCD_W / 4; // =60
            const int blocks_per_col = SRC_H / 4; // =34
            for (int by = 0; by < blocks_per_col; ++by) {
                for (int bx = 0; bx < blocks_per_row; ++bx) {
                    const int bi = by * blocks_per_row + bx;
                    write_block_to_lcd_bytes(dst, bx, by, &comp_buf[bi * 8]);
                }
            }

            queue_add_blocking(&q_ready, &idx);

            // 音声バッファ残量が少ないときだけ読む
            if (adpcm_available() < ADPCM_BUF_SIZE / 2) {
                uint8_t audio_buf[LOAD_AUDIO_BYTES_PER_FRAME];
                UINT br2;
                FRESULT fr2 = f_read(&adpcm_file, audio_buf, sizeof(audio_buf), &br2);
                if (fr2 != FR_OK || br2 == 0) {
                    g_file_end_reached = true;
                    break;
                }
                for (UINT i = 0; i < br2; i++) adpcm_push(audio_buf[i]);
            }
        }
    }
}

int main() {
    // シリアルポートの初期化
    // SPIの初期化
    // DMASPIを有効化
    // SPIのピン設定
    // チップセレクトピンをSPI機能に設定
    // クロックピンをSPI機能に設定
    // MOSIピンをSPI機能に設定
    // バックライトピン設定
    // リセットピンを初期化
    // PWM 初期化
    // SDカードの初期化
    // ドライブのマウント
    // LCD用のボーレートを66MHzにして再初期化
    // microSD内のファイルリストを作成
    // 音声再生システムの初期化
    // 乱数生成のシード値を取得
    // 乱数生成器を作成
    // LCDの初期化
    // DMA準備
    // ↑諸々準備の記述は省略

    // Core1 起動
    g_core1_should_run = true;
    // 起動前に一度コア1をリセット(安定化のため) 
    multicore_reset_core1();
    // 少し待つ(USB/クロック安定のため)
    sleep_ms(5);
    // core1を起動
    multicore_launch_core1(core1_entry);

    // タイマー割り込みの優先度を最高に設定
    irq_set_priority(TIMER_IRQ_0, 0); 

    while (true) {
        // ファイル終端フラグをリセット
        g_file_end_reached = false;

        // ファイルリストを取得(ランダム、順番)
        if (is_random){
            filepath = get_random_file(mt64);
        } else {
            filepath = get_file();
        }
        
        // ファイルを再生
        // 音声ファイルを開く
        // 拡張子.binを.adpcmに置換
        string adpcm_path = filepath;
        size_t pos = adpcm_path.rfind(".bin");
        adpcm_path.replace(pos, 4, ".adpcm");
        printf("音声ファイル: '%s'\r\n", adpcm_path.c_str() );
        if (f_open(&adpcm_file, (root_path + adpcm_path).c_str(), FA_READ) != FR_OK) {
            printf("音声ファイルのオープンに失敗しました\r\n");
            while (true);
        }

         // PWM タイマー (16kHz)
        add_repeating_timer_us(-62, audio_timer_cb, NULL, &timer);

        printf("動画再生を開始します\r\n");
        // 動画再生
        play_video();

        // タイマー停止
        cancel_repeating_timer(&timer);
    }
}

// 途中で呼び出してる他の関数たちの記述も省略
タイトルとURLをコピーしました