Uing FreeRTOS with Arduino AVR [3]

<rawat.s>
7 min readFeb 6, 2020

--

‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

บทความนี้แนะนำและสาธิตการใช้งาน FreeRTOS สำหรับ Arduino AVR เป็นตอนที่ 3 ต่อจากตอนที่ 2 ในตอนนี้เราจะมาลองทำความรู้จักกับเทคนิค เพื่อการประสานการทำงานระหว่างทาส์ก เช่น เพื่อรอจังหวะการทำงาน ส่งต่อการทำงาน หรือควบคุมการเข้าใช้ทรัพยากรร่วมกัน

การประสานการทำงานระหว่างทาส์ก (Inter-Task Synchronization)

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

เมื่อใช้ RTOS ในการเขียนโปรแกรม ก็คงต้องออกแบบการทำงานของโปรแกรมให้ประกอบด้วยทาส์ก (Tasks) ซึ่งทาส์กเหล่านี้ อาจทำงานอิสระจากกันก็ได้ แต่ในหลาย ๆ กรณีที่จำเป็นต้องทำให้ทาส์กเหล่านั้น ประสานการทำงานกัน และเพื่อวัตถุประสงค์นี้ FreeRTOS มีฟังก์ชันสำหรับสิ่งที่เรียกว่า Semaphore และ Mutex (Mutual Exclusion)

หลักการทำงานของเซมาฟอร์ (Semaphore)

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

เซมาฟอร์เป็นโครงสร้างข้อมูลที่สามารถเก็บสิ่งที่เรียกว่า Tokens หรือ โทเคน และมีฟังก์ชันที่เกี่ยวข้องกับการใช้งานคือ Take (นำ Token ออกไป ถ้ายังมี Token เหลืออยู่ภายใน) และ Take (นำ Token ใส่กลับคืนเข้าไป)

ในการสร้างเซมาฟอร์เพื่อใช้งาน จะต้องมีการกำหนดความจุ (Capacity) โดยทั่วไปก็มีความจุ เท่ากับ 1 หรือมากกว่า ถ้ามีความจุเท่ากับ 1 ก็เรียกว่า “เซมาฟอร์แบบไบนารี” (Binary Semaphore) แต่ถ้ามีมากกว่า 1 ก็เรียกว่า “เซมาฟอร์แบบนับ” (Counting Semaphore)

ทาส์กสามารถดึง Token ออกจาก Semaphore ได้ ถ้ายังมีเหลืออยู่ แต่ถ้าในขณะนั้นไม่มีเหลืออยู่ ทาส์กที่ต้องการจะนำ Token ออกไป (Take) จะถูกเปลี่ยนสถานะจาก RUNNING เป็น BLOCKED เพื่อหยุดรอ และรอจนกว่า จะมี Token ถูกนำมาใส่กลับคืนมา (อย่างน้อยต้องมี 1) ทาส์กดังกล่าวจึงจะสามารถขอ Token ใหม่อีกครั้งได้ ถ้าคราวนี้ทำได้สำเร็จ ก็ได้ทำงานต่อไปและไม่ต้องถูกหยุดรอ

ตัวอย่างของฟังก์ชันของ FreeRTOS ที่เกี่ยวข้องกับการใช้งาน Semaphores ได้แก่

  • xSemaphoreCreateBinary() สร้างเซมาฟอร์แบบไบนารี (Binary Semaphore) และใช้หน่วยความจำของ FreeRTOS Heap ในการเก็บข้อมูล
  • xSemaphoreCreateCounting() สร้างเซมาฟอร์แบบตัวนับ (Counting Semaphore) และใช้หน่วยความจำของ FreeRTOS Heap ในการเก็บข้อมูล
  • xSemaphoreTake() นำ Token ออกจากเซมาฟอร์
  • xSemaphoreGive() นำ Token มาใส่คืนลงในเซมาฟอร์
  • xSemaphoreTakeFromISR() นำ Token ออกไป (เรียกใช้จาก ISR)
  • xSemaphoreGiveFromISR() นำ Token มาใส่คืน (เรียกใช้จาก ISR)
  • vSemaphoreDelete() ทำลายเซมาฟอร์ทิ้งไป (ไม่ใช้งานแล้ว)

