ผ่านกันไป 2 ตอนกับ FreeRTOS ก็เริ่มรู้จักและพอจะนำไปใช้งานกันได้บ้างแล้วนะครับ สำหรับในตอนที่ 3 นี้ ก็จะต่อเนื่องกับตอนที่ 2 โดยจะนำโปรแกรมในตอนที่ 2 ซึ่งเราได้ลดฟังก์ชันเหลือเพียงฟังก์ชันเดียว ทำให้โปรแกรมสั้นลง แต่มีบางอย่างที่หายไป คือ แต่เดิมไฟแต่ละดวง กระพริบด้วยอัตราที่ไม่เท่ากัน แต่ในโปรแกรมในตอนที่ 2 ไฟทุกดวงกระพริบด้วยอัตราเดียวกัน เนื่องจากเราส่งพารามิเตอร์ไปที่ Task เพียงพารามิเตอร์เดียว

การส่งพารามิเตอร์หลายตัวไปที่ Task

ดังนั้นในตอนนี้ เราจะแก้ไขโปรแกรมเพื่อให้สามารถส่งพารามิเตอร์ไปยัง Task ได้มากกว่า 1 ตัว เพื่อไม่ให้เป็นการเสียเวลา ก็ดูโปรแกรมเลยก็แล้วกัน

ในโปรแกรมนี้ ในบรรทัดที่ 11 เราสร้างอาเรย์ 2 มิติขนาด 3x2 ขึ้นมาตัวหนึ่ง โดย 3 หมายถึง LED จำนวน 3 ดวง และ 2 หมายถึง 2 พารามิเตอร์ (กรณีที่พารามิเตอร์ที่ส่งเป็นคนละชนิดกัน ต้องเปลี่ยนเป็น Array of Structure) โดยพารามิเตอร์ตัวแรกคือ LED Pin และพารามิเตอร์ตัวที่ 2 คือ ค่า delay

จากนั้นในบรรทัดที่ 14–16 ก็จะส่งพารามิเตอร์ โดยส่งตำแหน่งของ taskParam[0] หรือ taskParam[1] หรือ taskParam[2] ไป ซึ่งจะเสมือนกับเป็น Array 1 มิติไป เมื่อ Task Function รับพารามิเตอร์ไปก็สามารถอ้างเหมือน Array 1 มิติได้เลย โดยเพื่อให้เข้าใจง่าย ผมจึงถ่ายมาที่ตัวแปรอีกที ในบรรทัดที่ 23–24

โปรแกรมนี้เมื่อทำงาน ก็จะสามารถกำหนดทั้ง LED Pin และ เวลาที่ใช้ในการ delay ให้กับแต่ละ Task ได้ตามที่ต้องการ

การ Delay ใน Multitask

ในตอนที่ 2 ผมได้กล่าวถึงการกำหนดพารามิเตอร์ของ Task ซึ่งจะมีเฉพาะ Task ที่มี Priority สูงกว่าเท่านั้น ที่จะได้รับ CPU Time และทำงานได้ ซึ่ง Task ที่มี Priority ต่ำกว่า จะไม่ได้ทำงานเลย ซึ่งอาจทำให้หลายคนสงสัยว่า อ้าว! แล้วอย่างนี้ Task อื่นๆ จะทำงานได้อย่างไร

เพื่อที่จะเข้าใจเรื่องนี้ ผมจะขออธิบายวงจรของ Task ก่อน โดยมีรายละเอียดดังรูปข้างล่าง

จากหนังสือ Mastering The FreeRTOS Real Time Kernel

จากรูป เมื่อเราสร้าง Task ขึ้นมาทุก Task จะไปเริ่มอยู่ที่สถานะ Ready จากนั้น RTOS จะเลือกเอา Task หนึ่งขึ้นมาทำงาน Task นั้นก็จะทำงานและอยู่ในสถานะ Running (ซึ่งหมายความว่าในเวลาใดเวลาหนึ่ง จะมีเพียง Task เดียวที่อยู่ใน Running และทำงานอยู่) โดยตัวที่ทำหน้าที่เลือก Task ขึ้นมาทำงาน จะมีชื่อว่า Scheduler ซึ่งจะมีวิธีการดังนี้

  • ถ้ามี Task ที่มี Priority สูงกว่า จะเลือก Task นั้น
  • ถ้ามีหลาย Task ที่มี Priority เท่ากัน จะใช้วิธีวนเลือก (วิธีการนี้ทางคอมพิวเตอร์ เรียกว่า Round Robin) เช่น สมมติว่ามี Task A, B, C ก็จะวนเลือก A, B, C ดังนั้นทุก Task จะได้รับการทำงานเท่ากัน

