Arduino上でFreeRTOSの基本的な動作を試す
FreeRTOS
Arduinoでの準備
FreeRTOSライブラリをインストールする。
インストールするとFreeRTOSに関するいくつかのスケッチサンプルが見つかる。今回は、タスク間通信を試したかったので、次のサンプルを利用した(組み合わた)。
- AnalogRead_DigitalRead セマフォ
- integerQueue キュー
- TaskStatus タスクSuspend/Resume
具体的に実現したいこと
- PCからの入力数値の回数分、LEDのオンオフを行う
- PCからの入力により、LEDのオンオフを一時的に止める、その後再開する
- LEDのオンオフのメッセージおよびボタン押下のメッセージをPCに送るが、共有資源(ここではシリアル回線)の競合でメッセージが送れないケースがあることの確認
ソースコード
インクルード、関数定義など
#include <Arduino_FreeRTOS.h> // Include Arduino FreeRTOS library
#include <queue.h> // Include queue support
#include <semphr.h> // add the FreeRTOS functions for Semaphores (or Flags).
//define task handles
TaskHandle_t TaskBlink_Handler;
TaskHandle_t TaskButton_Handler;
TaskHandle_t TaskSerial_Handler;
QueueHandle_t InterTaskQueue; // define Queue
SemaphoreHandle_t SerialSemaphore; // define Semaphore
// define tasks for Blink & Button & Serial
void TaskBlink(void *pvParameters);
void TaskButton(void *pvParameters);
void TaskSerial(void* pvParameters);
- タスクはArduino上のコンポネートを扱う次の3つ
- LED(TaskBlink)
- ボタン(TaskButton)
- PCとの通信(TaskSerial)
- タスク間のデータ通信用のキュー(InterTaskQueue)
- 共有資源(ここではSerial回線)をコントロールするセマフォ(SerialSemaphore)
その他は上記のとおり。
Arduinoお決まりのsetup(),loop()
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
}
if ( SerialSemaphore == NULL ) // Check to confirm that the Serial Semaphore has not already been created.
{
SerialSemaphore = xSemaphoreCreateMutex(); // Create a mutex semaphore we will use to manage the Serial Port
if ( ( SerialSemaphore ) != NULL )
xSemaphoreGive( ( SerialSemaphore ) ); // Make the Serial Port available for use, by "Giving" the Semaphore.
}
// Create a queue
InterTaskQueue = xQueueCreate(1, // Queue length
sizeof(int)); // Queue item size
if (InterTaskQueue != NULL) {
xTaskCreate(
TaskBlink
, "Blink" // A name just for humans
, 128 // This stack size can be checked & adjusted by reading the Stack Highwater
, NULL //Parameters passed to the task function
, 2 // Priority, with 2 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
, &TaskBlink_Handler );//Task handle
xTaskCreate(TaskButton, "Button", 128, NULL, 1, &TaskButton_Handler);
xTaskCreate(TaskSerial, "Serial", 128, NULL, 0, &TaskSerial_Handler);
vTaskStartScheduler(); // start scheduler, however, if it doesn't exist, it seems to work.
}
}
void loop()
{
// Empty. Things are done in Tasks.
}
- setup()
- セマフォ作成
- キュー作成
- タスク作成(優先度Priorityには注意が必要のようです。)
- loop()
- なし
TaskSerial
void TaskSerial(void* pvParameters){
(void) pvParameters;
for (;;) // A Task shall never return or exit.
{
while(Serial.available()>0){
String str = Serial.readString();
if (str[0] == 's') {
vTaskSuspend(TaskBlink_Handler); // Make TaskBlink Suspend
Serial.println("Suspend!");
} else if (str[0] == 'r') {
vTaskResume(TaskBlink_Handler); // Make TaskBlink Resume
Serial.println("Resume!");
} else { // not considering all error cases
int val = str.toInt();
xQueueSend(InterTaskQueue, &val, portMAX_DELAY); // Send value to TaskBlink
}
vTaskDelay(1);
}
}
}
シリアル回線から受け取るデータによるアクションの定義
- 's'を受け取ったら、TaskBlinkを一時中断
- 'r'を受け取ったら、TaskBlinkを再開
- それ以外(ここでは数値、細かいエラー制御は省略)は数値に変換してキューに積む
vTaskDelay()で他のタスクに制御が確実移行する(ようだ)。
TaskButton
void TaskButton(void *pvParameters)
{
(void) pvParameters;
const int DIN_PIN = 7; // Pin from 5V is connected to one leg of button. The other leg is connected to GND.
int value;
pinMode( DIN_PIN, INPUT_PULLUP ); // External resistor is not necessary.
for (;;) // A Task shall never return or exit.
{
value = digitalRead( DIN_PIN );
if ( value == LOW ){
// If the semaphore is not available, wait 5 ticks of the Scheduler to see if it becomes free.
if ( xSemaphoreTake( SerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
{
Serial.println("Button Pushed");
delay(1500); // sleep at purpose to make other tasks delay getting the Serial Semaphore (prevention of Serial.println)
xSemaphoreGive( SerialSemaphore ); // Now free or "Give" the Serial Port for others.
}
}
vTaskDelay(1); // one tick delay (15ms) in between reads for stability??
}
}
- PIN 7をPULL_UPとして入力に設定
- PIN 7から入力があったらセマフォ取得
- シリアル回線にメッセージを送る
- わざと1500ms眠る(TaskBlinkのセマフォ取得を妨げるため)
- セマフォ解放
TaskBlink
void TaskBlink(void *pvParameters)
{
(void) pvParameters;
pinMode(LED_BUILTIN, OUTPUT);
for (;;) // A Task shall never return or exit.
{
int count;
if (xQueueReceive(InterTaskQueue, &count, portMAX_DELAY) == pdPASS) {
for (int i=0; i<count; i++) {
if ( xSemaphoreTake( SerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
{
char buf[10];
sprintf(buf, "N=%d", i+1);
Serial.println(buf);
xSemaphoreGive( SerialSemaphore ); // Now free or "Give" the Serial Port for others.
}
digitalWrite(LED_BUILTIN, HIGH);
vTaskDelay( 500 / portTICK_PERIOD_MS ); // wait for 500ms
digitalWrite(LED_BUILTIN, LOW);
vTaskDelay( 500 / portTICK_PERIOD_MS ); // wait for 500ms
}
}
}
}
- Arduino搭載LEDの設定
- キューからデータ(LEDのオンオフを行う回数)を受け取る
- セマフォ取得
- 何回目のLEDオンオフかのメッセージをシリル回線に送る
- LEDのオンオフ
実験
TaskBlinkの一時中断および再開
再開(Resume)されるまで、TaskBlinkのメッセージ送信が止まる。
セマフォロック中のためメッセージが送れないケース
TaskButtonにてボタン押下後1500msもSleepし、TaskBlinkにてセマフォ取得できず、"N=4"および"N=6"メッセージが送ることができていない。
おわりに
次は、タスク優先度(Priority)の実験を行う。