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
}
}
}
