01076051Lecture 06

Interrupts & Exceptions

บทที่ 6: การขัดจังหวะ (Interrupt) และข้อยกเว้น (Exception)

ตอนอ่านปุ่มกดในบทก่อน ๆ เราใช้วิธี “คอยไปเช็คเรื่อย ๆ” (polling) ซึ่งเปลือง CPU และบางทีทำให้ระบบดูหน่วง. บทนี้แนะนำวิธีที่ดีกว่าคือ interrupt — ปล่อยให้ฮาร์ดแวร์ “สะกิด” CPU เมื่อมีเหตุการณ์เกิดขึ้นจริง ๆ แทนที่จะคอยถามเอง. เราจะเข้าใจความต่างระหว่าง polling กับ interrupt, รู้จัก exception กับ interrupt controller ชื่อ NVIC ของ Cortex-M7, เรื่อง priority และ vector table และลงมือตั้งค่าปุ่ม B1 บน NUCLEO-F767ZI ให้ทำงานแบบ interrupt ผ่านโมดูล EXTI.

ผลการเรียนรู้ (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) เสร็จแล้วค่อยกลับมาทำงานเดิมต่อจากจุดที่ค้างไว้.

หัวข้อPollingInterrupt
วิธีตรวจจับCPU คอยไปอ่านสถานะเองฮาร์ดแวร์ส่งสัญญาณมาบอก CPU
ใช้ CPUเปลือง(วนเช็กตลอด)ประหยัด (ทำงานอื่นได้)
ความเร็วตอบสนองขึ้นกับรอบลูป อาจหน่วงตอบทันทีที่เหตุการณ์เกิด
โอกาสพลาด eventมี (ถ้าไม่ได้อ่านตอนนั้น)น้อย (hardware จับขอบให้)
ความซับซ้อนโค้ดง่ายกว่าต้องตั้ง ISR/priority
Polling เทียบกับ Interrupt
VIZ-28Polling vs Interrupt Timeline
eventPollingงานหลักเช็กงานหลักเช็กงานหลักเช็กงานหลักเช็กInterruptISRสะกิด → service ทันที
Polling
หน่วง 12 หน่วยเวลา กว่าจะถึงรอบ "เช็ก input" ถัดไป
Interrupt
หน่วง ~0 — สะกิดแล้วแทรก ISR ทันที

เลื่อนตำแหน่งที่ 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 อยู่
สถานะของ interrupt
VIZ-29Interrupt State Machine
event เกิดCPU serviceservice เสร็จ + acknowledge / clearinactivependingactive
pending bit:0

สถานะปัจจุบัน: 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หมายเหตุ
ResetReset_Handlerเริ่มโปรแกรมใหม่, non-maskable
NMINMI_Handlerปิดกั้นไม่ได้
HardFaultHardFault_Handlerเกิดข้อผิดพลาดร้ายแรง
SysTickSysTick_Handlerตัวจับเวลาของระบบ
IRQ 0..nเช่น EXTI15_10_IRQHandlerinterrupt ของอุปกรณ์รอบข้าง (maskable)
ตัวอย่างการจัดวาง vector table (เรียงตามหมายเลข)
VIZ-30NVIC Preemption + EXTI Line Sharing
เลขน้อย = สำคัญกว่า
B มาถึงISR A (prio 2)AA (ต่อ)ISR B (prio 1)B แทรก

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.

ISR ของปุ่ม B1 (PC13) ผ่าน EXTI ด้วย HALc
// 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)
  }
}
VIZ-30NVIC Preemption + EXTI Line Sharing
เลขน้อย = สำคัญกว่า
B มาถึงISR A (prio 2)AA (ต่อ)ISR B (prio 1)B แทรก

B แทรก A ได้ (prio 1 < 2) → nesting

สองมุมที่สัมพันธ์กัน: priority/preemption (interrupt ที่สำคัญกว่าแทรกตัวที่กำลังทำอยู่) และ EXTI line sharing (พินหมายเลขเดียวกันทุกพอร์ตแชร์สายและ handler เดียวกัน).

แบบฝึกหัด

12 ข้อ — แต่ละข้อมีชนิดและระดับความยากกำกับ กดปุ่ม "ดูเฉลย" เพื่อดูคำตอบและคำอธิบาย

  1. 1.ปรนัยง่าย

    ข้อใดอธิบายการทำงานแบบ interrupt ได้ถูกต้อง

  2. 2.ปรนัยง่าย

    ข้อเสียหลักของ polling คืออะไร

  3. 3.ปรนัยกลาง

    maskable interrupt ต่างจาก non-maskable interrupt อย่างไร

  4. 4.ปรนัยกลาง

    ชื่อเต็มของ interrupt controller ในตัวของ Cortex-M คือข้อใด

  5. 5.ปรนัยยาก

    ทำไม NVIC ของ Cortex-M จึงตอบสนอง interrupt ได้เร็ว (latency ต่ำ) กว่าสถาปัตยกรรมที่วาง controller ไว้บนบัส

  6. 6.ถูก/ผิดง่าย

    ค่า priority ที่เป็นตัวเลขน้อยกว่า หมายถึง interrupt ที่สำคัญกว่า (ได้ service ก่อน)

  7. 7.ถูก/ผิดกลาง

    ขา PA0, PB0 และ PC0 สามารถใช้เป็น EXTI interrupt แยกกันพร้อมกันได้ เพราะคนละพอร์ต

  8. 8.ถูก/ผิดง่าย

    การกดปุ่ม Reset เป็นการตัดไฟออกจากบอร์ดชั่วคราว

  9. 9.เติมคำกลาง

    สถานะของ interrupt ที่ 'เกิดขึ้นแล้วและกำลังรอ CPU มา service' เรียกว่า ____ ส่วนที่ 'กำลังถูก service อยู่' เรียกว่า ____

  10. 10.เติมคำกลาง

    ปุ่ม B1 บน NUCLEO-F767ZI ต่อที่ขา PC____ จึงใช้สาย EXTI หมายเลข ____

  11. 11.วิเคราะห์ยาก

    โปรแกรมหนึ่งมีลูปหลักที่หน่วงด้วย HAL_Delay(2000) แล้วค่อยอ่านปุ่มแบบ polling ผู้ใช้บ่นว่ากดปุ่มแล้วบางครั้งไม่ติด จงอธิบายสาเหตุและเสนอวิธีแก้

  12. 12.วิเคราะห์ยาก

    ขณะ CPU กำลัง service interrupt A (priority ตัวเลข 5) อยู่ เกิด interrupt B (priority ตัวเลข 2) ขึ้นมา จะเกิดอะไรขึ้น และคุณสมบัติใดของ NVIC ที่ทำให้เป็นเช่นนั้น