ดิว.นินจา

ดิว.นินจา

Monday, February 5, 2018

ทำความรู้จักกับ FreeRTOS บน ESP32

อธิบายโดยย่อ RTOS ย่อมาจาก Real-Time Operating System คือระบบปฏิบัติการสำหรับงานที่เงื่อนไขด้านเวลามีความสำคัญ มักจะใช้ในระบบสมองกลฝังตัวที่ออกแบบสำหรับงานเฉพาะ ซอฟต์แวร์ RTOS มีทั้งแบบเป็นผลิตภัณฑ์เชิงพาณิชย์และแบบไม่เสียเงิน FreeRTOS จัดอยู่ในประเภทหลัง โครงการนี้พัฒนาโดย Richard Barry ผู้ตั้งบริษัท Real Time Engineers ปัจจุบันเป็นวิศวกรหลักของ Amazon Web Services ผู้เป็นเจ้าของและบำรุงรักษาซอฟต์แวร์ ความจริง FreeRTOS เริ่มต้นตั้งแต่ปี คศ. 2003 โดยใช้งานได้กับไมโครคอนโทรลเลอร์เพียงไม่กี่บริษัท ต่อมาโครงการได้ขยายตัวขึ้นเรื่อยๆ จนปัจจุบันรองรับตัวประมวลผลมากกว่า 35 สถาปัตยกรรม จุดเด่นคือมีลิขสิทธิ์แบบ MIT ที่เปิดกว้างให้ใช้ในผลิตภัณฑ์เชิงพาณิชย์ได้ และไม่จำเป็นต้องเปิดเผยโค้ดในส่วนของผู้พัฒนา สำหรับรายละเอียดทั้งหมดอ่านได้จาก https://www.freertos.org สำหรับในบทความนี้จะแนะนำเฉพาะไลบรารีของ FreeRTOS ที่รวมอยู่ใน Arduino core ของ ESP32

FreeRTOS บน ESP32

ในการพัฒนาบนตัวประมวลผลคอร์เดี่ยว เช่น ESP8266 เราคงไม่มีความจำเป็นต้องใช้ FreeRTOS ในงานควบคุม เนื่องจากสามารถใช้โครงสร้างแบบ cyclic executive ได้อย่างมีประสิทธิภาพ กล่าวคืออัลกอริทึมควบคุมรันในฟังก์ชันของไทเมอร์ ขณะที่ส่วนรับคำสั่งและแสดงผลจะรันในฟังก์ชัน loop() ซึ่งได้ใช้โครงสร้างนี้มาโดยตลอด มีข้อดีคือสามารถกำหนดคาบเวลาการสุ่มของตัวควบคุมได้อย่างแม่นยำ และเป็นอิสระต่อคาบเวลาของ loop() ยกตัวอย่างเช่นให้คาบเวลาของตัวควบคุมสั้นเพียง 10 มิลลิวินาที ส่วนคาบเวลาในการส่งค่าให้จอแสดงผล 100 วินาที วิธีนี้อาจเรียกได้ว่าเป็นการโปรแกรมหลายเทรด (multi-thread) แบบง่ายที่สุด ถึงแม้ว่าบน ESP8266 จะเป็นแบบเทียม เพราะมีฮาร์ดแวร์ตัวประมวลผลเพียง 1 ตัว ที่จะต้องถูกจัดสรรเวลาให้แต่ละเทรดใช้งาน

สำหรับงานที่ต้องการสมรรถนะสูงขึ้น ผู้พัฒนาอาจขยับมาใช้ชิพ ESP32 ที่มีความเร็วสูงกว่าและที่สำคัญคือมีคอร์ประมวลผล 2 ตัว ถูกกำหนดหมายเลขเป็น Core 0 และ Core 1 สิ่งที่เราอาจไม่ทราบคือ ในการเขียนโปรแกรมโดยปกติ ESP32 จะประมวลผลโดยใช้คอร์ตัวเดียวเท่านั้น คือ Core 1 ส่วน Core 0 จะไม่ถูกใช้งาน!

ฟังก์ชัน (ของไลบรารี FreeRTOS) ที่ใช้ตรวจสอบว่าฟังก์ชันหรือเทรดกำลังทำงานบนคอร์ใดคือ xPortGetCoreID() เช่นถ้าต้องการให้พิมพ์ออกพอร์ตอนุกรม ใช้คำสั่ง


Serial.println(xPortGetCoreID());

ในตัวอย่างนี้ เราจะลองตรวจสอบ 2 ประเด็นคือ

  1. ระดับความสำคัญ (priority) ระหว่างฟังก์ชันไทเมอร์กับลูป
  2. แต่ละเทรดทำงานบนคอร์หมายเลขใด

วิธีการง่ายๆ คือเขียนให้ทั้งสองเทรดทำงานนับวนลูปซ้อนเหมือนกันดังนี้


  for (i=0;i<lcnts;i++) {
    for (j=0;j<lcnts;j++);
  }

เมื่อกำหนดค่า lcnts = 20000000 จับเวลาที่ใช้โดยฟังก์ชัน millis() ได้ประมาณ 750 มิลลิวินาที ตั้งค่าคาบเวลาไทเมอร์ให้มากกว่าเล็กน้อยคือ 800 มิลลิวินาที คือฟังก์ชันไทเมอร์จะใช้เวลาเกือบเต็มคาบในการทำงานนับวนลูปนี้ และให้ทั้งสองเทรดส่งข้อความออกพอร์ตอนุกรมโดยระบุที่มาของข้อความและหมายเลขคอร์ เมื่อรันโปรแกรม coretest.ino ผลที่แสดงบน Serial Monitor เป็นดังรูปที่ 1

