Changes between Version 5 and Version 6 of ESP32


Ignore:
Timestamp:
06/12/21 09:29:54 (3 years ago)
Author:
krit
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • ESP32

    v5 v6  
    1818
    1919RTOS มีวิธีจัดการ Task ต่างๆ โดยการดูระดับความสำคัญ ( Priority ) ซึ่ง Task ที่มีระดับความสำคัญสูงสุดจะได้ทำงานก่อน และใช้ Time slicing ในการแบ่งเวลากันทำงาน ซึ่งทำให้ ESP32 สามารถทำงานคล้ายแบบ Multi-tasking ได้
     20
    2021Time slicing
    2122
     
    2829ในช่วงที่ฟังก์ชัน Delay() ทำงาน ช่วงนั้น CPU จะหยุดการประมวลผล ซึ่งหาก Delay มีค่าที่ตั้งไว้นาน จะเกิดช่วงเวลาที่สูญเปล่าเพราะไม่ได้ทำงานฟังก์ชันอื่นต่อเนื่องเลย
    2930
    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 ==
    3234
    3335การสร้าง Task ใน RTOS เราจะเขียน function ที่เราต้องการให้ทำงานไว้ก่อน แล้วก็สร้าง Task เพื่อไปเรียก function มาใช้งานอีกที
     
    4547มาดูตัวแปรที่ใช้ตอนสร้าง Task ทีละตัวกัน
    4648{{{
    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 ตัวอย่างเพิ่มเติมครับ )
    5355}}}
    5456แต่ถ้าอยากกำหนดให้ลึกว่า 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{{{
    58607. Core : สำหรับ ESP เราสามารถกำหนดให้ Task ทำงานที่ CPU 0 หรือ 1
    59 
     61}}}
    6062ลองมาดู code ตัวอย่างการสร้าง Task เพื่อความเข้าใจกันครับ โดยตัวอย่างนี้ เราจะเขียนโปรแกรมสร้าง Task ในบอร์ด ESP32 เพื่อปริ้นดูการทำงานของแต่ละ Task ผ่าน Serial port
    6163
     
    6466const TickType_t xDelay2000ms = pdMS_TO_TICKS(2000);
    6567}}}
    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 วินาทีไว้ )
    6971{{{
    7072TaskHandle_t Task1 = NULL;
     
    8789}
    8890}}}
    89 มาถึงส่วนของฟังก์ชั่น setup(…) เราจะประกาศ Created Task กันในฟังก์ชันนี้ โดยใช้คำสั่ง xTaskCreatePinnedToCore(…)
     91มาถึงส่วนของฟังก์ชั่น setup(…) เราจะประกาศ Created Task กันในฟังก์ชันนี้ โดยใช้คำสั่ง `xTaskCreatePinnedToCore(…)`
    9092{{{
    9193    Task ที่ 1 : ผูกกับฟังก์ชั่น func1_Task() , ตั้งชื่อ Task ว่า “func1_Task”,DepthStack = 1000 ,ส่งตัวแปร PassValue เข้าไปใน Task นี้ด้วย , มีค่า Priority = 1 , ผูกกับ TaskHandle ชื่อว่า Task1 และให้ Task นี้ทำงานอยู่บน Core 0
     
    107109  }
    108110}}}
    109 ในส่วนของ func1_Task() ที่ผูกกับ Task1 ไว้
    110 
    111     สร้างตัวแปร int ชื่อ f1param ไว้เก็บค่า pvvalue ที่ส่งเข้ามา(แปลง type ให้เป็น int ไว้แล้ว )
     111ในส่วนของ `func1_Task()` ที่ผูกกับ Task1 ไว้
     112
     113    สร้างตัวแปร int ชื่อ f1param ไว้เก็บค่า `pvvalue` ที่ส่งเข้ามา(แปลง type ให้เป็น int ไว้แล้ว )
    112114    ปริ้นค่า “Hello from Task1” พร้อมกับจำนวนรอบที่ทำงาน ( ตัวแปร f1param ) ผ่าน Serial port
    113     อัพเดทค่า f1param +1 เข้าไป ซึ่งตัวแปร passValue ก็จะถูกอัพเดทค่าใหม่นี้ด้วย เพราะเป็น pointer ของ f1param
     115    อัพเดทค่า f1param +1 เข้าไป ซึ่งตัวแปร `passValue `ก็จะถูกอัพเดทค่าใหม่นี้ด้วย เพราะเป็น pointer ของ f1param
    114116    ทำการ Delay Task1 ไว้ 2 วินาที แล้วจะวนมาทำงานใหม่
    115117{{{
     
    1461482) Ready state : Task ที่รอเข้าไปทำงานใน Running state จะกองกันอยู่ที่นี่ ( เป็นพวก Task ที่มี Priority น้อยว่า Task ใน Running state)
    147149
    148 3) Blocked state : Task ที่อยู่ใน state นี้คือ Task ที่ถูก Blocked การทำงานชั่วคราว เช่น Task ที่ใช้คำสั่ง vTaskDelay()
    149 
    150 4) Suspended state : Task ที่อยู่ใน state นี้คือ Task ที่โดนสั่งให้พักการทำงาน โดยใช้คำสั่ง vTaskSuspend() และหาต้องการให้ Task นั้นๆ กลับมาทำงานตามปกติ เราต้องส่งคำสั่ง vTaskResume() เพื่อปลดล็อค
     1503) Blocked state : Task ที่อยู่ใน state นี้คือ Task ที่ถูก Blocked การทำงานชั่วคราว เช่น Task ที่ใช้คำสั่ง `vTaskDelay()`
     151
     1524) Suspended state : Task ที่อยู่ใน state นี้คือ Task ที่โดนสั่งให้พักการทำงาน โดยใช้คำสั่ง `vTaskSuspend()` และหาต้องการให้ Task นั้นๆ กลับมาทำงานตามปกติ เราต้องส่งคำสั่ง vTaskResume() เพื่อปลดล็อค
    151153}}}
    152154RTOS API บางส่วนที่เราจะมาดูกันเพิ่ม เพื่อลองเขียนโปรแกรมเปลี่ยน State ของ Task มีดังนี้
     
    197199ฟังก์ชั่น f4_Task() ที่ผูกกับ Task4
    198200
    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(…)`
    202204{{{
    203205void f3_Task(void *pvParam){
     
    213215
    214216    ปริ้น “Hello from Task3” ผ่าน Serial Port
    215     Suspend Task2 และ Task3 ด้วยคำสั่ง vTaskSuspend(…)
    216     ลบ Task3 ทิ้ง โดยคำสั่ง vTaskDelete(…)
     217    Suspend Task2 และ Task3 ด้วยคำสั่ง `vTaskSuspend(…)`
     218    ลบ Task3 ทิ้ง โดยคำสั่ง `vTaskDelete(…)`
    217219{{{
    218220void f2_Task(void *pvParam){
     
    227229
    228230    ปริ้น “Hello from Task2” ผ่าน Serial Port
    229     Resume Task3 ด้วยคำสั่ง vTaskResume(…)
    230     ลบ Task2 ทิ้ง โดยคำสั่ง vTaskDelete(…)
     231    Resume Task3 ด้วยคำสั่ง `vTaskResume(…)`
     232    ลบ Task2 ทิ้ง โดยคำสั่ง `vTaskDelete(…)`
    231233{{{
    232234void f1_Task(void *pvParam){
     
    241243
    242244    ปริ้น “Hello from Task1” ผ่าน Serial Port
    243     Resume Task2 ด้วยคำสั่ง vTaskResume(…)
    244     ลบ Task1 ทิ้ง โดยคำสั่ง vTaskDelete(…)
     245    Resume Task2 ด้วยคำสั่ง `vTaskResume(…)`
     246    ลบ Task1 ทิ้ง โดยคำสั่ง `vTaskDelete(…)`
    245247
    246248การทำงานของตัวอย่าง code นี้
    247 
     249{{{
    248250    เริ่มต้น Task4 จะเริ่มทำงานก่อน โดยการปริ้น “Hello from Task4” พร้อมทั้งค่า Priority ของ Task4 เอง
    249251    Task4 ทำการเปลี่ยนค่า Priority ของตัวเอง จาก 4 เป็น 0 ( ค่าต่ำสุด )
     
    258260    Task4 กลับมาทำงานอีกครั้งเนื่องจาก Priority สูงสุด และทำงานต่อจากการทำงานเดิม โดยการ delete ตัวเอง พร้อมกับปริ้น “Hello again from Task4 , then delete Task4”
    259261    ขั้นตอนนี้นี้ไม่เหลือ Task ให้ทำงานแล้ว จึงจบการทำงาน
    260 
     262}}}
    261263เป็นไงครับ เห็นการทำงานโยนไปโยนมาของ Task ที่ไปอยู่ใน State ต่างๆ หวังว่าผู้อ่านคงเข้าใจเพิ่มขึ้นเกี่ยวกับการทำงานของ Task และเผื่อจะเป็นไอเดียเอาไปประยุกต์ใช้ในโปรเจ็คตัวเองนะครับ
    262264