10. External & Timer Interrupts
Polling is precise but ties up the CPU. With interrupts, main does its work and, when an event (button·timer overflow) occurs, the CPU briefly jumps into an ISR and returns. "React only when something happens" is the core embedded paradigm. We enable interrupts via IE, write ISRs with C51's interrupt N syntax, and learn why shared variables need volatile.
What you'll learn
- 1Explain interrupts (ISR·vector·async events) vs polling
- 2Enable interrupts via IE (EA/EX0/ET0)
- 3Write ISRs with C51's void isr(void) interrupt N
- 4Set up external (INT0) and timer interrupts
- 5Know why ISR↔main shared variables need volatile
Introduction
When an event occurs, hardware halts current code and jumps to the function (ISR) at the interrupt vector, then returns. Polling's waiting waste disappears and the reaction is immediate.
Key concepts
1) Interrupt sources / IE
| No. | Source |
|---|---|
| 0 | external interrupt 0 (INT0, P3.2) |
| 1 | Timer 0 overflow |
| 2 | external interrupt 1 (INT1) |
| 3 | Timer 1 overflow |
| 4 | serial (UART) |
Rule: enable individual interrupts (EX0=1…), then global EA=1 last. With EA=0, nothing fires no matter what you enable.
2) ISR syntax and trigger mode
void int0_isr(void) interrupt 0 /* external0 = 0, timer0 = 1 ... */
{
/* short and fast! avoid heavy work like printf·delay */
}
/* IT0=0: level trigger (while Low), IT0=1: falling edge (once per press) */3) volatile — shared variables
volatile unsigned char press_count; /* ISR ↔ main shared */
/* the ISR changes it asynchronously, so re-read from memory each time */Keep ISRs short. For heavy work, set only a volatile flag in the ISR and handle it in the main loop.
Core example
#include <reg52.h>
sbit LED = P1 ^ 0;
volatile unsigned char press_count = 0;
void int0_isr(void) interrupt 0 { /* called on each falling edge */
press_count++;
LED = (bit)!LED;
}
void main(void) {
IT0 = 1; /* falling-edge trigger */
EX0 = 1; /* enable external0 */
EA = 1; /* global enable (master) */
while (1) { /* main is free */ }
}Simulator: each time you flip P3.2 from 1→0 in Port 3, int0_isr toggles P1.0 and increments press_count.
Common mistakes
Q. The ISR is never called.
A. Most likely EA=1 (global enable) is off. Individual enables aren't enough. Also check the interrupt N number matches the source (external0=0, timer0=1).
Q. A variable changed in the ISR looks unchanged in main.
A. You dropped volatile on the shared variable. If main caches it, ISR changes go unseen.
Q. One press fires the ISR several times.
A. IT0=0 (level) triggers continuously while held. Switch to IT0=1 (edge). If it's chatter, debounce (lesson 21).
Summary
- Interrupts run an ISR only on events, removing polling's waiting waste
- Enable individual IE bits (EX0/ET0) then EA=1 globally last
- C51 ISR is void isr(void) interrupt N (external0=0, timer0=1…)
- External interrupts choose level/edge via IT0
- Mark ISR↔main shared variables volatile, keep ISRs short
Exercises
- Write a Timer0-overflow ISR (interrupt 1) that toggles an LED periodically
- Toggle another LED in main every time press_count reaches 5 (ISR only counts)
- Switch IT0 to 0 (level) and observe/explain the difference
All lecture materials and example code (with homework and answers) are openly available on GitHub.
View on GitHub ↗