ชื่อโปรเจกต์: คู่มือเฟิร์มแวร์แบบโมดูลาร์
Project Overview
"คู่มือเฟิร์มแวร์แบบโมดูลาร์" คือบทเรียนระดับสูงด้านวิศวกรรมซอฟต์แวร์ฝังตัว (embedded software engineering) ที่ออกแบบมาโดยเฉพาะสำหรับนักพัฒนาที่ก้าวข้าม Arduino sketches พื้นฐานแล้ว และกำลังประสบปัญหาความซับซ้อนที่เพิ่มขึ้นของโปรเจกต์ C/C++ ขนาดใหญ่ ในขณะที่บทเรียนหลายบทมุ่งเน้นไปที่ "การทำให้มันทำงานได้" คู่มือนี้จะมุ่งเน้นไปที่ "การทำให้มันบำรุงรักษาได้" ด้วยการประยุกต์ใช้หลักการออกแบบซอฟต์แวร์ระดับมืออาชีพ เช่น encapsulation, isolation และ interface-based programming กับโปรเจกต์ LED animation อย่างง่าย ผู้เขียนจะสาธิตวิธีเปลี่ยนโค้ดที่อ่านยากให้เป็นสถาปัตยกรรมแบบโมดูลาร์ระดับมืออาชีพ
บทความนี้สำหรับนักพัฒนา embedded software ที่มีความรู้พื้นฐานที่ดีเกี่ยวกับ C หรือ C++ แต่ประสบปัญหาในการจัดการโปรเจกต์ขนาดใหญ่และซับซ้อน
หากคุณเรียนรู้ที่จะพัฒนา embedded code เช่น โดยใช้ Arduino IDE คุณจะพบโปรแกรมตัวอย่างขนาดเล็กจำนวนมาก ซึ่งช่วยให้เริ่มต้นได้อย่างรวดเร็ว แต่ทันทีที่โปรเจกต์ของคุณเริ่มใหญ่ขึ้น ความช่วยเหลือที่เกี่ยวข้องกับการออกแบบซอฟต์แวร์ก็เริ่มหายาก
ในทางตรงกันข้าม เมื่อคุณเรียนรู้การพัฒนาซอฟต์แวร์สำหรับ desktop applications โครงสร้างโปรเจกต์และการออกแบบซอฟต์แวร์เป็นส่วนสำคัญของกระบวนการเรียนรู้
ในบทความสั้นๆ นี้ ผมจะให้คำแนะนำง่ายๆ เกี่ยวกับวิธีที่คุณสามารถสร้างโครงสร้างแบบโมดูลาร์สำหรับ firmware ของคุณได้ ซึ่งจะช่วยให้โค้ดของคุณสะอาดและบำรุงรักษาได้สำหรับโปรเจกต์ขนาดใหญ่และซับซ้อน
Refactor โค้ดของคุณ
หากคุณเริ่มโปรเจกต์ใหม่ คุณสามารถเตรียมโครงสร้างตามที่อธิบายไว้ได้เลย สำหรับบทความนี้ ผมจะถือว่าคุณมี working firmwareอยู่แล้ว แต่ต้องการปรับปรุงคุณภาพโค้ด
การปรับปรุงโค้ดของคุณในกระบวนการแบบวนซ้ำเรียกว่า refactoring. การทดสอบเป็นส่วนสำคัญของกระบวนการนี้: หลังจากการเปลี่ยนแปลงเล็กๆ น้อยๆ แต่ละครั้ง คุณต้องทดสอบว่าซอฟต์แวร์ยังคงทำงานได้ตามที่คาดหวังหรือไม่
ในการพัฒนา desktop application มี unit tests เพื่อรับรองความสมบูรณ์ของโมดูลขนาดเล็ก ผมพบว่ามันยากที่จะนำ unit tests มาใช้กับ embedded code ยกเว้นสำหรับ functions หรือ modules ขนาดเล็กที่เป็นอิสระ ดังนั้น คุณต้องใช้ run-time test แบบง่ายๆ ของซอฟต์แวร์ของคุณเพื่อให้แน่ใจว่ายังคงทำงานได้อย่างถูกต้อง
Refactoring เพียงแค่เปลี่ยนโค้ด แต่ไม่เปลี่ยน functionality. แม้ว่าคุณจะเปลี่ยนชื่อ ย้ายโค้ด หรือเปลี่ยน implementations การทำงานของโค้ดของคุณยังคงเหมือนเดิมทุกประการ สิ่งสำคัญคือคุณควรจะเปลี่ยนหรือขยาย functionality หรือ ทำ refactoring แต่คุณไม่ควรทำทั้งสองอย่างพร้อมกัน (หรือใน commit เดียวกัน)
ใช้ Version Control System
การเปลี่ยนแปลงโค้ดของคุณโดยไม่มีประวัติเวอร์ชันเป็นความคิดที่ไม่ดี หากคุณยังไม่ได้จัดการโค้ดของคุณใน version control system ตอนนี้คือเวลาที่จะเริ่มใช้มัน!
หากคุณไม่เคยใช้ version control system มาก่อน ให้ใช้ GIT และอ่านหนึ่งในบทเรียนมากมายเกี่ยวกับวิธีใช้งาน มี graphical user interfaces สำหรับทุก operating system ดังนั้นคุณไม่จำเป็นต้องทำงานบน console สิ่งสำคัญไม่ใช่ วิธีที่คุณจัดการโค้ด แต่เป็น การนำ version control system มาใช้
หลังจากการเปลี่ยนแปลงเล็กๆ น้อยๆ ที่ประสบความสำเร็จแต่ละครั้ง คุณควร commit เวอร์ชันใหม่ หากคุณประสบปัญหาในภายหลัง คุณสามารถวิเคราะห์การเปลี่ยนแปลงโค้ดที่นำไปใช้แต่ละครั้งได้อย่างง่ายดาย และย้อนกลับไปยังเวอร์ชันที่ทำงานได้ล่าสุด
การตั้งค่า Demo
หากคุณต้องการทำตามโดยใช้การตั้งค่า demo จริง คุณจะต้องมี Arduino Uno, LED สามดวงพร้อม resistors ที่เข้ากัน และ pushbuttons สองตัว โค้ดตัวอย่างคาดหวังวงจรตามที่แสดงในภาพประกอบถัดไป

