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
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'.
-
Power up timer To prevent the program from starting
too soon after the power is switched on, (everything may not be settled yet),
you can activate a power up timer with '_PWRTE_ON'
-
Type of Oscillator The oscillator you use for the
PIC is either an RC oscillator, a crystal oscillator, (XT), a high speed
crystal,(HS), or a low speed low power, (LP) crystal. Your circuit may not
respond properly if you have the wrong oscillator type selected.
-
Watchdog Timer To prevent your program from running
off into 'never-never land' without the ability to recover, a watchdog timer,
(WDT), can be activated. It will restart the program every 18 milliseconds
or so unless a CLRWDT, (clear watchdog timer), command is executed during
this period. Normally you don't want it activated,(_WDT_OFF). It can easily
cause your program to seem to act strangely.
-
Code Protection To prevent someone from copying the
code you put into the PIC, you can 'code protect' PIC. Normally you don't
want to do this and '_CP_OFF' is the default so you can leave it out.
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.
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.
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.
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.
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.
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' 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.
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.
'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.
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.
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.
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: