鳥なき里のマイコン屋(147) ラズパイPico、C/C++SDK、マルチコアで割り込み2

Joseph Halfmoon

前回、マルチコアの両方にGPIO割り込みを入れてみましたが、割り込み受付後のタイマ処理が上手く行きませんでした。暫定対策したものの、実はそれもダメダメでした。今回はタイマ処理が上手く行かなかった理由を調査。対策打って「安定して動いている」ように「今のところ」見えているところまでやってみました。継続試験中。

前回のおさらいをしておくと、意図したコードは以下のようでした。

  1. ラズパイPicoのコア0にはGPIO18番から割り込み入れる
  2. 上記の割り込みハンドラ内で、GPIO20番に接続しているLEDを点灯。また、1秒後にLEDを消灯するようにタイマアラームその0を仕掛ける。
  3. しかけたタイマアラームその0が発動したら、LEDを消灯させ、GPIO18番からの割り込みを再度許可する
  4. ラズパイPicoのコア1にはGPIO19番から割り込み入れる
  5. 上記の割り込みハンドラ内で、GPIO21番に接続しているLEDを点灯。また、1秒後にLEDを消灯するようにタイマアラームその1を仕掛ける。
  6. しかけたタイマアラームその1が発動したら、LEDを消灯させ、GPIO19番からの割り込みを再度許可する

しかし、どうもタイマアラームその1が意図したようには動かず、しかたなく「やっつけ」で禁断の「暫定対策」をしてみたあげくに傷を広げた、というのがここまでの状況でした。

タイマアラームその1が動作しなかった理由

タイマアラームは、2つハンドリングすることは可能な筈で、実際、その0,その1とも発動していました。しかし、

タイマアラームはコア0で処理されていました

デフォルトのアラーム・プールというものはコア0で初期化されており、他のコアからも仕掛けることはできるものの、実際にアラーム処理がされるときはコア0で処理されているようでした。このため、コア1で消灯するのではなく、コア0で消灯していました。すると消灯に引き続く割り込みの再設定はコア0で行われてしまいます。結果、コア1の割り込みは不許可となり、本来コア1でハンドリングする筈のGPIO19番の割り込みが、コア0に送られてしまっていました。ここには、割り込み受付時にGPIOの番号を見ないことがある、というドキュメントに書かれた問題が横たわっているようでした。

対策

コア1側のタイマアラームがコア1で処理されれば、意図通りの動作となる筈なので、

コア1用のアラーム・プールを増設

してみることにいたしました。デフォルトと増設のアラーム・プールはAPIの名前や引数から異なりますが、いたしかたありませぬ。コア1側でアラーム・プールを増設し、コア1の割り込みハンドラ内ではそちらの消灯アラームを使うことにいたしました。アラームに関するAPIについてのドキュメントは以下に

Raspberry Pi Pico SDK Documentation   alarm

対策の結果

対策の結果、前回みられた、コア1側のLED(青)が点灯したまま消灯しない、といった問題は消えたようです。また、コア0とコア1の両方のスイッチを同時押ししても、どちらも点灯、そして1秒後に消灯と意図どおりの動きをみせています。修正いらい6時間ほど動かし続けており、気が付くたびにスイッチを押してみているのですが、問題は出ておりませぬ。

標準出力への動作状況報告の様子は以下に。

EXECresult本当に、これで良いのかね。まあ、前回よりは進歩したのでいいか。適当な。

鳥なき里のマイコン屋(146) ラズパイPico、C/C++SDK、マルチコアで割り込み1 へ戻る

鳥なき里のマイコン屋(148) SAMD21マイコン搭載、Seeeduino XIAO到着 へ進む

実験に使用したRaspberry Pi Pico用の C ソースコード
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "pico/time.h"
#include "hardware/gpio.h"
#include "hardware/irq.h"

#define EXT_SW0     (18)
#define EXT_SW1     (19)
#define EXT_LED0    (20)
#define EXT_LED1    (21)

alarm_pool_t* core1Alarm;

int64_t alarm_callback0(alarm_id_t id, void *user_data) {
    cancel_alarm(id);
    gpio_put(EXT_LED0, false);
    gpio_set_irq_enabled(EXT_SW0, GPIO_IRQ_EDGE_FALL, true);
    printf("alarm0: %d\r\n", sio_hw->cpuid);
}

int64_t alarm_callback1(alarm_id_t id, void *user_data) {
    alarm_pool_cancel_alarm(core1Alarm, id);
    gpio_put(EXT_LED1, false);
    gpio_set_irq_enabled(EXT_SW1, GPIO_IRQ_EDGE_FALL, true);
    printf("alarm1: %d\r\n", sio_hw->cpuid);
}

gpio_irq_callback_t gpio_callback0(uint gpio, uint32_t event) {
    gpio_set_irq_enabled(EXT_SW0, GPIO_IRQ_EDGE_FALL, false);
    printf("CORE 0 INTR!\r\n");
    if (gpio == EXT_SW0) {
        gpio_put(EXT_LED0, true);
        add_alarm_in_ms(1000, alarm_callback0, NULL, false);
    } else {
        printf("CORE 0 received unexpected INTR.\r\n");
    }
}

gpio_irq_callback_t gpio_callback1(uint gpio, uint32_t event) {
    gpio_set_irq_enabled(EXT_SW1, GPIO_IRQ_EDGE_FALL, false);
    printf("CORE 1 INTR!\r\n");
    if (gpio == EXT_SW1) {
        gpio_put(EXT_LED1, true);
        alarm_pool_add_alarm_in_ms(core1Alarm, 1000, alarm_callback1, NULL, false);
    } else {
        printf("CORE 1 received unexpected INTR.\r\n");
    }
}

void core1_task() {
    int core1Counter = 0;
    core1Alarm = alarm_pool_create(1, 4);

    gpio_init(EXT_SW1);
    gpio_init(EXT_LED1);
    gpio_set_dir(EXT_LED1, GPIO_OUT);
    gpio_set_dir(EXT_SW1, GPIO_IN);
    gpio_put(EXT_LED1, false);
    gpio_set_irq_enabled_with_callback(EXT_SW1, GPIO_IRQ_EDGE_FALL, true, gpio_callback1);

    while (true) {
        printf("CORE1: %d\r\n", ++core1Counter);
        sleep_ms(7777);
    }
}

int main()
{
    int loopCounter = 0;
    stdio_init_all();
    gpio_init(EXT_SW0);
    gpio_init(EXT_LED0);
    gpio_set_dir(EXT_LED0, GPIO_OUT);
    gpio_set_dir(EXT_SW0, GPIO_IN);
    gpio_put(EXT_LED0, false);
    gpio_set_irq_enabled_with_callback(EXT_SW0, GPIO_IRQ_EDGE_FALL, true, gpio_callback0);

    multicore_launch_core1(core1_task);

    while (true) {
        printf("CORE0: %d\r\n", ++loopCounter);
        sleep_ms(5000);
    }

    puts("NEVER!");
    return 0;
}
上記コードのビルドに使用した CMakeLists.txt
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# initalize pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/pi/pico/pico-sdk")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

project(multINTR C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(multINTR multINTR.c )

pico_set_program_name(multINTR "multINTR")
pico_set_program_version(multINTR "0.1")

pico_enable_stdio_uart(multINTR 0)
pico_enable_stdio_usb(multINTR 1)

# Add the standard library to the build
target_link_libraries(multINTR
    pico_stdlib
    pico_time
    pico_multicore)

pico_add_extra_outputs(multINTR)