Lab4 Debounce

PIC16F690 Use Interrupt-On-Change of 2 Push Buttons

Use the IOC capability to produce interrupts from 2 push buttons along with interrupts from Timer 0 (TMR0) introduced in Lab3. Button debouncing is performed with a software timer inside the mainline code: there is no busy-wait. Additionally, an LED blinks with regularity without interference from other work.

Below is the schematic. This will be used for Lab4 and Lab5. The components labeled *-sbb go on the solderless breadboard.

 

lab4-sch-BHere is the a photo of the breadboard.

lab4-wiring

Show text file suitable for cut-and-paste.

/*
 * File:   lab4.c
 * Author: Brian
 *
 * DESCRIPTION: The PIC16F690 interrupt system serves 2 buttons and timer 0.
 *              Two buttons are set up to interrupt when pressed (IOC).
 *              Debouncing is accomplished by a software counter which fires an
 *              event: no busy-wait. This improves responsiveness.
 *              Blinking of an LED is provided by the same technique.
 */

#include <xc.h>
#include <stdint.h>
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
// CONFIG
#pragma config FOSC = INTRCIO   // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = OFF      // Brown-out Reset Selection bits (BOR disabled)
#pragma config IESO = OFF       // Internal External Switchover bit (Internal External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is disabled)

// function prototypes
void Initialize(void);
void interrupt ISR(void);

#define TMR0_PRELOAD    58          // see "Timer 0 Configuration"
#define N_X_SYSCLOCK_DEBOUNCE 50    /* number of sysClocks per debounce
                                     * 0.2 msec * 50 = 10 msec
                                     */
#define N_X_DEBOUNCE_BLINK    50    /* number of debounce ticks per blink
                                     * 10 msec * 50 = 500 msec
                                     */
#define DEBOUNCE_SCALE  5           // debounce period = 10 * 5 msec

volatile union {            // ISR modifies this
    uint8_t port;

    struct {
        unsigned RA0 : 1;
        unsigned RA1 : 1;
        unsigned RA2 : 1;
        unsigned RA3 : 1;
        unsigned RA4 : 1;
        unsigned RA5 : 1;
    } ;
} shadowPortA;

union {
    uint8_t port;

    struct {
        unsigned RC0 : 1;
        unsigned RC1 : 1;
        unsigned RC2 : 1;
        unsigned RC3 : 1;
        unsigned RC4 : 1;
        unsigned RC5 : 1;
        unsigned RC6 : 1;
        unsigned RC7 : 1;
    } ;
} shadowPortC;

#define SHDW_GREEN      shadowPortC.RC4
#define SHDW_RED_LEFT   shadowPortC.RC5
#define SHDW_RED_RIGHT  shadowPortC.RC0

#define SHDW_BTN_LEFT   shadowPortA.RA4
#define SHDW_BTN_RIGHT  shadowPortA.RA2

/* ISR modifies these events: each is a message between actors */
volatile uint32_t sysClock;         // running time in units of 200 microseconds
volatile bit eventIocA2Down = 0;    // IOC ISR => task loop
volatile bit eventIocA4 = 0;        // IOC ISR => task loop
volatile bit eventDebounceTick = 0; // TMR0 ISR => task loop

uint8_t debounceTickClock = 0;      // tick counter for debouncing switches

bit debounceA2 = 0;                 // event handler => debounce handler
uint8_t pinA2DebounceTarget = 0;    // event handler sets debounceTick target
bit debounceA4 = 0;
uint8_t pinA4DebounceTarget = 0;

void main(void) {
    Initialize();       // Initialize the relevant registers
    while (1) {
        /******************
         * EVENT HANDLERS
         ******************/
        if (eventIocA2Down) {
            eventIocA2Down = 0;     // clear event
            debounceA2 = 1;         // tell debouncer to do its thing
            pinA2DebounceTarget = debounceTickClock + DEBOUNCE_SCALE;
            /*
             * do the work BEFORE debouncing
             */
            // RED_LED_LT = 0;
        }
        if (eventIocA4) {
            eventIocA4 = 0;         // clear event
            debounceA4 = 1;         // tell debouncer to do its thing
            pinA4DebounceTarget = debounceTickClock + DEBOUNCE_SCALE;
        }

        /******************
         * DEBOUNCE TASK
         ******************/
        if (eventDebounceTick) {
            static uint8_t blinkCounter = 0;
            /*
             * initiated from ISR
             * individual event handler tells me to run.
             * for debounce of an IOC pin, just start it.
             */
            eventDebounceTick = 0;      // clear event
            debounceTickClock += 1;     // advance debounce timer

            if (debounceA2) {
                if (pinA2DebounceTarget == debounceTickClock) {
                    debounceA2 = 0; // done
                    IOCA2 = 1;      // re-enable IOC for this pin
                    /*
                     * here is where we do the work
                     *      button press (down) => LED toggle
                     */
                    SHDW_RED_RIGHT = ~SHDW_RED_RIGHT;
                }
            }
            if (debounceA4) {
                if (pinA4DebounceTarget == debounceTickClock) {
                    debounceA4 = 0; // done
                    IOCA4 = 1;      // re-enable IOC for this pin
                    /*
                     * here is where we do the work
                     *      button down => LED ON
                     *      button up => LED OFF
                     */
                    SHDW_RED_LEFT = ~PORTAbits.RA4;
                }
            }

            blinkCounter += 1;
            if (blinkCounter == N_X_DEBOUNCE_BLINK) {
                blinkCounter = 0;
                // blink period, derived from debounce rate
                // sysclock period = 0.2 msec
                // debounce period = 50 * sysclock = 10 msec
                // blink period = 50 * 10 = 500 msec
                SHDW_GREEN = ~SHDW_GREEN;
            }
        }
        PORTC = shadowPortC.port;
    }
}

