← Back to Embedded C series
🔌
8051
8051 · Prereq: lesson 8 · 8051 · Prereq: lesson 9

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.

timerTMODTH/TLoverflowTFreloadinterruptISRIEinterrupt NvolatileINT0
Duration
⏱ ~1.5 hours + ~1.5 hours
Level
📊 Beginner
Prerequisite
🎯 Lesson 8
OUTCOME
Set Timer0/1 to Mode1, compute reload (65536−count), and poll TF for precise delays. / Enable interrupts via IE, write external/timer ISRs with C51's interrupt N, and mark ISR↔main shared variables volatile for safe handling.

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

c
/* 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

BitRole
TR0/TR11 runs the timer (start)
TF0/TF1set on overflow (software clears)
c
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

c
#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

  1. Hand-compute TH/TL for 10ms·100ms at 12MHz and verify in pc_test
  2. Write a 1-second delay by counting twenty 50ms overflows
  3. 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
0external interrupt 0 (INT0, P3.2)
1Timer 0 overflow
2external interrupt 1 (INT1)
3Timer 1 overflow
4serial (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

c
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

c
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

c
#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

  1. Write a Timer0-overflow ISR (interrupt 1) that toggles an LED periodically
  2. Toggle another LED in main every time press_count reaches 5 (ISR only counts)
  3. Switch IT0 to 0 (level) and observe/explain the difference
Example code / lecture materials

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