เริ่มต้นกับ Modern Arduino Programming
วิดีโอด้านล่างนี้จะอธิบายวิธีดาวน์โหลดและติดตั้งซอฟต์แวร์ทั้งหมดที่จำเป็นต้องใช้เพื่อเริ่มต้นกับ Modern Arduino Programming ดูให้จบนะน้อง!
Project Overview
"QP-Arduino" นี่คือคลาสระดับมาสเตอร์ในเรื่อง Event-Driven RTOS Forensics และ Hierarchical State-Machine (HSM) Orchestration เลยนะตัวดี! มันต่างจาก Arduino แบบดั้งเดิมที่เขียนแบบ sequential แล้วต้องพึ่งพาการเรียกฟังก์ชัน delay() ที่บล็อกการทำงาน QP-Arduino ใช้เฟรมเวิร์ก QP/C++ เพื่อสร้างสภาพแวดล้อมการทำงานแบบ asynchronous ที่มี deterministic (หรือก็คือคาดการณ์ผลได้นั่นแหละ) โดยการนำ "Active Object" design pattern มาใช้ โปรเจกต์นี้ทำให้เราจัดการงานหลายๆ งานที่ต้องทำพร้อมกันได้ โดยไม่ต้องกลัวโค้ดจะกลายเป็น "สปาเก็ตตี้โค้ด" (Spaghetti-Code) เฟรมเวิร์กนี้ยังรวม zero-latency cooperative QV scheduler เข้ามาด้วย ทำให้ได้ประสิทธิภาพ real-time สูงๆ บนบอร์ด Arduino ที่ใช้ ARM ได้
Traditional Sequential Arduino Programming
โมเดลการเขียนโปรแกรม Arduino แบบดั้งเดิมเนี่ย เราเรียกมันว่า "sequential programming" นั่นแหละ เพราะลำดับเหตุการณ์ที่เราคาดหวัง มักจะถูกเขียนตายตัวไว้ในโค้ดเป็นลำดับของการเรียกฟังก์ชันที่บล็อกการทำงาน ซึ่งมันจะรอคอยเหตุการณ์ที่คาดไว้ในบรรทัดนั้นๆ
ตัวอย่างเช่น ในตัวอย่างมาตรฐาน Arduino Blink เจ้า Blink นี่แหละ เราคาดหวังลำดับของเหตุการณ์ timeout สองครั้ง ซึ่งเราก็จะรอมันในบรรทัดโดยการเรียกฟังก์ชัน Arduino delay() สองครั้ง
Listing 1: ตัวอย่าง Arduino sequential programming (จากตัวอย่าง Arduino Blink มาตรฐาน)
void loop() {
digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(13, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
วิธีแบบนี้ใช้ได้กับปัญหาง่ายๆ แต่พอโปรเจกต์เริ่มซับซ้อนขึ้น มันก็เริ่มไม่ไหวแล้วล่ะ เช่น ถ้าอยากให้ไฟ LED ดวงที่สองกระพริบในอัตราที่ *ต่าง* กันล่ะก็ งานเข้า!
ปัญหาพื้นฐานก็คือ ขณะที่โปรแกรม sequential กำลังรอเหตุการณ์หนึ่งอยู่ (เช่น การหน่วงเวลา) มันก็จะไม่ทำงานอื่นเลย และมันก็จะ *ไม่ตอบสนอง* ต่อเหตุการณ์อื่นๆ (เช่น การหน่วงเวลาอื่นๆ หรือการกดปุ่ม) นี่แหละจุดอ่อน
Arduino Programming without Blocking
ด้วยเหตุนี้ พี่ๆ Arduino โปรแกรมเมอร์ที่ช่ำชองแล้วจึง *หลีกเลี่ยงการบล็อก* (หรือการ polling) และการรอเหตุการณ์ในบรรทัด เช่น การใช้ฟังก์ชัน delay() แบบนั้นเลย แต่โปรแกรมจะแค่ตรวจสอบเงื่อนไขที่น่าสนใจ (ด้วยคำสั่ง if() โดยตรง) และจัดการกับเหตุการณ์เฉพาะตอนที่ตรวจพบเท่านั้น สิ่งสำคัญคือฟังก์ชัน Arduino loop() ต้องรีเทิร์นกลับมาเร็วเสมอ ซึ่งจะทำให้โปรแกรม *ตอบสนอง* ต่อเหตุการณ์อื่นๆ ได้ดี
ตัวอย่างของวิธีนี้ก็เช่น การใช้ฟังก์ชัน Arduino millis() (ลองหาอ่านเพิ่มเติมเกี่ยวกับการใช้ millis() เป็นทางเลือกแทน delay() ได้ตามแหล่งความรู้ทั่วไป) เอาไปศึกษาต่อเองนะน้อง ฝึกฝนเยอะๆ!
ลิสติ้ง 2: ตัวอย่างการเขียนโปรแกรม Arduino แบบไม่บล็อก (จากโปรเจค "Using millis() Function as an Alternative to Using delay()")
void loop() {
ms_from_start = millis();
if (ms_from_start-ms_previous_read_LED1>LED1_interval) {
ms_previous_read_LED1 = ms_from_start;
if (LED1_state == 0) LED1_state = 1;
else LED1_state=0;
digitalWrite(LED1,LED1_state);
}
}
ตัวอย่างพื้นฐานของโปรแกรม Arduino แบบไม่บล็อกนี่ถือว่าเดินมาถูกทางแล้วนะน้อง เพราะโค้ดส่วนที่ไม่บล็อกเนี่ยมันมีคุณสมบัติที่เรียกว่า composable (ประกอบได้) หมายความว่าเราสามารถเอาโค้ดแบบไม่บล็อกหลายๆ ชิ้นมาต่อกัน (โดยไม่ต้องแก้อะไร) ลงในฟังก์ชัน loop() เดียวของ Arduino ได้เลย
แต่ปัญหาหลักของวิธีนี้คือ พอโปรเจคเริ่มซับซ้อนขึ้น จำนวนเงื่อนไข if กับ else ที่ซ้อนกันก็จะเพิ่มขึ้นแบบรัวๆ ตามไปด้วย สถาปัตยกรรมโค้ดที่พังทลายลงแบบนี้ วงการเค้าเรียกกันว่า ปัญหา "สปาเก็ตตี้โค้ด" (spaghetti code) ไงล่ะ งานเข้าแล้วสิ
อีกอย่าง โปรแกรมแบบไม่บล็อกธรรมดาๆ มันไม่มีระบบป้องกันการเสียหายของข้อมูล "อินพุต" ระดับโกลบอลที่แชร์กันระหว่างอินเตอร์รัพต์กับฟังก์ชัน loop() เลยนะ ซึ่งนี่อาจนำไปสู่สิ่งที่เรียกว่า race conditions (เงื่อนไขการแข่งกัน) ได้ ตัวดีเลย
การเขียนโปรแกรม Arduino แบบขับเคลื่อนด้วยเหตุการณ์ (Event-Driven)
ทางแก้ปัญหาทั้งหมดนี้ที่รู้จักกันมานานแล้วก็คือสิ่งที่เรียกว่า "การเขียนโปรแกรมแบบขับเคลื่อนด้วยเหตุการณ์ (event-driven programming)" ซึ่งมีคุณลักษณะดังนี้:
- การสื่อสารและการซิงค์ทั้งหมดภายในโปรแกรมจะถูกจัดการผ่าน อ็อบเจ็กต์เหตุการณ์ (event objects) พิเศษ ที่ออกแบบมาเพื่อการสื่อสารแบบอะซิงโครนัสโดยเฉพาะ
- โปรแกรมแบบขับเคลื่อนด้วยเหตุการณ์จะถูกแบ่งออกเป็นสองส่วนตามธรรมชาติ ได้แก่ ส่วนแอปพลิเคชัน (ซึ่งเป็นส่วนที่จัดการเหตุการณ์จริงๆ) และส่วนโครงสร้างพื้นฐานหรือเฟรมเวิร์ก (framework) ที่ทำหน้าที่รอและส่งต่อเหตุการณ์ไปให้แอปพลิเคชัน
- การควบคุมจะอยู่ที่โครงสร้างพื้นฐานแบบขับเคลื่อนด้วยเหตุการณ์ (เฟรมเวิร์ก) ซึ่งนำไปสู่สิ่งที่เรียกว่า การกลับด้านของการควบคุม (inversion of control) ในมุมมองของนักพัฒนาแอปพลิเคชัน
- โค้ดแอปพลิเคชันต้องประมวลผลเหตุการณ์ให้เสร็จในครั้งเดียว (ทีละเหตุการณ์) โดย ไม่บล็อก หรือไม่ใช้การวนรอ (polling)
เฟรมเวิร์ก QP/C++ นี่แหละที่ให้โครงสร้างพื้นฐานแบบขับเคลื่อนด้วยเหตุการณ์แบบนี้มา โดยมันถูกออกแบบมาเฉพาะสำหรับระบบเอมเบดเด็ดลึก (deeply embedded systems) อย่างบอร์ด Arduino โดยตรงเลย สู้งานนะน้อง!
ลงลึกเทคนิคแบบจัดเต็ม
- การจัดการ Active Object แบบขั้นเทพ:
- การสืบสวน Event-Queue: แต่ละ logic-node ใน QP-Arduino ถูกออกแบบให้เป็น "Active Object" (AO) ที่มีคิวของตัวเอง (event queue) เป็นของตั๊วเอง การสืบสวนระบบนี้ใช้การส่งต่อข้อมูลแบบอะซิงโครนัส $(\text{queue.get()} \rightarrow \text{dispatch(e)})$ เพื่อให้มั่นใจว่าแต่ละอีเวนต์จะถูกประมวลผลจนเสร็จสมบูรณ์ (RTC - Run-To-Completion) โดยไม่มีการบล็อกภายใน การวินิจฉัยด้วยวิธีนี้ช่วยป้องกัน race conditions และทำให้ Active Object ที่มีความสำคัญสูงยังคงทำงานได้อย่างมีเสถียรภาพ
- กลไก Inversion of Control (IoC): การควบคุมหลักอยู่ที่เฟรมเวิร์ก QP ไม่ใช่โค้ดแอปพลิเคชันของเรา การวินิจฉัยด้วย IoC นี้ทำให้ระบบสามารถเข้าสู่ โหมด Low-Power Sleep โดยอัตโนมัติเมื่อไม่มีอีเวนต์ค้างอยู่ ช่วยเพิ่มประสิทธิภาพการใช้งานแบตเตอรี่สำหรับการติดตั้ง IoT แบบรีโมทได้อย่างดีเยี่ยม
QP/C++ สามารถใช้งานร่วมกับเคอร์เนลเรียลไทม์ได้หลายแบบ สำหรับการผสานรวมกับ Arduino นี้ เราได้ตั้งค่า QP/C++ ให้ใช้ QV scheduler แบบ cooperative ที่เรียบง่ายที่สุด ซึ่งซอฟต์แวร์จะถูกจัดโครงสร้างเป็นลูป event-loop ที่วนไม่รู้จบ ตามที่แสดงในไดอะแกรมด้านล่าง:

องค์ประกอบที่สำคัญที่สุดของการออกแบบ QV kernel คือการมีหลายคิวอีเวนต์ โดยแต่ละคิวมีความสำคัญ (priority) เป็นของตัวเองและมี "active object" ถูกกำหนดให้ดูแลคิวนั้นๆ ตัว QV scheduler จะคอยตรวจสอบคิวเหล่านี้ตลอดเวลา ในแต่ละรอบของลูป มันจะเลือกคิวที่มีความสำคัญสูงสุดและไม่ว่างมาทำงาน หลังจากเจอคิวแล้ว scheduler จะดึงอีเวนต์ออกมาจากคิว (queue.get()) และเรียก "active object" ที่เกี่ยวข้องให้มาจัดการอีเวนต์นั้น (dispatch(e)) ซึ่งเราเรียกกระบวนการนี้ว่า "การส่งต่ออีเวนต์"
ฟีเจอร์เพิ่มเติมของ QV kernel คือมันสามารถตรวจจับสถานการณ์ที่ไม่มีอีเวนต์ให้ประมวลผลได้อย่างง่ายดาย ในกรณีนั้นระบบสามารถถูกสั่งให้เข้าสู่ โหมด low-power sleep ได้ สิ่งนี้ทำให้เราบรรลุประสิทธิภาพการใช้พลังงานที่ดีขึ้น และนำซอฟต์แวร์ Arduino ไปใช้ในแอปพลิเคชันที่ใช้แบตเตอรี่ได้ (หมายเหตุ: การประหยัดพลังงานที่แท้จริงยังต้องการการออกแบบฮาร์ดแวร์ที่เหมาะสมด้วยนะน้อง)
วิศวกรรมและการลงมือทำ
- การวินิจฉัยเคอร์เนลแบบ Deterministic:
- QV Cooperative Scheduling: QV kernel ทำงานเป็น event-loop วนไม่รู้จบ การวินิจฉัยแสดงให้เห็นว่าโมเดล cooperative นี้ช่วยลดการใช้หน่วยความจำ $(\text{RAM usage forensics})$ ได้อย่างมากเมื่อเทียบกับเคอร์เนล RTOS แบบดั้งเดิม (preemptive) ทำให้มันเหมาะสุดๆ กับทรัพยากรที่จำกัดของฮาร์ดแวร์ Arduino
- การประกอบโค้ดแบบ Non-Blocking: ด้วยการหลีกเลี่ยงการวนลูป
millis()และการใช้delay()อย่างเคร่งครัด ระบบนี้ช่วยให้สามารถ "ประกอบ" โมดูลโค้ดได้ Active Object ต่างๆ เช่น AO "Blinker" และ AO "Serial-Parser" สามารถถูกสร้างและทำงานพร้อมกันได้โดยไม่รบกวนการทำงานของกันและกัน
Hierarchical State Machines
นอกจากจะให้โครงสร้างพื้นฐานแบบ event-driven สำหรับการทำงานพร้อมกันของคอมโพเนนต์ซอฟต์แวร์ (ที่เรียกว่า "Active Objects") แล้ว เฟรมเวิร์ก QP/C++ ยังให้เครื่องมือที่ทันสมัยอย่าง Hierarchical State Machines สำหรับจัดการอีเวนต์ภายใน active objects อีกด้วย ประโยชน์หลักของการใช้ state machines คือการหลีกเลี่ยงปัญหา "สปาเก็ตตี้โค้ด" นั่นเอง
ลงลึกเรื่องเทคนิค (ต่อ)
- การวินิจฉัย Hierarchical State-Machine (HSM):
- UML Statechart Forensics: QP-Arduino นั้นใช้มาตรฐาน UML 2.0 Statechart เลยนะตัวนี้ มันอนุญาตให้ทำ "Level-N" state nesting ได้ด้วย การวินิจฉัยจะเกี่ยวข้องกับการจับ "State-Inheritance" หรือการที่ sub-states สืบทอดพฤติกรรมมาจาก super-states ของมัน การวิเคราะห์โครงสร้างแบบนี้ช่วยกำจัดพวก
if-elseกิ่งก้านซับซ้อนที่ซ้ำซ้อนได้หมดเกลี้ยง แล้วแทนที่ด้วยเมทริกซ์การเปลี่ยนสถานะแบบ deterministic ที่แม่นยำกว่า - การสร้างโค้ดจาก QM-Model: โปรเจคนี้ใช้เครื่องมือ QM modeling ในการออกแบบสถาปัตยกรรมลอจิกแบบ visual นะ การตรวจสอบโค้ด C++ ที่ถูก generate ออกมา จะทำให้มั่นใจได้ว่าภาพ visual ของ state machine นั้นตรงกับโค้ดเฟิร์มแวร์ที่รันอยู่ $100%$ แบบเป๊ะๆ เลย เป็นการวินิจฉัยแบบ "Single-Source-of-Truth" สำหรับลอจิกพฤติกรรมที่ซับซ้อน
- UML Statechart Forensics: QP-Arduino นั้นใช้มาตรฐาน UML 2.0 Statechart เลยนะตัวนี้ มันอนุญาตให้ทำ "Level-N" state nesting ได้ด้วย การวินิจฉัยจะเกี่ยวข้องกับการจับ "State-Inheritance" หรือการที่ sub-states สืบทอดพฤติกรรมมาจาก super-states ของมัน การวิเคราะห์โครงสร้างแบบนี้ช่วยกำจัดพวก
การสร้างโมเดลและการสร้างโค้ดอัตโนมัติ
สุดท้ายแล้ว เฟรมเวิร์ก QP/C++ และวิธีการเขียนโค้ด hierarchical state machine แบบมีโครงสร้างสูงนี้ เป็นพื้นฐานให้กับการสร้างโมเดลแบบ visual และการสร้างโค้ดอัตโนมัติได้เลย การผสาน QP-Arduino นั้นมาพร้อมกับ เครื่องมือสร้างโมเดล QM ซึ่งเป็นฟรีแวร์ที่ให้เราออกแบบ hierarchical state machines ของ active objects ต่างๆ ได้แบบกราฟิกเลย ภาพด้านล่างนี้คือตัวอย่างโมเดลจาก QP-Arduino integration ที่แสดง state machines และโค้ด

วิศวกรรมและการนำไปใช้ (ต่อ)
- เวิร์กโฟลว์ Modern Embedded:
- การนำไปใช้นี้เน้นที่เวิร์กโฟลว์ระดับมืออาชีพแบบ "Model-Based-Design" (MBD) การใช้ QM ในการออกแบบเส้นทางลอจิกแบบกราฟิกก่อนจะ generate เป็นโค้ดนั้น เป็นวิธีวิทยาระดับอุตสาหกรรมที่ช่วยรับประกันความน่าเชื่อถือของซอฟต์แวร์ที่สูงขึ้น และทำให้การวิเคราะห์เพื่อบำรุงรักษาระยะยาวง่ายขึ้น
Application Note
รายละเอียดการผสาน QP-Arduino ถูกอธิบายไว้ในเอกสาร Application Note: "Event-Driven Arduino Programming with QP and QM"
แหล่งข้อมูลเพิ่มเติม
เรื่อง Modern Embedded Systems Programming นี่เป็นวิชาที่ลึกซึ้งมากนะ อย่าคิดว่าจะเข้าใจได้หมดแค่อ่านบล็อกโพสต์เดียวหรือดูวิดีโอแนะนำ 15 นาที อย่าเพิ่งท้อถ้าตอนแรกยังไม่เข้าใจทันทีเลยนะ ไม่มีใครเข้าใจทันทีหรอก แต่ยิ่งเรียนเรื่องนี้มากเท่าไหร่ ทักษะการเป็นนักพัฒนาซอฟต์แวร์ของน้องจะดีขึ้นเท่านั้น แถมทัศนคติต่อการเขียนโปรแกรมแบบ concurrent ทั้งหมดของน้องจะเปลี่ยนไปเลย
เอาล่ะ นี้คือแหล่งข้อมูลที่อาจจะมีประโยชน์:
- ช่อง Quantum Leaps บน YouTube และโดยเฉพาะอย่างยิ่งคอร์สวิดีโอ "Modern Embedded Systems Programming" คอร์สนี้เริ่มมาตั้งแต่ปี 2013 แต่ยังอัพเดทต่อเนื่องอยู่เลย วิดีโอใหม่ๆ สอนเรื่องที่พวกเรามักจะติดปัญหาพอดี ไม่ต้องดูทุกคลิปก็ได้ แค่เปิดลิสต์ดูแล้วเลือกหัวข้อที่อยากจะทบทวนก็พอ
- ซีรีส์วิดีโอ "Beyond the RTOS"
- หนังสือ "Practical UML Statecharts in C/C++, 2nd Ed." ซึ่งมีให้ดาวน์โหลดในรูปแบบ PDF ฟรี
- เอกสาร application notes และบทความอื่นๆ บนเว็บไซต์ state-machine.com
เรื่องลิขสิทธิ์ (Licensing)
เฟรมเวิร์ก QP/C++ ที่อยู่ใน QP-Arduino นี่นะ ใช้สัญญาอนุญาตโอเพ่นซอร์ส GPLv3 แต่ว่า... มีข้อยกเว้นพิเศษให้ด้วยนะ เรียกว่า Arduino GPLv3 Exception.
ข้อยกเว้นนี้แหละที่ทำให้เราสามารถใช้ QP/C++ กับบอร์ดที่ได้รับการรับรองจาก Arduino (Arduino-Certified Board) ได้แบบสบายใจ โดยไม่ต้องเปิดเผยซอร์สโค้ดแอปพลิเคชันที่เป็นลิขสิทธิ์ของเราเองให้ใครดู
ส่วนเครื่องมือสร้างแบบจำลอง QM ที่แจกฟรีนั้น ใช้สัญญาอนุญาตแบบ EULA ง่ายๆ อย่าลืมอ่านรายละเอียดกันด้วยล่ะ
การสนับสนุน (Support)
ถ้ามีคำถามหรือข้อสงสัยเกี่ยวกับซอฟต์แวร์ QP-Arduino ล่ะก็ ถามมาได้หลายช่องทางเลยจ้า:
- คอมเมนต์ไว้ในโปรเจกต์นี้บน Arduino PROJECT HUB ได้เลย
- โพสต์ถามใน ฟอรัมสนับสนุนฟรี QP/QM (Free QP/QM Support Forum)
- ไปรายงานหรือตั้งกระทู้ issues บน GitHub Repository ของ qp-arduino
- คอมเมนต์ไว้ที่ หน้าเว็บโปรเจกต์ QP-Arduino
สรุปสั้นๆ
QP-Arduino นี่มันคือตัวแทนของ สถาปัตยกรรมซอฟต์แวร์ฝังตัวระดับเทพ (Embedded Software Architecture) เลยนะพวกเรา โดยการเชี่ยวชาญในศาสตร์ การวิเคราะห์สถานะเครื่อง (State-Machine Forensics) และ การประสานงานอ็อบเจกต์แอคทีฟ (Active-Object Orchestration) ทำให้เรามีสภาพแวดล้อม RTOS ระดับมืออาชีพ ที่ช่วยให้นักพัฒนา Arduino สร้างระบบที่ซับซ้อนและสำคัญระดับภารกิจได้อย่างแม่นยำแบบมีหลักการ