Program #2 - Counting Presses of a Button

;-----------------------------------------------------------------------;
; SWCNT.ASM            Counts presses of a pushbutton on RB7            ;
;-----------------------------------------------------------------------;
        LIST P=16F84           ;  tells which processor is used
        INCLUDE "p16f84.inc"   ;  defines various registers etc. Look it over.
        ERRORLEVEL -224        ;  supress annoying message because of tris
        __CONFIG _PWRTE_ON & _LP_OSC & _WDT_OFF   ;  configuration switches

;-----------------------------------------------------------------------;
;    Here we set up our own registers start at the 1st free address     ;
;-----------------------------------------------------------------------;
           CBLOCK H'0C'
               dlycount         ; counter used in delays
           ENDC

           ORG 0              ; start a program memory location zero

;-----------------------------------------------------------------------;
;        First we set up all bits of PORT A and B as outputs            ;
;-----------------------------------------------------------------------;
         movlw B'00000000'    ; all bits low in W
         tris PORTA           ; contents of W copied to PORT A ...
         movlw B'00010000'    ; RB4 input, all other output
         tris PORTB           ; and PORT B
         movlw B'00000000'    ; port B pull-ups active
         option               
 
;-----------------------------------------------------------------------;
;                       This is the main program                        ;
;-----------------------------------------------------------------------;
         clrf PORTB           ; start with zero
loop:
         btfsc PORTB, 4       ; switch closed, (gives 0)?
         goto loop            ; not yet
                              ; switch has been detected closed
         incf PORTB, f        ; add 1 to port B
                              ; wait a while to make sure switch has 
                              ; settled  (debounce closure)
         movlw D'10'          ; wait about 10 msec
         call nmsec
                              ; now wait for release 
         btfss PORTB, 4       ; will be high (1) when released
         goto $ -1            ; still low
                              ; now must wait a make sure bouncing stopped
         movlw D'10'          ; 10 milliseconds
         call nmsec
                              ; and check again
         btfss PORTB, 4       ; if set, still released
         goto $ -5            ; still low start release wait all over

         goto loop            ; loop forever

;-----------------------------------------------------------------------;
;  Here is a subroutine: delays number of millisec in 'W' on entry      ;
;-----------------------------------------------------------------------;
nmsec:
         movwf dlycount       ; save 'W' in a register of our own
dlyloop: 
         nop                  ; each nop is 0.122 milliseconds
         nop
         nop                  ; each total loop is 8 X 0.122 = 0.976 msec
         nop
         nop
         decfsz dlycount, f   ; this is 0.122 msec if skip not taken
         goto dlyloop         ; this is  2 X 0.122 msec  
         return               ; back to calling point

         end                  ; end of program

Configuration switches

There are four items that are set when you "burn" the program into the PIC that are entirely separate from the program itself. These are set by supplying a configuration word which is burned into the PIC. The bits making up this word are formed in the line that starts...'__CONFIG'.

Subroutines

In this program we use a subroutine instead of having everything in line. Subroutines are useful for code that has to be called many times. It saves space because only one copy in needed in the code. The routine is entered using a 'call' instruction and the 'return' instruction returns to the instruction immediately after the call instruction.

Instruction Cycle Counting for Delays

Time delays can be handled by using the time taken to execute instructions rather than waiting for a flag set by at timer overflow. An advantage of the PIC instruction set is that almost all instructions take the same amount of time to execute. This is the period of the oscillator divided by four. For a 4 mHz crystal this is 1 microsecond. For a 32.768 kHz crystal it is about 122 microseconds. The only exceptions to this are when the next instruction is not the next in sequence, (goto,call,return,btfss etc.). In these cases, the program counter has to be changed and the instruction takes two instruction cycles rather than one. Notice the delay subroutine at the end of the program which delays for the number of milliseconds given in 'W' on entry.

Port B Pull-ups

To keep from having external resistors to pull up pins set to inputs, you can use the internal weak-pull-ups available on port B only. To activate them, bit 7 of OPTION, (RBPU), must be set to 0. This affects all the bits of port B but only when they are set up as inputs. They are essentially like a high resistance to Vdd.

Note that Port B now has an input, (RB4), with a pushbutton switch to ground. This could be as simple as a loose wire attached to GND. You touch this to a wire attached to the RB4 pad.

