Electronic – Debouncing of a push button in assembly using Timer1 interrupt – PIC16F628A


I am new at assembly programming and I need to develop a debouncing code to use on a push button, for that I am required to use timer1 interrupt.

The push button will be used to increment a counter that will output to a 7seg display. I already have it displaying the numbers, using a counter and a table, but now I am stuck on the interrupt, i never did anything like that.

There should be a second button that will reset the counter. Push buttons will change state with "0" and should only count one number while pressed no matter the duration of the click. If anyone could post an example I would be very thankful. Regards

Best Answer

Here's some of that promised help.

The PIC16F628A has two vectors you need to set up. It's often done something like this:

        ORG 000H
        GOTO MAIN

        ORG 004H

        ORG 010H

When the processor comes out of reset, it will start at MAIN. But if an interrupt happens (from any source), it will then branch to ALLINTS. The names can be anything you want, though. But those will suffice here.

You can debounce any and all switches on PORTA, at once. (See the code I've provided for a different processor here: Differences between Interrupts and sampling for hardware button?

Your MAIN code will need to initialize the timer to my preference of \$8\:\textrm{ms}\$. I'll assume \$4\:\textrm{MHz}\$ is the operating clock rate. If you set up for a divide by 8, your counter will need to start out at -1000 and be reset to that, each interrupting event. Of course, you'll need to initialize variables and the TMR1 register and associated configuration bits in T1CON.

Some of the variables I'm considering might be placed in a CBLOCK, like this:


You'll need a constant, like this:

COUNTDOWN   EQU     -1000

The MAIN code (replace the above example with the following) might start out something like this:

        ORG     010H
        ; TODO: Configure PORTA as required. Assume all switches are on PORTA.

            CLRF    INTCON
            CLRF    SWDEBPREV
            CLRF    SWDEBCURR
            CLRF    T1_SWPREV
            CLRF    T1_SWCURR
            CLRF    T1_STPREV
            CLRF    T1_STCURR
            CLRF    PIR1
            BSF     STATUS, RP0
            CLRF    PIE1
            BSF     PIE1, TMR1IE
            BCF     STATUS, RP0
            MOVLW   34H
            MOVWF   T1CON
            MOVWF   TMR1L
            MOVLW   LOW (COUNTDOWN >> 8)
            MOVWF   TMR1H
            BSF     INTCON, PEIE
            BSF     INTCON, GIE
            BSF     T1CON, TMR1ON
        ; TODO: Use SWDEBCURR and SWDEBPREV here.

            GOTO    LOOP        

That's before getting into the main LOOP which polls things.

Skip past the main loop for a minute and let's get to the interrupt routine. I've provided a link above which provides the basic logic of the state machine that can be applied to debounce a switch. But... it turns out that this state machine can be performed simultaneously on an entire port at one time, as easily as it is performed on one port pin. The processor works in 8-bit bytes, so why not take advantage of that?

The interrupt code might look like the following code. But be aware that I'm assuming (for now) that there is only one interrupt that has been enabled. I'm making this assumption because (1) I have no information about other interrupts; and, (2) it would greatly complicate things.

            SWAPF   STATUS, W
            MOVWF   T1_SAVES

            BCF     T1CON, TMR1ON
            MOVWF   TMR1L
            MOVLW   LOW (COUNTDOWN >> 8)
            MOVWF   TMR1H
            BSF     T1CON, TMR1ON

            MOVF    SWDEBCURR, W    ; Make the last debounced value
            MOVWF   SWDEBPREV       ;   the prior debounced value.
            MOVF    T1_STCURR, W    ; Make the last switch state value
            MOVWF   T1_STPREV       ;   the prior switch state value.
            MOVF    T1_SWCURR, W    ; Make the last raw port value
            MOVWF   T1_SWPREV       ;   the prior raw port value.
            MOVF    PORTA, W        ; Read the port and save it as
            MOVWF   T1_SWCURR       ;   the current raw port value.
            MOVWF   SWDEBCURR       ; Start as debounced value, too.
            XORWF   T1_SWPREV, W
            MOVWF   T1_STCURR
            IORWF   T1_STPREV, F
            COMF    W, W
            ANDWF   SWDEBCURR, F
            MOVF    T1_STPREV, W
            ANDWF   SWDEBPREV, W
            IORWF   SWDEBCURR, F    ; Generate final debounced value.

            MOVF    T1_SAVES, W
            SWAPF   STATUS, W
            MOVF    T1_SAVEW, W
            BCF     PIR1, TMR1IF

I have tried to avoid any need to switch banks in the above code and that simplifies the state saving, just slightly. It's been a while (okay -- 10 years) since I've written PIC16 code. So please check it over carefully for mistakes. I think I got the basic inner logic correct. But the state saving and restoring is worth a close look. (Also note that in resetting TMR1 I felt I had to disable the counter long enough to load the next countdown value. This will cause the timing to "slide" a bit -- the time between interrupts won't be exactly \$8\:\textrm{ms}\$. But this is okay for just debouncing switches. So I didn't go to any further trouble about it.)

The MAIN code now only needs to observer the debounced states. If you want to look for a specific button value, you can read up SWDEBCURR and mask it with whatever appropriate mask is required to extract the specific pin and then just test the debounced bit value.

If, instead, you want to look for a debounced button change, then read up SWDEBCURR and XOR it with SWDEBPREV. Those bits that are then set to a 1 are cases where there has been a debounced button PRESS or RELEASE change.

Whichever choice you want is yours.