EventGroup หรือ Flag ใช้สื่อสารระหว่าง Task มีได้ 2 สถานะ คือ สถานะ Set (เซ็ต) และสถานะ Cleared (เครียร์) โดยการใช้งานจริงเมื่อ Task หนึ่งมีการ Set Flag จะมี Task อีก Task ที่รออยู่ทำงานทันที โดยเมื่อทำงานเสร็จแล้วค่อย Clear Flag หรือ Clear Flag ทันทีที่ Set ก็แล้วแต่จะออกแบบให้ Flag แสดงสถานะแบบไหน
Clear Flag ทันทีที่ได้รับข้อมูล - เพื่อใช้แสดงสถานะงานถูกเริ่มทำแล้ว (แต่ไม่สามารถบอกได้ว่าเสร็จหรือยัง)
Clear Flag หลังทำงานเสร็จ - เพื่อใช้แสดงสถานะงานถูกทำเสร็จแล้ว (ไม่สามารถบอกได้ว่างานเริ่มทำหรือยัง)
ตัวอย่างการใช้งาน Flag แสดงดังรูปด้านล่าง
หมายเหตุ. thread = Task
ในบทความนี้จะแนะนำการใช้งาน EventGroup เบื้องต้น โดยใช้บอร์ด ESP32 บนแพลตฟอร์ม Arduino ในการทดสอบสร้าง Task และสื่อสารระหว่างกันด้วย Event Flag
ข้อมูลเกี่ยวกับ Event Group ใน FreeRTOS
Event Group ประกอบด้วย Event Bits (Flag) จำนวน 24 ตัว สามารถ Set และ Clear แยกกันได้อิสระ และ 1 Event Group สามารถเรียกใช้ได้จากหลาย Task โครงสร้างของ Event Group แสดงดังรูปด้านล่าง
การสร้าง Event Group
ใช้คำสั่ง xEventGroupCreate() สร้าง Event Group แบบ Dynamic (ใช้แรมที่เหลือในขณะนั้นสร้าง) หรือสร้างแบบ xEventGroupCreateStatic() (ใช้สงวนไว้ตั้งแต่ตอนคอมไพล์โค้ด)
EventGroupHandle_t xEventGroupCreate();
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer);
พารามิเตอร์: ไม่มี
ค่าที่ส่งกลับ: ข้อมูลชนิด EventGroupHandle_t โดย
หากสร้าง Event Group สำเร็จ - ส่งกลับ handle ของ Event Group นี้ จำเป็นต้องใช้ในคำสั่ง Set หรือ Clear ต่อไป
หากไม่สำเร็จ (แรม ณ ขณะนั้นเหลือไม่พอให้สร้างคิวตามขนาดที่ร้องขอ) - ส่งกลับ NULL
ตัวอย่างการสร้าง Event Group มีดังนี้
#include <Arduino.h>
EventGroupHandle_t xEventGroup; // สร้างตัวแปร xEventGroup ใช้เก็บ handle
void setup() {
xEventGroup = xEventGroupCreate(); // สร้าง Event Group เก็บลงตัวแปร xEventGroup
}
การ Set Flag
ใช้คำสั่ง xEventGroupSetBits() กรณี Set Flag นอกฟังก์ชั่นอินเตอร์รัพท์ หรือ xEventGroupSetBitsFromISR() กรณีต้องการ Set Flag จากในฟังก์ชั่นอินเตอร์รัพท์
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
BaseType_t xEventGroupSetBitsFromISR(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken );
พารามิเตอร์:
(EventGroupHandle_t) xEventGroup - handle ของ Event Group ที่ต้องการจะ Set ค่า
(EventBits_t) uxBitsToSet - บิตที่ต้องการ Set โดยใช้การเลื่อนบิต (shift bit), หากต้องการ Set Flag หลายตัว ให้ใช้ | (or bit) ไปเรื่อย ๆ
ค่าที่ส่งกลับ: (EventBits_t) สถานะของ Flag แต่ละตัวหลังถูก Set แล้ว
ตัวอย่างการ Set Flag มีดังนี้
void loop() {
xEventGroupSetBits(xEventGroup, (1 << 0)); // Set flag บิต 0
// xEventGroupSetBits(xEventGroup, (1 << 5)); // Set flag บิต 5
// xEventGroupSetBits(xEventGroup, (1 << 2) | (1 << 3)); // Set flag บิต 2 และ 3
// xEventGroupSetBits(xEventGroup, BIT2 | BIT3); // Set flag บิต 2 และ 3 (แพลตฟอร์ม Arduino เท่านั้น)
delay(1000);
}
การ Clear Flag
ใช้คำสั่ง xEventGroupClearBits() กรณี Set Flag นอกฟังก์ชั่นอินเตอร์รัพท์ หรือ xEventGroupClearBitsFromISR() กรณีต้องการ Set Flag จากในฟังก์ชั่นอินเตอร์รัพท์
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);
BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);
พารามิเตอร์:
(EventGroupHandle_t) xEventGroup - handle ของ Event Group ที่ต้องการจะ Set ค่า
(EventBits_t) uxBitsToClear - บิตที่ต้องการ Clear โดยใช้การเลื่อนบิต (shift bit), หากต้องการ Clear Flag หลายตัว ให้ใช้ | (or bit) ไปเรื่อย ๆ
ค่าที่ส่งกลับ: (EventBits_t) สถานะของ Flag แต่ละตัวหลังถูก Clear แล้ว
ตัวอย่างการ Clear Flag มีดังนี้
void stop_blink {
xEventGroupClearBits(xEventGroup, (1 << 0)); // Clear flag บิต 0
// xEventGroupClearBits(xEventGroup, (1 << 5)); // Clear flag บิต 5
// xEventGroupClearBits(xEventGroup, (1 << 2) | (1 << 3)); // Clear flag บิต 2 และ 3
// xEventGroupClearBits(xEventGroup, BIT2 | BIT3); // Clear flag บิต 2 และ 3 (แพลตฟอร์ม Arduino เท่านั้น)
}
การดูสถานะ Flag
ใช้คำสั่ง xEventGroupGetBits() กรณี Set Flag นอกฟังก์ชั่นอินเตอร์รัพท์ หรือ xEventGroupGetBitsFromISR() กรณีต้องการดูสถานะ Flag จากในฟังก์ชั่นอินเตอร์รัพท์
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup);
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup);
พารามิเตอร์:
(EventGroupHandle_t) xEventGroup - handle ของ Event Group ที่ต้องการจะอ่านสถานะ
ค่าที่ส่งกลับ: (EventBits_t) สถานะของ Flag ปัจจุบัน
ตัวอย่างการดูสถานะ Flag มีดังนี้
void loop() {
EventBits_t uxBits = xEventGroupGetBits(xEventGroup); // อ่านสถานะ Flag เก็บลงตัวแปร uxBits
if ((uxBits & (1 << 0)) != 0) { // ตรวจสอบว่า Flag ใน Bit ที่ 0 ถูก Set หรือไม่
// if ((uxBits & BIT0) != 0) { // ตรวจสอบว่า Flag ใน Bit ที่ 0 ถูก Set หรือไม่ (แพลตฟอร์ม Arduino เท่านั้น)
// if (bitRead(uxBits, 0) != 0) { // ตรวจสอบว่า Flag ใน Bit ที่ 0 ถูก Set หรือไม่ (แพลตฟอร์ม Arduino เท่านั้น)
Serial.println("Running");
}
delay(100);
}
การรอ Flag ถูก Set
ใช้คำสั่ง xEventGroupWaitBits() (ใช้ในในฟังก์ชั่นอินเตอร์รัพท์ไม่ได้)
EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
พารามิเตอร์:
(EventGroupHandle_t) xEventGroup - handle ของ Event Group
(EventBits_t) uxBitsToWaitFor - บิตที่ต้องการรอให้ถูก Set
(BaseType_t) xClearOnExit - ให้ Clear Flag หลังออกจากคำสั่งนี้หรือไม่ (Clear Flag ทันทีที่ได้รับข้อมูล - เพื่อใช้แสดงสถานะงานถูกเริ่มทำแล้ว) กำหนดได้เป็น
pdTRUE - Clear Flag ทันที
pdFALSE - ให้ Flag ยังอยู่ในสถานะ Set ต่อไป
(BaseType_t) xWaitForAllBits - ให้รอ Flag ทุกตัวถูก Set หรือไม่ กำหนดได้เป็น
pdTRUE - ต้องรอให้ Flag ทุกตัวที่กำหนดใน uxBitsToWaitFor ถูก Set จึงจะออกจากคำสั่งนี้
pdFALSE - ออกจากคำสั่งนี้ทันทีเมื่อมี Flag ตัวใดตัวหนึ่งที่กำหนดใน uxBitsToWaitFor ถูก Set
(TickType_t) xTicksToWait - ระยะเวลานานที่สุดที่จะรอ Flag ถูก Set ในหน่วย Tick
ค่าที่ส่งกลับ: (EventBits_t) สถานะของ Flag ปัจจุบัน
ตัวอย่างการรอให้ Flag ถูก Set แล้วไฟติด 1 วินาที
void loop() {
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup,
(1 << 0), // รอ Flag Bit 0
pdTRUE, // ให้ Clear Flag ทันที
pdTRUE, // รอ Flag ทุกตัวถูก Set
portMAX_DELAY); // รอแบบไม่มีเวลากำหนด (รอตลอดไป)
if ((uxBits & (1 << 0)) != 0) { // ถ้า Flag Bit 0 ถูก Set ให้
digitalWrite(5, HIGH); // ขา 5 ส่งลอจิก 1 (เปิดไฟ)
delay(1000); // หน่วงเวลา 1 วินาที
digitalWrite(5, LOW); // ขา 5 ส่งลอจิก 0 (ปิดไฟ)
}
}
การลบ EventGroup
ใช้คำสั่ง vEventGroupDelete() ลบ EventGroup ที่สร้างจาก xEventGroupCreate() ... อย่างไรก็ตาม การใช้งานจริงมักไม่มีการลบ Event Group เนื่องจาก Event Group มีการแชร์การใช้งานในหลาย Task อาจมีโอกาศที่ Task หนึ่งสั่งลบ Event Group ไปแล้ว แต่ Task อื่น ๆ ยังพยามอ่านค่าจาก Event Group ที่ถูกลบไปแล้วอยู่ ซึ่งจะทำให้ระบบโดยรวมทำงานไม่ถูกต้องได้
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
พารามิเตอร์:
(EventGroupHandle_t) xEventGroup - handle ของ Event Group ที่ต้องการลบ
ค่าที่ส่งกลับ: ไม่มี
ตัวอย่างการใช้งาน Event Group (Flag)
แสดงข้อความ Hello !!! แบบไม่บล็อกการทำงาน
ตัวอย่างนี้เป็นการใช้ Event Group โดยสั่ง Set Flag จากใน void loop() แล้ว Task ชื่อ hello_task มีหน้าที่รอ Set Flag โดยเมื่อ Flag Set แล้ว จะแสดงข้อความ Hello !!! ออกมาที่ Serial Monitor
#include <Arduino.h>
EventGroupHandle_t xEventGroup; // สร้างตัวแปร xEventGroup ใช้เก็บ handle
void hello_task(void*) ;
void setup() {
Serial.begin(115200);
xEventGroup = xEventGroupCreate(); // สร้าง Event Group เก็บลงตัวแปร xEventGroup
xTaskCreate(hello_task, "hello_task", 1024, NULL, 20, NULL); // สร้าง Task ใหม่ ใช้รอรับ Flag จากใน void loop()
}
void loop() {
xEventGroupSetBits(xEventGroup, BIT0); // Set Flag บิต 0
delay(3000);
}
void hello_task(void*) {
for(;;) {
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup,
BIT0, // รอ Flag Bit 0
pdTRUE, // ให้ Clear Flag ทันที
pdTRUE, // รอ Flag ทุกตัวถูก Set
portMAX_DELAY); // รอแบบไม่มีเวลากำหนด (รอตลอดไป)
if ((uxBits & (1 << 0)) != 0) { // ถ้า Flag Bit 0 ถูก Set ให้
Serial.println("Hello !!!"); // แสดงข้อความ Hello !!! ทาง Serial Monitor
}
}
vTaskDelete(NULL);
}
จากตัวอย่างสามารถนำไปประยุกต์ใช้งานได้หลากหลาย เช่น โค้ดโปรแกรมที่ใช้ส่งการแจ้งเตือนทางไลน์เป็นแบบบล็อกการทำงาน ส่งผลให้เมื่อนำมาเรียกใช้ใน void loop() ทำให้งานอื่น ๆ ที่อยู่ใน void loop() ต้องหยุดรอไปด้วย การแก้ไขคือแยกโค้ดโปรแกรมส่งไลน์ไป Task ใหม่ แล้วเมื่อต้องการส่งไลน์ก็ใช้การ Set Flag เพื่อสื่อสารไปยัง Task ใหม่แทน
ไฟกระพริบสั่งงานจาก ISR
คำสั่งที่ใส่ลงบน Interrupt Service Routine (ISR) หรือฟังก์ชั่นอินเตอร์รัพท์ ไม่สามารถใส่คำสั่งที่ทำงานนาน ๆ ได้ เช่น การวนรอบรอนาน ๆ หรือการใช้คำสั่ง Delay เนื่องจากฟังก์ชั่นอินเตอร์รัพท์ควรถูกทำงานได้เร็วที่สุดเพื่อไม่ให้เกิด Interrupt ซ้อน
ตัวอย่างนี้เป็นการใช้ Event Group โดยสั่ง Set Flag จากในฟังก์ชั่นอินเตอร์รัพท์ จากนั้นใน void loop() เมื่อได้รับสถานะ Flag ถูก Set จึงสั่งเปิดไฟเป็นเวลา 1 วินาที โดยโค้ดโปรแกรมมีดังนี้
#include <Arduino.h>
#define SWITCH_PIN (12)
#define LED_PIN (5)
EventGroupHandle_t xEventGroup; // สร้างตัวแปร xEventGroup ใช้เก็บ handle
void switch_press_handle_isr() {
xEventGroupSetBitsFromISR(xEventGroup, BIT0, NULL); // Set Flag บิต 0
}
void setup() {
Serial.begin(115200);
xEventGroup = xEventGroupCreate(); // สร้าง Event Group เก็บลงตัวแปร xEventGroup
pinMode(LED_PIN, OUTPUT);
pinMode(SWITCH_PIN, INPUT_PULLUP);
attachInterrupt(SWITCH_PIN, switch_press_handle_isr, FALLING);
}
void loop() {
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup,
BIT0, // รอ Flag Bit 0
pdTRUE, // ให้ Clear Flag ทันที
pdTRUE, // รอ Flag ทุกตัวถูก Set
portMAX_DELAY); // รอแบบไม่มีเวลากำหนด (รอตลอดไป)
if ((uxBits & (1 << 0)) != 0) { // ถ้า Flag Bit 0 ถูก Set ให้
Serial.println("Turn on LED");
digitalWrite(LED_PIN, HIGH); // เปิดไฟ
delay(1000); // หน่วงเวลา 1 วินาที
digitalWrite(LED_PIN, LOW); // ปิดไฟ
Serial.println("Turn off LED");
}
}
จากตัวอย่างสามารถนำไปประยุกต์ใช้งานได้หลากหลาย เช่น เซ็นเซอร์ I2C มักมีขา INT (Interrupt) มาให้ เมื่อเซ็นเซอร์พร้อมให้อ่านค่า ขา INT จะเปลี่ยนลอจิก ที่ฝั่งไมโครคอนโทรลเลอร์สามารถใช้ Interrupt ร่วมกับ Event Flag ไปสั่งให้ Task เข้าไปอ่านข้อมูลจากเซ็นเซอร์ตามขั้นตอนได้
การสั่งอ่านค่าเซ็นเซอร์แบบพร้อมกัน
ปกติแล้วการเขียนโปรแกรมอ่านค่าจากเซ็นเซอร์มักเขียนในรูปแบบ Sequence คืออ่านค่าของเซ็นเซอร์ออกมาทีละตัว เรียงลำดับกันไป ในขณะที่การอ่านค่าจากเซ็นเซอร์แต่ละตัวมักจะติด Delay เล็กน้อยด้วย เนื่องจากเซ็นเซอร์ต่าง ๆ มักจะต้องใช้เวลาประมาณหนึ่งในการแปลงค่าสัญญาณจากทรานสดิวเซอร์ (Transducer) เป็นสัญญาณดิจิทัล จากนั้นคำนวณเทียบค่าให้ถูกต้อง แล้วค่อยแจ้งไมโครคอนโทรลเลอร์ แต่หากต้องการให้อ่านค่าพร้อมกันในเวลาที่ใกล้เคียงกันสามารถทำได้โดยใช้การแยกทุกเซ็นเซอร์ให้อยู่คนละ Task แล้วค่อยแจ้งให้ใน Task หลักทราบว่าอ่านค่าเสร็จแล้วผ่าน Event Flag
ตัวอย่างนี้สร้าง Task ขึ้นมา 3 Task คือ sensor1_read, sensor2_read และ sensor3_read โดยทั้ง 3 Task ถูกควบคุมการเริ่มทำงานผ่าน Event Flag บิตที่ 0, 1, 2 ตามลำดับ โดยเมื่อมีการ Set Flag จากใน void loop() โปรแกรมใน Task sensor จะเริ่มอ่านค่าเซ็นเซอร์ทันที เมื่ออ่านค่าเสร็จจึง Clear Flag เพื่อส่งสัญญาณไปบอก void loop() ว่าอ่านค่าจากเซ็นเซอร์เสร็จแล้ว พร้อมให้นำค่าไปใช้งานต่อ
#include <Arduino.h>
EventGroupHandle_t xEventGroup; // สร้างตัวแปร xEventGroup ใช้เก็บ handle
#define SENSOR1_ENABLE_FLAG (1 << 0)
#define SENSOR2_ENABLE_FLAG (1 << 1)
#define SENSOR3_ENABLE_FLAG (1 << 2)
void sensor1_task(void*) ;
void sensor2_task(void*) ;
void sensor3_task(void*) ;
float sensor1_value; // ตัวแปรเก็บค่าที่อ่านได้จากเซ็นเซอร์ 1
float sensor2_value; // ตัวแปรเก็บค่าที่อ่านได้จากเซ็นเซอร์ 2
float sensor3_value; // ตัวแปรเก็บค่าที่อ่านได้จากเซ็นเซอร์ 3
void setup() {
Serial.begin(115200);
xEventGroup = xEventGroupCreate(); // สร้าง Event Group เก็บลงตัวแปร xEventGroup
xTaskCreate(sensor1_task, "sensor1_task", 1024, NULL, 20, NULL); // สร้าง Task sensor1
xTaskCreate(sensor2_task, "sensor2_task", 1024, NULL, 20, NULL); // สร้าง Task sensor2
xTaskCreate(sensor3_task, "sensor3_task", 1024, NULL, 20, NULL); // สร้าง Task sensor3
}
void loop() {
Serial.printf("Start read all sensor at %ld\n", millis());
xEventGroupSetBits(xEventGroup, SENSOR1_ENABLE_FLAG | SENSOR2_ENABLE_FLAG | SENSOR3_ENABLE_FLAG); // Set Flag เพื่อให้ Task sensor1..3 เริ่มทำงาน
while (1) {
EventBits_t uxBits = xEventGroupGetBits(xEventGroup); // อ่านสถานะ Flag เก็บลงตัวแปร uxBits
if ((uxBits & (SENSOR1_ENABLE_FLAG | SENSOR2_ENABLE_FLAG | SENSOR3_ENABLE_FLAG)) == 0) {
break;
}
delay(10);
}
Serial.printf("All sensor done at %ld\n", millis());
Serial.printf("Sensor value [%.2f, %.2f, %.2f]\n", sensor1_value, sensor2_value, sensor3_value);
delay(3000);
}
void sensor1_task(void*) {
for(;;) {
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup,
SENSOR1_ENABLE_FLAG, // รอ SENSOR1_ENABLE_FLAG
pdFALSE, // ไม่ให้ Clear Flag
pdTRUE, // รอ Flag ทุกตัวถูก Set
portMAX_DELAY); // รอแบบไม่มีเวลากำหนด (รอตลอดไป)
if ((uxBits & SENSOR1_ENABLE_FLAG) != 0) { // ถ้า Flag ถูก Set ให้
// ---- จำลองสถานการณ์อ่านค่าเซ็นเซอร์โดยสุ่มค่าเวลาที่ใช้
int random_delay_ms = random(0, 500);
delay(random_delay_ms);
sensor1_value = random(0, 10000) / 100.0f; // จำลองใส่ค่าที่อ่านได้จากเซ็นเซอร์ลงตัวแปรโดยการสุ่มค่า
xEventGroupClearBits(xEventGroup, SENSOR1_ENABLE_FLAG); // อ่านค่าจากเซ็นเซอร์เสร็จจึงสั่ง Clear Flag เพื่อบอกใน void loop() ว่าทำงานเสร็จแล้ว
}
}
vTaskDelete(NULL);
}
void sensor2_task(void*) {
for(;;) {
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup,
SENSOR2_ENABLE_FLAG, // รอ SENSOR2_ENABLE_FLAG
pdFALSE, // ไม่ให้ Clear Flag
pdTRUE, // รอ Flag ทุกตัวถูก Set
portMAX_DELAY); // รอแบบไม่มีเวลากำหนด (รอตลอดไป)
if ((uxBits & SENSOR2_ENABLE_FLAG) != 0) { // ถ้า Flag ถูก Set ให้
// ---- จำลองสถานการณ์อ่านค่าเซ็นเซอร์โดยสุ่มค่าเวลาที่ใช้
int random_delay_ms = random(0, 500);
delay(random_delay_ms);
sensor2_value = random(0, 10000) / 100.0f; // จำลองใส่ค่าที่อ่านได้จากเซ็นเซอร์ลงตัวแปรโดยการสุ่มค่า
xEventGroupClearBits(xEventGroup, SENSOR2_ENABLE_FLAG); // อ่านค่าจากเซ็นเซอร์เสร็จจึงสั่ง Clear Flag เพื่อบอกใน void loop() ว่าทำงานเสร็จแล้ว
}
}
vTaskDelete(NULL);
}
void sensor3_task(void*) {
for(;;) {
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup,
SENSOR3_ENABLE_FLAG, // รอ SENSOR3_ENABLE_FLAG
pdFALSE, // ไม่ให้ Clear Flag
pdTRUE, // รอ Flag ทุกตัวถูก Set
portMAX_DELAY); // รอแบบไม่มีเวลากำหนด (รอตลอดไป)
if ((uxBits & SENSOR3_ENABLE_FLAG) != 0) { // ถ้า Flag ถูก Set ให้
// ---- จำลองสถานการณ์อ่านค่าเซ็นเซอร์โดยสุ่มค่าเวลาที่ใช้
int random_delay_ms = random(0, 500);
delay(random_delay_ms);
sensor3_value = random(0, 10000) / 100.0f; // จำลองใส่ค่าที่อ่านได้จากเซ็นเซอร์ลงตัวแปรโดยการสุ่มค่า
xEventGroupClearBits(xEventGroup, SENSOR3_ENABLE_FLAG); // อ่านค่าจากเซ็นเซอร์เสร็จจึงสั่ง Clear Flag เพื่อบอกใน void loop() ว่าทำงานเสร็จแล้ว
}
}
vTaskDelete(NULL);
}
จากตัวอย่างสามารถนำไปแก้ไขโค้ดโปรแกรมส่วนจำลองอ่านค่าเซ็นเซอร์ เป็นโค้ดโปรแกรมอ่านค่าเซ็นเซอร์จริง ๆ ได้เลย
หมายเหตุ. กรณีเซ็นเซอร์ใช้ทรัพยากรร่วมกัน เช่น เซ็นเซอร์ทั้ง 3 Task ใช้ I2C จำเป็นต้องใช้ Mutexes เข้ามาป้องกันการใช้งานทรัพยากรพร้อมกันด้วย โดยจะมีสอนการใช้งาน Mutexes ในบทความถัดไป
Comments