*** UPDATE ***

I no longer recommend attaching loose wires to the test circuit. The problem is that if these are made outputs they can move around and short against something else. I recently smoked a power supply and burnt out some ports on a PIC. I think the problem was loose wires. For the same reason I also recommend mounting the test circuit board on stand-offs or at least covering the bottom with electrical tape. It is easy to lay the board down on something that will short things out.

Button Debouncing

Much of the code is taken up in 'de-bouncing' the button. When a pushbutton is pressed or released it is not usually a simple on-off contact. The contact usually bounces on and off a number of times before settling down. How long this takes depends on the construction of the switch, but 10 milliseconds is usually long enough unless the switch is real poor.

My approach is to look only for the first contact. Usually the action determined by closing the switch takes over 10 msec to happen so further checking for closure is not required. If it is, a 10 msec delay can be built in. This is done in the program above since the increment takes hardly any time at all. This does not allow for a false trigger or noise spike, that would require an additional check after the 10 msec delay.

You must be concerned with bounce on release also. We cannot loop back and check for closure again as soon as we detect the first open. We must be certain that no bouncing remains on release. In the program, a second check is made after 10 msec and if the switch still appears closed, additional 10 msec check/s are made. A faster way would require checking for a certain number of consecutive highs at regular intervals.

User Defined Registers

We have created our own register 'dlycount'. One way to do is in MPASM is to use a block of names between 'CBLOCK' and 'ENDC'. The number after 'CBLOCK' indicates where in the register memory these names are to start.

BINCNT.ASM used only what Microchip calls special purpose registers, those built into the chip and given definite names in 'p16F84.inc'. There are 12 of these at register locations 0-11 and that is why our register was defined at location 12, ( H'0C' ). There are 68 eight bit register locations in total. Actually, there are two 'pages' of 68 bit registers, but most are duplicated in both pages, including all of the general purpose registers that we get to make up names for. It is just those few that are different in the two pages that screw everything up. You don't want to know about this 'page flipping' until you have to.

NOP

'nop' stands for no operation. It doesn't do anything really but it does take up time and that is what we use it for here. The time taken is one instruction cycle, (the crystal period divided by four), 0.122 msec in this case.

DECFSZ

A branching instruction we haven't seen before is 'decfsz'. In words it's: 'decrement the given register and skip the next instruction if the result of the decrement gives zero'. It's very handy for counting down to zero from a given number.

Now it's your turn:

goto and '$'

'goto $ -1' is pretty straight forward but 'goto $ -5' requires a little counting and larger jumps get worse. What could you use to replace the 'goto $ -5'? Try it.

play with debounce values

Try commenting out parts or all of the debounce code and see what happens. You could also try changing those 10 msec values and try different switches. Some of the cheaper pushbuttons are really bad. You will find that the simple loose wire 'switch' is also very bad.

counting something else

Do you have an old rotary dial phone around? If you take the cover off and find the wire coming from the rotary dial to the same terminal as the red wire, you can disconnect it. Use this wire from the dial and the other one from the dial, (to terminal with green wire), in place of the pushbutton. As you rotate the dial clockwise, these two wires will short. As the dial returns counter-clockwise it will open and close giving pulses at a rate of 10 per second. Modify swcnt.asm to reset to zero at the first short and then count the pulses.

count to higher values, (without more LEDs)

We really need higher count values. Can you think of a way to do this? How about adding a 1 second delay routine, (make it a subroutine). You could then write a program that showed the high 4 bits of PORTB for a second or two and then the low 4 bits. Of course you couldn't count additional switch closures while this was going on, ( can anyone say 'interrupt'?).

How would you go about getting only the low 4 bits of Port B? Do you know about AND? You could AND PORTB with H'0F' but that destroys the top part of PORTB. You really need a copy of PORTB that you can destroy, or actually maybe you shouldn't be incrementing PORTB anyway. You are going to need to destroy PORTB to display the top 4 bits of the count. Maybe you should really be incrementing a register that you create. You can then take parts of this register and transfer them to PORTB for display. Quite a bit to think about. Have fun!

Khristine Joyce de Jesus Cruz of De La Salle University Says:

This tutorial is a big help for me...it helped me eternalize what the datasheet is talkin about. Thank s for the unselfish guys who willingly impart their knowledge....REGARDS n keep helpin'!

See: