用途
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);
}
}
// 途中で呼び出してる他の関数たちの記述も省略
