Request for 16f628 design help

Discussion in 'Programmer's Corner' started by NomadAU, Jan 14, 2014.

  1. NomadAU

    Thread Starter Member

    Jul 21, 2012

    I'm looking for some design help with the logic for a fairly straightforward circuit I've built. Basically, I have a stepper motor that raises or lowers a gate and at each extreme there is a microswitch that is set when the gate slides by. In my PIC I have logic for controlling the movement of the stepper motor (clockwise or anticlockwise) and have also enabled RB7-RB4 change interrupt - the 2 switches are wired to RB4 and RB5. In my ISR I detect which pin has changed state and set a corresponding bit in a status register. The latter are checked during the routine that raises or lowers the gate so that the routine knows when to exit its stepper loop.

    It all kinda works, except for the odd occasion when it looks like a switch change might be getting missed. I've come to the conclusion that I need to implement switch debounce and that's where I could use some advice.

    I understand how debounce logic works, in fact I've implemented it several times in inline code, but in this case, I'm wondering what might be the best way to design my PIC code so that the switch changes are still detected by the PIC interrupts but somehow a 20ms time delay can be introduced to eliminate switch bounce.

    I think that what might be causing my problem is that a state change interrupt (due to bounce) might be happening before I have processed the first one in my ISR and then I really don't know what the behaviour of the PIC will be. Anyhow, here's my current ISR routine to help you understand what I've done so far. Also, note I'm also using RB6 as an activation switch, i.e. it causes the gate to lower.

    Code ( (Unknown Language)):
    2. ; start of ISR
    3. ISR     CODE    0x0004
    4.         ; save context
    5.         movwf   cs_W                        ;   1 instr
    6.         movf    STATUS,w                    ;   1 instr
    7.         movwf   cs_STATUS                   ;   1 instr
    9.         ; Test why ISR has been driven
    10. processInterrupt
    11.         btfsc   INTCON, RBIF    ; RB pin change
    12.         call    processRBPinChange
    14.         goto    ISR_END                     ; 2 instr
    17. processRBPinChange
    18.         bcf     INTCON, RBIF            ; clear interrupt flag   ; 1 instr
    20.         ; compare PORTB with previous state
    21.         movf    PORTB, w
    22.         xorwf   stateWord, w
    23.         btfsc   STATUS,Z
    24.         return
    26.         ; something has changed, store the bitstrip and figure out what
    27.         movwf   bitStrip
    28.         ; refresh last state
    29.         movf    PORTB, w
    30.         movwf   stateWord
    32.         btfsc   bitStrip, UPPER_SWITCH
    33.         call    processUpperSwitchChange
    35.         btfsc   bitStrip, LOWER_SWITCH
    36.         call    processLowerSwitchChange
    38.         btfsc   bitStrip, BUTTON
    39.         call    processButtonChange
    41.         return          ; not interested in other changes
    43. ;-----------------------------------------
    44. processUpperSwitchChange    ; get state of upper switch
    45.         btfsc   PORTB, UPPER_SWITCH
    46.         goto    upperSwitchSet
    47. upperSwitchReset
    48.         bcf     FLAGWORD, UPPER_SWITCH_SET
    49.         bcf     PORTA, LED_PIN
    50.         return
    51. upperSwitchSet
    52.         bsf     FLAGWORD, UPPER_SWITCH_SET
    53.         bsf     PORTA, LED_PIN
    54.         return
    56. ;-----------------------------------------
    57. processLowerSwitchChange    ; get state of lower switch
    58.         btfsc   PORTB, LOWER_SWITCH
    59.         goto    lowerSwitchSet
    60. lowerSwitchReset
    61.         bcf     FLAGWORD, LOWER_SWITCH_SET
    62.         bcf     PORTA, LED_PIN
    63.         return
    64. lowerSwitchSet
    65.         bsf     FLAGWORD, LOWER_SWITCH_SET
    66.         bsf     PORTA, LED_PIN
    67.         return
    69. ;-----------------------------------------
    70. processButtonChange         ; get state of button
    71.         btfsc   PORTB, BUTTON
    72.         goto    buttonSet
    73. buttonReset
    74.         bcf     FLAGWORD, BUTTON_PRESSED
    75.         return
    76. buttonSet
    77.         bsf     FLAGWORD, BUTTON_PRESSED
    78.         return
    80. ISR_END ; restore context then return
    81.         movf    cs_STATUS,w             ; 1 instr
    82.         movwf   STATUS                  ; 1 instr
    83.         swapf   cs_W,f                  ; 1 instr
    84.         swapf   cs_W,w                  ; 1 instr
    85.         retfie                          ; 2 instr
    86. ; end ISR
    Thanks for your feedback.
  2. JohnInTX


    Jun 26, 2012
    RBIF can be a bit tricky. A couple of things jump out here.

    You get the interrupt based on a change on the port but then you re-sample the port in your various processing - its not guaranteed to be the same, particularly with bouncing switches. A better way to do it would be to movf PORTB,W then MOVWF PORTBimg once then do all further processing for that interrupt on PORTBimg.

    Keep in mind that after the IRQ, PORTB can change due to bouncing bit also legitimate changes. Each time you read PORTB you clear the mismatch condition so you might lose quick changes there.

    If you needed to ensure that the port settled down, there are ways to do that but delays inside an interrupt routine is not recommended. If you have 20ms to burn debouncing the port, you can do that by polling instead of interrupt on change.

    See what happens when you process a port image first and we can go from there.

    Have fun.

    EDIT: sorry, didn't see the PIC type in the header.

    .. and actually, since these are slow switches, I would run debouncers off a timer interrupt then post the results for examination by the main routine..

    EDIT EDIT: sorry for the rambling post.. the latter is exactly what I would do. I would also encourage you (strongly) to shadow the output ports. In a noisy environment, bcf, bsf on a midrange PIC's port can be problematic.
    Last edited: Jan 14, 2014
    NomadAU likes this.
  3. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    I do it by making the 20 mS time delay with the ISR and testing the switches.

    Using Timer2 it's straightforward to set up a regular "heartbeat" inside the code. I've found 1mS pretty easy to get all the time.

    So every 20 ISR's or so (counted by a local static variable) I do a button scan, and only accept a change if I see it for 2 consecutive reads. That works well for manually pressed button, you might need to play with the time for your project.

    Note the buttons themselves do not fire interrupts, the timer does, and the buttons are read and tested over and over.

    Once I have a new stable pattern I place it into a global variable. I've also tried setting bits when a button is pressed, and another variable where released buttons set bits; the code to set these variables works but I've never applied them ;-)

    In my main loops when I need a button I just use a while loop until a button forces an exit... or loop till the button is released.
    NomadAU likes this.
  4. NomadAU

    Thread Starter Member

    Jul 21, 2012
    Many thanks to both JohnInTx and ErnieM for your very helpful ideas and insights. I've made a few important mods to the code based on your input.
    1 - the ISR copies PORTB and subsequent tests are done on the copy
    2 - the first time a change is detected for a switch, I set a 'in debounce' bit in a flagword to indicate that a debounce period has started and kick off TMR0 for 20ms. When the time interval expires, the bit is reset.
    3 - the ISR code that processes the pin changes just returns if the 'in debounce' bit is set.

    As an aside, if I didn't already have the interrupt on change ISR logic implemented, given the 'slow' nature of my application (i.e. just managing a stepper motor), I'd most likely try ErnieM's approach and scan the buttons every every 20ms - so much simpler.
  5. takao21203

    Distinguished Member

    Apr 28, 2012
    What works for me is to sample key bits through a low pass, copy them to one specific flag, and then work a state machine and maintain it's state with a byte variable.

    It only takes very few lines C.

    The low pass is done via timer interrupt.