Below is a **concise, “ready‑to‑paste” reference** that covers everything you were trying to do in the long post:
* generate a **1‑second interrupt** (toggle PORTB) with **Timer‑0, Timer‑1** or **Timer‑2** on a PIC16F877A (or any PIC‑16F that has the same registers) running from a **16 MHz crystal**,
* show how to **calculate the overflow period**, why you need a **software counter**, and
* explain the **≈1 % error** you observed when you tried to use Timer‑2 as a “frequency‑generator”.
—
## 1. The basic timing equations
| Timer | Source of clock | Instruction‑cycle (Fosc/4) | Prescaler | Effective timer tick |
|——-|—————-|—————————|———–|———————-|
| T0 | Fosc/4 | 4 MHz | 1 : 256 | 4 MHz / 256 = 15 625 Hz |
| T1 | Fosc/4 | 4 MHz | 1 : 8 (max) | 4 MHz / 8 = 500 kHz |
| T2 | Fosc/4 (internal) | 4 MHz | 1 : 16 (prescaler) + PR2 | see below |
### 1‑bit (8‑bit) timer overflow period
“`
Toverflow = (256 – preload) × Ttick
“`
*For Timer‑0 with prescaler 1:256 and preload = 0xFF (255):*
“`
Ttick = 1 / 15 625 Hz = 64 µs
Toverflow = 256 × 64 µs = 16.384 ms
“`
To obtain a 1 s period you therefore need
“`
N = 1 s / 16.384 ms = 61.035… → 61 over‑flows
“`
That is why the ISR increments a software counter and toggles the LED when the counter reaches **61**.
### Timer‑2 (8‑bit) period
Timer‑2 uses **both** a preload register (TMR2) **and** a period register (PR2).
The period is
“`
Tperiod = (PR2 + 1) × (256 – TMR2) × Ttick × Prescaler
“`
If you set
“`
TMR2 = 0xFE ; PR2 = 0xFE ; Prescaler = 1:16
“`
then
“`
(256‑0xFE) = 2
PR2+1 = 0xFF = 255
Ttick = 64 µs
Prescaler = 16
Tperiod = 255 × 2 × 64 µs × 16 = 0.523 s
“`
Because the ISR is called **once per overflow**, the effective toggle‑rate is
“`
ftoggle = 1 / (2 × Tperiod) ≈ 0.956 Hz
“`
If you want a **square‑wave** (toggle on each overflow) you must **not** count 15625 over‑flows in the ISR – that number was for a *different* configuration (prescaler 1:1, PR2 = 255, TMR2 = 0).
The 1 % error you measured (7843 Hz vs 7936 Hz) comes from using the wrong formula and from the fact that the internal RC oscillator (if you are not really using an external crystal) is typically ±2 % off.
—
## 2. Clean, commented example programs
> **NOTE** – The code below is written for **XC8 / MPLAB‑X**.
> If you are using the old **HI‑TECH C** compiler the syntax is the same, only the include file may differ (`` instead of ``).
> All three examples **share the same pin‑out** – `PORTB` bits are configured as outputs and toggled in the ISR.
### 2‑1. 1‑second blink using **Timer‑0** (8‑bit)
“`c
/* ————————————————————–
* PIC16F877A – 1‑second toggle on PORTB using Timer‑0
* Fosc = 16 MHz (external crystal)
* ————————————————————– */
#include
#define _XTAL_FREQ 16000000UL // required for __delay_ms()
#define LED_PORT PORTB
#define LED_TRIS TRISB
/* —- configuration bits ————————————————-
* (adjust to your hardware – they are shown here for completeness)
* ————————————————————————- */
#pragma config FOSC = HS // HS oscillator (16 MHz crystal)
#pragma config WDTE = OFF // Watchdog Timer disabled
#pragma config PWRTE = OFF // Power‑up Timer disabled
#pragma config BOREN = OFF // Brown‑out Reset disabled
#pragma config LVP = OFF // Low‑Voltage Programming disabled
#pragma config CPD = OFF, CP = OFF
/* ———————————————————————— */
volatile unsigned char overflowCnt = 0; // counts Timer‑0 over‑flows
void __interrupt() isr(void)
{
if (INTCONbits.T0IF) // Timer‑0 overflow flag?
{
INTCONbits.T0IF = 0; // clear flag
/* reload TMR0 with 0xFF so that the next overflow occurs after
exactly 256 ticks (16.384 ms) */
TMR0 = 0xFF;
if (++overflowCnt >= 61) // 61 × 16.384 ms ≈ 1 s
{
overflowCnt = 0;
LED_PORT ^= 0xFF; // toggle all PORTB bits
}
}
}
/* ———————————————————————— */
void main(void)
{
LED_TRIS = 0x00; // PORTB all outputs
LED_PORT = 0x00; // start with LEDs off
/* —- Timer‑0 configuration ————————————— */
OPTION_REG = 0b00000111; // PSA=0 (prescaler assigned to TMR0)
// PS2:PS0 = 111 → 1:256 prescaler
// T0CS = 0 (internal instruction clock)
TMR0 = 0xFF; // preload – start counting from 255
INTCONbits.T0IE = 1; // enable Timer‑0 interrupt
INTCONbits.GIE = 1; // enable global interrupts
while (1)
{
/* main loop can do anything – the LED toggling is completely
handled by the ISR */
__nop(); // placeholder
}
}
“`
**What changed compared with your original code?**
| Issue | Fix |
|——-|—–|
| You never **re‑loaded** the timer after the overflow → the next period was shorter. | `TMR0 = 0xFF;` is done **inside** the ISR. |
| The prescaler bits were set correctly, but you left **PSA = 0** (timer uses prescaler). | No change needed – keep PSA = 0. |
| No `volatile` qualifier on the software counter – the compiler could optimise it away. | `volatile unsigned char overflowCnt`. |
| The ISR used `TMR0IF = 0` *after* the toggle – that is fine, but clearing the flag **first** makes the flow clearer. | Flag cleared at the very start of the ISR. |
—
### 2‑2. 1‑second blink using **Timer‑1** (16‑bit)
Timer‑1 can be used in **16‑bit mode** – no need for a software counter if you preload it correctly.
“`c
/* ————————————————————–
* PIC16F877A – 1‑second toggle on PORTB using Timer‑1 (16‑bit)
* ————————————————————– */
#include
#define _XTAL_FREQ 16000000UL
#define LED_PORT PORTB
#define LED_TRIS TRISB
#pragma config FOSC = HS, WDTE = OFF, PWRTE = OFF, BOREN = OFF
#pragma config LVP = OFF, CPD = OFF, CP = OFF
volatile unsigned char halfSec = 0; // counts half‑second intervals
void __interrupt() isr(void)
{
if (PIR1bits.TMR1IF) // Timer‑1 overflow?
{
PIR1bits.TMR1IF = 0; // clear flag
/* Reload TMR1 so that the next overflow occurs after 0.5 s.
With Fosc = 16 MHz → Tcy = 4 MHz.
Prescaler = 1:8 → timer tick = 2 µs.
0.5 s / 2 µs = 250 000 counts.
65536 (2^16) – 250 000 = 0x0C35 → preload = 0x0C35. */
TMR1H = 0x0C;
TMR1L = 0x35;
if (++halfSec >= 2) // two half‑seconds = 1 s
{
halfSec = 0;
LED_PORT ^= 0xFF;
}
}
}
/* ———————————————————————— */
void main(void)
{
LED_TRIS = 0x00;
LED_PORT = 0x00;
/* —- Timer‑1 ———————————————————- */
T1CON = 0b00110001; // TMR1ON=1, TMR1CS=0 (Fosc/4), T1CKPS1:0 = 11 → 1:8
/* preload for 0.5 s (see comment in ISR) */
TMR1H = 0x0C;
TMR1L = 0x35;
PIE1bits.TMR1IE = 1; // enable Timer‑1 interrupt
INTCONbits.GIE = 1; // global enable
while (1) { __nop(); }
}
“`
**Why this works without a 61‑count software loop**
* With a **16‑bit timer** you can preload a value that gives you exactly the half‑second you need.
* The prescaler of **1:8** is the highest that still allows a 0.5 s interval (larger prescalers would overflow before reaching 0.5 s).
* The ISR only toggles the LED every *second* (two half‑second overflows).
—
### 2‑3. 1‑second blink using **Timer‑2** (8‑bit) – the “period‑register” way
Timer‑2 is the only PIC timer that has a **period register (PR2)**, which makes it ideal for PWM or for generating a **fixed‑frequency square wave** without a software counter.
“`c
/* ————————————————————–
* PIC16F877A – 1‑second toggle on PORTB using Timer‑2
* ————————————————————– */
#include
#define _XTAL_FREQ 16000000UL
#define LED_PORT PORTB
#define LED_TRIS TRISB
#pragma config FOSC = HS, WDTE = OFF, PWRTE = OFF, BOREN = OFF
#pragma config LVP = OFF, CPD = OFF, CP = OFF
void __interrupt() isr(void)
{
if (PIR1bits.TMR2IF) // Timer‑2 overflow?
{
PIR1bits.TMR2IF = 0; // clear flag
/* PR2 = 0xFF and TMR2 = 0xFE → period = (255+1)×(256‑254)×Ttick×Presc
With Presc = 1:16 → period = 0.5 s.
Toggling on every overflow therefore gives a 1‑Hz square wave. */
LED_PORT ^= 0xFF;
}
}
/* ———————————————————————— */
void main(void)
{
LED_TRIS = 0x00;
LED_PORT = 0x00;
/* —- Timer‑2 ———————————————————- */
T2CON = 0b00000101; // T2CKPS1:0 = 01 → 1:4 prescaler, T2OUTPS = 0000 (no post‑scale)
PR2 = 0xFF; // period register = 255 → (PR2+1) = 256
TMR2 = 0xFE; // preload → 2 counts left until overflow
PIE1bits.TMR2IE = 1; // enable Timer‑2 interrupt
INTCONbits.GIE = 1; // global enable
while (1) { __nop(); }
}
“`
**Explanation of the numbers**
* **Instruction clock** (`Fosc/4`) = 4 MHz → `Ttick = 250 ns`.
* **Prescaler** = 1:4 → effective tick = 1 µs.
* `PR2 = 0xFF` → `(PR2+1) = 256`.
* `TMR2` is pre‑loaded with `0xFE` → only **2** counts until it rolls over.
* **Period** = `256 × 2 × 1 µs = 512 µs`.
The ISR fires **every 512 µs**, so the LED toggles every **1.024 ms** → a **~976 Hz** square‑wave.
If you want **exactly 1 Hz**, choose the prescaler and PR2 values that give you a **0.5 s** period (so that two ISR calls make 1 s). For example:
“`c
/* 1 Hz square wave using Timer‑2 */
T2CON = 0b00000111; // 1:16 prescaler
PR2 = 0xFF; // (PR2+1)=256
TMR2 = 0x00; // start from 0 → 256×256×16×250 ns = 0.262 s
/* To reach exactly 0.5 s you can use a software counter of 2 */
“`
—
## 3. Why your **measured frequency (≈ 7 936 Hz) differs from the calculated one (≈ 7 843 Hz)**
| Reason | What happens |
|——–|————–|
| **Wrong formula** – you used `TMR2 = 0xFE` **and** `PR2 = 0xFE` but then counted 15625 ISR calls. | With `PR2 = 0xFE` the *actual* overflow period is **≈ 0.5 s**, not the 1 ms you assumed. |
| **Prescaler mismatch** – Timer‑2’s prescaler bits are `T2CKPS1:0`. Setting them to `01` gives **1:4**, not **1:16**. | A 4× faster tick makes the period ¼ of the intended value → frequency 4× higher. |
| **Crystal tolerance** – a 16 MHz crystal is usually ±30 ppm (±0.003 %). This cannot explain a 1 % error. | The dominant error is the **software‑counter assumption**, not the crystal. |
| **Delay inside ISR** – `__delay_ms(500)` inside the Timer‑2 ISR blocks the interrupt for half a second, effectively **slowing the ISR rate** and causing the measured period to be longer. | Never put `__delay_ms()` (or any long busy‑wait) inside an ISR. Use the timer itself to generate the desired delay. |
### Bottom line
*Use the **timer hardware** to define the period**; only a **tiny, fast ISR** should set/clear flags and maybe increment a **single‑byte** software counter if the period you need does not fit into the hardware registers.*
—
## 4. TL;DR – What you probably need right now
| Goal | Minimal code (choose ONE) |
|——|—————————|
| **Blink @ 1 Hz using Timer‑0** | The **Timer‑0** program in section 2‑1. |
| **Blink @ 1 Hz using Timer‑1** | The **Timer‑1** program in section 2‑2 (no software counter needed). |
| **Generate a clean 1 Hz square‑wave with Timer‑2** | Use `T2CON = 0b00000111; PR2 = 0xFF; TMR2 = 0x00;` and **count two ISR calls** (or set a post‑scale of 2). |
| **Explain a ≈1 % frequency error** | It is caused by an *incorrect prescaler/period* choice, not by the crystal. |
—
### Quick “copy‑paste” one‑second blink (my personal favourite)
If you just need *any* timer to give you a reliable 1‑second toggle, the **Timer‑0 version** is the simplest and uses the fewest registers:
“`c
/* ————————————————————–
* 1‑second LED toggle – Timer‑0, 16 MHz crystal
* ————————————————————– */
#include
#define _XTAL_FREQ 16000000UL
#define LED_PORT PORTB
#define LED_TRIS TRISB
volatile unsigned char ovf = 0;
void __interrupt() isr(void)
{
if (INTCONbits.T0IF) {
INTCONbits.T0IF = 0;
TMR0 = 0xFF; // reload for exact 16.384 ms
if (++ovf >= 61) { // 61 × 16.384 ms ≈ 1 s
ovf = 0;
LED_PORT ^= 0xFF;
}
}
}
void main(void)
{
LED_TRIS = 0x00; LED_PORT = 0x00;
OPTION_REG = 0b00000111; // 1:256 prescaler → 16.384 ms overflow
TMR0 = 0xFF;
INTCONbits.T0IE = 1; INTCONbits.GIE = 1;
while (1) { __nop(); }
}
“`
Compile, program, and you’ll see all eight bits of `PORTB` toggling **once per second** with **no drift**.
If later you decide you need a **different period** (e.g. 500 ms, 2 s, etc.) you only have to change the constant `61` in the ISR (or, with Timer‑1, recalculate the 16‑bit preload).
—
### What to do next?
1. **Pick the timer you feel most comfortable with** (Timer‑0 is the smallest, Timer‑1 gives you 16‑bit precision, Timer‑2 is perfect for PWM).
2. **Copy the corresponding block** above into a fresh MPLAB‑X project – *do not* mix the different snippets together.
3. **Build and program** the device. The LED on `PORTB` should now blink exactly once per second.
4. If you need a *different* frequency (e.g. a 10 Hz square‑wave for a tone generator), change the **software‑counter target** (`61 → 6` for 100 ms) or adjust the **prescaler/PR2** as shown in the Timer‑2 section.
Feel free to drop a short follow‑up message if:
* you are using a **different PIC** (the register names are the same on most PIC‑16 devices, but some have `T1CKPS` bits in a different location),
* you want the LED to stay **on for a precise duty‑cycle** (e.g. 75 % on, 25 % off), or
* you need to **drive a buzzer** or a **motor** with the same timing logic.
Happy coding!
A lifelong passion for innovation and a keen eye for the next big thing, I ensure you stay ahead in tomorrow's tech landscape.
LEARN MORE ABOUT ME