タスクプライオリティ(優先度)による動作の振る舞いを知る
今回のお題は、FreeRTOSにてタスクプライオリティにより、複数のタスクがどのように振る舞うかを検証する。
環境、タスク構成
Arduino利用。3つのタスクを設ける。
- シリアルタスク(SerialIntr):PCからの入力時に、キューにメッセージを送信
- タスクA(TaskA):0から63までインクリメントした数値を「常に」PCに出力(63の次は0に戻る)
- タスクB(TaskB):前述したキューでメッセージを受信したとき「のみ」PCにメッセージを出力
PCに出力するタイミングがポイント。
ソースコード
FreeRTOS導入
# include <Arduino_FreeRTOS.h>
# include <queue.h>
TaskHandle_t xSerial, xTaskA, xTaskB;
QueueHandle_t qMsg;
タスクハンドラ、キューの定義。
シリアルタスク
void SerialIntr(void *arg) {
static uint8_t val = 1;
for (;;) {
if (Serial.available()) {
(void)Serial.readString();
xQueueSend(qMsg, &val, portMAX_DELAY);
val++;
}
vTaskDelay(1);
}
}
PCから入力されたデータは読み捨てる。キューにメッセージ(数値)を積む。数値は1から始まり、キューに積むたびにインクリメントする。
タスクA
void TaskA(void *arg) {
uint8_t val;
char buf[8];
static uint8_t count = 0;
uint32_t i, sum;
for (;;) {
sprintf(buf, "%2d ", count);
Serial.print(buf);
count++;
if (!(count % 32)) {
Serial.println("");
}
if (count == 64) {
count = 0;
}
vTaskDelay(1); // ①
}
}
単に、数値(0-63)をPCへ出力。63まで出力したら0に戻る。①のvTaskDelay(1)でタスクスイッチが行われる(後述)。
タスクB
void TaskB(void *arg) {
uint8_t val;
char buf[32];
static uint32_t count = 1;
for (;;) {
if (xQueueReceive(qMsg, &count, portMAX_DELAY) == pdPASS) { // Sleep(= Task Switch) ②
Serial.println("");
sprintf(buf, "TaskB Start: N=%d", count);
Serial.println(buf);
for (int i = 0; i < 10; i++) {
sprintf(buf, " Cnt=%d", i);
Serial.println(buf);
//vTaskDelay(1); // Task Switch ③
}
count++;
}
}
}
キューにメッセージが積まれるまでにずっと待つ(②)。メッセージを受けると、タスクBが開始したこと及びカウンタ10回分をPCに出力する。ここでも、③のvTaskDelay(1)でタスクスイッチが行われるが、ケースにより未使用(後述)。
Arduinoお決まり部分
void setup() {
Serial.begin(9600);
qMsg = xQueueCreate(1, sizeof(uint8_t));
xTaskCreate(SerialIntr, "Serial", 256, NULL, 3, &xSerial); // ④
xTaskCreate(TaskA, "TaskA", 256, NULL, 1, &xTaskA); // ⑤
xTaskCreate(TaskB, "TaskB", 256, NULL, 2, &xTaskB); // ⑥
vTaskStartScheduler();
}
void loop() {
// Nothing
}
タスクプライオリティ(④、⑤、⑥の'3'、'2'、'1')、数値が大きいほど優先度が高い。ここでは、⑤や⑥の値を変化させる(後述)。
結果
Case 1: タスクBのプライオリティ2、タスクAのプライオリティ1
タスクBの実行中、タスクAは動作しない。

タスクAが'54'出力後、次の'55'が出力されるまで、タスクB出力しかされていないことがわかる。
Case 2: タスクBのプライオリティ2、タスクAのプライオリティ1、タスクBのfor文内でvTaskDelay()実行(③)
タスクB実行中でも、vTaskDelay()によるタスクスイッチがあれば、タスクAは動くことがわかる。

タスクB出力中でも、タスクAの出力('38'-'46')が見える。
Case 3: タスクBのプライオリティ1、タスクAのプライオリティ2
タスクB実行中に、タスクBにvTaskDelay()(上述③)がなくても、タスクAは動作する。ただし、タスクAとタスクBとの出力が同時に発生し、正しく表示されていない(上記の「6Cnt=6」および「6Cnt=8」、'60'と'61'とが隠れている)。
Case 3: 番外
正しく表示させるために、PCへの出力(シリアル出力)に排他制御を設ける。
# include <Arduino_FreeRTOS.h>
# include <queue.h>
# include <semphr.h> // ⑦
TaskHandle_t xSerial, xTaskA, xTaskB;
QueueHandle_t qMsg;
SemaphoreHandle_t semSerial; // ⑧
void SerialIntr(void *arg) {
static uint8_t val = 1;
for (;;) {
if (Serial.available()) {
(void)Serial.readString();
xQueueSend(qMsg, &val, portMAX_DELAY);
val++;
}
vTaskDelay(1);
}
}
void TaskA(void *arg) {
uint8_t val;
char buf[8];
static uint8_t count = 0;
uint32_t i, sum;
for (;;) {
sprintf(buf, "%2d ", count);
count++;
if (xSemaphoreTake(semSerial, portMAX_DELAY) == pdTRUE) { // ⑨
Serial.print(buf);
if (!(count % 32)) {
Serial.println("");
}
xSemaphoreGive(semSerial); // ⑩
}
if (count == 64) {
count = 0;
}
vTaskDelay(1);
}
}
void TaskB(void *arg) {
uint8_t val;
char buf[32];
static uint32_t count = 1;
for (;;) {
if (xQueueReceive(qMsg, &count, portMAX_DELAY) == pdPASS) { // Sleep(= Task Switch)
sprintf(buf, "TaskB Start: N=%d", count);
if (xSemaphoreTake(semSerial, portMAX_DELAY) == pdTRUE) { // ⑪
Serial.println("");
Serial.println(buf);
xSemaphoreGive(semSerial); // ⑫
}
for (int i = 0; i < 10; i++) {
sprintf(buf, " Cnt=%d", i);
if (xSemaphoreTake(semSerial, portMAX_DELAY) == pdTRUE) { // ⑬
Serial.println(buf);
xSemaphoreGive(semSerial); // ⑭
}
//vTaskDelay(1); // Task Swtich
}
count++;
}
}
}
void setup() {
Serial.begin(9600);
qMsg = xQueueCreate(1, sizeof(uint8_t));
semSerial = xSemaphoreCreateMutex(); // ⑮
xSemaphoreGive(semSerial); // ⑯
xTaskCreate(SerialIntr, "Serial", 256, NULL, 3, &xSerial);
xTaskCreate(TaskA, "TaskA", 256, NULL, 2, &xTaskA);
xTaskCreate(TaskB, "TaskB", 256, NULL, 1, &xTaskB);
vTaskStartScheduler();
}
void loop() {
// Nothing
}
- ⑦、⑧、⑮、⑯:シリアル出力用セマフォの導入および初期化
- ⑨、⑩:タスクAでのセマフォ取得待ちおよび開放
- ⑪、⑫、⑬、⑭:タスクBでのセマフォ取得待ちおよび開放
正しく表示されている。
おわりに
期待どおりの結果。