ตัวอย่างเริ่มต้นที่แย่จนน่าตกใจ
โค้ดตัวอย่างที่เราจะเริ่มด้วยนี้เป็นสิ่งที่ผมเห็นบ่อยครั้งอย่างน่าเสียดาย กรุณาเปิดหน้าต่างเบราว์เซอร์ที่สองพร้อมโค้ดที่ URL ต่อไปนี้:
https://github.com/LuckyResistor/guide-modular-firmware/blob/master/fade_demo_01/fade_demo_01.ino
ผมไม่สามารถใช้ firmware ที่ซับซ้อนมากสำหรับบทความนี้ และ source code ของคุณอาจอยู่ในสถานะที่แตกต่างกัน อย่างไรก็ตาม โค้ดตัวอย่างนี้มีองค์ประกอบส่วนใหญ่ที่ผมต้องการพูดถึง
เนื่องจากความยาวของโค้ด ผมจะเพียงแค่ลิงก์ไปยังตัวอย่างฉบับเต็ม code snippets ในบทความควรมีหมายเลขบรรทัดที่ถูกต้อง เพื่อให้คุณสามารถค้นหาตำแหน่งได้ง่าย
อ่านโค้ดตัวอย่าง
ลองอ่านโค้ดตัวอย่างและระบุวัตถุประสงค์ก่อนที่จะทดสอบ ที่นี่ไม่มี documentation ไม่มี functions และดังนั้นจึงไม่มีโครงสร้างที่จะให้คำแนะนำใดๆ
การอ่านและทำความเข้าใจโค้ดอาจใช้เวลาพอสมควร เนื่องจากมี unnamed literals และ nested control statements
Compile และ Upload
หากคุณตั้งค่า hardware สำหรับ demo ให้ compile และ upload ไปยังบอร์ด มิฉะนั้น คุณก็แค่ทำตามได้เลย ไม่จำเป็นต้องมีวงจรที่ทำงานได้จริง
firmware ที่นี่ทำงานได้ดี: มี animated pattern บน LED สามดวง และคุณสามารถใช้ buttons สองปุ่มเพื่อสลับระหว่างสี่ animation ที่แตกต่างกันได้อย่างง่ายดาย
นี่คือปัญหาของการพัฒนา embedded: หากอุปกรณ์ดูเหมือนจะทำงานได้ตามที่คาดหวัง ไม่มีใครถามว่าโค้ดถูกเขียนแบบโมดูลาร์และมีคุณภาพดีหรือไม่
Module คืออะไร?
ในบริบทของการพัฒนา desktop software โดยทั่วไป UML จะกำหนดคำศัพท์ที่ใช้ในการพูดคุยเกี่ยวกับการออกแบบ เมื่อใช้ UML เราจะพูดถึง component ที่มี interfaces ผมมักจะใช้คำว่า moduleในความหมายที่คล้ายกัน
ใน firmware, module คือโค้ดที่มี interface และ encapsulates functionality ของมัน:
- ใน C, module คือ compiling unit ที่มี header, implementation file และ functions หลายตัวที่สร้าง interface
- ใน C++, มันอาจเป็น compiling unit ที่มี header และ implementation file…
- … และ functions หลายตัว ใน namespace ที่สร้าง interface
- … โดยมี class declaration เป็น interface
สิ่งสำคัญคือต้องเข้าใจว่า module นั้น encapsulates functionality ของมันในลักษณะที่สร้างระดับของ abstraction มี ผู้ใช้ moduleอยู่เสมอ และผู้ใช้นี้ เข้าถึงได้เฉพาะ interfaceของ module เท่านั้น
module อาจขึ้นอยู่ กับ interfaces ของ modules อื่นๆ แต่ ไม่เคยขึ้นอยู่กับโค้ดของผู้ใช้ moduleเลย
หลักการนี้เรียกว่า interface-based programming ซึ่งเป็นแนวคิดการออกแบบที่สำคัญอย่างยิ่ง
สถานะปัจจุบันของตัวอย่าง

วิธีดำเนินการต่อไป
ณ จุดนี้ เราควรเริ่ม refactoring โค้ดที่มีอยู่และสร้าง modules ใหม่ นี่เป็นกระบวนการแบบวนซ้ำ:
1. Encapsulate functionality บางส่วน:
- ย้ายจาก low-level ไปยัง high-level (bottom-up)
- Refactoring ไม่ควรเปลี่ยน functionality
2. ทดสอบโค้ดที่ปรับปรุงใหม่
3. Commit การเปลี่ยนแปลง