A timer in a PIC microcontroller is a hardware register that automatically increments its value at a predictable rate derived from the system clock, allowing the CPU to measure time intervals or count external events without software polling. When you integrate hardware timers into your design, you fundamentally change your circuit's capabilities: you offload timekeeping from the main CPU, enabling precise PWM generation, accurate RTOS ticks, and low-power sleep modes while maintaining background timing. Beginners commonly confuse these dedicated hardware timers (like TMR0 or TMR1) with software delay loops like __delay_ms(), which block the CPU entirely, or with the Watchdog Timer (WDT), which is strictly a reset-safety mechanism rather than a timekeeping tool.
The Core Timer Architecture: Timer0, Timer1, and Timer2
While modern PIC16F1xxx and PIC18 families feature up to six or more timers, the classic trio—Timer0, Timer1, and Timer2—forms the backbone of almost every 8-bit PIC timing application. Understanding their silicon-level differences prevents you from forcing the wrong peripheral to do a job it was not designed for.
- Timer0 (The Generalist): Typically an 8-bit or 16-bit counter. It can be clocked by the internal instruction cycle (Fosc/4) or an external pin (T0CKI). It is best for simple, short-duration delays or counting external pulses, but it lacks a dedicated period register, meaning you must reload it manually in software upon overflow.
- Timer1 (The Heavy Lifter): A true 16-bit register (max count 65,535). It features a dedicated low-power 32.768 kHz secondary oscillator, making it the undisputed choice for Real-Time Clock (RTC) applications and long-interval interrupts while the main CPU sleeps.
- Timer2 (The PWM Engine): An 8-bit timer, but with a crucial hardware addition: the Period Register (PR2). Instead of overflowing at a fixed 255, Timer2 resets to zero whenever it matches the value in PR2. This hardware-level reset is what generates the stable, jitter-free base frequency for the PIC’s CCP (Capture/Compare/PWM) modules.
Worked Numeric Example: Calculating a 50ms Interrupt
Let’s move from theory to the workbench. Suppose you are building a non-blocking state machine on a PIC16F18446 running at an internal oscillator frequency (Fosc) of 32 MHz. You need a reliable 50 ms interrupt to serve as your system heartbeat. Here is the exact math to configure Timer1 for this task.
- Determine the Instruction Clock: PIC 8-bit architectures execute one instruction every four oscillator cycles.
Fcy = 32 MHz / 4 = 8 MHz. - Calculate the Base Tick: The period of the instruction clock is
1 / 8 MHz = 125 ns. - Apply the Prescaler: A 125 ns tick means Timer1 would overflow in just 8.19 ms (65,536 × 125 ns). To stretch this, we set the Timer1 prescaler to 1:8.
New Tick = 125 ns × 8 = 1,000 ns = 1 µs. - Calculate Ticks Needed: For a 50 ms interrupt, we need
50 ms / 1 µs = 50,000 ticks. - Calculate the Preload Value: Timer1 counts up to 65,535 and overflows on the 65,536th tick. To make it overflow in exactly 50,000 ticks, we preload it with the difference.
Preload = 65,536 - 50,000 = 15,536. - Convert to Hex for Registers: 15,536 in decimal is
0x3CB0in hexadecimal. You will loadTMR1H = 0x3CandTMR1L = 0xB0.
TMR1 = 15536 destroys those "lost" cycles, causing timing drift. Instead, use the PR1 (Period Register) if your specific PIC variant supports it for Timer1, or accept the slight jitter for non-critical UI blinking.
Where You Meet Timers in Practice
Hardware timers are not just for blinking LEDs; they are the silent engines behind critical embedded subsystems. According to the Microchip Developer Timer Documentation, proper timer routing is essential for peripheral coordination.
- Ultrasonic Distance Sensing (HC-SR04): You do not use software loops to measure the echo pulse width. Instead, you route the echo pin to the Timer1 Gate or Input Capture module. The hardware captures the exact timer value on the rising edge and the falling edge, subtracting them to get the pulse width with zero CPU intervention and microsecond precision.
- DC Motor Speed Control: When driving an H-bridge, you need a fixed-frequency PWM signal (typically 1 kHz to 20 kHz) to avoid audible motor whine. Timer2, paired with the PR2 register, generates this base frequency, while the CCPR register dictates the duty cycle. The CPU only updates the duty cycle; the timer handles the waveform toggling.
- Switch Debouncing: Instead of using blocking delays to wait out mechanical switch bounce, a Timer0 interrupt firing every 5 ms can sample the GPIO pin. If the pin reads stable for three consecutive 5 ms ticks (15 ms total), the state change is registered.
Decision Tree: Which PIC Timer Should You Configure?
Do not default to Timer0 for everything. Use this decision matrix to select the correct hardware block for your specific requirement.
| If your application requires... | Then you need... | Concrete Pick (Register) |
|---|---|---|
| Hardware PWM for motors or LEDs | A timer with a Period Register to set a fixed frequency base | Timer2 (or TMR4/6 on larger chips) |
| Long-interval interrupts (100ms to seconds) | A 16-bit register with a high-ratio prescaler | Timer1 |
| Counting external pulses (e.g., flow meter, encoder) | A timer with an external clock input pin and gate control | Timer0 (or Timer1 Gate) |
| Measuring exact pulse widths (Input Capture) | A 16-bit timer that free-runs without resetting | Timer1 (paired with CCP module) |
| Low-power RTC tracking while CPU sleeps | A timer driven by the secondary 32.768 kHz LP oscillator | Timer1 (with SOSC enabled) |
FAQ: Common PIC Timer Configuration Pitfalls
Why is my Timer0 interrupt firing at half the expected rate?
In legacy PIC architectures, the Timer0 prescaler is shared with the Watchdog Timer (WDT). If you do not explicitly assign the prescaler to Timer0 via the OPTION_REG bits (specifically the PSA bit), it defaults to the WDT, and Timer0 runs at the raw instruction clock speed, completely ignoring your prescaler math.
Do I need to clear the interrupt flag manually in the ISR?
Yes. Unlike some ARM Cortex-M processors that auto-clear interrupt flags, 8-bit PICs require you to manually clear the Timer Interrupt Flag (e.g., PIR1bits.TMR1IF = 0;) inside the ISR. If you forget this, the ISR will immediately re-trigger the moment it exits, locking your CPU in an infinite interrupt loop.
Can I read a 16-bit timer (TMR1) while it is running without corruption?
If you read the low byte (TMR1L) and then the high byte (TMR1H), the low byte might roll over between the two read instructions, giving you a wildly incorrect value. Always read TMR1L first, which latches the high byte into a buffer, then read TMR1H. Alternatively, use the microcontroller's built-in 16-bit read/write mode if supported by your specific silicon variant.
The Default Recommendation: When designing a new PIC firmware architecture from scratch, initialize Timer1 as your system's 1 ms or 10 ms heartbeat using a 16-bit preload, and route all other software-based timing needs (debouncing, timeouts, state machine delays) through software counters driven by that single interrupt. Reserve Timer2 strictly for PWM, and leave Timer0 for external event counting. This separation of concerns eliminates timing collisions and guarantees deterministic execution.