เขียนโค้ดเปิดปิด LED หลายดวงไปตามลำดับ

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

เรามาดูโค้ด Arduino Sketch ที่ใช้สำหรับ LEDs หลายดวง และจะทำให้ LED สว่างขึ้นแล้วดับลงทีละดวง เช่น เริ่มต้นที่ดวงแรกแล้วเรียงไปตามลำดับ จากนั้นจึงวนเริ่มต้นใหม่ โดยใช้ตัวแปร index ในการระบุว่า ถัดไปขาเอาต์พุตใดที่จะต้องทำให้เกิดพัลส์ HIGH เป็นเวลา 100 มิลลิวินาที

#define LED_ON     LOW
#define LED_OFF HIGH
#define NUM_LEDS (8)
const byte LED_PINS[] = { 5,6,7,8,9,10,11,12 };void setup() {
randomSeed( analogRead(0) );
for ( int i=0; i < NUM_LEDS; i++) {
pinMode( LED_PINS[i], OUTPUT );
digitalWrite( LED_PINS[i], LOW );
}
}
void loop() {
static int index = 0;
int ledPin = LED_PINS[ index ];
digitalWrite( ledPin, LED_ON );
delay( 100 );
digitalWrite( ledPin, LED_OFF );
index = (index+1) % NUM_LEDS;
}

ถัดไปลองมาออกแบบโปรแกรมโดยการแบ่งงานให้ทาส์กทำงาน ตามจำนวนของ LED ที่ใช้ ในกรณีก็คือ ให้สร้างทาส์กตามจำนวนหลอดแอลอีดี 8 ชุด แต่ละทาส์กมีหน้าที่ทำให้ LED สำหรับขาเอาต์พุตที่เกี่ยวข้องสว่างแล้วดับลง

คำถาม: เนื่องจากทาส์กเหล่านี้ จะทำงานแบบอิสระจากกัน ดังนั้นจะจัดลำดับการทำงานให้ทาส์กเหล่านั้นได้อย่างไร เช่น ให้ทาส์กหมายเลข 0 ทำงานก่อนถัดไปจึงเป็นทาส์กหมายเลข 1 ไปตามลำดับ จนถึงทาส์กหมายเลข 7

ตัวอย่างฮาร์ดแวร์สำหรับการทดสอบการทำงานของโค้ดตัวอย่าง

ลองใช้เซมาฟอร์แบบไบนารีเพื่อช่วยในการสื่อสารกันระหว่างทาส์ก (เวอร์ชันแรก)

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

โค้ดตัวอย่างที่ 2 สาธิตการสร้างเซมาฟอร์แบบไบนารี เพื่อใช้สื่อสารกันระหว่างทาส์กเป็นคู่ ๆ ไป ทาส์กจะถูกสร้างขึ้นมาทั้งหมด 8 ชุด (T0, T1, …, T7) โดยใช้ตัวแปรอาร์เรย์ taskHandles[…] ในการอ้างอิง และเมื่อทาส์กได้ทำงาน จะทำให้เกิดพัลส์ HIGH หนึ่งครั้ง กว้างประมาณ 100 มิลลิวินาที ที่ขาเอาต์พุตที่เกี่ยวข้อง (ขา Digital I/O ของ Arduino หมายเลข 5, 6, …, 12 ตามลำดับ) ก่อนจะส่งต่อให้ทาส์กอื่นได้ทำงานถัดไป

นอกจากการสร้างทาส์กแล้ว จะมีการสร้างเซมาฟอร์แบบไบนารี จำนวน 8 ชุด และใช้ตัวแปรอาร์เรย์ semaphores[…] ในการอ้างอิง

