ku_thai
ku_thai

Reputation: 41

Debounce button in PIC

I newbie in PIC mcu. I use pic12f675 MPLAB and XC8 for make an LED multiple blink pattern. and I have problem with push button (after review it call Bounce and Debounce). Sometime when I press button it will in sequence ex. 1->2->3->4->5 but sometime it will jump ex. 1->3->4->6 etc.

Please advice me How to debounce in pic mcu or another way to solve my problem.

Thank you. everyone.

(PS.I connect push button with 10K resistor)

my code at below

    #include <xc.h>
    #pragma config FOSC=INTRCIO,WDTE=OFF,MCLRE=OFF,BOREN=OFF
    #define _XTAL_FREQ 4000000
    
    int cnt = 0;
    int k = 0;

void __interrupt() MyISR(void){
   
    if(INTCONbits.INTF)  //If External Edge INT Interrupt
    {
        
        cnt++;
        INTCONbits.GIE = 0;
            
        INTCONbits.INTF = 0;    // Clear the interrupt
        
        INTCONbits.GPIF = 0;
        
        
        if( cnt > 6 ){
            cnt = 1;
            
        }
        
    }
    
    INTCONbits.GIE = 1;
    
}

void main(void) {
    ANSEL = 0;
    CMCON = 0b00000111; //turns comparators off
    TRISIO = 0b00000100;
    GPIO   = 0;
    
    TRISIO2 = 1;                // Make GP2 pin as input
    
    INTCONbits.GIE = 1;
    INTCONbits.INTE = 1;
    INTCONbits.GPIF = 1;
    INTCONbits.INTF = 0;

    
    OPTION_REG = 0b01000000;   

    while(1){
     
        if( cnt == 1 ){
            GP0 = 1;
            GP5 = 1;
        }else if( cnt == 2 ){
            
            for(k=0;k<30;k++){
                GP5 = 1;
                GP0 = 1;
            }
            k=0;
            while(k<3){
                GP5 = ~GP5;
                __delay_ms(70);
                GP0 = ~GP0;
                __delay_ms(70);
                k++;
            }
        }else if( cnt == 3 ){
            for(k=0;k<5;k++){
                GP5 = 1;
                GP0 = 1;
                __delay_ms(70);
                GP5 = 0;
                GP0 = 0;
                __delay_ms(70);
            }
            GP5 = 0;
            GP0 = 0;
            __delay_ms(1200);
            
            
        }else if( cnt == 4 ){
            
            for(k=0;k<3;k++){
                GP0 = 1;
                __delay_ms(50);
                GP0 = 0;
                __delay_ms(50);
            }
            
            
            for(k=0;k<3;k++){
                GP5 = 1;
                __delay_ms(50);
                GP5 = 0; 
                __delay_ms(50);
            }
            
            
        }else if( cnt == 5 ){
            
            GP0 = 1;
            GP5 = 1;
            for(k=0;k<3;k++){
                GP5 = 1;
                __delay_ms(50);
                GP5 = 0; 
                __delay_ms(50);
            }
            GP0 = 1;
            GP5 = 1;
            for(k=0;k<3;k++){
                GP0 = 1;
                __delay_ms(50);
                GP0 = 0; 
                __delay_ms(50);
            }
            
        }else if( cnt == 6 ){
            
            GP0 = 1;
            GP5 = 1;
            __delay_ms(20);
            GP0 = 0;
            GP5 = 0;
            __delay_ms(3000);
            
        }
        
    }
    return;
}

Upvotes: 2

Views: 2029

Answers (1)

Kozmotronik
Kozmotronik

Reputation: 2520

I rewrite your code and tested it in MPLAB simulation. It works as expected. It changes modes in ascending order, then runs in the selected mode until the change button pressed again, then it changes to the next mode. You can add more working modes if you want or you can modify the way the how GPIOs blinking. There is no __delay_ms(), that's why the delays run without consuming the CPU. Please test it in a real circuit and give me a feedback.

/*
 * File:   main.c
 * Author: kozmotronik
 *
 */

#define _XTAL_FREQ 4000000
#include <xc.h>
#include <stdint.h>
#include <stdbool.h>

#pragma config FOSC=INTRCIO,WDTE=OFF,MCLRE=OFF,BOREN=OFF

// Work mode definitions
#define MODE_IDLE   0
#define MODE_OFF    1
#define MODE_ON     2
#define MODE_SLOW   3
#define MODE_FAST   4
#define MODE_CANCEL 5
#define LAST_MODE   MODE_FAST

// Button states
#define BUTTON_IDLE             0
#define BUTTON_PRESS_DETECTED   1
#define BUTTON_DEBOUNCING       2
#define BUTTON_PRESS_CONFIRMED  3

#define SYSTEM_CLOCK_MS 1
#define SYSTEM_CLOCK_uS (SYSTEM_CLOCK_MS * 1000)
#define _XTAL_FREQ_MHZ (_XTAL_FREQ / 1000000) // Oscillator freq in MHz
#define TMR0_RELOAD_VALUE 256 - ( (SYSTEM_CLOCK_uS * _XTAL_FREQ_MHZ) / (8 * 4) ) // Result must be 131
#define MS_TO_TICKS(msTime) (msTime / SYSTEM_CLOCK_MS)

typedef struct{
    unsigned int start;
    unsigned int ticks;
} time_t;

char mode = MODE_IDLE;
char lastMode = MODE_OFF;
char buttonState = BUTTON_IDLE;
char k = 0;
unsigned int systemTick = 0; // Time value count by Timer0


