Help needed with push buttons

Thread Starter

Carl03

Joined Oct 27, 2012
10
Hi,
I'm building a binary clock and I have been working on this project for a while now, I have completed all programming up until the buttons. I have timer0 interrupt going for state machine/multiplexing and timer2 interrrupt going for the actual time keeping. I'm using assembler to code this and I don't know where the button coding would come.

Here is my interrupt routine:
Rich (BB code):
    ;**************** INTERRUPT SERVICE *********************
	ORG 0x0004
isr:   
 	 	movwf   W_TEMP            ; save off current W register contents
    	movf    STATUS,W          ; move status register into W register
 	   	movwf   STATUS_TEMP       ; save off contents of STATUS register
 	   	movf    PCLATH,W          ; move pclath register into W register
   		movwf   PCLATH_TEMP       ; save off contents of PCLATH register
		
; Test to see if the interrupt was generated by TMR0 or TMR2

      	btfsc   INTCON,TMR0IF  	 ; TMR0 interrupt?
       	goto    tmr0int		  	 ; Yes
		btfsc	PIR1, TMR2IF
		goto	tmr2int
		goto	intout


tmr0int:
		bcf		INTCON, TMR0IF	 ; clear interrupt flag (HAVE TO DO THIS)

state_machine:
		movlw	.0
		xorwf	current_state, w
		btfsc	STATUS, Z
		goto	state_machine_hours
		movlw	.1
		xorwf	current_state, w
		btfsc	STATUS, Z
		goto	state_machine_min
		goto	state_machine_sec

state_machine_hours:
		bcf		SEC_EN
		comf	hour, w
		movwf	PORTB
		bsf		HOUR_EN
		incf	current_state, f
		goto	state_machine_end
state_machine_min:
		bcf		HOUR_EN
		comf	min, w
		movwf	PORTB
		bsf		MIN_EN
		incf	current_state, f
		goto	state_machine_end
state_machine_sec:
		bcf		MIN_EN
		comf	sec, w
		movwf	PORTB
		bsf		SEC_EN
		clrf	current_state
state_machine_end:
		goto	intout


tmr2int:
		bcf 	PIR1, TMR2IF	 ; clear interrupt flag (HAVE TO DO THIS)
sec_s:
		banksel sec
		incf	sec_split, f
		movf	sec_split, w
		sublw	d'250'
		skpweq
		goto 	intout
every_sec:
		clrf	sec_split
		incf	sec, f
		movf	sec, w
		sublw	d'60'
		skpweq
		goto 	intout

		;gets here every minute
every_min:
		clrf 	sec
		incf	min, f
		movf	min, w
		sublw	d'60'
		skpweq
		goto 	intout
		
		;gets here every hour
every_hour:
		clrf	min
		incf	hour, f
		movf	hour, w
		sublw	d'24'
		skpweq
		goto	intout

clear:
		clrf 	sec
		clrf	min
		clrf	hour

intout:
    	movf    PCLATH_TEMP,W     ; retrieve copy of PCLATH register
    	movwf   PCLATH            ; restore pre-isr PCLATH register contents
    	movf    STATUS_TEMP,W     ; retrieve copy of STATUS register
    	movwf   STATUS            ; restore pre-isr STATUS register contents
    	swapf   W_TEMP,F
    	swapf   W_TEMP,W          ; restore pre-isr W register contents
    	retfie                    ; return from interrupt
Does anyone have a code snippet of debouncing a button?
Any help would be greatly appreciated.
 

Attachments

Thread Starter

Carl03

Joined Oct 27, 2012
10
My hardware has already been built. Where should the button code go? How do I turn off the timer2 interrupt?
 

ErnieM

Joined Apr 24, 2011
8,377
I handle my buttons inside the ISR and pass the results in a global variable (as they typically all are anyway in assembler).

In my latest app I have an ISR that trips every millisecond. I test my buttons every 25 mS, which makes the app responsive while avoiding any bouncing issues.

I keep several variables:

KeyDelay: Counts every 1 mS, reset at 25 mS.
LastKeys: Last reading of buttons.
Keys: Current debounced button state.

So every ms I increment KeyDelay, and when it's 25 I reset it and read the current buttons, and compare to the previous read in LastKeys. If they are the same I have stable buttons, and that information is saved in the Keys variable. The current button state is then saved in LastKeys for the next time thru the loop.

The main loop of code monitors Keys, and can take action when pressed. Note the pressed action code needs something to wait till the button is released least it just do the function over and over until the button is released.

Note sometimes that action is desired to say have the UP button keep incrementing some parameter, and that can be added at a later time if you so wish.

I would post my code but A) it's written in C, and 2) it's somewhat specific as my buttons share the data lines with an LCD display so there is a lot of extraneous code that doesn't apply here.

Generally I try to not write anything in assembler. <grin> Nothing wrong with it, I'm just more productive when using C.
 

JohnInTX

Joined Jun 26, 2012
4,787
The debounce routine here was lifted from a project and is implemented as a macro which can be invoked multiple times for multiple switches. The resulting debounce routines are called by the timer IRQ routine and work by incrementing a dedicated RAM byte (1 per switch) when the switch is closed and decrementing it when its open. Counting up is limited when a selected bit in the count register goes to '1' and limited the other way when it counts back to '0' so it debounces both ways.

Of note is the output. Like ErnieM's, it posts a global status flag so that the main routines only have to check the flag to see what the switch status (TRUE/FALSE) is. I also incorporate a second flag (defined in DebInputReqs) that is set ONCE when the switch gets fully debounced. Its useful for things like counting switch closures as main does not have to remember what the state of the switch is, it just reads then CLEARS the flag and waits for the next ..Req(uest)flag.

In use, just call the macro-generated debouncers on each timer IRQ. Your main routine has only to examine the various status flags, never the switches directly.

The macro is general purpose as it allows specifying whether the input is active high or low to be logically TRUE. You also specify the port,bit and RAM active and request flags in the macro invocation.

I like macros for something like this. All you have to do is invoke it once and it makes your code for you. Adding switches is as simple as defining them and invoking the macro again with the new defs. The downside is that it can take up more ROM but building and passing parameters to a subroutine does so as well so pick your poison. A lot of switches might require another approach.

If you are uncomfortable with macros, maybe you can use it as a model.

Have fun.

BTW: skpz and skpnz are built in macros for testing the Z flag i.e. skpz = btfss STATUS,Z
Also, ignore any references to rambanks. That' up to you.

Rich (BB code):
          ;*********************  BUTTON  DEBOUNCER  ***********************

          ; Debounces button inputs active low or hi. Debounces both ways
          ;  port,bit is the input line
          ;  swreg is the XXXdeb ram byte/counter 
          ;  debK is the bit to count up to to be debounced (2^^debK)*timer tik
          ;  flagbit is the bit in the DebInputs regs that indicates cond.
          ;  active 0/1 for active low/hi input
          ; Expects rambank 0 to see ports


debounce  macro   port,bit,swreg,debK,flagbit,active
          local   false,exit
          expand

          if (active==0)          ; active low or hi
           btfsc   port,bit ; TRUE or FALSE?
          else
           btfss   port,bit
          endif
          goto    false

          btfsc   swreg,debK      ; iff bit set, switch is already TRUE, thazzit
          goto    exit

          incf    swreg,F         ; else, inc debounce reg
          btfss   swreg,debK      ; now.. iff just done..
          goto    exit            ; it aint

                          ; ..else, set button-held and request (just changed) flags
          bsf     DebInputs,flagbit
          bsf     DebInputReqs,flagbit
          goto    exit            ; done

false:  
          movf    swreg,F         ; switch input FALSE, count already Z?
          skpz                    ; yup
          decf    swreg,F         ; else, dec it
          
          skpnz                   ; and iff Z now, clear the SW active flag
          bcf     DebInputs,flagbit
exit:
          noexpand
         endm

    ; *********** IO and FLAG BIT DEFINITIONS  ***************
#define POWERSW_    PORTC,0 ; switch connected to RC0
#define TESTSW_        PORTC,1    ; RC1
#define PanelDebK     5    ; count up to bit 5 (2^5 tiks) to debounce