ทาส์กหมายเลข i จะสื่อสารผ่านเซมาฟอร์กับทาส์กในลำดับถัดไป ซึ่งก็คือ หมายเลข (i+1)%8 โดยใช้เซมาฟอร์หมายเลข (i+1)%8 โดยที่ i=0,1,…,7

เมื่อเริ่มต้นทำงาน เซมาฟอร์หมายเลข 0 จะมีจำนวน Token เป็น 1 แต่เซมาฟอร์หมายเลข 1,2, …,7 จะมีจำนวน Token เป็น 0

การสื่อสารจะเกิดขึ้นระหว่างทาส์กในลักษณะเป็นวงกลม (Circular) โดยเริ่มต้นจากทาส์กหมายเลข 0 ก่อน เนื่องจากเป็นทาส์กแรกที่จะได้ทำงานและสามารถดึง Token ออกจากเซมาฟอร์หมายเลข 0 ได้ และเมื่อได้ทำงาน ก็จะนำ Token ไปใส่ลงในเซมาฟอร์หมายเลข 1 แล้วก็ปล่อยให้ทาส์กอื่นได้มีโอกาสทำงานบ้าง โดยทำคำสั่ง taskYIELD()

จากนั้นจะทำให้ทาส์กหมายเลข 1 พร้อมที่จะทำงาน เพราะสามารถดึง Token ออกจากเซมาฟอร์หมายเลข 1 ได้ และการสื่อสารผ่านเซมาฟอร์เกิดขึ้นไปตามลำดับในถึงทาส์กหมายเลข 7 แล้ววนเป็นวงกลม ทาส์กหมายเลข 0 จะได้ทำงานอีกครั้งเมื่อทาส์กหมายเลข 7 ได้นำ Token มาใส่ในเซมาฟอร์หมายเลข 0

ข้อสังเกต: ในโค้ดนี้จะเห็นได้ว่า มีการกำหนดขนาดของ Stack หรือ Stack Depth ให้น้อยลง (เช่น กำหนดค่าเป็น 60) เนื่องจากมีการสร้างทาส์กจำนวนมากขึ้น อาจมีหน่วยความไม่พอ เช่น ถ้าจะนำโค้ดไปใช้กับชิป ATmega328P เช่น Uno หรือ Nano ที่มีหน่วยความจำ SRAM เพียง 2KB (แต่ถ้าใช้ MEGA2560 จะมีสูงสุด 8KB)

#include <Arduino_FreeRTOS.h> // tested on Uno
#include <task.h>
#include <semphr.h>
#define LED_ON LOW
#define LED_OFF HIGH
#define NUM_LEDS (8)
const byte LED_PINS[] = { 5,6,7,8,9,10,11,12 };
TaskHandle_t taskHandles[ NUM_LEDS ];
SemaphoreHandle_t semaphores[ NUM_LEDS ];
void task( void *pvParameters ); // task function prototypevoid setup() {
randomSeed( analogRead(0) );
char sbuf[4];
int priority = (tskIDLE_PRIORITY + 2);
for ( int id=0; id < NUM_LEDS; id++ ) {
semaphores[id] = xSemaphoreCreateBinary();
if ( id == 0 ) {
xSemaphoreGive( semaphores[0] );
}
sprintf( sbuf, "T%d", id );
xTaskCreate(
task, (const char *)sbuf, 60,
(void *)id, priority, &taskHandles[id]
);
}
// Note the task scheduler is started automatically.
}
void loop() {} // do nothingsvoid task( void *pvParameters ) {
int id = (int)pvParameters;
int ledPin = LED_PINS[id];
pinMode( ledPin, OUTPUT );
digitalWrite( ledPin, LED_OFF );
vTaskDelay( pdMS_TO_TICKS( random(100,1000) ) ); while(1) {
if (xSemaphoreTake( semaphores[id], portMAX_DELAY )==pdTRUE){
digitalWrite( ledPin, LED_ON );
vTaskDelay( pdMS_TO_TICKS(100) );
digitalWrite( ledPin, LED_OFF );
xSemaphoreGive( semaphores[ (id+1) % NUM_LEDS ] );
taskYIELD();
}
}
}

