Nerdegutta's logo

nerdegutta.no

PIC16F690 - Day 8: Multiplexing I/O for 7-Segment or LED Control

23.11.25

Embedded

Goal

Learn to multiplex multiple digits using shared segment lines and digit control lines.
You’ll:

Hardware Setup

If using a common cathode display, invert logic accordingly.

Concepts

Code 1: Multiplexing 4-digit Display via Timer0 Interrupt
#define _XTAL_FREQ 4000000
#include < xc.h >
#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

const uint8_t segment_map[] = {
    0b00111111, // 0
    0b00000110, // 1
    0b01011011, // 2
    0b01001111, // 3
    0b01100110, // 4
    0b01101101, // 5
    0b01111101, // 6
    0b00000111, // 7
    0b01111111, // 8
    0b01101111  // 9
};

volatile uint8_t digit_values[4] = {1, 2, 3, 4};
volatile uint8_t current_digit = 0;

void __interrupt() isr(void) {
    if (T0IF) {
        T0IF = 0;

        // Turn off all digits
        PORTB |= 0xF0;

        // Output segment value (invert for common anode)
        PORTC = ~segment_map[digit_values[current_digit]];

        // Activate current digit (RB4-RB7)
        PORTB &= ~(1 << (4 + current_digit));

        // Move to next digit
        current_digit = (current_digit + 1) % 4;
    }
}

void main(void) {
    TRISC = 0x00; // Segment outputs
    TRISB = 0x0F; // RB4–RB7 as outputs (digit selects), RB0–RB3 as input if needed
    ANSEL = 0;
    ANSELH = 0;
    PORTC = 0;
    PORTB = 0xF0; // Turn all digits off

    // Setup Timer0
    OPTION_REGbits.T0CS = 0; // Internal clock
    OPTION_REGbits.PSA = 0;
    OPTION_REGbits.PS = 0b010; // 1:8 prescaler

    TMR0 = 0;
    T0IE = 1;
    PEIE = 1;
    GIE = 1;

    while (1) {
        // Here you could change `digit_values[]` dynamically
        // For example: read from EEPROM or increment a counter
    }
}
Code 2: Manual Multiplexing in Main Loop (Polling)
#define _XTAL_FREQ 4000000
#include < xc.h >
#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

const uint8_t segment_map[] = {
    0b00111111, // 0
    0b00000110, // 1
    0b01011011, // 2
    0b01001111, // 3
    0b01100110, // 4
    0b01101101, // 5
    0b01111101, // 6
    0b00000111, // 7
    0b01111111, // 8
    0b01101111  // 9
};

uint8_t digit_values[4] = {2, 0, 2, 5};

void display_digit(uint8_t digit, uint8_t value) {
    PORTB |= 0xF0; // Turn off all digits
    PORTC = ~segment_map[value]; // Invert for common anode
    PORTB &= ~(1 << (4 + digit)); // Activate one digit
    __delay_ms(2); // Short delay to allow display
}

void main(void) {
    TRISC = 0x00;
    TRISB = 0x0F;
    ANSEL = 0;
    ANSELH = 0;
    PORTC = 0;
    PORTB = 0xF0;

    while (1) {
        for (uint8_t i = 0; i < 4; i++) {
            display_digit(i, digit_values[i]);
        }
    }
}