Software delay using 7 loops (for empty loop) is simple but inaccurate. The time fluctuates depending on the clock, compiler optimizations, and the number of instructions, and during that time the CPU does nothing. If accurate timing is needed, a hardware timer is used. A timer is a counter that counts clock independently from the CPU, allowing you to create "exactly 50ms."
In this chapter, we create accurate delays by setting the 8051's Timer0/Timer1 to 16-bit mode. The key points are threefold: choose the mode with TMOD, put the reload value in TH/TL so an overflow occurs after the desired time, and poll TF (overflow flag) to detect that moment. Reload value calculation is verified on the PC gcc.
Learning Objectives
- Explain the advantages of hardware timers over software delays.
- Set the of-Timer0/1 mode using
TMOD(Mode 1 = 16-bit). - Calculate reload values (TH/TL) for the desired delay.
- Implement polling delay with
TR(start) andTF(overflow flag). - Long delays exceeding the 16-bit limit are created by counting multiple overflows.
Target Environment
- Compiler: Keil C51.
- Device: AT89C52 (μVision simulator). Assumes 12MHz crystal (1µs machine cycle).
- Verification: μVision simulator (observe TH0/TL0/TF0 in the register window). Reload calculation is on PC
gcc(pc_test.c).
Key Concepts
1) Operation of the 8051 Timer
A timer is a counter that increments by 1 every machine cycle. In the standard 8051, machine cycle = 12 clocks, so with a 12MHz crystal, the machine cycle is 1µs. When the 16-bit counter (TH:TL) increments once more from 0xFFFF, it moves to 0x0000, and the overflow flag (TF) becomes 1.
If you pre-load a large value, it will overflow faster. By inserting "65536 - desired count," the timer will count that number of times before the overflow occurs.
2) TMOD — Timer Mode Register
The lower 4 bits of TMOD configure Timer0, and the upper 4 bits configure Timer1.
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| Meaning | GATE1 | C/T1 | M1 | M0 | GATE0 | C/T0 | M1 | M0 |
| Target | ←─ Timer1 ─→ | ←─ Timer0 ─→ |
| M1 M0 | Mode | Description |
|---|---|---|
| 0 0 | Mode 0 | 13-bit |
| 0 1 | Mode 1 | 16-bit (used in this section) |
| 1 0 | Mode 2 | 8-bit auto-reload |
| 1 1 | Mode 3 | Split |
- Timer0 Mode1:
TMOD = 0x01(lower nibble0001). - Timer1 Mode1:
TMOD = 0x10(upper nibble0001).
To preserve other timer settings, do not overwrite entirely; use a mask: TMOD &= 0xF0; TMOD |= 0x01; (only affects Timer0).
3) Calculating Reload Values
To create a 50ms delay with 12MHz (1µs/count):
Required count = 50ms ÷ 1µs = 50000
Reload value = 65536 - 50000 = 15536 = 0x3CB0
TH0 = 0x3C (high), TL0 = 0xB0 (low)
General formula: reload = 65536 - (delay µs), TH = reload >> 8, TL = reload & 0xFF.
4) TCON — Start and Overflow Flags
| Bit | Name | Function |
|---|---|---|
| TR0 / TR1 | Timer Run | Timer runs if 1 |
| TF0 / TF1 | Timer Flag | 1 on overflow (cleared by software) |
Since reg52.h provides these to sbit, use them directly like TR0 = 1;, while (TF0 == 0);.
5) Polling Delay Flow
Set TMOD → Reload TH/TL → TF=0 → TR=1 (Start)
→ while(TF==0) wait → TR=0 (Stop) → TF=0 (Clear)
Polling locks the CPU while waiting. To do other tasks during the wait, use timer interrupts (chapter 10).
Creating a μVision Project
Project → New µVision Project…→ name itc51_timer.- Device: Atmel → AT89C52. Include STARTUP.A51 (Yes).
- Add
main.cand then copy and paste the code below. - (Optional) To display accurate time, set the Xtal(MHz) on the
Options for Target → Targettab to 12.0. Options for Target → Debug → Use Simulator.- Rebuild(F7) → Start Debug(Ctrl+F5).
- Observe with-TH0/TL0/TF0 operation in the register window (or
Peripherals → Timer → Timer 0).
Examples
Example 1 — main.c: Blinking with Timer Polling Delay
Create 50ms with Timer0 in Mode1 and toggle the LED.
#include <reg52.h>
/*
*Lesson 09 Example 1 — Create an accurate delay using a hardware timer to blink an LED.
*Target: AT89C52, Compiler: Keil C51. Assuming 12MHz crystal (machine cycle 1us).
*
*Use Timer0 in Mode 1 (16-bit). When the counter exceeds 0xFFFF, TF0 (overflow flag) becomes 1.
*Preload the reload value so that an overflow occurs after 50ms:
*Required count = 50ms / 1us = 50000
*Reload = 65536 - 50000 = 15536 = 0x3CB0 → TH0=0x3C, TL0=0xB0
*/
sbit LED = P1 ^ 0; /*Active-low LED*/
/*Approximately 50ms delay with Timer0 Mode 1 (polling method).*/
static void delay_50ms(void)
{
TMOD &= 0xF0; /*Set only Timer0 bits to 0 (preserve Timer1 settings)*/
TMOD |= 0x01; /*Timer0 = Mode 1 (16-bit)*/
TH0 = 0x3C; /*Reload high byte*/
TL0 = 0xB0; /*Reload low byte*/
TF0 = 0; /*Clear overflow flag*/
TR0 = 1; /*Start Timer0*/
while (TF0 == 0) { /*Wait until overflow (=50ms elapsed)*/
}
TR0 = 0; /*Stop Timer0*/
TF0 = 0; /*Clear flag for next round*/
}
void main(void)
{
while (1) {
LED = (bit)!LED; /*Toggle LED*/
delay_50ms(); /*50ms interval → approx. 10Hz blinking*/
}
}
Example 2 — pc_test.c: Verifying Reload Value Calculation
Calculate reload and TH/TL for various delays.
#include <stdio.h>
/*
*Lesson 09 Example 2 — Verify the timer reload value calculation on a PC.
*Based on 12MHz (machine cycle 1us), find the 16-bit reload value and
*the TH/TL bytes required for the desired delay (us). Mode 1 is 16-bit, so a maximum of 65536us in 1us units.
* reload = 65536 - us, TH = reload>>8, TL = reload & 0xFF
*/
int main(void)
{
unsigned int delays_us[] = { 1000, 5000, 50000 };
int n = (int)(sizeof(delays_us) / sizeof(delays_us[0]));
int k;
for (k = 0; k < n; k++) {
unsigned int us = delays_us[k];
unsigned int reload = (unsigned int)(65536UL - us);
unsigned char th = (unsigned char)(reload >> 8);
unsigned char tl = (unsigned char)(reload & 0xFF);
printf("%5u us -> reload=%5u TH0=0x%02X TL0=0x%02X\n",
us, reload, th, tl);
}
return 0;
}
Checking with Simulator
- The mode and execution state can be seen in the ZX window, and TH0/TL0/TF0 can be seen in the register window.
- When run with F5, P1.0 toggles periodically.
- Expected behavior: When set to Xtal 12MHz, it can be confirmed that it toggles approximately every 50ms on the debugger's Cycle/Time display (
Registers → sec). If P1.0 is added to a Logic Analyzer, a roughly 10Hz square wave is observed.
Verification on a PC
gcc pc_test.c -o pc_test && ./pc_test
Expected output (stdout):
1000 us -> reload=64536 TH0=0xFC TL0=0x18
5000 us -> reload=60536 TH0=0xEC TL0=0x78
50000 us -> reload=15536 TH0=0x3C TL0=0xB0
Common Mistakes
Q. When I assigned it all at once to TMOD = 0x01, the Timer1 settings were lost.
A. TMOD contains both timers together. If you assign it all at once, the other timer settings are erased. Only change the bits for the respective timer: TMOD &= 0xF0; TMOD |= 0x01; (Timer0). Timer1 is TMOD &= 0x0F; TMOD |= 0x10;.
Q. The delay time is different from what I expected.
A. ① If the crystal frequency is not 12MHz, the machine cycle is not 1µs (recalculate reload). ② Check that the simulator's Xtal setting matches the real one. ③ Some 8051 variants are in "1T" mode (machine cycle = 1 clock), making it 6–12 times faster.
Q. The first delay works, but subsequent delays pass immediately.
A. This is because TF (overflow flag) is not cleared again. Since Mode1 is not auto-reload, for each delay you need to reload TH/TL and clear TF to 0.
Q. I can't create a long delay, like 1 second.
A. 16-bit (1µs unit) can go up to ~65ms at a time. For longer periods, create it by counting several short overflows (e.g., 50ms) multiple times (1 second = 50ms × 20). You will practice this calculation in Assignment 2.
Summary
- The hardware timer creates accurate time independently of the CPU.
- Set mode with TMOD (Mode1 = 16-bit), Timer0 is the lower nibble, Timer1 is the upper nibble.
- Reload = 65536 − count, TH = high, TL = low.
- Start with TR, catch overflow timing by polling TF, and clear TF each time.
- Delays beyond the 16-bit limit are created by counting overflow occurrences.
Try it yourself
Solve the assignment below. The reference solution is shown below.