ในบทนี้พี่จะพาน้องไปรู้จักกับการใช้ฟังก์ชัน millis() แทน delay() กัน ว่ามันเจ๋งและมีข้อได้เปรียบทางด้านสถาปัตยกรรมยังไงบ้าง
ภาพรวมโปรเจค
แนวคิด "Async-Core" นี้มาแก้ปัญหาหลักของระบบฝังตัวเลยนะตัวดี นั่นก็คือ การหน่วงแบบบล็อกกิ้ง (Blocking Delay) ฟังก์ชัน delay() มันใช้ง่ายก็จริง แต่เวลามันทำงาน มันจะหยุดพอยน์เตอร์การทำงานของ CPU ซะงั้น ทำให้ระบบกลายเป็น "หูหนวก" ไม่รับรู้การขัดจังหวะหรืออินพุตจากภายนอกเลย
โปรเจคนี้จะพาน้องย้ายสถาปัตยกรรมมาใช้ การทำงานหลายงานแบบไม่บล็อก (Non-Blocking Multitasking) ด้วยฟังก์ชัน millis() ทำให้ Arduino ของเราจัดการงานหลายๆ อย่างพร้อมกันได้แบบไม่สะดุด เช่น กะพริบ LED, อ่านเซนเซอร์, และประมวลผลข้อมูลจาก Serial โดยไม่ต้องรอให้งานหนึ่งจบก่อน
ล้วงลึกเทคนิค
- เจาะลึกฮาร์ดแวร์ Timer:
- Timer0 Overflow: ภายใน Arduino core มันใช้ Timer0 (ซึ่งเป็นฮาร์ดแวร์ไทเมอร์ 8-bit) ในการเพิ่มค่าให้ตัวนับ 32-bit (unsigned long) ทุกๆ 1 มิลลิวินาที กระบวนการนี้ทำงานผ่าน Interrupt Service Routine (ISR) ที่รันอยู่เบื้องหลัง โดยไม่ขึ้นกับฟังก์ชันหลัก
loop()เลย - กฎการลบ (ป้องกัน Overflow): หลุมพรางคลาสสิกคือการกลัว "49-day rollover" ที่ตัวนับ 32-bit จะรีเซ็ตกลับไปที่ศูนย์ โปรเจคนี้จะโชว์ความสวยงามทางคณิตศาสตร์ของ
(currentTime - startTime >= interval)ให้ดู เพราะพฤติกรรมการลบของ unsigned integer นี่แหละ ที่ทำให้ได้เวลาที่ผ่านไปถูกต้องเสมอ แม้จะเกิด rollover ก็ตาม
- Timer0 Overflow: ภายใน Arduino core มันใช้ Timer0 (ซึ่งเป็นฮาร์ดแวร์ไทเมอร์ 8-bit) ในการเพิ่มค่าให้ตัวนับ 32-bit (unsigned long) ทุกๆ 1 มิลลิวินาที กระบวนการนี้ทำงานผ่าน Interrupt Service Routine (ISR) ที่รันอยู่เบื้องหลัง โดยไม่ขึ้นกับฟังก์ชันหลัก
- การจัดการสถานะแบบอะซิงโครนัส:
- ตรรกะ Finite State Machine (FSM): แทนที่จะเขียนโค้ดแบบเรียงลำดับเป็นเส้นตรง โค้ดจะถูกออกแบบเป็นลูปแบบตรวจจับเหตุการณ์ CPU จะ "ตรวจสอบ" เวลา, ทำงานย่อยๆ ถ้าถึงเวลาที่กำหนด, แล้วก็ไปทำคำสั่งถัดไปทันที วิธีนี้ทำให้ระบบยังคงตอบสนองต่อ อินพุตจากโพเทนชิโอมิเตอร์ หรือ การกดปุ่ม ได้ 100% แม้จะมีตัวจับเวลา LED 5 วินาทีกำลังทำงานอยู่ก็ตาม
- วิเคราะห์รอบสัญญาณ CPU:
- กำจัดช่วงรอเปล่า: ใน
delay(1000)ทั่วไป CPU จะเสียรอบสัญญาณไป 16,000,000 รอบโดยไม่ได้ทำอะไรเลย แต่กับ Async-Core รอบสัญญาณ 16 ล้านรอบนั้นจะถูกนำไปใช้ประโยชน์ เช่น ตรวจสอบเซนเซอร์, จัดการบัฟเฟอร์ Serial, หรือคำนวณสมการซับซ้อนอื่นๆ ซึ่งเพิ่ม "ความหนาแน่นของความฉลาด" ให้ระบบได้อย่างมีประสิทธิภาพ
- กำจัดช่วงรอเปล่า: ใน
วิศวกรรมและการนำไปใช้
- การออกแบบงานที่ทำงานพร้อมกัน:
- การนำไปใช้จะสาธิตงานสองงานที่ทำงานพร้อมกัน:
- งาน A: กะพริบ LED ด้วยช่วงเวลาคงที่ 1000ms
- งาน B: อ่านและพล็อตค่าอะนาล็อกจากโพเทนชิโอมิเตอร์ลง Serial ทุกๆ 10ms
- การใช้
millis()จะทำให้กราฟบน Serial Plotter เป็นเส้นเรียบแบบเรียลไทม์ ในขณะที่ถ้าใช้delay(1000)สำหรับ LED กราฟจะ "หยุดนิ่ง" และสูญเสียข้อมูลไปเต็มๆ 1 วินาที
- การนำไปใช้จะสาธิตงานสองงานที่ทำงานพร้อมกัน:
- แพทเทิร์นการออกแบบซอฟต์แวร์:
- โปรเจคนี้จะแนะนำ "แพทเทิร์นการตรวจจับ (Polling Pattern)":
unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; // Execute non-blocking task here } - แพทเทิร์นนี้คือบล็อกพื้นฐานสำคัญสำหรับระบบปฏิบัติการที่ซับซ้อนและเคอร์เนลของ RTOS เลยนะเออ
- โปรเจคนี้จะแนะนำ "แพทเทิร์นการตรวจจับ (Polling Pattern)":
สรุปสั้นๆ แต่อัดแน่น
Async-Core นี่แหละคือประตูบานใหญ่สู่โลกของ การพัฒนา Embedded แบบโปร (Professional Embedded Development) เลยนะน้อง! พอเราเชี่ยวชาญเรื่อง Non-Blocking Logic และ Hardware Timer Forensics แล้วเนี่ย จากที่เคยเขียนโค้ดแบบสคริปต์ง่ายๆ ก็จะอัพเลเวลขึ้นมาเป็นวิศวกรที่ออกแบบแอปพลิเคชันที่ทั้งแข็งแรง (robust) และทำงานแบบมัลติเธรดได้ รับรู้สภาพแวดล้อมแบบเรียลไทม์ได้ชัวร์ๆ
Mastery แบบจัดเต็ม: ยึดครอง CPU ผ่านการทำงานแบบ Async และรู้ลึก รู้จริงเรื่อง Timer
อยากเห็นภาพชัดๆ ว่าทำยังไง ตามไปดูในคลิปด้านล่างนี้ได้เลยจ้า รับรองว่าดูจบแล้วทำเป็น!