ลองเขียนโค้ดอีกวิธีหนึ่ง (เวอร์ชันที่สอง)

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

โค้ดตัวอย่างที่ 3 สาธิตการใช้เซมาฟอร์เพื่อสื่อสารกันระหว่างทาส์ก ตัวอย่างนี้แตกต่างจากตัวอย่างที่ 2 ดังนี้ จะมีการสร้างอีกหนึ่งทาส์ก และให้ทำฟังก์ชันที่เรียกว่า sequencer() และคอยใส่ Token ลงในเซมาฟอร์หมายเลข 0, 1, ..., 7 ไปตามลำดับ โดยเว้นระยะเวลาประมาณ 100 มิลลิวินาที ในขณะที่ทาส์ก T0, T1, , T7 จะคอยดึง Token ออกจากเซมาฟอร์ที่ถูกกำหนดไว้ให้ใช้สำหรับแต่ละทาส์ก แต่ถ้าทาส์กยังไม่สามารถดึง Token ออกมาได้ จะถูกบล็อกไว้ก่อน

#include <Arduino_FreeRTOS.h> // tested on Uno
#include <task.h>
#include <semphr.h>
#define LED_ON LOW
#define LED_OFF HIGH
#define NUM_LEDS (8)
const byte LED_PINS[] = { 5,6,7,8,9,10,11,12 };
TaskHandle_t taskHandles[ NUM_LEDS ];
SemaphoreHandle_t semaphores[ NUM_LEDS ];
// task function prototypes
void task( void *pvParameters );
void sequencer( void *pvParameters );
void setup() {
char sbuf[4];
int priority = (tskIDLE_PRIORITY + 2);
for ( int id=0; id < NUM_LEDS; id++ ) {
semaphores[id] = xSemaphoreCreateBinary();
sprintf( sbuf, "T%d", id );
xTaskCreate(
task, (const char *)sbuf, 60,
(void *)id, priority, &taskHandles[id]
);
}
xTaskCreate( sequencer, "sequencer", 60, NULL, priority, NULL );
// Note the task scheduler is started automatically.
}
void loop() {} // do nothingsvoid sequencer( void *pvParameters ) {
int index = 0;
while (1) {
xSemaphoreGive( semaphores[ index ] );
index = (index+1) % NUM_LEDS;
vTaskDelay( pdMS_TO_TICKS(100) );
}
}
void task( void *pvParameters ) {
int id = (int)pvParameters;
int ledPin = LED_PINS[id];
pinMode( ledPin, OUTPUT );
digitalWrite( ledPin, LED_OFF );
while(1) {
if (xSemaphoreTake( semaphores[id], portMAX_DELAY )==pdTRUE){
digitalWrite( ledPin, LED_ON );
vTaskDelay( pdMS_TO_TICKS(100) );
digitalWrite( ledPin, LED_OFF );
taskYIELD();
}
}
}

ถ้าลองนำโค้ดไปทดสอบกับฮาร์ดแวร์จริง ก็จะเห็นว่า LED จะสว่างขึ้นและดับลงทีละดวง เรียงไปตามลำดับ

ลองดัดแปลงแก้ไขโค้ดเล็กน้อย (เวอร์ชันที่สาม)

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ตัวอย่างนี้ได้สร้างเซมาฟอร์แบบไบนารีเพิ่มสำหรับใช้งาน (เริ่มต้นมี Token ใส่ไว้แล้ว) โดยใช้ตัวแปรชื่อ semaphore_seq ในการอ้างอิง และใช้เพื่อการสื่อสารระหว่างทาส์ก sequencer กับทาส์กที่ได้ทำงานในขณะนั้น

