Nerdegutta's logo

nerdegutta.no

PIC16F690 - 4 digit 7 segment hour counter

21.04.25

Embedded

In this article I'm using a PIC 16F690.

#include < xc.h > // remove the extra spaces
#define _XTAL_FREQ 4000000

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

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

const unsigned char digitMask[4] = {
    0b00010000,  // RB4 → digit 4 (rightmost)
    0b00100000,  // RB5 → digit 3
    0b01000000,  // RB6 → digit 2
    0b10000000   // RB7 → digit 1 (leftmost)
};

// VARIABLES
volatile unsigned int counter = 0;
volatile unsigned char digits[4] = {0, 0, 0, 0};
volatile unsigned char currentDigit = 0;
volatile unsigned int tickMs = 0;
volatile unsigned char dpState = 0;

// FUNCTION PROTOTYPE
void updateDigits();
void init();
void __interrupt() isr();
void eeprom_write(unsigned char address, unsigned char data);
unsigned char eeprom_read(unsigned char address);
void saveCounterToEEPROM(unsigned int value);
unsigned int loadCounterFromEEPROM();

// Function to update the digits on the 7-seg
void updateDigits() {
    digits[3] = counter % 10;
    digits[2] = (counter / 10) % 10;
    digits[1] = (counter / 100) % 10;
    digits[0] = (counter / 1000) % 10;
}

// Function to initialize the MCU portts
void init() {
    ANSEL = 0; ANSELH = 0;      // Disable input buffer on ANSEL & ANSELH
    CM1CON0 = 0; CM2CON0 = 0;   // Disable comtarators on C1 & C1

    //TRISA = 0;
    TRISA = 0b00100000; // RA5 input -> Reset button
    TRISB = 0b00001111; // RB4–RB7 as output
    TRISC = 0x00;       // RC0–RC7 as output (segments + DP)

    PORTAbits.RA2 = 0;  // Set RA2 LOW
    PORTB = 0x00;       // Set all to LOW
    PORTC = 0xFF;       // Set all to HIGH

    WPUA |= 0b00100000; // Enable weak pull-ups on RA2
    
    // --- Timer0 Setup ---
    OPTION_REG = 0b00000001;    // Prescaler 1:256 (with 4MHz -> ~1.024ms per overflow)
    OPTION_REGbits.nRABPU = 0;  // Enable pull-ups on induvidial ports
    TMR0 = 6;                   // Preload TMR0 with 6
    INTCON = 0b10100000;        // Enable GIE, TMR0IE
}

// Interrupt Service Routine
void __interrupt() isr() {
    if (T0IF) {
        T0IF = 0;
        TMR0 = 0;

        // Turn off all digits
        PORTB &= 0x0F;
        PORTC = 0xFF;

        // Set segments
        unsigned char seg = segmentMap_CA[digits[currentDigit]];
        
        // Apply DP globally, regardless of digit
        if (dpState) {
            seg &= ~(1 << 7); // DP ON
        } else {
            seg |= (1 << 7);  // DP OFF
        }
        
        PORTC = seg;

        // Activate digit
        PORTB |= digitMask[3 - currentDigit];
        currentDigit = (currentDigit + 1) % 4;

        // Tick counter (~1ms per interrupt)
        tickMs++;
        
       // Blink LED (RA2) and update DP state every 500ms
        if (tickMs % 500 == 0) {
            dpState = !dpState;
            RA2 = dpState ? 1 : 0;            
        }        

        // Count hours
        static unsigned long msCounter = 0;
        msCounter++;
        if (msCounter >= 3600000UL) { // 1 hour = 3,600,000 ms
            msCounter = 0;
            counter++;
            if (counter > 9999) counter = 0;
            updateDigits();
            saveCounterToEEPROM(counter);
        }
    }
}

// Function to write data to the eeprom
void eeprom_write(unsigned char address, unsigned char data) {
    while (WR); // Wait for previous write to finish
    EEADR = address;
    EEDATA = data;
    EECON1 = 0b00000100; // Access data EEPROM
    EECON2 = 0x55;
    EECON2 = 0xAA;
    EECON1bits.WR = 1;   // Start write
}

// Function to read data from the eeprom
unsigned char eeprom_read(unsigned char address) {
    while (WR); // Wait for previous write to finish
    EEADR = address;
    EECON1 = 0b00000000; // Access data EEPROM
    EECON1bits.RD = 1;
    return EEDATA;
}

// Save function
void saveCounterToEEPROM(unsigned int value) {
    eeprom_write(0x00, (unsigned char)(value & 0xFF));        // Low byte
    eeprom_write(0x01, (unsigned char)((value >> 8) & 0xFF)); // High byte
}

// Read function
unsigned int loadCounterFromEEPROM() {
    unsigned char low = eeprom_read(0x00);
    unsigned char high = eeprom_read(0x01);
    return ((unsigned int)high << 8) | low;
}

// Main program
void main() {
    init();
    
    counter = loadCounterFromEEPROM();
    updateDigits();
    while (1) {
        // Everything handled in interrupt
        if (RA5 == 0) {
            __delay_ms(50);
            if (RA5 == 0) {
                counter = 0;
                updateDigits();
                saveCounterToEEPROM(counter);
                while (RA5 == 0);
                __delay_ms(50);
            }
        }
    }
}
/*
 * PIN  - Connection
 * 1    - VDD
 * 2    - RA5 => reset button to gnd
 * 3    - RA4 
 * 4    - MCLR
 * 5    - RC5 => segment F
 * 6    - RC4 => segment E
 * 7    - RC3 => segment D
 * 8    - RC6 => segment G
 * 9    - RC7 => DP
 * 10   - RB7 => CA Digit 1, leftmost
 * 11   - RB6 => CA Digit 2
 * 12   - RB5 => CA Digit 3
 * 13   - RB4 => CA Digit 4, rightmost
 * 14   - RC2 => segment C
 * 15   - RC1 => segment B
 * 16   - RC0 => segment A
 * 17   - RA2 => LED
 * 18   - RA1
 * 19   - RA0
 * 20   - GND
 * 
 * All the segments are connected with a 240 resistor. 
 */