PIC16F877A Timers and Interrupts in PICSimLab
Delay loops waste the CPU and make timing drift whenever the code changes. The grown-up pattern is a hardware timer raising an interrupt at a fixed rate. This tutorial blinks an LED from a Timer0 interrupt on a PIC16F877A in PICSimLab, and shows how to verify the timing with the built-in oscilloscope.
What you need
- PICSimLab 0.9.3 — see the download page.
- MPLAB X IDE and the XC8 compiler.
- Board: McLab2 or PICGenios, microcontroller PIC16F877A.
- One LEDs spare part connected to RB0 (same setup as the blink tutorial).
The timing maths (example numbers)
With a 20 MHz clock, the instruction cycle is Fosc/4 = 5 MHz. Timer0 clocked internally with the prescaler set to 1:256 ticks at about 19.53 kHz, so its 8-bit counter overflows every 256 ticks — roughly every 13.1 ms. Counting 38 overflows in the interrupt gives a toggle about every 0.5 s. These numbers are examples tied to the 20 MHz assumption in the code; recompute them if your board clock differs.
Steps
1. Generate the project and add the LED
Create the MPLAB X project for the PIC16F877A, add the LEDs spare part via Modules → Spare parts and connect LED 1 to RB0 in Properties.
2. Configure Timer0 and its interrupt
OPTION_REG = 0x07 assigns the prescaler to Timer0 at 1:256 with the internal instruction clock. Then enable the Timer0 interrupt (T0IE) and global interrupts (GIE) in INTCON. The interrupt flag T0IF must be cleared inside the ISR, or the interrupt re-fires immediately.
3. Keep the ISR short
The example ISR only counts overflows and toggles the LED — all real work belongs in the main loop. The main loop in this tutorial is deliberately empty to make the point: the blink no longer depends on it.
4. Build, load, run and measure
Build in MPLAB X, load the .hex and run. Then open Modules → Oscilloscope, probe RB0 and add a Frequency measurement: you should read about 1 Hz, confirming the interrupt timing. The PIC16F877A also has Timer1 and Timer2 — the same pattern (configure, enable its interrupt bit plus PEIE for peripheral interrupts, clear its flag in the ISR) applies to them.
Example code (XC8)
// Example: Timer0 interrupt blink (~0.5 s) - PIC16F877A, XC8
#pragma config FOSC = HS
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config BOREN = ON
#pragma config LVP = OFF
#pragma config CPD = OFF
#pragma config WRT = OFF
#pragma config CP = OFF
#include <xc.h>
#define _XTAL_FREQ 20000000 // Example: match the board clock
static volatile unsigned char overflow_count = 0;
void __interrupt() isr(void) {
if (INTCONbits.T0IF) {
INTCONbits.T0IF = 0; // clear the flag, always
if (++overflow_count >= 38) {
overflow_count = 0;
PORTBbits.RB0 ^= 1; // toggle the LED
}
}
}
void main(void) {
TRISBbits.TRISB0 = 0; // RB0 output (LED)
PORTBbits.RB0 = 0;
OPTION_REG = 0x07; // Timer0: internal clock, prescaler 1:256
TMR0 = 0;
INTCONbits.T0IF = 0;
INTCONbits.T0IE = 1; // Timer0 interrupt on
INTCONbits.GIE = 1; // global interrupts on
while (1) { } // the interrupt does the work
}
Try next
- Oscilloscope basics — measure interrupt timing and PWM duty cycle properly.
- 7-segment display — move its refresh into a timer interrupt.
- Step Motor spare part — generate step pulses at a precise rate.
Troubleshooting
The interrupt never fires. All three enables must be set: the timer running (not stopped by OPTION_REG settings), T0IE and GIE. For Timer1/Timer2 interrupts you also need PEIE, because they are peripheral interrupts.
The LED toggles at the wrong rate. Recompute with your real board clock: overflow period = 4 / Fosc × prescaler × 256. A factor-of-four error almost always means Fosc was used where Fosc/4 was needed.
