As you see adding more buttons will need more code, eventually getting larger and making development messier.
To make it dynamic, at first it needs more complex code, but then it'll be a lot better.
Using pointers is the usual way when the architecture allows it, but PIC16 are not meant for this, neither their ecosystem/header definitions.
The terrible memory bank switching messes everything up, so manually making a pointer to 0x05 could be PORTA, TRISA, WDTCON or SRCON depending on the selected bank.
Ports are declared as RAM volatile chars, so adding pointer to them will cause a "operand not constant" error by the compiler.
The only solution I found was to hardcode the PORT addresses, manually set the bank and use indirect addressing through FSR / INDF.
I recycled the debouncing code from
here, but barely resembles it now!
Now this code is way better! Plus you can use getTick() to read the system time for other timing you need.
Definitely 10ms is better suited here, a 32bit timer will overflow after 49 days at 1KHz, 497 days at 100Hz.
Keep in mind I made this code for buttons to be active-low!
If you're using them active-high, you need to invert the definitions in
button_physical_state_t.
When a short/long press was detected, the code won't update the button anymore until the software reads it using
readButtonLogicState, this will reset the button state and resume the sampling.
This will give the software the time it needs to process the events. Of course one button won't affect the rest.
#include <xc.h>
#pragma config FOSC = INTRC_NOCLKOUT// Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF // RE3/MCLR pin function select bit (RE3/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 = ON // Internal External Switchover bit (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)
#pragma config LVP = OFF // Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming)
// CONFIG2
#pragma config BOR4V = BOR40V // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V)
#pragma config WRT = OFF // Flash Program Memory Self Write Enable bits (Write protection off)
#define TIMEBASE 10 // Timebase in ms (100Hz timer)
#define PR1 ((const unsigned int)(0xFFFFU - (8000000U/(4UL*8*(1000/TIMEBASE))) + 4))// +4 for compensating interrupt latency and reload delay
typedef enum { button_pressed=0, button_released=1 }button_physical_state_t; // Physical states. Adjust as required if inverted.
typedef enum { button_idle, button_short, button_long, button_wait_release }button_logic_state_t; // Logical states
typedef enum { // Have to do this because PIC addressing/bank switching and/or compiler sucks
PORT_A=0x5, PORT_B=0x6, PORT_C=0x7,
PORT_D=0x8, PORT_E=0x9, PORT_F=0xA
}port_addr_t;
typedef enum { button_RB1=0, button_RB2=1 }button_t; // Button index definition, just to make it easier
typedef struct { // Input filter structure
union{
volatile uint8_t d;
struct{
unsigned pin :3; // Pins: 0-7
unsigned logic_state :2; // idle, short, long, wait_release
unsigned last :1; // last reading
unsigned state :1; // current stable state
unsigned stable :1; // stable flag
};
};
uint8_t port; // input port (port_addr_t))
uint32_t timer; // Time store for debouncing, events
}inputFilter_t;
volatile inputFilter_t buttons[] = { // Button structure initialization
{ .port = PORT_B, .pin = 1, .state = button_released }, // RB1
{ .port = PORT_B, .pin = 2, .state = button_released }, // RB2
};
volatile uint32_t tick; // System tick
uint32_t GetTick(void);
unsigned readButtonValue(button_t b);
uint8_t readButtonLogicState(button_t b, button_logic_state_t state);
void main(void)
{
OSCCON = 0x70;
ANSEL = 0b0000000;
ANSELH = 0b0000000;
TRISA = 0x0;
TRISC = 0x0;
PORTA = 0x0;
PORTC = 0x0;
TRISBbits.TRISB0 = 1;
TRISBbits.TRISB1 = 1;
TRISBbits.TRISB2 = 1;
TRISCbits.TRISC0 = 0;
TMR1H = (PR1 >> 8) & 0xFF;
TMR1L = PR1 & 0xFF;
T1CONbits.TMR1CS = 0;
T1CONbits.T1CKPS = 0b11;
T1CONbits.TMR1ON = 1;
PIE1bits.TMR1IE = 1;
INTCONbits.GIE = 1;
INTCONbits.PEIE = 1;
while(1)
{
if(readButtonLogicState(button_RB1, button_short) {
Lcd_Goto(1,1);
Lcd_Print("Button RB1 pressed");
}
if(readButtonLogicState(button_RB1, button_long) {
Lcd_Goto(1,1);
Lcd_Print("Button RB1 L_pressed");
}
if(readButtonLogicState(button_RB2, button_short) {
Lcd_Goto(1,1);
Lcd_Print("Button RB2 pressed");
}
if(readButtonLogicState(button_RB2, button_long) {
Lcd_Goto(1,1);
Lcd_Print("Button RB2 L_pressed");
}
}
}
uint32_t GetTick(void){ // returns system time in tens of ms (1=10ms, 100=1000ms)
return tick;
}
uint8_t readButtonValue(button_t b) { // Read filtered button state
return (buttons[b].state == button_pressed); // Return true if pressed
}
uint8_t readButtonLogicState(button_t b, button_logic_state_t state) { // Read logical button states
button_logic_state_t s = buttons[b].logic_state;
if(s==button_wait_release){ // treat button_wait_release as idle
s=button_idle;
}
if(s==state) { // If state matching request
if(s==button_short || s==button_long) { // If we have a pending state to be read
buttons[b].logic_state = button_wait_release; // Set flag to wait for release
}
return 1; // Return true
}
return 0; // Return false
}
#define BUTTON_COUNT (sizeof(buttons)/sizeof(inputFilter_t))
#define BUTTON_DEBOUNCE (20/TIMEBASE) // Debounce time
#define BUTTON_LONG_MIN (1000/TIMEBASE) // Min long press time
void updateButtons(void) { // To be called periodically by main or some interrupt every few ms
uint32_t now = GetTick();
for(uint8_t b=0; b<BUTTON_COUNT; b++) { // Scan buttons
STATUSbits.IRP = 0; // Select lower memory bank (For PORT registers) - PIC addressing is ancient!
FSR = buttons[b].port; // Use indirect addressing, load port address into FSR
uint8_t current = (INDF >> buttons[b].pin) & 1; // Read value, convert to boolean
if(buttons[b].last != current){ // If button changed
buttons[b].stable = 0; // Changed, not stable
buttons[b].last = current; // Update last state
buttons[b].timer = now; // Restart timer
}
else if(!buttons[b].stable){ // If not stable
if((now-buttons[b].timer)>BUTTON_DEBOUNCE ) { // Check if button was stable for the specified time
if(buttons[b].state != current) { // New state
if( current==button_released && // If button released,
buttons[b].logic_state == button_idle && // logic state was idle
buttons[b].state == button_pressed) { // and previous button state was pressed
buttons[b].logic_state = button_short; // Short press
}
buttons[b].state = current; // Update state
}
buttons[b].stable = 1; // Set stable condition
}
}
else { // Button stable
if(current==button_pressed) { // If pressed
if(buttons[b].logic_state == button_idle) { // And logic state idle
if(now - buttons[b].timer > BUTTON_LONG_MIN) { // If enough time passed
buttons[b].logic_state = button_long; // Long press
}
}
}
else if(buttons[b].logic_state == button_wait_release) { // If released and logic button state was waiting
buttons[b].logic_state = button_idle; // Set idle
}
}
}
}
void __interrupt() isr(void)
{
if(PIR1bits.TMR1IF) {
T1CONbits.TMR1ON = 0;
TMR1H = PR1 >> 8; // Preload timer
TMR1L = PR1 & 0xFF;
T1CONbits.TMR1ON = 1;
PIR1bits.TMR1IF = 0; // Reset flag
tick++;
updateButtons();
}
}