RP2040マルチコア

# RP2040のマルチ（デュアル）コア動作を確認する
秋月電子の「[RP2040マイコンボードキット](https://akizukidenshi.com/catalog/g/g117542/)」、購入時に意識していなかったが、マルチ（デュアル）コアであることがわかったので、簡単な動作を確認してみた。

## 何をするか？
あるコアにて定期的にシリアル出力している状態で、別コアにてシリアル入力された内容をシリアル出力するもの。

## 準備
### Arduino IDE環境：Macシリコンプロセッサ
最初はまともにBuildできなかったが、調べると下記内容が見つかる。
- [Error "bad CPU type in executable" on macOS](https://support.arduino.cc/hc/en-us/articles/7765785712156-Error-bad-CPU-type-in-executable-on-macOS)

これに従い、下記を実行。
```
$ softwareupdate --install-rosetta
```

### BOOTSELボタン
最初の１回だけ、BOOTSELボタンを押しながらケーブルを接続することが必要のようだ。下記サイトなど、多数情報が見つかる。
- [Raspberry Pi Pico (RP2040) のArduino IDEでの使い方](https://steam-tokyo.com/rp2040-arduino/)

## 検証
### マルチコア未使用
まず、マルチコアを使用せず、逐次処理を行ったケース。

#### ソースコード
```c++
#define INTERVAL 2000

void setup() {
  Serial.begin(9600);
  Serial.setTimeout(INTERVAL);
}

void loop() {
  char buf[16];
  memset(buf, 0, sizeof(buf));
  while (1) {
    if ((Serial.readBytesUntil('\n', buf, sizeof(buf))) > 0) {
      Serial.println(buf);
      memset(buf, 0, sizeof(buf));
    } else {
      Serial.println(millis());   
    }
  }
}
```
- シリアル入力のタイムアウトを2000msに設定
- シリアル入力があればそのまま出力
- シリアル入力がなければ起動からの経過時間（ms）を出力

#### 結果
```
09:54:42.193 -> 102004
09:54:44.207 -> 104004
09:54:46.185 -> 106004
09:54:50.142 -> =====
09:54:52.155 -> 111951
09:54:54.130 -> 113951
09:54:56.141 -> 115951
09:54:58.151 -> 117951
09:55:00.129 -> 119951
09:55:03.230 -> @@@@
09:55:05.241 -> 125031
09:55:07.220 -> 127031
```
- "====="や"@@@@"はシリアル入力の折返し出力
- シリアル入力がなければ、2000msごとに表示あり
- シリアル入力があれば、その分の遅れ（時間経過）がわかる

至極まっとうな結果である。次に、これをデュアルコア化したソースコードで検証する。

### デュアル（マルチ）コア使用
#### ソースコード
```c++
#define INTERVAL 2000
void setup() {
  Serial.begin(9600);
  Serial.setTimeout(INTERVAL);
}

void setup1() {
}

void loop() {
  Serial.println(millis());
  delay(INTERVAL);
}

void loop1() {
  char buf[16];
  memset(buf, 0, sizeof(buf));
  if ((Serial.readBytesUntil('\n', buf, sizeof(buf))) > 0) {
    Serial.println(buf);
  }
}
```
RP2040のArduino IDE環境では、デュアルコアソースコードを簡単に記述できる。ネット上に各種情報あり。
- setup()およびloop()が１つ目のコア用処理
- setup1()およびloop1()が２つ目のコア用処理
- loop()：定期的にシリアル出力（2000msごと）
- loop1():シリアル入力を折り返し出力

#### 結果
```
09:56:46.250 -> 36006
09:56:48.231 -> 38006
09:56:50.243 -> 40006
09:56:50.836 -> =====
09:56:52.253 -> 42006
09:56:54.231 -> 44006
09:56:56.239 -> 46007
09:56:58.250 -> 48007
09:56:59.469 -> @@@@
09:57:00.261 -> 50007
09:57:02.239 -> 52007
```
- "====="や"@@@@"はシリアル入力の折返し出力
- シリアル入力の有無に関わらず、2000msごとに起動からの経過時間（ms）を出力、つまり、デュアルコアが機能していることがわかる。

### おまけ：FreeRTOS
上記マルチコア用ソースコードとほぼ同じ内容を、シングルコア上のFreeRTOSで実現してみる。

#### ソースコード
```c++
#include <FreeRTOS.h>
#include <task.h>

#define INTERVAL 2000
TaskHandle_t xKbd, xOnlyPrt;

void TaskKeyBoard(void *arg) {
  char buf[16];
  for (;;) {
    memset(buf, 0, sizeof(buf));
    if ((Serial.readBytesUntil('\n', buf, sizeof(buf))) > 0) {
      Serial.println(buf);
    }
    vTaskDelay(1);
  }
}

void TaskOnlyPrt(void *arg) {
  for (;;) {
    Serial.println(millis());
    vTaskDelay(INTERVAL);         // For task switch
  }
}

void setup() {
  Serial.begin(9600);
  Serial.setTimeout(INTERVAL);
  xTaskCreate(TaskKeyBoard, "TaskKeyBoard", 128, NULL, 1, &xKbd);
  xTaskCreate(TaskOnlyPrt, "TaskOnlyPrt", 128, NULL, 1, &xOnlyPrt);
  //vTaskStartScheduler(); # 存在してはいけない
}

void loop() {
}
```
- TaskKeyBoard：キーボードから入力された内容を折返し出力するタスク
- TaskOnlyPrt：2000msごとに起動からの経過時間（ms）を出力するタスク
- RP2040では、vTaskStartScheduler()があると動作しないようだ


#### 結果
```
20:39:23.914 -> 30019
20:39:25.922 -> 32020
20:39:27.703 -> =====
20:39:27.934 -> 34021
20:39:29.909 -> 36022
20:39:31.921 -> 38023
20:39:33.933 -> 40024
20:39:35.909 -> 42025
20:39:36.834 -> @@@@
20:39:37.920 -> 44026
20:39:39.932 -> 46027
20:39:41.943 -> 48028
```
- "====="や"@@@@"はシリアル入力の折返し出力
- キーボード入力タスクTaskKeyBoardのタスクスイッチ時間である1ms分（vTaskDelay(1)）だけ、定期出力タスクTaskOnlyPrtの出力が遅れている（2001ms）
- ただし、1ms以上の遅れが見えたこともある、このあたりはマイコンの精度の話だろう

EOF