ESP32でシリアルを並列処理でポーリングさせておく感じの事を

2020年5月2日Arduino/ESP32Arduino,ESP32,FreeRTOS,watchdog,マルチタスク

前回敗北しました、開発ボードにあるUSBポートの割り込みイベントドリブン処理ですが、、、

なんか時間がかかりそうなので後回しに。

代わりに、ひとまず並列処理を試してみます。

いづれイベントの処理方法が判った時にも使うますので・・・

ESP32のリファレンスを漁りまして、この辺を見て、、

GitHub – ESP-IDFの中のヘッダーファイルもちょっと見てみます。

どうやるのかをざっくりとまとめると

1.ハンドルタスクを作成する(別スレッドで処理する関数とか)
2.xTaskCreatePinnedToCore 等で1のタスクを登録する

これだけ。

じゃあちょっと書いてみます。

#include <Arduino.h>

/*
	本体に付いているUSBシリアルを並列処理でポーリングさせておくテスト

*/

TaskHandle_t pBehindTask;

void behindPollingSerial(void *param)
{
	// ポーリングなどを
	while (1)
	{
		if (Serial.available())
		{
			int inputchar = Serial.read();

			// この処理がどのコアで動いているか確認
			//	とりあえず @ 押したらCPU出る様に
			if (char(inputchar) == '@')
			{
				Serial.print("CPU Core(behindPollingSerial):");
				Serial.println(xPortGetCoreID());
			}

			// オウム返し charにキャストしてシリアルモニタに表示
			Serial.print(char(inputchar));

			// 自爆終了させる
			if (char(inputchar) == '[')
			{
				// タスクを自爆削除する
				Serial.println("will be stop at behindPollingSerial.");
				vTaskDelete(pBehindTask);
			}
		}

	}
}

void setup()
{
	// put your setup code here, to run once:

	// 少し起動待ち
	delay(5000);

	// シリアル通信を開始
	Serial.begin(115200);
	delay(3000);

	// 初期化処理がどのコアで動いているかを確認
	if (xPortGetCoreID())
	{
		Serial.print("CPU Core(boot setup):");
		Serial.println(xPortGetCoreID());
	}

	// Core 0 でタスク起動
	//	

// コアを明確に指定しない場合は xTaskCreateUniversal ラッパーを使う方がいい xTaskCreatePinnedToCore( behindPollingSerial, // TaskFunction_t pvTaskCode, "behindPollingSerial", // const char * const pcName, 2048, // const uint32_t usStackDepth, NULL, // void * const pvParameters, 1, // UBaseType_t uxPriority, &pBehindTask, // TaskHandle_t * const pvCreatedTask, 0); // const BaseType_t xCoreID); } // メインループ void loop() { // put your main code here, to run repeatedly: // メインでは適当にコメントを出しておく if (xPortGetCoreID()) { Serial.print("CPU Core(main loop):"); Serial.println(xPortGetCoreID()); } // 5秒くらいおきに delay(5000); }

こんな感じで。

メインループとは別の所で、泥臭い感じのポーリングでパソコンからの入力をそのまま返させてみました。

ESP32に転送してシリアルモニターで見てみます。

ぎゃああああぁぁぁあぁあぁ

        :
              :
ts Jun CPU Core(boot setup):1
CPU Core(main loop):1        
E (26202) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (26202) task_wdt:  - IDLE0 (CPU 0)
E (26202) task_wdt: Tasks currently running:
E (26202) task_wdt: CPU 0: behindPollingSe
E (26202) task_wdt: CPU 1: IDLE1
E (26202) task_wdt: Aborting.
abort() was called at PC 0x400d384b on core 0

Backtrace: 0x4008b560:0x3ffbe170 0x4008b78d:0x3ffbe190 0x400d384b:0x3ffbe1b0 0x400845ed:0x3ffbe1d0 0x4000bfed:0x3ffb8b00 0x40089095:0x3ffb8b10 0x40088163:0x3ffb8b30 0x400d17f1:0x3ffb8b50 0x400d0cc1:0x3ffb8b70 0x400d0c8c:0x3ffb8b90 0x40088215:0x3ffb8bb0

Rebooting...

リセットがかかって再起動されてしまっとりますな

調べてみると、なんかループが激し過ぎるとwatchdogタイマーが入り込む余地が無くなり、ハングしてると見做されるんだとか。

そんなわけで CPU 0 タスクのループ中に、watchdogがアクセスできる様な隙を与えます。

これを38行目に追加

		// これを入れないとwatchdogにヤラレル
		//	正確には1msではなく vTaskDelay(ms / portTICK_PERIOD_MS) らしい
		delay(1);

再びビルドして転送、モニタしてみますと

大丈夫な様ですね

文字を入力中もメインループが適当に5秒毎になんか喋っており

途中で「@」を押してみてもちゃんと想定どおりにコア番号を返してくれています。

尚、メインのloop()内では、watchdogタイマーは無効化されている様です。

あんまり良い実装では無い気がしますので、メインループにもdelayを入れておいてwatchdogタイマーを有効化しておくと良いかもしれません。

watchdogタイマーの有効化は以下の記述でできるそう。

enableCore1WDT();

また、xTaskCreatePinnedToCore に渡す xCoreID を、メインループと同じ 1 にしても(シングルコアでも)ちゃんと別プロセスとして動作します。

FreeRTOSがちゃんとマルチタスクのOSだって事ですね。

ふぅんという感じ。

FreeRTOSというのを少し垣間見た感じがします。

これからずっと使うものだと思いますので、慣れておきたいですね。