#define PWRbit        0    ; bit in status regs corresponding to PWR switch
#define TESTbit        1    ; bit in status regs corresponding to TEST switch

    ;************* RAM DECLARATIONS  *************************

    cblock 20h

    PWRdeb:        1    ; counter debounces PWR sw
    TESTdeb:    1    ; counter debounces TEST sw
    DebInputs:    1    ; switch held status bits
    DebInputReqs:    1    ; switch just closed status bits

    endc

;************* SWITCH STATUS FOR MAIN TO USE  **********************
#define PWR_SW_TRUE    DebInputs,0
#define PWR_SW_CHANGED    DebInputReqs,0

#define TEST_SW_TRUE    DebInputs,1
#define TEST_SW_CHANGED DebInputReqs,1


;****************** MACRO INVOCATIONS FOR TWO SWITCHES *******

          ; Invoked via IRQ every 4 msec. These generate the debounce code.

    ; Make code to debounce PWR switch active low, 2^5 timer tiks to debounce
    ; Switch is on RC0, Active and Changed flags on bit 0 in respective registers.

debouncePOWERSW:
          debounce POWERSW_,PWRdeb,PanelDebK,PWRbit,0
          return

    ; Make code to debounce TEST switch active low, 2^5 timer tiks to debounce
    ; Switch is on RC1, Active and Changed flags on bit 1 in respective registers.

debounceTESTSW:
          debounce TESTSW_,TESTdeb,PanelDebK,TESTbit,0
          return


    ;******************* INIT CODE  **********************
    ; Call once to init the debouncers
initDebounce:
    clrf    PWRdeb
    clrf    TESTdeb
    clrf    DebInputs
    clrf    DebInputReqs
    return
Note the pressed action code needs something to wait till the button is released least it just do the function over and over until the button is released.
This is handled above by the flags in the DebInputReqs register. When the flag is TRUE (indicating that the switch just completed debouncing), take action and clear the flag.

Generally I try to not write anything in assembler. <grin> Nothing wrong with it, I'm just more productive when using C.
I personally prefer assembler <stupid grin> for midrange PICs (after the macros are written..) but have also used this approach in C.
 
Last edited:

Thread Starter

Carl03

Joined Oct 27, 2012
10
I'm writing my code in assembler. My buttons are to set the hours and minutes. My hours set button is connected to RA3 and minutes set button is connected to RA4 and this also resets the seconds. I've written a bit of code but when I start the program, it scrolls through the hours fast (very fast) until I press the hours set button does the same with minutes. I would post the code but at the moment I'm replying via my mobile. Any ideas what my problem might be?
 

ErnieM

Joined Apr 24, 2011
8,377
Any ideas what my problem might be?
<resisting urge to just reply with just the single word "yes.">

First off your buttons are working backwards, as if you have pull up resistors, switch to ground, so a press makes a zero in but your code tests for a one as a press.

Next since they increment over and over you never test for a button release, so the increment routine just keeps on going and going and going...

Last you test hours before minutes, so when you actually press hours the code stops sensing the button down (it's reversed remember?) so then it tests the minutes resulting in a similar bug as the hours has.

Knowing this the solutions should be fairly evident.
 

Thread Starter

Carl03

Joined Oct 27, 2012
10
This is the code I have

Rich (BB code):
check_delay:
	incf	debounce, f
	movf	debounce, w
	sublw	.255
	skpweq
	goto	check_delay
	clrf	debounce
	goto	check_buttons

check_buttons:
	movf	PORTA, w
	movwf	buttons	
	btfss	buttons,4
	goto	check_end
	btfss	buttons, 3
	btfss	buttons, 4
	goto	set_hours
	goto	set_min

set_hours:
	btfss	buttons, 3
	goto	check_end
	incf	hour, f
	movf	hour, w
	sublw	d'24'
	skpweq
	goto	show_hour
	clrf	hour
show_hour:
	movf	hour, w
	movwf	hour
	bsf		T2CON, TMR2ON		
	return

set_min:
	incf	min, f
	movf	min, w
	sublw	d'60'
	skpweq
	goto	show_min
	clrf	min	
show_min:
	movf 	min, w
	movwf	min

check_end:
	bsf		T2CON, TMR2ON
	return
Have no idea what I'm doing.
 
Top