RP2040で簡単マルチコアコーディング(C言語)

RP2040はCPU(Cortex-M0+)を2つ内蔵しているデュアルコアのマイコンで、2つの異なるコードを並列に実行することができます。

使い方も簡単で以下のように専用関数の呼び出しで使用できます。

#include "pico/multicore.h"

void core1_entry() {
    // Core1で動作させる処理
    ...  
}

// main関数はCore0で動作する
int main() {
    // Core1でcore1_entry関数を動作させる
    multicore_launch_core1(core1_entry);
    // 以下Core0で動作させる処理
    ...
}

"pico/multicore.h" を使用するために、『CMakeLists.txt』の『target_link_libraries』に『pico_multicore』を追記します。

CMakeLists.txt
・・・

# pico_multicore を追記 (『YourProjName』はプロジェクト名に変更してください)
target_link_libraries(YourProjName pico_stdlib pico_multicore)

・・・

マルチコアプログラミングでは、複数のコアから同時に同じメモリや同じIO等にアクセスしないように排他処理が必要です。

ここでは semaphore_t を使ったセマフォによる排他処理を使用します。

#include "pico/multicore.h"
// 排他処理用のセマフォ
static semaphore_t sem;

void core1_entry() {
    // Core1で動作させる処理
    // セマフォから許可を要求。許可が得られるまで待機
    sem_acquire_blocking(&sem);
    // ここでCore0と共用するメモリ・IO等ハードウェアにアクセスする処理を記述
    ...
    // 完了後、セマフォ解放。Core0を止めるのでなるべく早く解除する。
    sem_release(&sem);
}

// main関数はCore0で動作する
int main() {
    // セマフォを初期化
    sem_init(&sem, 1, 1);
  
    // Core1でcore1_entry関数を動作させる
    multicore_launch_core1(core1_entry);
    // 以下Core0で動作させる処理
    ...
    // セマフォから許可を要求。許可が得られるまで待機する。
    sem_acquire_blocking(&sem);
    // ここでCore1と共用するメモリ・IO等ハードウェアにアクセスする処理を記述
    ...
    // セマフォの許可を解除
    sem_release(&sem);
}

次のコードでは片方のCPU(Core0)でLEDを点滅させ、もう片方のCPU(Core1)でその点滅速度を変更しています。

マルチコアによるLED点滅速度変更
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"

// 排他処理用のセマフォ
static semaphore_t sem;
// Core0 と Core1 で共有する変数
static uint32_t share_sleep_time_ms = 0;

// Core1で動作させる処理
void core1_entry()
{
    uint32_t core1_sleep_time_ms = 0;
    while (true)
    {
        // セマフォから許可を要求。許可が得られるまで待機
        sem_acquire_blocking(&sem);
        // Core0 と Core1 で共有する変数に値を入れる
        share_sleep_time_ms = core1_sleep_time_ms;
        // セマフォの許可を解除
        sem_release(&sem);

        // LED点滅速度用の値を調整
        core1_sleep_time_ms += 10;
        if (core1_sleep_time_ms >= 5000) core1_sleep_time_ms = 0;
        sleep_ms(core1_sleep_time_ms * 2);
    }
}

// main関数はCore0で動作する
int main()
{
    // ボード上のLEDを点灯させるためにGPIO25を初期化
    uint32_t core0_sleep_time_ms;
    const uint GPIO_LED = PICO_DEFAULT_LED_PIN;
    gpio_init(GPIO_LED);
    gpio_set_dir(GPIO_LED, GPIO_OUT);

    // セマフォを初期化
    sem_init(&sem, 1, 1);

    // Core1でcore1_entry関数を動作させる
    multicore_launch_core1(core1_entry);

    while(true)
    {
        // セマフォから許可を要求。許可が得られるまで待機
        sem_acquire_blocking(&sem);
        // Core0 と Core1 で共有する変数から値を取得
        core0_sleep_time_ms = share_sleep_time_ms;
        // セマフォの許可を解除
        sem_release(&sem);

        // ボード上のLEDを点灯
        gpio_put(GPIO_LED, true);
        // Core1 で変更された時間分待機
        sleep_ms(core0_sleep_time_ms);
        // ボード上のLEDを消灯
        gpio_put(GPIO_LED, false);
        // Core1 で変更された時間分待機
        sleep_ms(core0_sleep_time_ms);
    }
}    

上記コードを実行すると、ボード上のLEDの点滅速度が徐々に遅くなっていくのが確認できると思います。

マルチコアの実装を行う際には、2つのコア間で同一のメモリやデバイスに、同時にアクセスしてしまう事が無いように細心の注意が必要です。

コア間の通信には『multicore_fifo_pop_blocking()』や『multicore_fifo_pop_blocking()』といった専用関数が用意されていますが、ここではセマフォ(semaphore_t)を使用した排他処理を実施しています。

セマフォ(semaphore_t)を使用した排他処理は、『sem_acquire_blocking(&sem);』にて許可が得られたら『sem_release(&sem);』で解放するまで、他のコアでは『sem_acquire_blocking(&sem);』による許可が出ず、待機させられる仕組みで実現します。

この方法は少量のデータしか扱えない『multicore_fifo_pop_blocking()』と違い、コア間で大容量のデータを共有することが可能ですが、処理を止めてしまうデメリットがあるので可能な限り速く『sem_release(&sem);』で解放する必要があります。

セマフォを使用した排他処理の内部ではハードウェアスピンロックという機構が用いられており、Core0とCore1が同時に許可を求めた場合、Core0の方が許可が得られる仕組みになっているとの事です。

コメント

このブログの人気の投稿

v4l2-ctlで行うUSBカメラ設定方法まとめ

Raspberry Piでシリアル通信する方法(通信設定・通信確認)

Raspberry Pi Picoのステップ実行できる開発環境づくり