By: Tim Box
Recently I wrote a password controlled switch module for a friend and thought it might make a good project.
The other one was quite
sophisticated so for this project so we are going to make a simple version.
As with all projects you
have to make a specification list. We will start by making the spec very simple
and as we enhance it we establish more what is needed.
OK thatís phase 1. But
itís not really enough to go on, so we need to elaborate more.
Thatís it were getting
let people spend a year entering the password? That would not work, and what if
they knew they entered a wrong number? They have to be able to start afresh. So
an expansion of part 1 would be.
1.a Password entry is timed, X seconds between each key press or it resets.
cancel key is to be implemented
Not much to add to that apart from to decide how we enable the password change procedure, a key? A switch inside the case? Well there are lots of options but I am going to choose a Master password.
quite important, as we need some indication what is going on. We need to be made
aware if we are in master mode other wise we might change the password
inadvertently. So we will implement a flashing led to indicate the current
will not be entering any passwords at this stage a simple delay will do
controlling the switch or what ever the device is used for.
From the above, some things
stand out as posing a problem, the time out on the key entry and the flashing
lights. You could have timed loops but personally I hate them, so I am going to
introduce an Interrupt routine. This will take care of the timing of the key
entry and the led flashing.
Really all we need is a 12
button, key pad. There are loads out there but for this project I picked one up
from a mail order firm. The Pic I choose is a 16F268 as it had enough pins for
the keypad the LEDís and it also has a 16 bit timer, TMR1. As I did not fancy
pulling the pic out every time I changed my code, the development was done on my
Proton board. Having a display is vital to aid debugging and as it turned out I
was able to use the key pad as well, I just matched the wiring as per the Proton
(Find the code here )
To start with we need to sort out the key scanning. There really is no point in making life hard and reinventing the wheel, so I opted to use the INKEY command. Having wired up the keypad I copied the example code from the manual, placed it in a loop, and ran it.
DIM VAR1 as BYTE LOOP: VAR1 = INKEY ' Scan the keypad DELAYMS 50 ' Debounce by waiting 50ms PRINT @VAR1 , " " ' Display the result on the LCD GOTO LOOP
When you press a key you see that the numbers do not make much sense 1 = 0, 2 = 1.. 9 = 10.
What we need to do is convert the data from the keypad to a more useful numeric arrangement. Again I looked in the manual and saw how its done. After messing with the values I came up with this.
LOOP: VAR1 = INKEY ' SCAN THE KEYPAD DELAYMS 50 ' DEBOUNCE BY WAITING 50MS KEY = LOOKUP VAR1, [1,2,3,255,4,5,6,255,7,8,9,255,"*",0,"#",255,255] PRINT AT 1,1,@KEY,32,@VAR1 , " " ' DISPLAY THE RESULT ON THE LCD GOTO LOOP
As I said above, the wiring matched that of the built in keypad, minus the right column. So I stuck with the Proton keypad for the duration of the coding / debugging session.
Only one last thing to do and that is a way to prevent the same key press being read twice. To do that we need a latch type arrangement. The method I adopted was:-
When no key press is detected it clears the NEW_KEY flag. Also note that the key is de-bounced only once, the first time it is seen.
KEY_SCAN: KEY = INKEY ' SCAN THE KEYPAD KEY = LOOKUP KEY, [1,2,3,255,4,5,6,NO_KEY,7,8,9,NO_KEY,"*",0,"#",NO_KEY,NO_KEY] IF KEY = NO_KEY THEN NEW_KEY = TRUE GOTO KEY_EXIT ' NEW KEY IS ONLY CLEARED BY A NO_KEY BEING SEEN ENDIF IF NEW_KEY = TRUE THEN ' IF THIS IS THE FIRST TIME WE HAVE SEEN IF KEY <> LAST_KEY THEN DELAYMS 50 ' THIS KEY, DE-BOUNCE ENDIF KEY_EXIT: LAST_KEY = KEY ' SAVE THE KEY VALUE RETURN
Thatís the key scan routine written, lets now see how you use it. Basically you only bother acting on a key if the key has not been read before. Something like this:-
IF NEW_KEY = TRUE ' IF THIS IS A NEW KEY THEN IF KEY = REQUIRED_KEY THEN ' IF ITS THE KEY YOU WANT NEW_KEY = FALSE ' LATCH TO PREVENT A RE-READ ' REST OF CODE HERE ENDIF ENDIF
That is real load of code to add every time you want to read a key. Itís better to keep it in a subroutine, and when required just make a call to that sub. We will though, need another flag to indicate if the result of the key check turned out to be true. I used KEY_STATUS
CHK_KEY_0_9: KEY_STATUS = FALSE ' PRECONDITION THE RESULT IF NEW_KEY = TRUE THEN ' IF THIS PRESS IS NEW TO US IF KEY >= 0 THEN ' IF WE ARE IN THE RANGE 0 - 9 IF KEY <= 9 THEN KEY_STATUS = TRUE ' RESULT IS TRUE NEW_KEY = FALSE ' LATCH THE KEY AS BEING READ RETURN ENDIF ENDIF ENDIF RETURN
To use the routine we make a call and check the result.
GOSUB CHK_KEY_0_9 IF KEY_STATUS = TRUE THEN ' REST OF CODE HERE ENDIF
In the above example, we check if the current key pressed is in the range of 0 Ė 9. If it is, we can use the value in KEY in the rest of the process.
In our password routine we only need to read the 0 Ė 9 keys and ď#Ē key so I only wrote two routines.
As I stated in the spec, we need to keep track of time. To do this I used an interrupt routine. I like running my interrupts at 100hz on TMR1, so used my standard interrupt routine.
The interrupt is a true hardware interrupt, as apposed to the software variant, this means you have to watch what instructions you use to prevent any of the system variables getting corrupted. Luckily single comparison IF THENís and DECís do not mess things up so we can use them with impunity.
For this project we need two timers, INPUT_TMR and LED_TMR. As we will be updating the timers 100 times a second, and we need to time for more than 2.5 seconds, so we need a WORD sized Var. The LED timer though can be BYTE sized. Lastly we need a flag to indicate if the timer is needed to run and when it has timed up. INPUT_TMR_RUNNING. The code to implement this is dead simple.
IF INPUT_TMR_RUNNING = TRUE THEN ' IS THIS TIMER RUNNING DEC INPUT_TMR ' DEC THE TIMER IF INPUT_TMR = 0 THEN INPUT_TMR_RUNNING = FALSE' IF WE ARE AT ZERO INDICATE SO ENDIF
In use, you simply load the timer with the required time in 100thís of a second. Then set it running with INPUT_TMR_RUNNING = TRUE. Its then only requires you to check the status of the flag to tell when times up.
Thatís the timer taken care of now for the LED. Some thing not discussed is the fact that we will have system Modes, STANDBY, PASSWORD_ENTRY and NEW_PASSWORD_ENTRY. These modeís will be indicated by the flash rate of the LED. As you have seen above we have allocated a timer to control how long the LED is ON or OFF. When the timer reaches 0 it looks at LED_TOGGLE_STATE. If itís set it un-sets it, turns off the LED and loads the value in LED_OFF into the LED_TMR. If itís un-set it resets it, turns on the led and loads in the value in LED_ON. By loading LED_ON and LED_OFF with appropriate values, the led will flash away all by itís self in the background.
Thatís the theory lets see the code
DEC LED_TMR ' DEC THE TIMER IF LED_TMR = 0 THEN ' IF ZERO IF LED_TOGGLE_STATE = TRUE THEN ' TOGGLE THE STATE IF LED_FORCED_OFF = FALSE THEN LED = ON LED_TMR = LED_ON ' LOAD THE TIMER LED_TOGGLE_STATE = FALSE ' TOGGLE STATE ELSE LED = OFF LED_TMR = LED_OFF ' LOAD THE TIMER LED_TOGGLE_STATE = TRUE ' TOGGLE STATE ENDIF ENDIF ENDIF
There is one problem though and thatís what happens when you want no LED flashes, as it will always flash for 100th of a second at the very least. So we have to force it off if required with another flag.
IF LED_FORCED_OFF = FALSE THEN LED = ON
Now to the password entry routine or PASSWORD_ ACCUMULATOR routine as I called it in the code. It works by checking