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.
Here is the a photo of the breadboard.
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 } } }