ESP32上でFreeRTOSによるマルチタスクを使う必要があったのでメモ。
利点
- 複数のタスクを、他の処理時間に影響を受けずに実行できる
- 優先順位や実行時間の振り分けはRTOSにお任せできる
- 複数のタスクを、シンプルに記述することができる
RTOSにおける最低限の主なAPI
- キュー:共有データの送受信
- キューにデータをFIFOで積む
- キューにデータがあった場合のみ処理が実行される
- セマフォ:動作許可とそれによる同期処理
- バイナリセマフォ:ほぼミューテックスと一緒 (FreeRTOSはこっち)
- カウンティングセマフォ:アクセス数監視と排他制御
- ミューテックス:複数スレッドからの共有資源アクセスの排他制御
API
####キューデータ通信
- xQueueCreate()
- xQueueSend()
- xQueueReceive()
セマフォ同期
- xSemaphoreCreateBinary()
- xSemaphoreGiveFromISR()
- xSemaphoreTake()
ミューテックス排他制御
- xSemaphoreCreateMutex()
- xSemaphoreGive()
- xSemaphoreTake()
FreeRTOSにおけるセマフォとミューテックス
- 動作の許可を示すバイナリセマフォを同期で使う時はセマフォ
- 排他制御で使う時はミューテックスと呼ぶ
- 使用するAPIは、どちらもxSemaphoreGive()とxSemaphoreTake()
- 正しくは違うがざっくりいうとこんな感じ
これら2つの明確な違いは、
- セマフォ同期のvSemaphoreCreateBinary()では、初期値で動作許可が無い
- ミューティックス排他制御のxSemaphoreCreateMutex()では、初期値で動作許可がある
サンプルコード
マルチタスクを作成するAPI
xtaskCreatePinnedToCore.cpp
BaseType_t xTaskCreatePinnedToCore(
TaskFunction_t pvTaskCode, // task_func_name
const char *constpcName, // task_name
const uint32_t usStackDepth, // stack_memory_size
void *constpvParameters, // ptr that will be used as the param for the task being created
UBaseType_t uxPriority, // task_priority (0-25, priority is 0 < 25)
TaskHandle_t *constpvCreatedTask, // task_handle_pointer
const BaseType_t xCoreID // core ID
)
シンプルなマルチタスク
simple_multitask.cpp
void task0(void* arg)
{
while (1)
{
static int count = 0;
Serial.print("task 0 : ");
Serial.println(count++);
delay(1000);
}
}
void task1(void* arg)
{
while (1)
{
static int count = 0;
Serial.print("task 1 : ");
Serial.println(count++);
delay(2000);
}
}
void setup()
{
Serial.begin(115200);
// create tasks
xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(task1, "Task1", 4096, NULL, 1, NULL, 1);
}
void loop()
{
static int count = 0;
Serial.print("main : ");
Serial.println(count++);
delay(500);
}
キューによる通信
queue.cpp
QueueHandle_t xQueue;
static void vSendTask (void *pvParameters)
{
BaseType_t xStatus;
int32_t SendValue = 10;
while (1)
{
xStatus = xQueueSend(xQueue, &SendValue, 0);
if(xStatus != pdPASS) // send error check
{
while(1)
{
Serial.println("rtos queue send error, stopped");
delay(1000);
}
}
if(SendValue == 10) SendValue = 20;
else SendValue = 10;
delay(2000);
}
}
static void vRecvTask(void *pvParameters)
{
BaseType_t xStatus;
int32_t ReceivedValue = 0;
const TickType_t xTicksToWait = 500U; // [ms]
while (1)
{
// loop in xTicksToWait and
// when data is received, this func is triggerred
xStatus = xQueueReceive(xQueue, &ReceivedValue, xTicksToWait);
Serial.println("check if data is received");
if(xStatus == pdPASS) // receive error check
{
Serial.print("received data : ");
Serial.println(ReceivedValue);
}
else
{
if(uxQueueMessagesWaiting(xQueue) != 0)
{
while(1)
{
Serial.println("rtos queue receive error, stopped");
delay(1000);
}
}
}
}
}
void setup()
{
Serial.begin(115200);
// create queue : type = int32_t, size = 5
xQueue = xQueueCreate(5, sizeof(int32_t));
if(xQueue != NULL)
{
xTaskCreate(vSendTask, "TaskA", configMINIMAL_STACK_SIZE, NULL, 1, (TaskHandle_t *) NULL);
xTaskCreate(vRecvTask, "TaskB", configMINIMAL_STACK_SIZE, NULL, 2, (TaskHandle_t *) NULL);
Serial.println("tasks registered");
}
else
{
while(1)
{
Serial.println("rtos queue create error, stopped");
delay(1000);
}
}
}
void loop()
{
}
セマフォによる割り込み同期
semaphore.cpp
SemaphoreHandle_t xBinarySemaphore;
void handleIRQ(void)
{
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken); // can be used in ISR
}
static void vISRTask (void *pvParameters)
{
BaseType_t xStatus;
const TickType_t xTicksToWait = 1000U; // [ms]
Serial.println("check for isr");
while(1)
{
xStatus = xSemaphoreTake(xBinarySemaphore, xTicksToWait);
Serial.println("check for interrupt");
if(xStatus == pdTRUE)
{
static int count = 0;
Serial.print("take : ");
Serial.println(count++);
}
}
}
void setup()
{
Serial.begin(115200);
xBinarySemaphore = xSemaphoreCreateBinary();
if(xBinarySemaphore != NULL)
{
xTaskCreate(vISRTask, "Task", configMINIMAL_STACK_SIZE, NULL, 1, (TaskHandle_t *)NULL);
}
else
{
while(1)
{
Serial.println("rtos semaphore create error, stopped");
delay(1000);
}
}
}
void loop()
{
// virtual interrupt
Serial.println("give");
handleIRQ();
delay(2000);
}
ミューテックスによる排他制御
mutex.cpp
SemaphoreHandle_t xMutex = NULL;
int sharedResource = 0;
static void vTask1(void *pvParameters)
{
BaseType_t xStatus;
const TickType_t xTicksToWait = 1000UL; //
xSemaphoreGive(xMutex);
while(1)
{
xStatus = xSemaphoreTake(xMutex, xTicksToWait);
Serial.println("check for mutex (task1)");
if(xStatus == pdTRUE)
{
sharedResource = 100;
Serial.print("shared resource change by task1 : ");
Serial.println(sharedResource);
}
xSemaphoreGive(xMutex);
delay(500);
}
}
static void vTask2(void *pvParameters)
{
BaseType_t xStatus;
const TickType_t xTicksToWait = 500UL;
xSemaphoreGive(xMutex);
while(1)
{
xStatus = xSemaphoreTake(xMutex, xTicksToWait);
Serial.println("check for mutex (task2)");
if(xStatus == pdTRUE )
{
sharedResource = 1;
Serial.print("shared resource change by task2 : ");
Serial.println(sharedResource);
}
xSemaphoreGive(xMutex);
delay(1000);
}
}
void setup()
{
Serial.begin(115200);
xMutex = xSemaphoreCreateMutex();
if( xMutex != NULL )
{
// higher priority task is done before lower priority task
// if there are some same priority tasks, they are kicked in order of registration
xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1,(TaskHandle_t *) NULL);
xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2,(TaskHandle_t *) NULL);
}
else {
while(1)
{
Serial.println("rtos mutex create error, stopped");
delay(1000);
}
}
}
void loop()
{
}
その他:マルチタスクにおけるdelay()について
- esp32-arduinoでは、delay() は vTaskDelay() で実装されてるので、タスク内でも使える
その他:関数などのプレフィックスについて
- x:非 stdint 型以外の返り値、size_t の返り値を持つ関数
- v:void 型の関数
- u:unsigned な返り値
- ux:unsigned な 非 stdint 型以外の返り値
- etc.
- Coding Standard and Style Guide > Naming Conventions