Changes between Version 5 and Version 6 of ESP32
- Timestamp:
- 06/12/21 09:29:54 (3 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
ESP32
v5 v6 18 18 19 19 RTOS มีวิธีจัดการ Task ต่างๆ โดยการดูระดับความสำคัญ ( Priority ) ซึ่ง Task ที่มีระดับความสำคัญสูงสุดจะได้ทำงานก่อน และใช้ Time slicing ในการแบ่งเวลากันทำงาน ซึ่งทำให้ ESP32 สามารถทำงานคล้ายแบบ Multi-tasking ได้ 20 20 21 Time slicing 21 22 … … 28 29 ในช่วงที่ฟังก์ชัน Delay() ทำงาน ช่วงนั้น CPU จะหยุดการประมวลผล ซึ่งหาก Delay มีค่าที่ตั้งไว้นาน จะเกิดช่วงเวลาที่สูญเปล่าเพราะไม่ได้ทำงานฟังก์ชันอื่นต่อเนื่องเลย 29 30 30 แต่ถ้าเราเปลี่ยนไปเขียนโปรแกรมแบบข้อ 2 ( เขียนเป็น Task โดยใช้ RTOS ) และใช้ vTaskDelay() แทน เมื่อ Taskอ่านค่าSensor ทำงานเสร็จ Taskส่งSMS ก็จะทำงานต่อทันที และระหว่างช่วงที่ Taskส่งSMS ถูก Blocked รอทำงานให้เสร็จ Taskอ่านค่าsensor ก็สามารถทำงานวนรอไปเรื่อยๆได้ เนื่องจาก vTaskDelay() จะหยุดการทำงานของ Task ที่ระดับ Software จึงทำให้ CPU ประมวลผล Task อื่นต่อเนื่องได้ 31 Task 31 แต่ถ้าเราเปลี่ยนไปเขียนโปรแกรมแบบข้อ 2 ( เขียนเป็น Task โดยใช้ RTOS ) และใช้ `vTaskDelay()` แทน เมื่อ Taskอ่านค่าSensor ทำงานเสร็จ Taskส่งSMS ก็จะทำงานต่อทันที และระหว่างช่วงที่ Taskส่งSMS ถูก Blocked รอทำงานให้เสร็จ Taskอ่านค่าsensor ก็สามารถทำงานวนรอไปเรื่อยๆได้ เนื่องจาก `vTaskDelay()` จะหยุดการทำงานของ Task ที่ระดับ Software จึงทำให้ CPU ประมวลผล Task อื่นต่อเนื่องได้ 32 33 == Task == 32 34 33 35 การสร้าง Task ใน RTOS เราจะเขียน function ที่เราต้องการให้ทำงานไว้ก่อน แล้วก็สร้าง Task เพื่อไปเรียก function มาใช้งานอีกที … … 45 47 มาดูตัวแปรที่ใช้ตอนสร้าง Task ทีละตัวกัน 46 48 {{{ 47 TaskFunction : ชื่อฟังก์ชันที่จะให้ทำงานเป็น Task ซึ่งในฟังก์ชันนั้นควรจะเขียนโปรแกรมแบบทำงานวนลูป( infinite loop )48 TaskName : ชื่อของ Task ( ข้อมูลตรงส่วนนี้จะใช้ตอน Debug ไม่มีผลต่อการทำงานของโปรแกรมโดยตรง )49 StackDepth : ขนาด Stack ของ Task (เพื่อจอง memory) การกำหนดขนาดของ Task ถ้ากำหนดไว้น้อยเกินไปจะทำให้ ESP32 Restart ตัวเองตลอดเวลา แต่ถ้ากำหนดไว้มากไปก็ทำให้เสีย memory ทิ้งเปล่าๆ การกำหนดค่านี้แบบคร่าวๆ คือ ลอง complie ดูขนาดของ function ที่ผูกกับ Task นี้ ลองดูจำนวน Byte ที่ใช้ไปครับ แล้วเอามาใส่ในตัวแปรนี้50 PassParameter : ชื่อตัวแปรที่จะส่งค่าเข้ามาทำงานต่อใน Task ( ดูวิธีการทำงานใน code ตัวอย่างเพิ่มเติมครับ )51 TaskPriority : กำหนดเลข Priority ให้ Task ซึ่งค่า 0 คือ Priority ที่ต่ำที่สุด52 TaskHandle : ชื่อตัวแปรของ Task ที่จะนำไปใช้ในการ Handle ทำงานอื่นๆต่อ ( ดูวิธีการทำงานใน code ตัวอย่างเพิ่มเติมครับ )49 1. TaskFunction : ชื่อฟังก์ชันที่จะให้ทำงานเป็น Task ซึ่งในฟังก์ชันนั้นควรจะเขียนโปรแกรมแบบทำงานวนลูป( infinite loop ) 50 2. TaskName : ชื่อของ Task ( ข้อมูลตรงส่วนนี้จะใช้ตอน Debug ไม่มีผลต่อการทำงานของโปรแกรมโดยตรง ) 51 3. StackDepth : ขนาด Stack ของ Task (เพื่อจอง memory) การกำหนดขนาดของ Task ถ้ากำหนดไว้น้อยเกินไปจะทำให้ ESP32 Restart ตัวเองตลอดเวลา แต่ถ้ากำหนดไว้มากไปก็ทำให้เสีย memory ทิ้งเปล่าๆ การกำหนดค่านี้แบบคร่าวๆ คือ ลอง complie ดูขนาดของ function ที่ผูกกับ Task นี้ ลองดูจำนวน Byte ที่ใช้ไปครับ แล้วเอามาใส่ในตัวแปรนี้ 52 4. PassParameter : ชื่อตัวแปรที่จะส่งค่าเข้ามาทำงานต่อใน Task ( ดูวิธีการทำงานใน code ตัวอย่างเพิ่มเติมครับ ) 53 5. TaskPriority : กำหนดเลข Priority ให้ Task ซึ่งค่า 0 คือ Priority ที่ต่ำที่สุด 54 6. TaskHandle : ชื่อตัวแปรของ Task ที่จะนำไปใช้ในการ Handle ทำงานอื่นๆต่อ ( ดูวิธีการทำงานใน code ตัวอย่างเพิ่มเติมครับ ) 53 55 }}} 54 56 แต่ถ้าอยากกำหนดให้ลึกว่า CPU ใหนจะทำงาน Task นั้นๆ ให้ประกาศฟังก์ชันตามรูปแบบนี้ 55 {{{ 56 xTaskCreatePinnedToCore(TaskFunction,TaskName,StackDepth,(void*)PassParameters,TaskPriority,TaskHandle,Core) 57 }}} 57 xTaskCreatePinnedToCore(TaskFunction,TaskName,StackDepth,(void*)PassParameters,TaskPriority,TaskHandle,Core) 58 59 {{{ 58 60 7. Core : สำหรับ ESP เราสามารถกำหนดให้ Task ทำงานที่ CPU 0 หรือ 1 59 61 }}} 60 62 ลองมาดู code ตัวอย่างการสร้าง Task เพื่อความเข้าใจกันครับ โดยตัวอย่างนี้ เราจะเขียนโปรแกรมสร้าง Task ในบอร์ด ESP32 เพื่อปริ้นดูการทำงานของแต่ละ Task ผ่าน Serial port 61 63 … … 64 66 const TickType_t xDelay2000ms = pdMS_TO_TICKS(2000); 65 67 }}} 66 ฟังก์ชัน vTaskDelay()จะรับค่า Tick ซึ่งเป็นค่า Timer ของ CPU ดังนั้นเราจึงต้องแปลงค่า เวลา(หน่วยมิลลิวินาที)ให้เป็นค่า Tick ก่อน จึงนำมาใช้งานได้67 68 เริ่มต้น เราจึงสร้างตัวแปรมาเก็บค่า Tick เพื่อใช้ในฟังก์ชั้น vTaskDelay(…) โดยเรียกใช้ฟังก์ชัน pdMS_TO_TICKS(…) ที่จะแปลงตัวเลขหน่วยมิลลิวินาทีเป็นจำนวน Tick (ในตัวอย่างนี้คือ xDelay2000msเก็บค่า Tick ของ 2 วินาทีไว้ )68 ฟังก์ชัน `vTaskDelay()` จะรับค่า Tick ซึ่งเป็นค่า Timer ของ CPU ดังนั้นเราจึงต้องแปลงค่า เวลา(หน่วยมิลลิวินาที)ให้เป็นค่า Tick ก่อน จึงนำมาใช้งานได้ 69 70 เริ่มต้น เราจึงสร้างตัวแปรมาเก็บค่า Tick เพื่อใช้ในฟังก์ชั้น `vTaskDelay(…)` โดยเรียกใช้ฟังก์ชัน `pdMS_TO_TICKS(…)` ที่จะแปลงตัวเลขหน่วยมิลลิวินาทีเป็นจำนวน Tick (ในตัวอย่างนี้คือ `xDelay2000ms` เก็บค่า Tick ของ 2 วินาทีไว้ ) 69 71 {{{ 70 72 TaskHandle_t Task1 = NULL; … … 87 89 } 88 90 }}} 89 มาถึงส่วนของฟังก์ชั่น setup(…) เราจะประกาศ Created Task กันในฟังก์ชันนี้ โดยใช้คำสั่ง xTaskCreatePinnedToCore(…)91 มาถึงส่วนของฟังก์ชั่น setup(…) เราจะประกาศ Created Task กันในฟังก์ชันนี้ โดยใช้คำสั่ง `xTaskCreatePinnedToCore(…)` 90 92 {{{ 91 93 Task ที่ 1 : ผูกกับฟังก์ชั่น func1_Task() , ตั้งชื่อ Task ว่า “func1_Task”,DepthStack = 1000 ,ส่งตัวแปร PassValue เข้าไปใน Task นี้ด้วย , มีค่า Priority = 1 , ผูกกับ TaskHandle ชื่อว่า Task1 และให้ Task นี้ทำงานอยู่บน Core 0 … … 107 109 } 108 110 }}} 109 ในส่วนของ func1_Task()ที่ผูกกับ Task1 ไว้110 111 สร้างตัวแปร int ชื่อ f1param ไว้เก็บค่า pvvalueที่ส่งเข้ามา(แปลง type ให้เป็น int ไว้แล้ว )111 ในส่วนของ `func1_Task()` ที่ผูกกับ Task1 ไว้ 112 113 สร้างตัวแปร int ชื่อ f1param ไว้เก็บค่า `pvvalue` ที่ส่งเข้ามา(แปลง type ให้เป็น int ไว้แล้ว ) 112 114 ปริ้นค่า “Hello from Task1” พร้อมกับจำนวนรอบที่ทำงาน ( ตัวแปร f1param ) ผ่าน Serial port 113 อัพเดทค่า f1param +1 เข้าไป ซึ่งตัวแปร passValueก็จะถูกอัพเดทค่าใหม่นี้ด้วย เพราะเป็น pointer ของ f1param115 อัพเดทค่า f1param +1 เข้าไป ซึ่งตัวแปร `passValue `ก็จะถูกอัพเดทค่าใหม่นี้ด้วย เพราะเป็น pointer ของ f1param 114 116 ทำการ Delay Task1 ไว้ 2 วินาที แล้วจะวนมาทำงานใหม่ 115 117 {{{ … … 146 148 2) Ready state : Task ที่รอเข้าไปทำงานใน Running state จะกองกันอยู่ที่นี่ ( เป็นพวก Task ที่มี Priority น้อยว่า Task ใน Running state) 147 149 148 3) Blocked state : Task ที่อยู่ใน state นี้คือ Task ที่ถูก Blocked การทำงานชั่วคราว เช่น Task ที่ใช้คำสั่ง vTaskDelay()149 150 4) Suspended state : Task ที่อยู่ใน state นี้คือ Task ที่โดนสั่งให้พักการทำงาน โดยใช้คำสั่ง vTaskSuspend()และหาต้องการให้ Task นั้นๆ กลับมาทำงานตามปกติ เราต้องส่งคำสั่ง vTaskResume() เพื่อปลดล็อค150 3) Blocked state : Task ที่อยู่ใน state นี้คือ Task ที่ถูก Blocked การทำงานชั่วคราว เช่น Task ที่ใช้คำสั่ง `vTaskDelay()` 151 152 4) Suspended state : Task ที่อยู่ใน state นี้คือ Task ที่โดนสั่งให้พักการทำงาน โดยใช้คำสั่ง `vTaskSuspend()` และหาต้องการให้ Task นั้นๆ กลับมาทำงานตามปกติ เราต้องส่งคำสั่ง vTaskResume() เพื่อปลดล็อค 151 153 }}} 152 154 RTOS API บางส่วนที่เราจะมาดูกันเพิ่ม เพื่อลองเขียนโปรแกรมเปลี่ยน State ของ Task มีดังนี้ … … 197 199 ฟังก์ชั่น f4_Task() ที่ผูกกับ Task4 198 200 199 ปริ้น “Hello from Task4” ผ่าน Serial Port พร้อมทั้งปริ้นค่า Priority ของ Task ด้วยฟังก์ชั้น uxTaskPriorityGet(…)200 เปลี่ยน Priority ของ Task4 จาก 4 ให้เป็น 0 ( ต่ำสุด ) ด้วยฟังก์ชัน vTaskPrioritySet(…)201 ลบ Task4 ทิ้ง โดยคำสั่ง vTaskDelete(…)201 ปริ้น “Hello from Task4” ผ่าน Serial Port พร้อมทั้งปริ้นค่า Priority ของ Task ด้วยฟังก์ชั้น `uxTaskPriorityGet(…)` 202 เปลี่ยน Priority ของ Task4 จาก 4 ให้เป็น 0 ( ต่ำสุด ) ด้วยฟังก์ชัน `vTaskPrioritySet(…)` 203 ลบ Task4 ทิ้ง โดยคำสั่ง `vTaskDelete(…)` 202 204 {{{ 203 205 void f3_Task(void *pvParam){ … … 213 215 214 216 ปริ้น “Hello from Task3” ผ่าน Serial Port 215 Suspend Task2 และ Task3 ด้วยคำสั่ง vTaskSuspend(…)216 ลบ Task3 ทิ้ง โดยคำสั่ง vTaskDelete(…)217 Suspend Task2 และ Task3 ด้วยคำสั่ง `vTaskSuspend(…)` 218 ลบ Task3 ทิ้ง โดยคำสั่ง `vTaskDelete(…)` 217 219 {{{ 218 220 void f2_Task(void *pvParam){ … … 227 229 228 230 ปริ้น “Hello from Task2” ผ่าน Serial Port 229 Resume Task3 ด้วยคำสั่ง vTaskResume(…)230 ลบ Task2 ทิ้ง โดยคำสั่ง vTaskDelete(…)231 Resume Task3 ด้วยคำสั่ง `vTaskResume(…)` 232 ลบ Task2 ทิ้ง โดยคำสั่ง `vTaskDelete(…)` 231 233 {{{ 232 234 void f1_Task(void *pvParam){ … … 241 243 242 244 ปริ้น “Hello from Task1” ผ่าน Serial Port 243 Resume Task2 ด้วยคำสั่ง vTaskResume(…)244 ลบ Task1 ทิ้ง โดยคำสั่ง vTaskDelete(…)245 Resume Task2 ด้วยคำสั่ง `vTaskResume(…)` 246 ลบ Task1 ทิ้ง โดยคำสั่ง `vTaskDelete(…)` 245 247 246 248 การทำงานของตัวอย่าง code นี้ 247 249 {{{ 248 250 เริ่มต้น Task4 จะเริ่มทำงานก่อน โดยการปริ้น “Hello from Task4” พร้อมทั้งค่า Priority ของ Task4 เอง 249 251 Task4 ทำการเปลี่ยนค่า Priority ของตัวเอง จาก 4 เป็น 0 ( ค่าต่ำสุด ) … … 258 260 Task4 กลับมาทำงานอีกครั้งเนื่องจาก Priority สูงสุด และทำงานต่อจากการทำงานเดิม โดยการ delete ตัวเอง พร้อมกับปริ้น “Hello again from Task4 , then delete Task4” 259 261 ขั้นตอนนี้นี้ไม่เหลือ Task ให้ทำงานแล้ว จึงจบการทำงาน 260 262 }}} 261 263 เป็นไงครับ เห็นการทำงานโยนไปโยนมาของ Task ที่ไปอยู่ใน State ต่างๆ หวังว่าผู้อ่านคงเข้าใจเพิ่มขึ้นเกี่ยวกับการทำงานของ Task และเผื่อจะเป็นไอเดียเอาไปประยุกต์ใช้ในโปรเจ็คตัวเองนะครับ 262 264