In short: The 8051 has two 16-bit timer/counter blocks (Timer 0 and Timer 1) that either count machine cycles to measure time or count external pulses, with their behaviour set by the TMOD and TCON registers and their four operating modes (0–3).
Timers and counters are the first piece of 8051 hardware that turns a bare microcontroller into something that can measure the real world. Whether you are generating a precise 1 ms tick, debouncing a switch, baud-rate-clocking a UART, or counting widgets off a conveyor, you are using the same two on-chip blocks. This article walks through how they are built, how you configure them, and how to compute a delay you can actually trust on a scope.
One block, two personalities: timer vs counter
Each block is a 16-bit up-counter. The only difference between timer mode and counter mode is where the clock pulses come from. In timer mode the register increments on the internal clock, specifically once every machine cycle. On the classic 8051 a machine cycle is 12 oscillator periods, so with a 12 MHz crystal the timer ticks once per microsecond. In counter mode the register increments on a 1→0 transition applied to the external pins T0 (P3.4) or T1 (P3.5).
With a 12 MHz crystal, 1 machine cycle = 12 / 12 MHz = 1 µs. That single number is the key to every software delay you will ever compute on an 8051.
TMOD: setting the mode
TMOD is a single 8-bit register (not bit-addressable) split into two identical nibbles, the upper for Timer 1 and the lower for Timer 0. Each nibble holds four controls:
GATE– when 1, the timer runs only while the external INTx pin is high (hardware-gated pulse-width measurement); when 0, software alone starts it.C/T– 0 = timer (internal clock), 1 = counter (external pin).M1 M0– the two mode-select bits that pick modes 0 through 3.
So to run Timer 0 as a 16-bit timer you write TMOD = 0x01 (lower nibble = 0001). To run Timer 1 as an 8-bit auto-reload timer for the UART you write the upper nibble 0x20. Because the nibbles are independent you usually OR the two halves together, e.g. TMOD = 0x21.
TCON: run control and flags
TCON is bit-addressable, which matters because you flip its bits constantly. Its upper half belongs to the timers:
TR0 / TR1– the run bits. SettingTR0 = 1starts Timer 0; clearing it freezes the count in place.TF0 / TF1– the overflow flags, set by hardware when the counter rolls over FFFFh→0000h. In polled code you wait on TFx and clear it in software; if interrupts are enabled it vectors the CPU to the timer ISR and is cleared automatically.
The lower half of TCON (IE0, IT0, IE1, IT1) handles the external interrupts and is covered in our companion piece on 8051 interrupts.
The four operating modes
| Mode | M1 M0 | Structure | Max count | Typical use |
|---|---|---|---|---|
| 0 | 0 0 | 13-bit (THx + 5 bits of TLx) | 8192 | Legacy 8048 compatibility |
| 1 | 0 1 | Full 16-bit | 65536 | General long delays |
| 2 | 1 0 | 8-bit with auto-reload from THx | 256 | UART baud rate, periodic tick |
| 3 | 1 1 | Timer 0 split into two 8-bit timers | 256 each | A third timer when you need one |
Mode 1 is the workhorse for one-shot delays up to ~65.5 ms at 12 MHz. Mode 2 is prized because after each overflow the hardware reloads TLx from THx with zero software overhead, giving a jitter-free periodic event; this is exactly why the UART uses Timer 1 in mode 2 to clock its baud rate. Mode 3 is a special trick: it freezes Timer 1's run control and turns Timer 0 into two separate 8-bit timers (TL0 using TR0/TF0, TH0 borrowing TR1/TF1), handy when a design is one timer short.
Computing a timer-based delay
Suppose you want a 50 ms delay on a 12 MHz part using Timer 0 in mode 1. At 1 µs per tick you need 50000 ticks. The counter counts up and overflows at 65536, so you preload it with the complement:
count = 65536 − 50000 = 15536 = 0x3CB0 → TH0 = 0x3C, TL0 = 0xB0.
The polled routine is simply: load TMOD, load TH0/TL0, set TR0, wait for TF0, then stop and clear:
TMOD = 0x01;— Timer 0, mode 1TH0 = 0x3C; TL0 = 0xB0;— preload for 50000 countsTR0 = 1;— startwhile(TF0 == 0);— poll overflowTR0 = 0; TF0 = 0;— stop and clear the flag
For delays longer than 65.5 ms you simply loop this block, e.g. call a 50 ms routine twenty times for one second. For rock-steady periodic ticks you move to mode 2 with an interrupt so the reload and flag-clear cost nothing.
Common pitfall: forgetting that the reload value is 2n − N, not N. Loading 50000 directly instead of 65536−50000 gives a 15.5 ms delay, not 50 ms — a classic lab bug.
Modern design flows let you verify this behaviour before touching silicon: an 8051 soft-core written in Verilog can be simulated in Synopsys VCS or Siemens Questa, and the timer overflow checked against a testbench assertion, so the reload arithmetic is proven in regression rather than on a breadboard. If you want to go deeper into synthesisable microcontroller IP and verification, our partners at Avecas VLSI design services build and tape out exactly this kind of block.
Timers, baud rates and interrupts together
In real firmware the three subsystems interlock. Timer 1 in mode 2 generates the UART clock; Timer 0 in mode 1 or 2 raises an interrupt for your periodic scheduler tick; and the external-interrupt bits in TCON handle asynchronous events. Learning to read TMOD and TCON fluently is what lets you make all of this coexist without stepping on each other. See how the overflow flag becomes a vectored event in 8051 interrupts, and how the same timer clocks serial traffic in 8051 serial communication.
Ready to master the 8051 hands-on, from registers to real firmware? Explore the full embedded systems track on our CourseTron courses page and build timer-driven projects in simulation and on hardware. Training-focused readers can also check ChipXpert's VLSI and embedded programs.
Frequently asked questions
Why does the timer tick once every 12 clock cycles?
The original Intel 8051 divides the oscillator by 12 to form one machine cycle, and the timer increments per machine cycle. Many modern derivatives (such as Dallas/Maxim and Silicon Labs cores) offer a single-cycle or divide-by-1/4 option, so always check your datasheet before computing delays.
What is the difference between TF0 polling and using an interrupt?
Polling while(TF0==0) blocks the CPU until overflow and you must clear TF0 yourself. With the timer interrupt enabled, the overflow vectors to the ISR and TF0 is cleared by hardware, freeing the CPU to do other work between ticks.
When would I use mode 2 over mode 1?
Choose mode 2 for periodic events that must not drift, such as a baud-rate clock or a fixed-interval scheduler tick, because the auto-reload from THx happens in hardware with no software jitter. Mode 1 is better for arbitrary one-shot delays up to 65.5 ms.
Can I count external events faster than the machine cycle?
No. In counter mode the pin is sampled once per machine cycle, so a full high-then-low needs at least two machine cycles. The maximum reliable external count rate is therefore about 1/24 of the oscillator frequency (roughly 500 kHz at 12 MHz).