สำหรับสถานะ Suspend นั้นจะเกิดขึ้นจากการกำหนดของโปรแกรม โดยการเรียกฟังก์ชัน vTaskSuspend ซึ่งจะทำให้ Task นั้นถูกแขวนเอาไว้ก่อน จะไม่สามารถทำงานได้ และจะกลับมาทำงานต่อได้เมื่อเรียกฟังก์ชัน vTaskResume

สำหรับสถานะสุดท้าย คือ สถานะ Blocked นั้น เกิดจากการที่ Task นั้นต้องมีการรอทำงาน เช่น การรอ Input ซึ่งในขณะที่รอ Input นั้น Task นั้นก็ทำอะไรไม่ได้อยู่ดี จึงถูกจับมาไว้ในสถานะ Blocked โดยเมื่อได้รับ Input แล้ว ก็จะถูกย้ายกลับมาที่สถานะ Ready เพื่อทำงานต่อไป

ก็กลับมาที่เรื่องของการ Delay กันต่อ ก็ขอยก Code เดิมมาแสดง

while (1)
{
delay(1000);
digitalWrite(pvParameters, digitalRead(pvParameters) ^ 1);
}

จากหลักการที่ว่า Scheduler จะเลือก Task ที่มีค่า Priority สูงสุด ซึ่งในท้ายตอนที่ 2 เรากำหนดให้เฉพาะ Task ของ LED สีแดงมีค่าเป็น 2 ดังนั้นจะมีเฉพาะ Task สีแดงที่ได้รับ CPU Time ดังนั้นไฟ LED สีแดงจึงกระพริบอยู่ดวงเดียว ทั้งที่ในความเป็นจริงแล้ว ในโปรแกรม 2 บรรทัดนี้ คำสั่งที่สั่งให้ LED กระพริบจะทำงานนิดเดียวเท่านั้น เวลาส่วนใหญ่จะเสียไปกับคำสั่ง delay ซึ่งการทำงานของคำสั่ง delay นี้ ก็เป็นการรันลูปว่างๆ เพื่อให้เสียเวลาไปเปล่าๆ ไม่เกิดประโยชน์อะไร

ดังนั้นใน FreeRTOS จึงได้สร้างคำสั่ง vTaskDelay ขึ้นมาใช้แทน โดยคำสั่ง vTaskDelay จะทำงานไม่เหมือนกับคำสั่ง delay ปกติ โดยคำสั่ง vTaskDelay จะทำให้ Task นั้นย้ายไปอยู่ในสถานะ Blocked เป็นระยะเวลาที่กำหนดในพารามิเตอร์ ดังนั้นในระหว่างนั้น Scheduler สามารถจะเลือก Task อื่นขึ้นมาทำงานได้ ทำให้แม้จะตั้งค่า Priority ไม่เท่ากัน แต่ทุก Task ก็ยังทำงานอยู่ ตามโปรแกรมข้างล่างนี้ ที่ไฟทุกดวงยังคงกระพริบทั้งหมด

พารามิเตอร์ที่ใช้ในฟังก์ชัน vTaskDelay จะเป็นเวลาที่ให้หยุดรอ โดยจะมีค่าเท่ากับ 10 ms ดังนั้นค่า 100 = 1000 ms หรือ 1 วินาที

การเปลี่ยน Priority ของ Task

ในโปรแกรมข้างต้นจะคล้ายกับว่า Priority ไม่มีความสำคัญ แต่อันที่จริงแล้วไม่ใช่ เพราะหากใน Task นั้น ไม่มีการใช้ vTaskDelay หรือไม่มีการรอ Input แล้ว Task ที่มีความสำคัญสูงสุด ก็จะทำงานอยู่ Task เดียว และการที่จะเปลี่ยนการทำงานในกรณีนี้ จะมีวิธีเดียว คือการเปลี่ยนค่า Priority ของ Task เพื่อให้งานอื่นๆ ขึ้นมาทำงานได้

และการจะทำเช่นนั้นได้ ก็จะต้องใช้พารามิเตอร์ตัวที่ 6 ( *pxCreatedTask) หรือ Task Handle ครับ! เราจะได้รู้กันซักทีว่าพารามิเตอร์ตัวนี้ใช้สำหรับทำอะไร

พารามิเตอร์ *pxCreatedTask นั้นเป็นพารามิเตอร์แบบส่งกลับ คือ เมื่อเรียกใช้ฟังก์ชัน xTaskCreate แล้วฟังก์ชันจะส่งกลับข้อมูลชุดหนึ่ง ที่ทำหน้าที่เป็นตัวชี้ไปยังฟังก์ชัน (Pointer) โดยเราจะต้องกำหนดตัวแปรขึ้นมารับดังนี้