void Initialize(void) {
    /*
     * I/O Configuration
     *
     * OUTPUT PINS
     *      6   RC4 = blinking green LED <== blink clock, output
     *      5   RC5 = red LED <== button, output
     *      16  RC0 = red LED <== button, output
     *
     * INPUT PINS
     *      17  RA2 = push button input, AN2
     *      3   RA4 = push button input, AN3
     */

    // PORTC
    TRISC4 = 0;     // output
    TRISC5 = 0;     // output
    TRISC0 = 0;     // output
    shadowPortC.port = 0;   // all OFF

    // PORTA
    ANS2 = 0;       // digital input, so disable Analog 2
    ANS3 = 0;       // digital input, so disable Analog 3
    TRISA2 = 1;     // digital input
    TRISA4 = 1;     // digital input

    /*
     * Timer 0 Configuration
     *
     * TMR0 will interrupt (on overflow) 5000 times per second (T0i = 200 usec).
     *      system clock = Fosc/4 = 1 MHz
     *      period T = 1 microsecond
     * want T0i = 0.0002 sec
     * prescale must be power of 2. The smaller the prescale, the closer we
     * can hit our target. The other constraint is the size of TMR0, 8-bits
     * We can preload TMR0 with 0 through 255.
     * So, with a prescale of 1, we need TMR0 to count
     *      N = 0.0002 / 0.000001 = 200
     * Per data sheet 5.1.1, we must compensate for a 2 cycle delay
     *      TMR0_PRELOAD = 256 - (200 - 2) = 58
     *
     * Resulting TMR0 interrupt interval:
     *      T0i = 200 * 0.000001 = 0.0002 second
     */

    OPTION_REGbits.PS = 0b111;     // XXX Don't need prescaler
    OPTION_REGbits.PSA = 1;        // Assign prescaler to WDT
    OPTION_REGbits.T0CS = 0;       // Select FOSC/4 as Timer0 clock source
    OPTION_REGbits.T0SE = 0;       // X don't care

    /* lines above may be achieved by a single statement:
     *     OPTION_REG = 0b00001111;
     */

    T0IF = 0;           // Clear the TMR0 interrupt flag
    TMR0 = TMR0_PRELOAD; // use this value consistently
    T0IE = 1;           // Enable TMR0 interrupts

    // Configure Interrupt-On-Change for RA2, RA4, RA5
    IOCA2 = 1;          // Enable RA2 interrupt-on-change
    IOCA4 = 1;          // Enable RA4 interrupt-on-change

    shadowPortA.port = PORTA; // Read PORTA to latch the current RAx voltage level
    RABIF = 0;          // Clear the change interrupt flag

    RABIE = 1;          // Enable IOC interrupts
    GIE = 1;            // Enable interrupt system
}

void interrupt ISR(void) {
    static uint8_t isrDebounceCounter = 0;
    /**********************
     * TIMER 0
     **********************/
    if (T0IE && T0IF)           // TMR0 overflow
    {
        T0IF = 0;               // Clear the TMR0 interrupt flag
        TMR0 = TMR0_PRELOAD;    // use this value consistently
        sysClock += 1;          // bump sysClock
        isrDebounceCounter += 1;

        if (isrDebounceCounter == N_X_SYSCLOCK_DEBOUNCE) {
            isrDebounceCounter = 0;
            eventDebounceTick = 1;  // fire event
        }
    }

    /**********************
     * INTERRUPT ON CHANGE
     **********************/
    if (RABIE && RABIF) {
        /*
         * When IOC occurs on a pin, further interrupts on the pin
         * must be ignored until the pin is debounced.
         */
        RABIF = 0;                  // clear interrupt flag
        shadowPortA.port = PORTA;   // read PORTA (all pins)
        if (0 == SHDW_BTN_RIGHT) {
            /*
             * only service the pin on high-to-low transition
             * disable further IOC interrupts until debounce is complete
             */
            IOCA2 = 0;
            eventIocA2Down = 1; // fire event
        }
        if (IOCA4) {
            /*
             * only service the pin if it is enabled
             * disable further IOC interrupts until debounce is complete
             */
            IOCA4 = 0;
            eventIocA4 = 1; // fire event
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *

Tools, techniques, design approaches and fun!