Nerdegutta's logo

nerdegutta.no

PIC16F690 - Day 5: Combined Interrupts – Timer0 + Button Input

10.08.25

Embedded

Goal:Learn to combine multiple interrupts:
Use Timer0 to blink an LED (every 500 ms)
Use RB4 input to toggle whether the blinking is enabled

Hardware:LED on RB0
Button on RB4 (with pull-down resistor)
Concepts:Enabling multiple interrupts (Timer0 + PORTB Change)

Distinguishing interrupt sources inside the ISR

Debounce logic

Using a global flag to control behavior

Code 1: Timer0 LED Blink + Button Control via RB4 Interrupt

#define _XTAL_FREQ 4000000
#include < xc.h > // Remove extra spaces
#include < stdint.h >

// CONFIG
#pragma config FOSC = INTRCIO
#pragma config WDTE = OFF
#pragma config PWRTE = OFF
#pragma config MCLRE = OFF
#pragma config BOREN = OFF
#pragma config CP = OFF
#pragma config CPD = OFF

volatile uint8_t overflow_count = 0;
volatile uint8_t blinking_enabled = 1;

void __interrupt() isr(void) {
    // Timer0 interrupt (every ~65.5ms with 1:256 prescaler)
    if (T0IF) {
        T0IF = 0;
        if (blinking_enabled) {
            overflow_count++;
            if (overflow_count >= 8) { // 8 * 65ms = ~520ms
                RB0 = !RB0;           // Toggle LED
                overflow_count = 0;
            }
        }
    }

    // PORTB Change interrupt (RB4-RB7)
    if (RBIF) {
        __delay_ms(20);      // Simple debounce
        if (RB4 == 1) {
            blinking_enabled ^= 1;  // Toggle blink enable flag
        }
        RBIF = 0;            // Clear interrupt-on-change flag
    }
}

void main(void) {
    TRISB0 = 0;  // RB0 output (LED)
    TRISB4 = 1;  // RB4 input (button)

    ANSEL = 0;
    ANSELH = 0;
    PORTB = 0;

    // Setup Timer0
    OPTION_REGbits.T0CS = 0;     // Internal clock
    OPTION_REGbits.PSA = 0;      // Prescaler to Timer0
    OPTION_REGbits.PS = 0b111;   // 1:256 prescaler

    // Enable Timer0 interrupt
    TMR0 = 0;
    T0IE = 1;

    // Enable RB change interrupt for RB4
    IOCB4 = 1;
    RBIF = 0;
    RBIE = 1;

    // Global Interrupt Enable
    PEIE = 1;
    GIE = 1;

    while (1) {
        // Nothing to do in main; logic handled in ISR
    }
}
Code 2: Same functionality without using ISR (polling + timer flag)
This simulates multiple-task behavior without real interrupts, useful when interrupts are not preferred.
#define _XTAL_FREQ 4000000
#include < xc.h > // Remove extra spaces
#include < stdint.h >

// CONFIG
#pragma config FOSC = INTRCIO
#pragma config WDTE = OFF
#pragma config PWRTE = OFF
#pragma config MCLRE = OFF
#pragma config BOREN = OFF
#pragma config CP = OFF
#pragma config CPD = OFF

uint8_t blinking_enabled = 1;

void main(void) {
    TRISB0 = 0;  // LED output
    TRISB4 = 1;  // Button input
    ANSEL = 0;
    ANSELH = 0;
    PORTB = 0;

    // Timer0 setup
    OPTION_REGbits.T0CS = 0;
    OPTION_REGbits.PSA = 0;
    OPTION_REGbits.PS = 0b111; // 1:256
    TMR0 = 0;

    uint8_t prev_btn = 0;
    uint8_t overflow_count = 0;
    uint8_t last_timer = 0;

    while (1) {
        // Polling button
        if (RB4 && !prev_btn) {
            __delay_ms(20); // debounce
            if (RB4) {
                blinking_enabled ^= 1;  // Toggle flag
            }
        }
        prev_btn = RB4;

        // Poll Timer0
        if (TMR0 < last_timer) {  // Overflow occurred
            overflow_count++;
            if (blinking_enabled && overflow_count >= 8) {
                RB0 = !RB0;
                overflow_count = 0;
            }
        }
        last_timer = TMR0;
    }
}