void __interrupt() MyISR(void){
   
    if(INTCONbits.INTF)  //If External Edge INT Interrupt
    {
        INTCONbits.INTF = 0;    // Clear the interrupt
        buttonState = BUTTON_PRESS_DETECTED; // Signal the detected press
    }
    // Check for 1 ms periodic interrupt for system clock
    else if(INTCONbits.T0IF){
        INTCONbits.T0IF = 0; // clear flag
        TMR0 = TMR0_RELOAD_VALUE; // Reload the calculated value for 1 ms
        systemTick++;
    }
}

// Setup Timer0 for 1ms interrupt
void setupTimer0(){
#define PRESCALER_VALUE 2
#define PRESCALER_MASK ~7
    OPTION_REG &= PRESCALER_MASK; // Clear prescaler bits
    OPTION_REG |= PRESCALER_VALUE; // Set prescaler value for 1:8
    OPTION_REGbits.PSA = 0; // Assign prescaler to Tim0
    OPTION_REGbits.T0CS = 0; // Set internal oscillator as clock source
    TMR0 = TMR0_RELOAD_VALUE;
    INTCONbits.T0IF = 0;
    INTCONbits.T0IE = 1; // Enable Timer0 interrupt
}

// Get count atomically
unsigned int getTickCount(){
    unsigned int count;
    di(); // disable interrupts
    count = systemTick;
    ei(); // enable interrupts again
    return count;
}

void performMode(){
    static time_t modeDelay;
    static char slowModeState = 1;
    static char fastModeState = 1;
    
    switch(mode){
        case MODE_OFF:
            // Always must save the current mode before put it into the IDLE
            lastMode = mode; // We have to save the last mode first then put it into the IDLE state
            mode = MODE_IDLE; // The rollover bug caused by here since we haven't save the last mode before put it into the IDLE state
            GP0 = 0; GP5 = 0;
            break;
            
        case MODE_ON:
            GP0 = 1; GP5 = 1;
            break;
            
        case MODE_SLOW:
            if(slowModeState == 1){
                GP0 = 1; GP5 = 1;
                modeDelay.ticks = MS_TO_TICKS(100);
                modeDelay.start = getTickCount();
                slowModeState = 2; // Proceed the next step
            }
            else if(slowModeState == 2){
                if( !((getTickCount() - modeDelay.start) >= modeDelay.ticks) ){
                    // Delay not expired yet
                    return;
                }
                GP0 = ~GP0; GP5 = ~GP5; // Toggle
                // Reload the start time
                modeDelay.start = getTickCount();
            }
            break;
            
        case MODE_FAST:
            if(fastModeState == 1){
                GP0 = 1; GP5 = 1;
                modeDelay.ticks = MS_TO_TICKS(50);
                modeDelay.start = getTickCount();
                fastModeState = 2; // Proceed the next step
            }
            else if(fastModeState == 2){
                if( !((getTickCount() - modeDelay.start) >= modeDelay.ticks) ){
                    // Delay not expired yet
                    return;
                }
                // Delay time expired, proceed toggle
                GP0 = ~GP0; GP5 = ~GP5; // Toggle
                // Reload the start time
                modeDelay.start = getTickCount();
            }
            break;
            
        case MODE_CANCEL:
            // Cancel the current running mode, reset everything
            modeDelay.start = 0;
            modeDelay.ticks = 0;
            slowModeState = 1;
            fastModeState = 1;
            // Also reset the outputs
            GP0 = 0; GP5 = 0;
            break;
            
        default:
            mode = MODE_IDLE;
    }
}

void checkButton(){
#define DEBOUNCE_DELAY_MS 100u // Debounce delay is 100 ms
    static time_t debounceTimer;
    
    switch(buttonState){
        case BUTTON_IDLE:
            break;
            
        case BUTTON_PRESS_DETECTED:
            debounceTimer.ticks = MS_TO_TICKS(DEBOUNCE_DELAY_MS);
            debounceTimer.start = getTickCount();
            buttonState = BUTTON_DEBOUNCING;
            break;
            
        case BUTTON_DEBOUNCING:
            if( !((getTickCount() - debounceTimer.start) >= debounceTimer.ticks) ){
                // Debounce time has not expired yet
                return;
            }
            // Debounce time has expired so check the button last time to confirm if it is still pressed
            if(GPIObits.GP2 != 1){
                // Not stable yet, debounce again
                buttonState = BUTTON_PRESS_DETECTED;
            }
            // Button press is stable, confirm it
            buttonState = BUTTON_PRESS_CONFIRMED;
            break;
            
        case BUTTON_PRESS_CONFIRMED:
            buttonState = BUTTON_IDLE; // Change state so that it can process a new button press
            if(mode != MODE_IDLE && mode != MODE_OFF){
                // Cancel the running mode first
                lastMode = mode; // save the last mode
                mode = MODE_CANCEL; // purge the current one
                performMode();
            }
            mode = lastMode + 1; // Switch to next mode
            if(mode > LAST_MODE){
                // Rewind mode to the beginning which is MODE_OFF
                mode = MODE_OFF;
            }
            break;
            
        default:
            buttonState = BUTTON_IDLE;
    }
}


void main(void) {
    ANSEL = 0;
    CMCON = 0b00000111; //turns comparators off
    TRISIO = 0b00000100;
    GPIO   = 0;
    
    TRISIO2 = 1;                // Make GP2 pin as input
    
    INTCONbits.INTF = 0;
    INTCONbits.INTE = 1;
    INTCONbits.GIE = 1;

    OPTION_REG = 0b01000000; // Rising edge interrupt
    
    setupTimer0();

    // Super loop
    while(1){
        
        // Task 1: Button check
        if(buttonState != BUTTON_IDLE){
            checkButton();
        }
        
        // Task 2: Mode check
        else if(mode != MODE_IDLE){
            performMode();
        }
        
    }
    return;
}

Upvotes: 3

Related Questions