เมื่อทาส์ก sequencer ทำงาน จะดึง Token จาก semaphore_seq จากนั้นจะใส่ Token ลงในเซมาฟอร์สำหรับทาส์กดังกล่าว เมื่อทาส์กที่เกี่ยวข้องสามารถดึง Token ออกมาได้ จึงเริ่มทำงานต่อ ทำให้ LED สว่างขึ้นและดับลง จากนั้นจะต้องใส่ Token ให้เซมาฟอร์ semaphore_seq เนื่องจาก sequencer จะต้องรอ Token จึงจะทำงานต่อไป

ข้อสังเกต: ในตัวอย่างนี้ ได้กำหนด NUM_LEDS ให้เป็น 7

#include <Arduino_FreeRTOS.h> // tested on Uno
#include <task.h>
#include <semphr.h>
#define LED_ON LOW
#define LED_OFF HIGH
#define NUM_LEDS (7)
const byte LED_PINS[] = { 5,6,7,8,9,10,11,12 };TaskHandle_t taskHandles[ NUM_LEDS ];
SemaphoreHandle_t semaphore_seq, semaphores[ NUM_LEDS ];
// task function prototypes
void task( void *pvParameters );
void sequencer( void *pvParameters );
void setup() {
char sbuf[4];
int priority = (tskIDLE_PRIORITY + 2);

semaphore_seq = xSemaphoreCreateBinary();
xSemaphoreGive( semaphore_seq );

for ( int id=0; id < NUM_LEDS; id++ ) {
semaphores[id] = xSemaphoreCreateBinary();
sprintf( sbuf, "T%d", id );
xTaskCreate(
task, (const char *)sbuf, 60,
(void *)id, priority, &taskHandles[id]
);
}
xTaskCreate( sequencer, "sequencer", 60, NULL, priority, NULL );
// Note the task scheduler is started automatically.
}
void loop() {} // do nothings
void sequencer( void *pvParameters ) {
int index = 0;
while (1) {
if (xSemaphoreTake( semaphore_seq, portMAX_DELAY )==pdTRUE){
xSemaphoreGive( semaphores[ index ] );
index = (index+1) % NUM_LEDS;
taskYIELD();
}
}
}
void task( void *pvParameters ) {
byte id = (int)pvParameters;
byte ledPin = LED_PINS[id];
pinMode( ledPin, OUTPUT );
digitalWrite( ledPin, LED_OFF );
while(1) {
if (xSemaphoreTake( semaphores[id], portMAX_DELAY )==pdTRUE){
digitalWrite( ledPin, LED_ON );
vTaskDelay( pdMS_TO_TICKS(100) );
digitalWrite( ledPin, LED_OFF );
xSemaphoreGive( semaphore_seq );
}
}
}

ถ้าไม่ใช้เซมาฟอร์ไบนารี จะมีวิธีอื่นหรือไม่ ?

‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ถ้าไม่ใช้เซมาฟอร์ไบนารีสำหรับโจทย์ตัวอย่างที่ทำให้ LED ติดดับทีละดวง อาจใช้วิธีอื่น เช่น การหยุดทาส์กทั้งหมดไว้ชั่วคราวก่อน (Suspend) และเลือกให้ทาส์กทำงานต่อ (Resume) ทีละหนึ่งทาส์ก ไปตามลำดับ

#include <Arduino_FreeRTOS.h> // tested on Uno
#include <task.h>
#define LED_ON LOW
#define LED_OFF HIGH
#define NUM_LEDS (8)
const byte LED_PINS[] = { 5,6,7,8,9,10,11,12 };
TaskHandle_t taskHandles[ NUM_LEDS ];
// task function prototypes
void task( void *pvParameters );
void setup() {
char sbuf[4];
int priority = (tskIDLE_PRIORITY + 2);
for ( int id=0; id < NUM_LEDS; id++ ) {
sprintf( sbuf, "T%d", id );
xTaskCreate(
task, (const char *)sbuf, 64,
(void *)id, priority, &taskHandles[id]
);
vTaskSuspend( taskHandles[id] );
}
vTaskResume( taskHandles[0] ); // resume the first task
// Note the task scheduler is started automatically.
}
void loop() {} // do nothingsvoid task( void *pvParameters ) {
byte id = (int)pvParameters;
byte ledPin = LED_PINS[id];
pinMode( ledPin, OUTPUT );
digitalWrite( ledPin, LED_OFF );
while(1) {
digitalWrite( ledPin, LED_ON );
vTaskDelay( pdMS_TO_TICKS(100) );
digitalWrite( ledPin, LED_OFF );
// resume the next task in the sequence
vTaskResume( taskHandles[ (id+1) % NUM_LEDS ] );
// suspend itself
vTaskSuspend( NULL );
}
}

เซมาฟอร์แบบนับใช้อย่างไร มีตัวอย่างไหม ?

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

เซมาฟอร์แบบนับที่มีความจุมากกว่า 1 จะใช้สำหรับควบคุมการทำงานของทาส์กในกลุ่มได้ ยกตัวอย่างเช่น มีทาส์ก 8 ชุด แต่ละทาส์กจะทำให้ LED ที่เกี่ยวข้อง สว่างขึ้นแล้วดับลง โดยเว้นช่วงเวลา และตั้งเงื่อนไนว่า ให้ LED ไม่เกิน 3 ดวง ที่ทำงานพร้อม กันได้ (เห็นว่า LED สว่างขึ้นและดับลงพร้อม ๆ กัน ได้ไม่เกิน 3 ดวง) ในกรณีนี้จึงใช้เซมาฟอร์แบบนับ โดยให้มีจำนวน Token เริ่มต้นเท่ากับ 3 (NUM_ACTIVE_TASKS)

#include <Arduino_FreeRTOS.h> // tested on Uno
#include <task.h>
#include <semphr.h>
#define LED_ON LOW
#define LED_OFF HIGH
#define NUM_LEDS (8)
#define NUM_ACTIVE_TASKS (3)
const byte LED_PINS[] = { 5,6,7,8,9,10,11,12 };TaskHandle_t taskHandles[ NUM_LEDS ];
SemaphoreHandle_t semaphore;
// task function prototypes
void task( void *pvParameters );
void sequencer( void *pvParameters );
void setup() {
char sbuf[4];
int priority = (tskIDLE_PRIORITY + 2);
randomSeed( analogRead(0) ); // initialize seed value semaphore = xSemaphoreCreateCounting(
NUM_ACTIVE_TASKS, NUM_ACTIVE_TASKS );
for ( int id=0; id < NUM_LEDS; id++ ) {
sprintf( sbuf, "T%d", id );
xTaskCreate(
task, (const char *)sbuf, 60,
(void *)id, priority, &taskHandles[id]
);
}
// Note the task scheduler is started automatically.
}
void loop() {} // do nothingsvoid task( void *pvParameters ) {
byte id = (int)pvParameters;
byte ledPin = LED_PINS[id];
pinMode( ledPin, OUTPUT );
digitalWrite( ledPin, LED_OFF );
while(1) {
if (xSemaphoreTake( semaphore, portMAX_DELAY )==pdTRUE){
digitalWrite( ledPin, LED_ON );
vTaskDelay( pdMS_TO_TICKS( random(500,1000) ) );
digitalWrite( ledPin, LED_OFF );
xSemaphoreGive( semaphore );
taskYIELD();
}
}
}

การใช้งานเซมาฟอร์เพื่อการสื่อสารระหว่าง ISR และทาส์ก

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

เราได้เห็นตัวอย่างการสื่อสารระหว่างทาส์ก โดยใช้เซมาฟอร์ไปบ้างแล้ว เช่น การทำให้ทาส์กหนึ่งรออีกทาส์กหนึ่ง โดยใช้วิธีใส่หรือดึงโทเคนออกจากเซมาฟอร์ ถัดไปลองมาดูตัวอย่างการใช้เซมาฟอร์ เพื่อสื่อสารระหว่าง ISR (Interrupt Service Routine) กับทาส์ก เมื่อเกิดเหตุการณ์อินเทอร์รัพท์จากภายนอก เช่น การกดปุ่ม แล้วให้ทาส์กที่เกี่ยวข้องรอจนกว่าจะได้รับการส่งต่อจาก ISR

