ผลการเรียนรู้ (Learning Outcomes)
- เปรียบเทียบ polling กับ interrupt ได้ว่าต่างกันอย่างไร ข้อดี-ข้อเสียของแต่ละแบบ
- อธิบายกลไกการเกิด interrupt/exception: จากเหตุการณ์ → ส่งสัญญาณถึง CPU → หยุดงานเดิม → วิ่งเข้า ISR → กลับมาทำต่อ
- แยกประเภท exception ได้ (reset, non-maskable vs maskable) และอธิบายสถานะ inactive / pending / active
- อธิบายบทบาทของ NVIC, vector table และการค้นหา handler ด้วยหมายเลข IRQ ได้
- อธิบายเรื่อง priority และ preemption ได้ รวมถึงการแบ่ง priority bit เป็น group priority กับ sub-priority
- ตั้งค่าให้ปุ่ม B1 (PC13) บน NUCLEO-F767ZI ทำงานแบบ interrupt ผ่าน EXTI ตรวจจับขอบสัญญาณ และเขียน callback ได้
§6.1 Polling เทียบกับ Interrupt
Polling คือวิธีตรวจจับ input ที่เราใช้มาตลอดกับปุ่มกด: โปรแกรมวนลูป (infinite loop) แล้วคอยไปอ่านสถานะของ input เป็นระยะ ๆ ว่ามีการกดปุ่มหรือยัง.
อุปมา polling: เรารู้ว่าจะมีพัสดุมาส่ง เลยต้องคอยลุกไปเปิดดูที่หน้าประตูเรื่อย ๆ ว่ามาหรือยัง. ระหว่างนั้นถ้าเรากำลังอ่านหนังสือ (งานหลัก) ก็ต้องวางหนังสือ ไปเช็ก แล้วกลับมาอ่านต่อ ทำซ้ำไปเรื่อย ๆ.
ปัญหาของ polling: ถ้างานอื่นในลูปกินเวลานาน กว่าจะวนกลับมาถึงคำสั่งอ่าน input ก็ช้า ทำให้ระบบดูเหมือนไม่ตอบสนอง (ทั้งที่จริงมันทำงานอยู่ แต่ไปติดงานอื่น). ถ้าใส่ delay เช่น 2 วินาทีต่อการอ่าน 1 ครั้ง ก็จะยิ่งหน่วง. การกดปุ่มช่วงที่โปรแกรมไม่ได้อ่านอยู่ อาจพลาดไปเลย.
Interrupt คือทางออก: แทนที่เราจะคอยเช็ก เราให้ฮาร์ดแวร์ “ส่งสัญญาณมาบอก” CPU เมื่อ input เกิดขึ้นจริง. อุปมา: ไปรษณีย์หย่อนจดหมายแล้ว “กดกริ่ง” บอกเรา เราจึงลุกไปรับเฉพาะตอนมีของจริง ระหว่างนั้นอ่านหนังสือได้เต็มที่ ไม่ต้องคอยลุกไปเช็ก.
เมื่อ interrupt เกิด CPU จะหยุดงานปัจจุบันชั่วคราว ไปทำโปรแกรมสั้น ๆ ที่ตอบสนองเหตุการณ์นั้น (เรียก Interrupt Service Routine หรือ ISR) เสร็จแล้วค่อยกลับมาทำงานเดิมต่อจากจุดที่ค้างไว้.
| หัวข้อ | Polling | Interrupt |
|---|---|---|
| วิธีตรวจจับ | CPU คอยไปอ่านสถานะเอง | ฮาร์ดแวร์ส่งสัญญาณมาบอก CPU |
| ใช้ CPU | เปลือง(วนเช็กตลอด) | ประหยัด (ทำงานอื่นได้) |
| ความเร็วตอบสนอง | ขึ้นกับรอบลูป อาจหน่วง | ตอบทันทีที่เหตุการณ์เกิด |
| โอกาสพลาด event | มี (ถ้าไม่ได้อ่านตอนนั้น) | น้อย (hardware จับขอบให้) |
| ความซับซ้อนโค้ด | ง่ายกว่า | ต้องตั้ง ISR/priority |
เลื่อนตำแหน่งที่ event เกิด. แบบ polling จะเห็นผลเฉพาะตอน CPU วนมาเช็ก input (อาจหน่วง/พลาด) ส่วนแบบ interrupt สะกิดให้ CPU แทรก ISR ทันที.
§6.2 Exception, ประเภท และสถานะของ Interrupt
คำว่า exception เป็นคำกว้างกว่า interrupt: มันคือเหตุการณ์ใด ๆ ที่ทำให้ CPU หยุดงานปกติแล้วไปทำ handler. interrupt (จากฮาร์ดแวร์ภายนอก เช่น ปุ่ม) ถือเป็น exception ชนิดหนึ่ง. กลไกนี้ไม่ได้มีเฉพาะในฮาร์ดแวร์ ใน OS ก็ใช้แนวคิดเดียวกันเพื่อรับหลาย ๆ เหตุการณ์พร้อมงานหลัก.
แบ่งตามแหล่งกำเนิด: exception ที่มาจากฮาร์ดแวร์ (เช่น สัญญาณจากขา GPIO) และ software exception ที่เกิดจากโค้ดเอง (เช่น คำสั่งที่ตั้งใจทำให้เกิด exception หรือข้อผิดพลาดขณะรัน).
แบ่งตามว่า “ปิดกั้นได้ไหม”: non-maskable กับ maskable. คำว่า mask แปลว่าหน้ากาก — maskable interrupt คือ interrupt ที่เราสั่ง “ไม่สนใจชั่วคราว” ได้ (เช่น ตอนกำลังทำงานสำคัญไม่อยากให้ถูกขัด). ส่วน non-maskable interrupt (NMI) สำคัญมากจนปิดไม่ได้ ต้องตอบสนองเสมอ.
Reset เป็น exception พิเศษที่สำคัญสุด. ปุ่ม Reset ไม่ใช่ปุ่มตัดไฟ — เมื่อกด มันสั่ง CPU ให้เริ่มกระบวนการรีเซ็ต โดยกระโดดเข้าฟังก์ชันชื่อ Reset handler เพื่อเริ่มโปรแกรมใหม่ตั้งแต่ต้น (ไฟยังอยู่ครบ).
แต่ละ interrupt มี 3 สถานะหลัก: inactive (ยังไม่เกิด ไม่รอคิว), pending (เกิดแล้วและกำลังรอให้ CPU มา service), active (กำลังถูก service อยู่). เมื่อ CPU เข้า service แล้ว เรามักต้อง acknowledge เพื่อเคลียร์ pending bit ไม่ให้มันค้างเกิดซ้ำ — บนหลายฮาร์ดแวร์การ acknowledge ทำโดยการอ่านสถานะของ interrupt นั้น.
| สถานะ | ความหมาย |
|---|---|
| inactive | ยังไม่เกิด ไม่ได้รอคิว |
| pending | เกิดขึ้นแล้ว รอ CPU มา service |
| active | กำลังถูก service อยู่ |
สถานะปัจจุบัน: inactive · pending bit = 0
วงจรชีวิตของ interrupt: inactive → (event) → pending → (service) → active → (acknowledge) → inactive. pending bit ถูกตั้งตอนเข้าสู่ pending และเคลียร์ตอน acknowledge.
§6.3 NVIC, Vector Table และ Priority
Cortex-M มี interrupt controller ในตัวชื่อ NVIC (Nested Vectored Interrupt Controller). จุดเด่นคือมันวางอยู่ “ติด” กับแกน (core) ของ Cortex เลย คุยกันด้วยสัญญาณตรง ๆ ไม่ต้องผ่านบัสระบบ (เช่น AHB/APB) ทำให้ตอบสนอง interrupt ได้เร็วกว่าสถาปัตยกรรมอื่น ลด latency (เวลาตั้งแต่ event เกิดจนได้รับ service). ส่วน memory/โค้ดยังต้องคุยผ่านบัส.
คำว่า Nested หมายความว่า interrupt ซ้อนกันได้: ขณะกำลัง service ตัวหนึ่ง ถ้ามีตัวที่ priority สูงกว่าเข้ามา CPU จะหยุดตัวแรกไว้ไป service ตัวสำคัญกว่าก่อน (preemption) แล้วค่อยกลับมาทำตัวเดิมต่อ. คำว่า Vectored หมายถึงมันหา handler จากตารางด้วยหมายเลขโดยตรง (ดูเรื่อง vector table).
Vector table: เป็นตารางที่เก็บที่อยู่ของ handler ของแต่ละ exception/interrupt เรียงตามหมายเลข. เมื่อเกิด interrupt CPU จะรู้ว่าเป็นหมายเลขอะไร แล้ว look up ในตารางเพื่อกระโดดไปยัง handler ที่ถูกต้อง. การใช้หมายเลขทำให้ look up เร็ว. หมายเลขต่ำ ๆ สงวนไว้สำหรับ exception ของระบบ (เช่น Reset, NMI, HardFault, SysTick) ส่วน interrupt ของอุปกรณ์รอบข้างจะเริ่มที่ IRQ หมายเลข 0 เป็นต้นไป.
Priority: เมื่อหลาย interrupt มาพร้อมกัน ต้องมีลำดับว่าจะ service ใครก่อน. อุปมา: นักศึกษายกมือถามพร้อมกัน 5 คน ครูต้องเลือกว่าให้ใครถามก่อน. ค่า priority ที่ตัวเลข “น้อย” คือสำคัญมากกว่า (มาก่อน).
Cortex-M (เช่น M7) มักมีช่อง priority 4 บิต = 16 ระดับ (2^4). บิตเหล่านี้แบ่งได้เป็น 2 กลุ่มด้วย priority grouping: group priority (ตัดสินเรื่อง preemption ว่าตัวไหนแทรกตัวไหนได้) กับ sub-priority (ตัดสินลำดับเฉพาะตอนมาพร้อมกันและ group เท่ากัน โดยไม่แทรกกัน). เช่น ถ้าใช้ 3 บิตเป็น group และ 1 บิตเป็น sub จะได้ 2^3 = 8 ระดับ preemption และ sub-priority 2 ค่า.
NVIC มี register ควบคุมหลายตัว เช่น enable/disable แยกของแต่ละ interrupt, บิต pending, และการตั้งค่า priority. งานที่ต้องการ latency ต่ำควรตั้ง priority ให้สูง (ตัวเลขน้อย).
| หมายเลข/ชนิด | ตัวอย่าง handler | หมายเหตุ |
|---|---|---|
| Reset | Reset_Handler | เริ่มโปรแกรมใหม่, non-maskable |
| NMI | NMI_Handler | ปิดกั้นไม่ได้ |
| HardFault | HardFault_Handler | เกิดข้อผิดพลาดร้ายแรง |
| SysTick | SysTick_Handler | ตัวจับเวลาของระบบ |
| IRQ 0..n | เช่น EXTI15_10_IRQHandler | interrupt ของอุปกรณ์รอบข้าง (maskable) |
B แทรก A ได้ (prio 1 < 2) → nesting
สองมุมที่สัมพันธ์กัน: priority/preemption (interrupt ที่สำคัญกว่าแทรกตัวที่กำลังทำอยู่) และ EXTI line sharing (พินหมายเลขเดียวกันทุกพอร์ตแชร์สายและ handler เดียวกัน).
§6.4 EXTI: ต่อปุ่มกด (PC13) ให้เป็น Interrupt
ถ้าใช้ขาเป็น GPIO ธรรมดา เราต้อง poll อ่านสถานะเอง. แต่ถ้าอยากให้ปุ่ม “สะกิด” CPU ได้ ต้องให้ขานั้นต่อเข้ากับโมดูล interrupt ของ GPIO ชื่อ EXTI (External Interrupt/Event Controller).
มอง EXTI เป็นส่วนขยายของ GPIO ที่คอยตรวจจับการ “เปลี่ยนแปลงระดับสัญญาณ” บนขา. ถ้า enable ไว้ มันจะส่งสัญญาณ interrupt ไปบอก NVIC. เพราะต้องตรวจ “การเปลี่ยน” มันจึงทำงานด้วยการตรวจจับขอบ (edge): ขอบขาขึ้น (rising edge, 0→1) หรือขอบขาลง (falling edge, 1→0) อย่างใดอย่างหนึ่งหรือทั้งสอง.
ข้อจำกัดสำคัญของ EXTI: มันแบ่งเป็นสาย EXTI0..EXTI15 ตาม “หมายเลขพิน” ไม่ใช่ตามพอร์ต. ทุกพอร์ตที่พินหมายเลขเดียวกันจะ “แชร์” สาย EXTI เส้นเดียวกัน — เช่น PA0, PB0, PC0, PD0 แชร์ EXTI0 ร่วมกัน; PA1, PB1, PC1 แชร์ EXTI1; ไล่ไปจนพิน 15. ดังนั้นในเวลาเดียวกันเลือกได้แค่พอร์ตเดียวต่อหนึ่งหมายเลขพิน.
ปุ่ม B1 (ปุ่ม user สีน้ำเงิน) บน NUCLEO-F767ZI ต่ออยู่ที่ขา PC13. พิน 13 จึงใช้สาย EXTI13. ใน vector table สาย EXTI10 ถึง EXTI15 ถูกรวมเข้า handler เดียวกันคือ EXTI15_10_IRQHandler (เพราะพิน 10–15 แชร์ช่องนี้ ต้องเช็กในโค้ดว่าเป็นพินไหนที่ทำให้เกิด).
ขั้นตอนตั้งค่าใน STM32CubeMX: ตั้งโหมดของ PC13 เป็น GPIO_EXTI (external interrupt) เลือกว่าจะจับ rising / falling / both แล้วไป enable เส้น EXTI ที่เกี่ยวข้องใน NVIC พร้อมตั้ง priority. เมื่อ generate code จะได้ handler ให้.
การเขียน ISR ด้วย HAL: เมื่อกดปุ่ม EXTI จะเรียก handler ซึ่ง HAL จะไปเรียกฟังก์ชัน callback ชื่อ HAL_GPIO_EXTI_Callback ให้เราอีกที โดยส่งหมายเลขพินมาด้วย เราแค่เขียนโค้ดตอบสนองในนั้น เช่น toggle LED. HAL ได้เคลียร์ pending bit ให้แล้วในชั้น handler.
// HAL จะเรียก callback นี้เมื่อเกิด EXTI interrupt
// ส่ง GPIO_Pin มาบอกว่าพินไหนเป็นต้นเหตุ (เพราะพิน 10..15 แชร์ช่องเดียว)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_13) { // ปุ่ม B1 อยู่ที่ PC13 -> EXTI13
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); // สลับสถานะ LD1 (PB0)
}
}B แทรก A ได้ (prio 1 < 2) → nesting
สองมุมที่สัมพันธ์กัน: priority/preemption (interrupt ที่สำคัญกว่าแทรกตัวที่กำลังทำอยู่) และ EXTI line sharing (พินหมายเลขเดียวกันทุกพอร์ตแชร์สายและ handler เดียวกัน).
แบบฝึกหัด
12 ข้อ — แต่ละข้อมีชนิดและระดับความยากกำกับ กดปุ่ม "ดูเฉลย" เพื่อดูคำตอบและคำอธิบาย
- 1.ปรนัยง่าย
ข้อใดอธิบายการทำงานแบบ interrupt ได้ถูกต้อง
- 2.ปรนัยง่าย
ข้อเสียหลักของ polling คืออะไร
- 3.ปรนัยกลาง
maskable interrupt ต่างจาก non-maskable interrupt อย่างไร
- 4.ปรนัยกลาง
ชื่อเต็มของ interrupt controller ในตัวของ Cortex-M คือข้อใด
- 5.ปรนัยยาก
ทำไม NVIC ของ Cortex-M จึงตอบสนอง interrupt ได้เร็ว (latency ต่ำ) กว่าสถาปัตยกรรมที่วาง controller ไว้บนบัส
- 6.ถูก/ผิดง่าย
ค่า priority ที่เป็นตัวเลขน้อยกว่า หมายถึง interrupt ที่สำคัญกว่า (ได้ service ก่อน)
- 7.ถูก/ผิดกลาง
ขา PA0, PB0 และ PC0 สามารถใช้เป็น EXTI interrupt แยกกันพร้อมกันได้ เพราะคนละพอร์ต
- 8.ถูก/ผิดง่าย
การกดปุ่ม Reset เป็นการตัดไฟออกจากบอร์ดชั่วคราว
- 9.เติมคำกลาง
สถานะของ interrupt ที่ 'เกิดขึ้นแล้วและกำลังรอ CPU มา service' เรียกว่า ____ ส่วนที่ 'กำลังถูก service อยู่' เรียกว่า ____
- 10.เติมคำกลาง
ปุ่ม B1 บน NUCLEO-F767ZI ต่อที่ขา PC____ จึงใช้สาย EXTI หมายเลข ____
- 11.วิเคราะห์ยาก
โปรแกรมหนึ่งมีลูปหลักที่หน่วงด้วย HAL_Delay(2000) แล้วค่อยอ่านปุ่มแบบ polling ผู้ใช้บ่นว่ากดปุ่มแล้วบางครั้งไม่ติด จงอธิบายสาเหตุและเสนอวิธีแก้
- 12.วิเคราะห์ยาก
ขณะ CPU กำลัง service interrupt A (priority ตัวเลข 5) อยู่ เกิด interrupt B (priority ตัวเลข 2) ขึ้นมา จะเกิดอะไรขึ้น และคุณสมบัติใดของ NVIC ที่ทำให้เป็นเช่นนั้น