Embedded C track

Lesson 09 · Part 2: 8051 (Keil C51 / AT89C52)

Timer 0/1 & Delays

Hardware timers

Part 2: 8051 (Keil C51 / AT89C52)7 min readFree lesson

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) and TF (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 nibble 0001).
  • Timer1 Mode1: TMOD = 0x10 (upper nibble 0001).

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

  1. Project → New µVision Project… → name it c51_timer.
  2. Device: Atmel → AT89C52. Include STARTUP.A51 (Yes).
  3. Add main.c and then copy and paste the code below.
  4. (Optional) To display accurate time, set the Xtal(MHz) on the Options for Target → Target tab to 12.0.
  5. Options for Target → Debug → Use Simulator.
  6. Rebuild(F7) → Start Debug(Ctrl+F5).
  7. 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.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/c51_timer/main.cC
#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*/
    }
}
src/c51_timer/pc_test.cC
#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;
}

Assignment

Question 1 is for C51 (AT89C52, μVision simulator), and Question 2 is for pure logic (PC gcc verification).


Problem 1 — Blinking LED with Timer1

  • File name: homework_01/main.c
  • Core concept: Timer1 setup (upper nibble of TMOD), TR1/TF1 polling
  • Platform: C51 (AT89C52)

Requirements

  • Set Timer1 to Mode 1 (16-bit) to create approximately 50ms delay, and toggle P1.0 LED.
  • Assuming 12MHz (1µs machine cycle), reload = 65536 − 50000 = 0x3CB0 → TH1=0x3C, TL1=0xB0.

Expected Operation (Simulator)

  • P1.0 LED toggles at about 50ms intervals (≈10Hz) (same cycle as Example 1, but using Timer1).

Hint

  • Timer1 TMOD upper nibble: TMOD &= 0x0F; TMOD |= 0x10;.
  • Control/Flag-too for Timer1: TR1, TF1. Reset TH1/TL1 + clear TF1 for each delay.

Problem 2 — Number of Overflows Needed for Long Delay (PC)

  • File name: homework_02/main.c
  • Core concept: 16-bit limit, overflow accumulation
  • Platform: Common (PC gcc)

Requirements

  • Assuming 1 overflow is 50ms, calculate and output the number of overflows needed for target time 100, 500, 1000(ms).
  • Output format must match "Expected Output" character by character (%4lu ms, %2lu overflows).

Expected output

 100 ms ->  2 overflows (50ms each)
 500 ms -> 10 overflows (50ms each)
1000 ms -> 20 overflows (50ms each)

Hint

  • Convert units to µs: total_us = ms * 1000, overflows = total_us / 50000.
  • Choose values that divide evenly since integer division is used.

Check your answers

After solving it yourself, compare it with the reference solution below. The solution files include comments on key points and common mistakes.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

homework_01/main.cC
#include <reg52.h>

/*
 *Answer 1 (C51, AT89C52) — Create delay with Timer1 to blink LED
 *
 *Key Points:
 *- Timer1 setting is in the 'upper nibble' of TMOD (Timer0 is in the lower nibble).
 *Timer1 = Mode1 is TMOD |= 0x10.
 *- Control/flags are also for Timer1: TR1 (start), TF1 (overflow).
 *
 *Common mistake:
 *- Can confuse Timer0 bit (0x01) with Timer1 bit (0x10).
 *- If you forget to clear TF1, it may immediately pass after the first wait.
 *
 *Expected behavior (simulator): P1.0 LED toggles at about 50ms intervals (approximately 10Hz).
 */

sbit LED = P1 ^ 0;

static void delay_50ms_timer1(void)
{
    TMOD &= 0x0F;          /*Set only Timer1 bits to 0 (preserve Timer0)*/
    TMOD |= 0x10;          /*Timer1 = Mode 1 (16-bit)*/

    TH1 = 0x3C;            /* 65536 - 50000 = 0x3CB0 */
    TL1 = 0xB0;

    TF1 = 0;
    TR1 = 1;               /*Start Timer1*/
    while (TF1 == 0) {     /*Wait until 50ms has elapsed*/
    }
    TR1 = 0;
    TF1 = 0;
}

void main(void)
{
    while (1) {
        LED = (bit)!LED;
        delay_50ms_timer1();
    }
}
homework_02/main.cC
#include <stdio.h>

/*
 *Answer 2 (Pure Logic, PC) — Calculating the Number of Overflows Needed for Long Delays
 *
 *Key Points:
 *- A 16-bit timer can count up to about ~65ms (in 1µs units) at a time.
 *Longer delays are created by 'counting multiple overflows'.
 *- If one overflow is considered as 50ms, then target time / 50ms = number of overflows needed.
 *
 *Common mistake:
 *- When mixing ms and µs units. Here, convert all to µs and divide.
 */
int main(void)
{
    unsigned long target_ms[] = { 100, 500, 1000 };
    int n = (int)(sizeof(target_ms) / sizeof(target_ms[0]));
    unsigned long per_overflow_us = 50000UL;   /*One overflow = 50ms*/
    int k;

    for (k = 0; k < n; k++) {
        unsigned long total_us = target_ms[k] * 1000UL;
        unsigned long overflows = total_us / per_overflow_us;
        printf("%4lu ms -> %2lu overflows (50ms each)\n",
               target_ms[k], overflows);
    }

    return 0;
}

Learn this lesson with a mentor

Work through the Embedded C track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now