ISR จะทำหน้าที่ใส่โทเคนลงในเซมาฟอร์ เมื่อเกิดอินเทอร์รัพท์ในแต่ละครั้ง ในขณะที่ทาส์กอยู่ในสถานะหยุดรอ (Blocked) เพื่อจะดึงโทเคนออกจากเซมาฟอร์

เมื่อกดปุ่ม (ทำงานแบบ Active-Low และเลือกใช้ขา D2 ของ Arduino) จะทำให้เกิดอินเทอร์รัพท์ภายนอก จากนั้น ISR จะสื่อสารไปยังทาส์กที่รออยู่ผ่านเซมาฟอร์ โดยใช้คำสั่ง xSemaphoreGiveFromISR() และเมื่อทาส์กทำงาน และทำคำสั่ง xSemaphoreTake() เพื่อดึงโทเคนได้สำเร็จ ก็จะทำให้ LED ที่ขา D13 กระพริบ 3 ครั้ง แล้วหยุดรอโทเคนถัดไป ถ้าทดสอบการทำงานของโค้ดนี้กับฮาร์ดแวร์ ก็จะเห็น LED สว่างขึ้นและดับลงไปตามลำดับ

#include <Arduino_FreeRTOS.h> // tested on Uno
#include <task.h>
#include <semphr.h>
#define LED_ON HIGH
#define LED_OFF LOW
#define LED_PIN (13) // use D13 pin for LED output
#define BUTTON_PIN (2) // use D2 pin for input button
SemaphoreHandle_t semaphore;// task function prototype
void task( void *pvParameters );
void ext_isr() { // interrupt service routine
xSemaphoreGiveFromISR( semaphore, NULL );
}
void setup() {
int priority = (tskIDLE_PRIORITY + 2);
// enable internal pull-up for the button pin
pinMode( BUTTON_PIN, INPUT_PULLUP );
// enable the external interrupt for the button pin
attachInterrupt( digitalPinToInterrupt( BUTTON_PIN ),
ext_isr, RISING );
// create a binary semaphore
semaphore = xSemaphoreCreateBinary();
// create a task for LED toggle
xTaskCreate( task, "Toggle", 64, NULL, priority, NULL );
// Note the task scheduler is started automatically.
}
void loop() {} // do nothingsvoid task( void *pvParameters ) {
pinMode( LED_PIN, OUTPUT );
digitalWrite( LED_PIN, LED_OFF );
while(1) {
// try to take the token from the semaphore
if (xSemaphoreTake( semaphore, portMAX_DELAY )==pdTRUE){
for ( int i=0; i < 3; i++ ) { // blink LED 3 times
digitalWrite( LED_PIN, LED_ON );
vTaskDelay( pdMS_TO_TICKS( 100 ) );
digitalWrite( LED_PIN, LED_OFF );
vTaskDelay( pdMS_TO_TICKS( 100 ) );
} // end-of-for
}
} // end-of-while
}

การใช้เซมาฟอร์ทั้งแบบ Binary และ Counting เป็นเพียงวิธีหนึ่งในการสื่อสารหรือประสานการทำงานระหว่างทาส์กที่สามารถพบเห็นได้เมื่อใช้งาน RTOS และในตอนถัดไปจะได้ทำความรู้จักกับ Mutex ของ FreeRTOS

Creative Commons, Attribution-Non Commercial-Share Alike 4.0 International (CC BY-NC-SA 4.0)

--

--

<rawat.s>
<rawat.s>

Written by <rawat.s>

I'm Thai and working in Bangkok/Thailand.

No responses yet