title: "Mastering ESP32 Timer Interrupts: ระบบจัดการเวลาอัจฉริยะ (ETA Framework)" description: "เจาะลึกการใช้งาน Hardware Timer ของ ESP32 เพื่อสร้างระบบจัดการ Task แบบขนานผ่าน Elapsed Timer Alerts (ETA) ที่มีความแม่นยำสูง"
บทนำ: ก้าวข้ามขีดจำกัดของ delay() ด้วย Hardware Timers
ในการเขียนโปรแกรมบน ESP32 ปัญหาที่วิศวกรระบบฝังตัวมักพบเจอคือการจัดการเหตุการณ์หลายอย่างที่ต้องเกิดขึ้นในเวลาที่ต่างกัน (Multi-tasking) หากเราใช้ฟังก์ชัน delay() โปรแกรมจะหยุดชะงักและไม่สามารถตอบสนองต่อเหตุการณ์อื่นได้ทันท่วงที
บทความนี้จะนำเสนอ ETA Framework (Elapsed Timer Alerts) ซึ่งเป็นโครงสร้างการเขียนโปรแกรมที่ใช้ประโยชน์จาก Timer Interrupts ของ ESP32 โดยตรง ช่วยให้คุณสามารถสร้าง "ตัวตั้งเวลา" ได้หลายตัวพร้อมกัน (Multiple Timers) ไม่ว่าจะเป็นแบบทำงานครั้งเดียว (One-off) หรือทำงานซ้ำ (Recurring) โดยไม่รบกวนการทำงานหลักของโปรแกรม
แนวคิดของ ETA Framework
หัวใจสำคัญของ Framework นี้คือการนิยามเงื่อนไขการแจ้งเตือนเมื่อเวลาผ่านไป ซึ่งเราเรียกว่า Elapsed Timer Alert (ETA) โดยแต่ละ ETA จะประกอบด้วย:
- Type: ประเภทของ Timer (ทำงานครั้งเดียว หรือ ทำงานวนลูป)
- Alert ID: รหัสประจำตัวที่ไม่ซ้ำกัน (ID 0-65535) เพื่อใช้ในการแยกแยะว่า Task ไหนทำงานเสร็จแล้ว
- Countdown (ms): ระยะเวลาถอยหลังในหน่วยมิลลิวินาที
เจาะลึกวิศวกรรม: ESP32 Hardware Timers (Example 1)
ESP32 มี Hardware Timers ทั้งหมด 4 ตัว (Timer 0 ถึง 3) ซึ่งเป็นตัวนับแบบ 64-bit ที่มีความแม่นยำสูง ในตัวอย่างแรก (Example 1) เราจะกำหนดให้ Timer 0 ทำงานที่ความถี่ 1,000Hz หรือทุกๆ 1 มิลลิวินาที
หลักการทำงานในเชิงลึก:
- Prescaler: สัญญาณนาฬิกาของ ESP32 อยู่ที่ 80MHz เราใช้ Prescaler ขนาด 80 เพื่อหารความถี่ลงมาให้เหลือ 1MHz (1 ล้านครั้งต่อวินาที)
- Alarm: เมื่อตัวนับ (Counter) นับครบ 1,000 ครั้ง มันจะสร้างสัญญาณ Interrupt ขึ้นมา 1 ครั้ง (เท่ากับ 1ms พอดี)
- ISR (Interrupt Service Routine): เมื่อเกิด Interrupt โปรแกรมจะกระโดดไปทำงานในฟังก์ชันพิเศษที่เก็บไว้ใน
IRAM_ATTRเพื่อความรวดเร็ว หน้าที่ของมันใน Framework นี้คือการเพิ่มค่าตัวแปรนับเวลา (Interrupt Count)
โครงสร้างหลักของ ETA Framework (Example 2)
Framework นี้ถูกออกแบบมาให้ทำงานแบบ Asynchronous หมายความว่าตัว Interrupt จะทำหน้าที่นับเวลาอย่างเดียว ส่วนการตัดสินใจหรือ Logic หนักๆ จะถูกยกมาทำใน loop() หลัก เพื่อไม่ให้เครื่องค้าง (WDT Reset)
ขั้นตอนการทำงาน (Pseudo Code):
main loop {
ตรวจสอบจำนวน Interrupt ที่ค้างอยู่;
while (มี Interrupt ที่ยังไม่ได้จัดการ) {
update_ETAs(); // ลดค่า countdown ของทุก Timer ลง 1ms
while (มี ETA ที่ถึงกำหนดเวลา) {
switch (ETA Alert ID) {
case heart_beat:
กะพริบไฟ LED;
case sensor_read_id:
อ่านค่าจากเซนเซอร์;
...
}
}
}
ทำงานส่วนอื่นๆ (เช่น เชื่อมต่อ Wi-Fi หรือประมวลผลข้อมูล);
}
การกำหนดจำนวน Timer
คุณสามารถกำหนดจำนวน Timer สูงสุดที่ระบบจะจัดการได้ผ่าน Macro #define max_ETAs เพื่อให้ระบบจัดสรรหน่วยความจำได้อย่างมีประสิทธิภาพ
การใช้งานฟังก์ชันหลักใน Framework
1. การสร้าง Timer (create_ETA)
ฟังก์ชันนี้ใช้สร้างภารกิจที่ต้องการตั้งเวลา มีพารามิเตอร์ 3 ส่วน:
ETA_type:one_off_ETA(ทำครั้งเดียวแล้วลบออก) หรือrecurring_ETA(ทำซ้ำต่อเนื่อง)ETA_alert_id: ID ของงาน (0 - 65535)ETA_interval: เวลาที่ต้องการ (ms)
ตัวอย่างการสร้าง:
if(create_ETA(recurring_ETA, 102, 500) == ETA_inserted) {
// สร้าง Timer สำหรับส่งข้อมูลทุก 500ms สำเร็จ!
}
2. การลบ Timer (delete_ETA)
เราสามารถสั่งยกเลิกการตั้งเวลาได้ทันทีผ่าน delete_ETA(type, id) ซึ่งมีประโยชน์มากในกรณีที่ต้องการหยุด Task บางอย่างตามเงื่อนไขที่กำหนด
3. การแสดงสถานะ (print_ETAs)
สำหรับการ Debugging ฟังก์ชันนี้จะพิมพ์ตาราง Timer ทั้งหมดออกมาทาง Serial Monitor (115200 baud) เพื่อตรวจสอบว่าค่า Countdown และ Interval ของแต่ละ ID ยังทำงานถูกต้องหรือไม่
กรณีศึกษา: ระบบควบคุมสภาพแวดล้อมอัจฉริยะ (Example 3)
เพื่อให้เห็นภาพการใช้งานจริง Example 3 จำลองระบบดูแลโรงเรือน (Greenhouse Control) ซึ่งต้องจัดการ Task ที่มีความถี่ต่างกันอย่างสิ้นเชิง:
- Heartbeat (500ms): กะพริบไฟ LED เพื่อบอกว่าระบบยังไม่ค้าง
- Sensor Sampling (2 seconds): อ่านค่าอุณหภูมิและความชื้น
- Soil Moisture Check (30 seconds): ตรวจสอบความชื้นในดิน (ซึ่งไม่ต้องตรวจบ่อยเท่าอุณหภูมิ)
- Stats Report (1 minute): สรุปผลการทำงานผ่าน Serial Port
ด้วย ETA Framework เราสามารถนิยามสิ่งเหล่านี้ได้ง่ายๆ ในรูปแบบ Array:
unsigned int my_ETA_data[max_ETAs][3] = {
{recurring_ETA, heart_beat_id, 500},
{recurring_ETA, sensor_read_id, 2000},
{recurring_ETA, soil_moisture_id, 30000},
{recurring_ETA, stats_report_id, 60000}
};
ข้อมูลสนับสนุนด้านเวลา (Time Macros)
Framework เตรียมค่าคงที่เพื่อช่วยให้ Code อ่านง่ายขึ้น (Self-documenting code):
one_day: 86,400,000 msone_hour: 3,600,000 msone_minute: 60,000 msone_second: 1,000 ms
คุณสามารถใช้ค่าเหล่านี้ได้ทันที เช่น create_ETA(one_off_ETA, 5, 2 * one_hour); เพื่อตั้งเวลาอีก 2 ชั่วโมงข้างหน้า
บทสรุปจากมุมมองวิศวกร
การใช้งาน ESP32 Timer Interrupts ผ่าน ETA Framework ช่วยเปลี่ยนจากการเขียนโปรแกรมแบบ "รอคอย" (Blocking) มาเป็นการเขียนโปรแกรมแบบ "ตอบสนองต่อเหตุการณ์" (Event-Driven) อย่างเต็มตัว
ระบบนี้มีความยืดหยุ่นสูง (Scalability) และช่วยลดภาระการคำนวณในส่วนของ ISR ให้เหลือน้อยที่สุด ทำให้ ESP32 ของคุณทำงานได้เสถียร ไม่ติดขัด และสามารถรองรับโปรเจค IoT ที่ซับซ้อนได้อย่างมืออาชีพ
หากต้องการนำไปใช้กับบอร์ดตระกูล Arduino (AVR) คุณเพียงแค่เปลี่ยนส่วนการตั้งค่า Register ของ Timer ให้ตรงกับสถาปัตยกรรมนั้นๆ (เช่นการใช้ AVR Timer Interrupts Calculator) แต่โครงสร้างตรรกะของ ETA Framework ยังคงสามารถใช้งานร่วมกันได้ทันที