รูปที่ 1 ข้อความจากเทรดไทเมอร์และลูปที่ทำงานบนคอร์เดียวกัน

จากรูปเราได้คำตอบสำหรับ 2 ประเด็นที่ต้องการทดสอบคือ

  1. ค่อนข้างชัดเจนว่าลำดับความสำคัญของไทเมอร์มีมากกว่า loop()คือเมื่อเกิดอินเทอร์รัพท์ ฟังก์ชันไทเมอร์จะถูกเรียกทำงานทันทีโดยการทำงานของ loop()จะหยุดไว้ก่อน ต่อเมื่องานในไทเมอร์เสร็จสมบูรณ์และเหลือเวลาประมาณ 50 มิลลิวินาทีที่จะทำงานใน loop()ต่อ ดังนั้นจะเห็นว่าต้องเกิดไทเมอร์ อินเทอร์รัพท์ประมาณ 16 ครั้งจึงจะทำงานในลูปเสร็จครั้งหนึ่ง
  2. ทั้ง 2 เทรดถูกประมวลผลบนคอร์หมายเลข 1

มาถึงจุดนี้ผู้อ่านคงจับประเด็นได้ว่า ในเมื่อ ESP32 มี 2 คอร์ หากสามารถแยกเทรดทั้งสองไปรันในแต่ละคอร์ได้ ควรจะได้เห็นข้อความจากสองเทรดในอัตราส่วนเท่าๆ กัน เพราะทำงานชิ้นเดียวกัน แต่สิ่งที่เกิดขึ้นคือเทรดที่เกิดจากการอินเทอร์รัพท์จะถูกกำหนดให้รันบนคอร์เดียวกับฟังก์ชัน setup()และ loop()คือคอร์ 1 ส่วนคอร์ 0 ไม่ได้ถูกใช้งานเลย เรียกได้ว่าเป็นการใช้ทรัพยากรด้านฮาร์ดแวร์อย่างไม่คุ้มค่า

นี่เป็นเหตุผลหลักที่เราต้องมาสนใจ FreeRTOS เมื่อใช้ ESP32 เพราะไลบรารีมีคำสั่งในการสร้างทาสก์ (task) โดยกำหนดคอร์ประมวลผลได้

ในตัวอย่างต่อมาจะทดลองใช้ FreeRTOS สร้างทาสก์ที่ทำงานคำสั่งวนลูปเดียวกันนี้ แต่กำหนดให้รันบนคอร์ 0 ขณะที่ loop()ทำงานทำสั่งที่เหมือนกันแต่รันบนคอร์ 1 เมื่อรัน freertos_coretest.ino จะเห็นเอาต์พุตบน Serial Monitor ดังรูปที่ 2 ซึ่งตรงกับที่เราคิดไว้ ตัวอย่างนี้เรียกได้ว่าเป็นการโปรแกรมหลายเทรดของแท้ ซึ่งต้องอาศัยไลบรารีของ FreeRTOS

รูปที่ 2 ข้อความจากเทรดไทเมอร์และลูปที่ประมวลผลบนคอร์แยกจากกัน

คำสั่งสำหรับสร้างทาสก์แบบกำหนดคอร์คือ xTaskCreatePinnedToCore() คำสั่งที่ใช้ทั้งหมดรวมอยู่ในโปรแกรม freertos_coretest.ino ที่รวมอยู่ใน zip file ด้านล่าง ผู้อ่านสามารถดาวน์โหลดและรันเปรียบเทียบกับโปรแกรมในตัวอย่างแรก ทั้งสองตัวอย่างนี้รันได้บนบอร์ด ESP32 จากบริษัทใดก็ได้โดยไม่ต้องมีฮาร์ดแวร์เพิ่มเติม

รายละเอียดของ FreeRTOS มีมากเกินกว่าจะกล่าวถึงในบทความนี้ ผู้เขียนจะรวบรวมไว้ในหนังสือ “ตัวควบคุมป้อนกลับบนอินเทอร์เน็ตโดย ESP8266 และ ESP32” ที่ยังไม่มีวางจำหน่ายที่ใด แต่มอบให้ฟรีสำหรับผู้อบรม และในการฝึกอบรม “IoT สำหรับงานควบคุมอุตสาหกรรมโดย ESP32 และ NETPIE” จะครอบคลุมการโปรแกรมแบบหลายเทรดโดยใช้คอร์ประมวลผลของ ESP32 อย่างเต็มประสิทธิภาพ

esp32_freertos_intro.zip ไฟล์รวมโปรแกรมสำหรับบทความนี้

No comments:

Post a Comment

แนะนำหนังสือ “ตัวควบคุมป้อนกลับบนอินเทอร์เน็ตโดย ESP8266”

ปัจจุบันเมื่อกล่าวถึงอุปกรณ์ IoT (Internet of Things) คงมีน้อยคนที่จะไม่รู้จัก ในยุคที่การเข้าถึงอินเทอร์เน็ตเป็นกิจวัตรประจำวันของมนุษย์เ...