บทความนี้นำเสนอตัวอย่างการเขียนโค้ดโดยใช้ FreeRTOS for Arduino (AVR) เพื่อสาธิตการประสานจังหวะการทำงานระหว่างทาส์กโดยใช้สิ่งที่เรียกว่า “Rendezvous” (จุดนัดพบ) และ “Barrier” (แบริเออร์ หรือ รั้วคั่น)
การทำงานแบบรอกันระหว่างทาส์ก: Rendezvous
สมมุติว่า เรามีทาส์ก A และ B ที่ทำงานแบบวนรอบซ้ำ (Repeated) และในแต่ละรอบอาจใช้เวลาไม่เท่ากันแต่มีระยะเวลาจำกัด และมีเงื่อนไขข้อหนึ่งคือ เมื่อแต่ละทาสก์ทำงานครบหนึ่งรอบ จะต้องรอให้อีกทาส์กหนึ่งทำงานครบหนึ่งรอบก่อน จึงจะทำงานในรอบต่อไปได้ ทาส์กทั้งสองนี้ทำงานในลักษณะที่เป็น “Lockstep” ถ้าแต่ละทาส์กมีการนับรอบ ตัวนับของแต่ละทาส์ก จะต้องเพิ่มขึ้นเหมือนกัน
การประสานการทำงานเพื่อรอกันในลักษณะนี้เรียกว่า Bilateral Rendezvous หรือ Two-Way Synchronization แต่ถ้าให้ทาส์กหนึ่งรออีกทาส์กหนึ่งแบบฝ่ายเดียว ก็จะเรียกว่า Unilateral Rendezvous หรือ One-Way Synchronization
โค้ดตัวอย่างที่ 1: Bilateral Rendezvous โดยใช้ Semaphores
เราสามารถเขียนโค้ดโดยใช้ FreeRTOS เพื่อสาธิตสถานการณ์ในลักษณะนี้ได้ และใช้ Binary Semaphore จำนวน 2 ชุด (S0 และ S1) สำหรับการประสานการทำงานระหว่างสองทาส์ก (ทาส์ก “T0” และ “T1” มีระดับความสำคัญเท่ากัน) ให้ทำงานได้แบบ Lockstep
ในแต่ละรอบการทำงาน ทาส์ก T0 จะรอ S0 โดยทำคำสั่ง xSemaphoreTake()
และทาส์ก T1 จะทำคำสั่ง xSemaphoreGive()
ให้กับ S0
ในลักษณะเดียวกัน ทาส์ก T1 จะรอ S1 โดยทำคำสั่ง xSemaphoreTake()
และทาส์ก T0 จะทำคำสั่ง xSemaphoreGive()
ให้กับ S1
ข้อสังเกต: ในตัวอย่างนี้ มีการสร้างและใช้งาน Mutex เพื่อจัดการเข้าใช้งาน Serial ของ Arduino สำหรับแต่ละทาส์ก
#include <Arduino_FreeRTOS.h>
#include <semphr.h>// Global variables
SemaphoreHandle_t mutex = NULL;
SemaphoreHandle_t sem[2] = {NULL}; // S0 and S1
char sbuf[20];void task( void* pvParameters ) {
int id = (int)pvParameters;
uint8_t cnt=0;
while (1) {
vTaskDelay( pdMS_TO_TICKS( random(0,500) ) );
xSemaphoreGive( sem[(id==0) ? 1:0] );
if ( xSemaphoreTake( sem[ id ], portMAX_DELAY )==pdTRUE ){
if ( xSemaphoreTake( mutex, portMAX_DELAY )==pdTRUE ){
sprintf( sbuf, "T%d #%d", id, cnt++ );
Serial.println( sbuf );
xSemaphoreGive( mutex );
}
}
}
vTaskDelete(NULL);
}void setup() {
Serial.begin(115200);
Serial.println( "Arduino FreeRTOS Demo..." );
randomSeed( analogRead( A0 ) );
// create a mutex which is used to guard Serial.
mutex = xSemaphoreCreateMutex();
// create binary semaphores
sem[0] = xSemaphoreCreateBinary();
sem[1] = xSemaphoreCreateBinary();
// create two tasks
int pri = tskIDLE_PRIORITY + 1; // task priority level
xTaskCreate( task, "T0", 128, (void*)0, pri, NULL );
xTaskCreate( task, "T1", 128, (void*)1, pri, NULL );
// vTaskStartScheduler();
}void loop() {}
โค้ดตัวอย่างที่ 2: Bilateral Rendezvous โดยใช้ Task Notifications
เราได้เห็นตัวอย่างการใช้ Binary Semaphore ไปแล้ว ลองมาดูการใช้เทคนิคที่เรียกว่า Task Notification ของ FreeRTOS เพื่อทำงานทาส์กทั้งสองทำงานได้แบบ Lockstep
เมื่อทาส์ก T0 ทำงานหนึ่งรอบแล้ว ให้แจ้งเหตุการณ์ไปยังทาส์ก T1 และในทิศทางตรงกันข้าม เมื่อทาส์ก T1 ทำงานเสร็จหนึ่งรอบแล้ว ให้แจ้งเหตุการณ์ไปยังทาส์ก T0
ตัวอย่างคำสั่งที่เกี่ยวข้องกับการใช้งาน FreeRTOS Task Notifications เช่น
ulTaskNotifyTake()
ให้ทาส์กนี้รอการแจ้งเหตุการณ์จากทาส์กอื่นxTaskNotifyGive()
แจ้งเหตุการณ์ไปยังทาส์กที่เจาะจง
#include <Arduino_FreeRTOS.h>
#include <task.h>
#include <semphr.h>// global variables
char sbuf[20];
SemaphoreHandle_t mutex = NULL;
TaskHandle_t task_handles[2] = {NULL};void task( void* pvParameters ) {
int id = (int)pvParameters;
uint8_t cnt=0;
while (1) {
vTaskDelay( pdMS_TO_TICKS( random(0,500) ) );
xTaskNotifyGive( task_handles[(id==0) ? 1:0] );
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
if ( xSemaphoreTake( mutex, portMAX_DELAY )==pdTRUE ){
sprintf( sbuf, "T%d #%d", id, cnt++ );
Serial.println( sbuf );
xSemaphoreGive( mutex );
}
}
vTaskDelete(NULL);
}void setup() {
Serial.begin( 115200 );
Serial.println( "Arduino FreeRTOS Demo..." );
randomSeed( analogRead( A0 ) );
// create a mutex which is used to guard Serial.
mutex = xSemaphoreCreateMutex();
// create two tasks: T0 and T1
int pri = tskIDLE_PRIORITY + 1; // task priority level
xTaskCreate( task, "T0", 120, (void*)0, pri, &task_handles[0] );
xTaskCreate( task, "T1", 120, (void*)1, pri, &task_handles[1] );
// vTaskStartScheduler();
}void loop() {}
โค้ดตัวอย่างที่ 3: Bilateral Rendezvous โดยใช้ Event Group และ Event Bits
อีกเทคนิคที่สำหรับการรอและแจ้งเหตุการณ์หรือเงื่อนไขสำหรับทาส์ก นอกจากการใช้วิธี Task Notifications แล้ว ก็สามารถใช้ Event Group / Event Bits ได้
ตัวอย่างคำสั่งที่เกี่ยวข้องกับ Event Group / Event Bits เช่น
xEventGroupCreate()
สร้าง Event Group สำหรับใช้งานxEventGroupSync()
กำหนดค่าให้เป็น 1 สำหรับบิตเหตุการณ์ (Event Bit) ที่เกี่ยวข้อง้ และรอดูว่า บิตที่เหลือในกลุ่ม ถูกเซตค่าเป็น 1 ครบตามจำนวนแล้วหรือไม่ และเมื่อครบแล้วให้เคลียร์บิตทั้งหมด
ในตัวอย่างนี้ เนื่องจากมีเพียงสองทาส์ก ดังนั้นค่าบิต (Event Bit Value) ของทาส์ก T0 และ T1 คือ (1<<0)
และ (1<<1)
หรือมีค่าเท่ากับ 1 และ 2 ตามลำดับ
#include <Arduino_FreeRTOS.h>
#include <semphr.h>
#include <event_groups.h>// Bilateral synchronisation (aka the Rendezvous)// global variables
char sbuf[20];
SemaphoreHandle_t mutex = NULL;
EventGroupHandle_t event_group = NULL;
EventBits_t all_sync_bits = ((1<<1)|(1<<0));void task( void* pvParameters ) {
int id = (int)pvParameters; // task ID
uint8_t cnt = 0;
EventBits_t ev_bits;
while (1) {
vTaskDelay( pdMS_TO_TICKS( random(0,500) ) );
ev_bits = xEventGroupSync(
event_group, (1 << id), all_sync_bits, portMAX_DELAY
);
if ( (ev_bits & all_sync_bits) == all_sync_bits ) {
if ( xSemaphoreTake( mutex, portMAX_DELAY )==pdTRUE ){
sprintf( sbuf, "T%d #%d", id, cnt++ );
Serial.println( sbuf );
xSemaphoreGive( mutex );
}
}
}
vTaskDelete(NULL);
}void setup() {
Serial.begin( 115200 );
Serial.println( "Arduino FreeRTOS Demo..." );
randomSeed( analogRead( A0 ) );
mutex = xSemaphoreCreateMutex(); // create a mutex
event_group = xEventGroupCreate(); // create an event group
// create two tasks
int pri = tskIDLE_PRIORITY + 1; // task priority level
xTaskCreate( task, "T0", 128, (void*)0, pri, NULL );
xTaskCreate( task, "T1", 128, (void*)1, pri, NULL );
// vTaskStartScheduler();
}void loop() {}
โค้ดตัวอย่างที่ 4: การสร้าง Barrier โดยใช้ Event Group และ Event Bits
ถ้าเราเพิ่มจำนวนทาส์กให้มีมากกว่า 2 ทาส์กขึ้นไป เราก็สามารถสร้างสิ่งที่เรียกว่า Barrier ได้ ทาสก์ทั้งหมดเมื่อทำงานเสร็จในแต่ละรอบ จะต้องมารอที่ “แบริออร์” ก่อนทำงานในรอบถัดไป
โค้ดตัวอย่างนี้ กำหนดจำนวนทาส์กไว้เท่ากับ 4 และแต่ละทาส์ก เมื่อผ่าน Barrier ได้ในแต่ละรอบ จะสร้างสัญญาณพัลส์ (High Pulse) กว้างประมาณ 5 มิลลิวินาที ที่ขา Digital I/O สำหรับแต่ละทาสก์ (ขา D5 — D8)
#include <Arduino_FreeRTOS.h>
#include <semphr.h>
#include <event_groups.h>#define NUM_TASKS 4
const int LED_PINS[] = {5,6,7,8};
// global variables
char sbuf[20];
SemaphoreHandle_t mutex = NULL;
EventGroupHandle_t event_group = NULL;
EventBits_t all_sync_bits = 0;void task( void* pvParameters ) {
int id = (int)pvParameters;
uint8_t cnt=0;
EventBits_t ev_bits;
while (1) {
vTaskDelay( pdMS_TO_TICKS(random(100, 200)) );
ev_bits = xEventGroupSync(
event_group, (1 << id), all_sync_bits, portMAX_DELAY
);
if ( (ev_bits & all_sync_bits) == all_sync_bits ) {
digitalWrite( LED_PINS[id], HIGH );
delay(5);
digitalWrite( LED_PINS[id], LOW );
if ( xSemaphoreTake( mutex, portMAX_DELAY )==pdTRUE ){
sprintf( sbuf, "T%d #%d", id, cnt++ );
Serial.println( sbuf );
xSemaphoreGive( mutex );
}
}
}
vTaskDelete(NULL);
}void setup() {
Serial.begin( 115200 );
Serial.println( "Arduino FreeRTOS Demo..." );
randomSeed( analogRead( A0 ) );
mutex = xSemaphoreCreateMutex(); // create a mutex
event_group = xEventGroupCreate(); // create an event group
String s;
int pri = tskIDLE_PRIORITY + 1; // task priority level
for ( int id=0; id < NUM_TASKS; id++ ) { // create tasks
pinMode( LED_PINS[id], OUTPUT );
digitalWrite( LED_PINS[id], LOW );
s = "T";
s += id;
Serial.print( "Create task " );
Serial.println( s.c_str() );
xTaskCreate( task, s.c_str(), 120, (void*)id, pri, NULL );
all_sync_bits |= (1 << id);
}
s = "";
// vTaskStartScheduler();
}void loop() {}
จากตัวอย่างการทำงานที่ใช้ FreeRTOS สำหรับบอร์ด Ardiuno ราคาไม่แพง จะเห็นได้ว่า เราก็สามารถเรียนรู้หลักการทำงานแบบ Multi-Tasking หรือ Multi-Threading ที่เกี่ยวข้องกับเนื้อหาวิชา “ระบบปฏิบัติการ” หรือ Operating System (OS) และสามารถนำมาทดลองใช้กับ RTOS สำหรับบอร์ดไมโครคอนโทรลเลอร์ แม้ว่ารูปแบบการเขียนโค้ดและ API หรือเทคนิคที่ใช้ จะแตกต่างกันไป เช่น ในกรณีที่ใช้ POSIX Pthreads เป็นต้น