Search code examples
cinterruptpic

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

Solution

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