TaskHandle_t blueHandle;

โดย TaskHanfle_t นั้นเป็นชนิดข้อมูล (Data Type) ของตัวแปรที่ใช้รับ Pointer ที่ชี้ไปยังแต่ละ Task โดยการใช้ตัวแปรที่จะรับ Pointer มีตัวอย่างการใช้ดังนี้

xTaskCreate(ฺblueLedTask, “BLUE LED Task”, 128, NULL, 1, &blueHandle);

หลังจากที่เรียกฟังก์ชัน xTaskCreate แล้ว ฟังก์ชันจะส่งคืน Pointer ที่ใช้ไปยัง Task blueLedTask คืนมาให้ ซึ่งเราจะใช้ตัวแปร Pointer นี้ในการอ้างอิง blueLedTask ได้ เช่น หากเราจะเปลี่ยนค่า Priority ของ blueLedTask เราสามารถอ้างถึง Task ได้ดังนี้

vTaskPrioritySet(blueHandle,2);

เมื่อเรียกใช้ฟังก์ชันดังกล่าว ค่า Priority ของ blueLedTask จะเปลี่ยนเป็น 2 และหากต้องการตรวจสอบค่า Priority ของ Task สามารถใช้ฟังก์ชัน

blueTaskPriority = uxTaskPriorityGet(blue_Handle);

ลองดูโปรแกรมต่อไปนี้ (โปรแกรมนี้จะย้อนกลับมาใช้ delay เพื่อให้เห็นผลของ Priority)

โปรแกรมข้างต้นจะสร้าง Task ขึ้นมา 3 Task โดยแต่ละ Task จะกำหนดให้กระพริบ LED แต่ละสี โดยเริ่มต้นจะกำหนดให้ Task ของ LED สีแดงมีระดับความสำคัญสูงที่สุด ดังนั้น LED สีแดง จะกระพริบอยู่ดวงเดียว โดยเราได้เพิ่มเงื่อนไขเข้าไป โดยสร้างตัวนับขึ้นมาในแต่ละ Task โดยเมื่อนับครบ 10 แล้ว ก็ให้กำหนด Priority ของ Task ปัจจุบันลดลงเป็น 1 และกำหนดให้ Task ของ LED สีถัดไป เพิ่มขึ้นเป็น 2 ซึ่งจะเป็นผลให้ Task ของ LED สีถัดไปขึ้นมาเป็น Active Task และไฟ LED สีถัดไปก็จะกระพริบแทน และจะวนเช่นนี้ไปเรื่อยใน LED 3 สี

ผลที่เกิดขึ้นก็คือ ไฟ LED สีแดงจะกระพริบก่อนจากนั้นจะหยุด จากนั้นไฟ LED สีน้ำเงินก็จะกระพริบและจะหยุดเช่นกัน และไฟ LED สีเหลืองก็จะกระพริบ เมื่อครบแล้วก็จะหยุดและไฟ LED สีแดงจะกระพริบแทน

จะเห็นว่าด้วยการกำหนดระดับความสำคัญของ Task ตามตัวอย่างนี้ จะทำให้เราสามารถควบคุมการทำงานของ Task ได้

ฟังก์ชันที่เกี่ยวกับ Task ที่น่าสนใจยังมีอีก เช่น

vTaskSuspend(blueHandle);

เป็นฟังก์ชันที่ใช้หยุดการทำงานของ Task ชั่วคราว ซึ่งเราสามารถใช้หยุดการทำงานของ Task ใดๆ ชั่วคราวได้ เช่น กดปุ่มแล้วงานนี้หยุดชั่วคราว เป็นต้น สำหรับฟังก์ชันที่ให้ Task ทำงานต่อจะใช้ฟังก์ชันดังนี้ (ซึ่งทำให้เราสามารถควบคุมการทำงานของ Task ได้)

vTaskResume(blueHandle);

ก็ขอจบ FreeRTOS ตอนที่ 3 เพียงเท่านี้ หวังว่าจะเป็นประโยชน์สำหรับผู้ที่สนใจการเขียนโปรแกรมระดับสูง

สำหรับตอนต่อไปจะพูดถึงเรื่องของ Queue ขอให้ติดตามกันต่อไปนะครับ

--

--

Thana Hongsuwan
Thana Hongsuwan

Written by Thana Hongsuwan

Maker สมัครเล่น สนใจเทคโนโลยีด้าน Hardware เช่น Arduino, ESP8266, ESP32, Internet of Things, Raspberry P, Deep Learning

Responses (1)