การใช้งาน RTOS บน MSP432
บทความนี้จะเล่าถึงการเขียนโค้ดสำหรับไมโครคอนโทรลเลอร์โดยใช้ระบบปฏิบัติการทันเวลา (Real-time Operating System, RTOS)
เกริ่นนำ
สิ่งแรกที่ควรทำความเข้าใจก่อนคือ เมื่อพูดถึง “Real-time” หรือที่เรียกทับศัพท์ว่า “เรียลไทม์” นักพัฒนาส่วนใหญ่มักจะเข้าใจในแง่การประมวลผลด้วยความเร็วสูง ทำให้คิดว่าต้องใช้หน่วยประมวลผลประสิทธิภาพสูงเป็นหลัก ความเข้าใจนี้ไม่ได้ถูกต้องมากนักเพราะหากเราพิจารณาถึงคอมพิวเตอร์ PC ที่มีหน่วยประมวลผลในระดับ GHz มีสถาปัตยกรรมหลายคอร์ รวมทั้งมีหน่วยความจำขนาดใหญ่ แต่ระบบคอมพิวเตอร์ที่พัฒนาด้วย PC อาจจะไม่สามารถทำงานแบบเรียลไทม์ได้ ตัวอย่างเช่น การใช้ PC ที่มีหน่วยประมวลผล Intel Core i7 และระบบปฏิบัติการ Windows 10 ในการควบคุมระบบเบรคของรถยนต์เป็นทางเลือกที่แย่มากในทุกทางโดยเฉพาะแง่ของความปลอดภัย เพราะเราไม่อาจยืนยันได้ว่าเมื่อเราเหยียบเบรคกระทันหันแล้ว PC เครื่องนั้นจะสามารถควบคุมแรงที่กระทำต่อเบรคได้จนหยุดอย่างปลอดภัย
ผมมักจะเรียก “Real-time system” ว่า “ระบบทันเวลา” มากกว่า เพราะการออกแบบระบบทันเวลาต้องเริ่มจากการกำหนดเงื่อนไขทางเวลา จากนั้นเลือกฮาร์ดแวร์และซอฟต์แวร์เพื่อให้สามารถทำงาน (รับรู้ ประมวลผล ตอบสนอง) ก่อนกำหนดเวลาที่เป็น “เส้นตาย” (deadline) ที่คิดจากเงื่อนไขทางเวลา คำว่า “ทันเวลา” ในมุมมองนี้จึงสื่อถึงการทำงานของระบบที่เสร็จสิ้นก่อนเวลาเส้นตาย ในกรณีระบบที่เรียบง่ายและมีเส้นตายในระดับใกล้เคียงหน่วยวินาที เช่น วงจรควบคุมปั๊มน้ำซึ่งกำหนดเงื่อนไขเวลาที่ 1 วินาที เราอาจเลือกใช้บอร์ด Arduino ที่มีไมโครคอนโทรลเลอร์ AVR 8 บิต และเขียนโค้ดด้วยลูปง่ายๆได้ โดยตัวระบบยังเคลมได้ว่าเป็นระบบทันเวลาเพราะสามารถวัดเซ็นเซอร์ เช็คความสูง และสั่งปิด/เปิดปั๊มได้ภายใน 1 วินาที
อย่างไรก็ตาม ระบบในกลุ่ม cyber-physical system ที่มีหน้าที่หลัก 3 ส่วนคือ Control (วัด-คุม) Compute (ประมวลผล) และ Communicate (สื่อสาร) การเขียนโค้ดแบบแทสค์เดี่ยว (single task) ที่ทำงานเรียงลำดับไปเรื่อยๆภายในลูปอนันต์ เป็นทางเลือกที่มีข้อจำกัดอยู่มาก เพราะการที่ยังทำงานไม่เสร็จในส่วนใดส่วนหนึ่งอาจทำให้ไม่ “ทันเวลา” ในการประมวลผลโค้ดส่วนอื่น แนวทางหลักในการแก้ไขข้อจำกัดนี้คือ การเขียนโค้ดแบบหลายแทสค์ (multitask) โดยแบ่งส่วนโค้ดออกเป็นหลายส่วนที่ทำงานพร้อมกัน (Concurrent) การทำงานแบบ concurrency เกิดจากการวางโครงสร้างของซอฟต์แวร์ให้สลับส่วนโค้ดที่เหมาะสมได้ทำงาน ณ เวลานั้น ซึ่งอาจใช้การประยุกต์ตัวช่วยทางฮาร์ดแวร์ได้แก่ กลไกอินเตอร์รัพท์ หรืออาจใช้ซอฟต์แวร์มาทำหน้าที่เลือกส่วนใค้ดให้ทำงาน (scheduling)
RTOS เป็นตัวเลือกที่เหมาะสมในงานพัฒนาระบบสมองกลฝังตัวที่มีความซับซ้อนมากกว่าการทำเป็นวงรอบควบคุม (control loop) โดยเฉพาะเมื่อตัวระบบจะต้องรองรับการสื่อสารกับระบบอื่นๆด้วย ในกรณีของการพัฒนาซอฟต์แวร์สำหรับไมโครคอนโทรลเลอร์ RTOS จะอยู่ในรูปแบบของซอร์สโค้ดหรือไฟล์ไลบรารีที่จะถูกคอมไพล์รวมไปกับซอร์สโค้ดส่วนแอพพลิเคชัน ส่วนโค้ดของ RTOS จะไปควบคุมอินเตอร์รัพต์ของไทเมอร์และซอฟต์แวร์อินเตอร์รัพต์ เพื่อให้ตัว RTOS เองถูกกระตุ้นใน 2 กรณีคือ (1) ไทเมอร์กระตุ้นตามเวลา (2) ส่วนโค้ดเรียกใช้ฟังก์ชันที่ไปกระตุ้นซอฟต์แวร์อินเตอร์รัพต์ เมื่อ RTOS ถูกกระตุ้นให้ทำงานแล้วก็จะไปบริหารการสลับส่วนโค้ดอื่นๆในส่วนแอพพลิเคชันให้เข้ามาทำงาน ดังนั้นการเขียนโค้ดที่รวม RTOS จึงสามารถทำให้เกิดสถานะ concurrency ภายในส่วนซอฟต์แวร์ได้ ทำให้สลับส่วนโค้ดที่ใกล้เส้นตายแล้วขึ้นมาทำงานได้
แนะนำบอร์ด MSP432 และ Energia
บอร์ด MSP432 Launchpad ของ Texas Instruments เป็นบอร์ดทดลองที่มีหน่วยประมวลผลในสถาปัตยกรรม ARM Cortex-M4 ซึ่งนอกจากจะประมวลผลได้เร็ว ยังมีขนาดหน่วยความจำภายในตัวชิพที่ใหญ่เพียงพอกับส่วนโค้ด RTOS การเขียนโค้ดอย่างง่ายสำหรับบอร์ด MSP432 Launchpad คือ การติดตั้งและใช้งาน Energia MT ซึ่งได้พัฒนาต่อยอดจาก Arduino IDE รวมทั้งได้รวม Arduino API ไว้ด้วย ผู้ที่เคยเขียนโค้ด Arduino บ้างจะสามารถเรียนรู้ได้เร็วหลังจากทำความเข้าใจเกี่ยวกับการอ้างชื่อขาของบอร์ด สิ่งที่ Energia MT มีเพิ่มเติมคือ TI-RTOS ที่เป็นระบบปฏิบัติการทันเวลาที่รวมไว้กับ IDE อย่างกลมกลืน โดยเราสามารถเขียนโค้ดแบบหลายแทสค์ได้เลยโดยไม่ต้องติดตั้งไลบรารีเพิ่มเติม
การเขียนโค้ดแบบแทสค์เดี่ยวใน Energia MT จะเหมือนกับ Arduino กล่าวคือ เขียนโค้ดที่ประมวลผลช่วงแรกในฟังก์ชัน setup() และโค้ดที่ทำงานต่อเนื่องในฟังก์ชัน loop() ในขณะที่การเขียนโค้ดแบบหลายแทสค์จะใช้การเพิ่ม tab ใน IDE แล้วเขียนฟังก์ชัน setupX() และ loopX() ขึ้นมา โดย X หมายถึงชื่อที่แตกต่างกันในแต่ละ tab โปรแกรม IDE ของ Energia MT จะรวม setupX() และ loopX() ในฐานะของแทสค์ใหม่ ซึ่งจะทำงานไปพร้อมกัน (concurrent) กับแทสค์หลักที่ทำงานด้วย setup() และ loop() หรืออีกนัยหนึ่ง เราสามารถใช้งานบอร์ด MSP432 ได้เหมือนกับบอร์ด Arduino หลายบอร์ดรวมกัน โดยการเขียนโค้ดของแต่ละบอร์ดแยกออกเป็นแทสค์ต่างกัน
คลิปด้านล่างนี้จะอธิบายหลักการของ RTOS และสาธิตการใช้ Energia MT ในการเขียนโค้ดแบบ multithread รวมถึงการใช้อินเตอร์รัพต์
คลิปแรก การใช้งาน Energia MT
คลิปที่สอง การใช้ RTOS ร่วมกับอินเตอร์รัพต์
การเขียนโค้ดหลายแทสค์
ปัญหาที่คนใช้งาน Arduino มักเจอคือ เมื่อนำโค้ดหลายส่วน (วัดเซ็นเซอร์ คุมมอเตอร์ แสดงผล สื่อสาร ฯลฯ) มารวมกัน กลับทำให้ตัวบอร์ดไม่ทำงานตามที่คิด แม้ว่าตอนทดสอบแต่ละส่วนจะทำงานได้ถูกต้อง สาเหตุของปัญหานี้คือ การทำงานแบบแทสค์เดี่ยวที่ทำให้ประมวลผลได้ทีละส่วน ทำให้ข้อมูลบางส่วนหรือบางเหตุการณ์ถูกละเลยไปส่งผลให้การทำงานผิดพลาด ตัวอย่างเช่น การเขียนโค้ดควบคุมมอเตอร์ซึ่งรับคำสั่งจากพอร์ตสื่อสาร แสดงสถานะการทำงาน และมีปุ่ม Emergency ปัญหาที่เจอกันบ่อยๆคือ ส่วนโค้ดจะไปหยุดในส่วนแสดงผลทำให้รับคำสั่งไม่ครบ หรือไม่สามารถตรวจจับการกดปุ่มได้ ดังนั้น มอเตอร์จึงทำงานไม่ตรงตามที่คิดไว้ ปัญหานี้จะเจอมากหน่อยในกรณี Arduino เพราะโค้ดตัวอย่างมักเรียกใช้ฟังก์ชัน delay() ซึ่งเมื่อ copy ไปแปะแบบไม่เข้าใจ ก็จะไปทำให้เกิดการหน่วงจนส่วนโค้ดอื่นๆทำงานผิดพลาด
การเขียนโค้ดแบบหลายแทสค์เป็นคำตอบเบื้องต้นสำหรับปัญหาดังกล่าว โดยเมื่อแยกโค้ดสำหรับแต่ละส่วนไปเป็นแทสค์ที่ต่างกัน (พูดอีกแบบคือ ตอน copy โค้ดของเขามา ก็ไป paste ลงใน tab ต่างกัน) ก็จะทำให้ส่วนจัดลำดับของ RTOS สามารถสลับระหว่างโค้ดแต่ละหน้าที่ได้ อย่างไรก็ตาม วิธีนี้ใช้กับบอร์ด Arduino ไม่ได้ด้วย 2 ประเด็น
- ส่วน tab ของ Arduino IDE เป็นแค่แยกไฟล์แต่ตอนสร้างโค้ดจริง ไม่ได้มีการเรียกใช้ RTOS มาหุ้มการทำงานภายใน tab นั้น
- บอร์ด Arduino ไม่สามารถเรียกใช้ไลบรารีของ RTOS ได้ เพราะหน่วยความจำ RAM ไม่เพียงพอ
ข้อจำกัดทั้งสองข้างต้นไม่เกิดขึ้นกับบอร์ด MSP432 Launchpad เพราะ Energia MT ถูกทำขึ้นเพื่อให้โค้ดในแต่ละ tab ถูกรวมเข้ากับการสร้างแทสค์โดยอัตโนมัติ นอกจากนี้ MSP432 เป็นหน่วยประมวลผล ARM ที่มีประสิทธิภาพสูง และยังมีหน่วยความจำ RAM เพียงพอในการสร้างแทสค์ได้จำนวนพอสมควร
เมื่อส่วนโค้ดของโปรแกรมถูกทำให้เป็นหลายแทสค์แล้ว วิธีง่ายที่สุดในการเชื่อมโยงการทำงานของส่วนโค้ดแต่ละส่วนคือ การใช้ตัวแปรแบบ global เพราะตอนคอมไพล์จะมีการรวมตัวแปร global ทั้งหมดเข้าด้วยกัน ทำให้ส่วนโค้ดในแต่ละแทสค์สามารถทำงานร่วมกันได้ วิธีนี้เข้าใจง่ายแม้จะเป็นคนที่มีทักษะในการเขียนโปรแกรมไม่ได้สูงมาก รวมทั้งสามารถใช้กับสถานการณ์ที่เงื่อนไขของเวลาเส้นตายไม่ได้สั้นมากได้ จึงเป็นข้อเสนอแนะแรกในการลองเปลี่ยนจากการเขียนโค้ดแบบแทสค์เดี่ยวสไตล์ Arduino เป็นแบบหลายแทสค์ อย่างไรก็ตาม การทำงานของอุปกรณ์ที่มีความซับซ้อนและเงื่อนไขเวลาที่สั้นก็ควรที่จะศึกษาเพิ่มเติมส่วน RTOS API ในด้านการเข้าจังหวะ (synchronization) และการส่งผ่านข้อมูล (data passing) ด้วย เพื่อให้ส่วนซอฟต์แวร์ของตัวอุปกรณ์ทำงานถูกต้องและน่าเชื่อถือ
คลิปตัวอย่างอีก 2 เรื่องจะแนะนำการใช้ semaphore และ event flag ของ TI-RTOS ในการเข้าจังหวะและส่งข้อมูลระหว่างโค้ด 2 ส่วน ผู้ที่ต้องการศึกษาเพิ่มเติมจะไปสืบค้นได้จากเอกสาร TI-RTOS หรือหนังสือที่อ้างถึง DSP/BIOS ซึ่งเป็นชื่อเดิมของระบบปฏิบัติทันเวลาของ Texas Instruments
คลิปที่สาม การใช้งาน semaphore
คลิปที่สี่ การรับคำสั่งจาก PC และส่งผ่านด้วย event flag
終わりに (ในตอนท้าย)
โพสต์นี้เดิมเผยแพร่ใน FB Page TENSAILAB.at.TU เมื่อปี 2017 แล้วมาปรับเป็นบล็อก เพราะผมค่อนข้าง ok กับการพิมพ์ story ใน medium มากกว่า คนที่สนใจบอร์ดนี้ แนะนำให้ไปลองเช็คกับเว็บ INEX ที่ทางคุณชัยวัฒน์มีบทความเพิ่มเติมในส่วน I/O ให้ศึกษา