09–10. Timer0/1 and Precise Delays · External & Timer Interrupts
Lesson 7's software delay is imprecise and ties up the CPU. Precise time comes from a hardware timer — a counter that ticks independently of the CPU. We set 8051's Timer0/1 to 16-bit mode (Mode1) for exact delays. The keys: TMOD for mode, a reload value (65536−count) in TH/TL, and polling the overflow flag (TF). Reload math is verified on PC. 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 the advantage of a hardware timer over software delay
- 2Set Timer0/1 mode via TMOD (Mode1=16-bit)
- 3Compute the reload value (TH/TL) for a desired delay
- 4Implement polling delay with TR (start) and TF (overflow flag)
- 5Make delays longer than the 16-bit limit by counting overflows
- 6Explain interrupts (ISR·vector·async events) vs polling
- 7Enable interrupts via IE (EA/EX0/ET0)
- 8Write ISRs with C51's void isr(void) interrupt N
- 9Set up external (INT0) and timer interrupts
- 10Know why ISR↔main shared variables need volatile
09. Timer0/1 and Precise Delays
Lesson 7's software delay is imprecise and ties up the CPU. Precise time comes from a hardware timer — a counter that ticks independently of the CPU. We set 8051's Timer0/1 to 16-bit mode (Mode1) for exact delays. The keys: TMOD for mode, a reload value (65536−count) in TH/TL, and polling the overflow flag (TF). Reload math is verified on PC.
Introduction
A timer is a counter that increments every machine cycle. A standard 8051 has machine cycle = 12 clocks, so at 12MHz a machine cycle is 1µs. The 16-bit counter (TH:TL) rolls 0xFFFF→0x0000 and sets the overflow flag (TF).
Key concepts
1) TMOD — mode register
TMOD's low nibble is Timer0, high nibble Timer1. M1M0=01 is Mode1 (16-bit). Timer0 Mode1 = TMOD=0x01, Timer1 Mode1 = TMOD=0x10. Preserve the other timer: `TMOD &= 0xF0; TMOD |= 0x01;`.
2) Reload value
/* 50ms at 12MHz (1us/count) */
needed count = 50ms / 1us = 50000
reload = 65536 - 50000 = 15536 = 0x3CB0
TH0 = 0x3C; TL0 = 0xB0;
/* general: reload = 65536 - us, TH = reload>>8, TL = reload & 0xFF */3) TCON — start and flags, polling flow
| Bit | Role |
|---|---|
| TR0/TR1 | 1 runs the timer (start) |
| TF0/TF1 | set on overflow (software clears) |
set TMOD → load TH/TL → TF=0 → TR=1
→ while(TF==0) wait → TR=0 → TF=0 (clear)Polling ties up the CPU while waiting. To do other work meanwhile, use a timer interrupt (lesson 10).
Core example
#include <reg52.h>
sbit LED = P1 ^ 0;
static void delay_50ms(void) {
TMOD &= 0xF0; TMOD |= 0x01; /* Timer0 Mode1 */
TH0 = 0x3C; TL0 = 0xB0; /* 50ms @ 12MHz */
TF0 = 0; TR0 = 1; /* start */
while (TF0 == 0) { } /* wait for overflow */
TR0 = 0; TF0 = 0; /* stop·clear */
}
void main(void){ while(1){ LED = (bit)!LED; delay_50ms(); } }Reload check: 1000µs→0xFC18, 5000µs→0xEC78, 50000µs→0x3CB0.
Common mistakes
Q. Assigning TMOD = 0x01 wiped Timer1's config.
A. TMOD holds both timers. Change only that timer's bits: Timer0 is `TMOD &= 0xF0; TMOD |= 0x01;`.
Q. The first delay works, then it passes instantly.
A. You didn't re-clear TF. Mode1 isn't auto-reload, so reload TH/TL and clear TF=0 each time.
Q. I can't make a long delay like 1 second.
A. 16-bit (1µs) caps at ~65ms per shot. Make longer delays by counting short overflows (1s = 50ms × 20).
Summary
- A hardware timer makes precise time independent of the CPU
- TMOD sets mode (Mode1=16-bit); Timer0=low nibble, Timer1=high nibble
- reload = 65536 − count, TH=high·TL=low
- TR starts, poll TF for overflow, clear TF every time
- Make long delays by counting overflows
Exercises
- Hand-compute TH/TL for 10ms·100ms at 12MHz and verify in pc_test
- Write a 1-second delay by counting twenty 50ms overflows
- Make the same delay with Timer1 and check the TMOD high-nibble setup
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.
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 ↗Comments 0
No comments yet. Be the first.
Only members can comment.
Comments with ads, abuse, personal information or illegal content are hidden or removed. Comments are shown with your nickname. Privacy Policy