NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.
Project Help and Ideas » Push Button combination lock
September 13, 2012 by live4the1 |
I am trying to figure out how to make a push button combination lock using 5 push buttons. What I have set up is 5 buttons connected to PC1-PC5. What I would like to do is assign each button a value of 1-5 and use them to enter a code that matches a preset unlock code but I really don't know how to write the code to do that. I know that I have to have a variable to hold the preset code, have a variable that holds the entered code, and then compare the 2 of them and if the entered code matches the preset code a pin will go high or low. I'm sure that this is very easy but I need a little direction. |
---|---|
September 13, 2012 by pcbolt |
@ live4the1 I think you just need to break the problem down into 3 tasks.
Starting with part 3, you might use something like...
Getting the input and figuring out the pin has been written about elsewhere, but be careful with de-bouncing the push buttons. The button that came with the NK (and most others) will trigger about 10-30 pin change interrupts each time they are pressed. Timer interrupts work better, but you still need to experiment a little. |
September 13, 2012 by sask55 |
Live4the1 Is it your plan to have an LCD screen to in the set up? If not I think you will likely want to consider at least one probably two additional buttons to make code entering simpler. I would consider a clear/restart button and an enter/unlock button. These buttons would help the user to establish exactly where in the code sequence he is and would be almost essential if you are not intending to use a screen. With the addition of the extra buttons the code on the MCU can be set up to start recording the button push sequence at the correct point and stop recording and make a comparison when the code has been entered. Using this method any number of digists could be used in the code. The user could restart the code sequence at any time in the event there was an error made or the user has lost track of where in the sequence he is. If you are using a LCD screen you could display what digit is expected next and therefore the user will know where he is in the sequence. There is always many ways to do this kind of thing. You could consider something like this. Assign the button a value of 1,2,3,4 and 5. After the reset button is pushed a code comparison variable is cleared to zero and the code is ready to start filling that variable with the user input digits. That is to say if you push the number 1 button, 1 will be added to the comparison variable or push 2 button would add 2 to that same variable and so on for the other buttons. Once the code has registered a button push and added a digit set up a loop to multiply the next button push by a value of 10. So now for the second digit if you push the number 1 button, the value of 10 will be added to the comparison variable or push 2 would add 20 to that same variable. Loop to multiply the next button push by a value 10 times the last one ie 100. Just keep looping multiplying each successive button push by a value of 10. When the mcu detects the enter button has been pushed a comparison is made to determine if the correct code was entered. This is not particularly efficient with only five buttons half of the possible values will never appear, but that is of little consequence. You may want to add some code to avoid variable overflow in the case someone just keeps pushing buttons. I hope this give you an idea or two of one way it could be done. Darryl |
September 14, 2012 by sask55 |
The button push inputs could be done using interrupts. It is not really required for any reasons that I can think of unless you have the MCU doing something else that you will need to interrupt when a button push has occurred. You could also just pole the statues of the port C register each time the main loop comes around. I would update the comparison variable when the button is released after a push has been registered. In that way the user could hold a button down for any period of time from very short to very long and the code would only register one digit of input. Once the MCU detects a period of time when no buttons are being pushed it is ready to accept the next digit after the next button push. Use a short delay function say 20 ms after any push is detected to allow the button to settle down to eliminate button bounce issues. It should take about 10 to 15 lines of code to get bounce free button input for any number of digests filled into the comparison variable. I could elaborate more on my approach or possibly even give an example of the input code I have in mind. |
September 14, 2012 by live4the1 |
I'm trying to go one step at a time. Here is code that I would expect to read a single button push and send the button value to the LCD but it does not work. It does work when I move all of the if statements from the read_button function to the the while loop. I guess that somehow I goofed up the function or the call for the function. Take a look and let me know what I have done wrong.
|
September 14, 2012 by sask55 |
You have your function inside the main loop it should be located above the main. Secondly you are not actually calling your function anywhere in your code. I really don’t feel qualified to explain this. There a number of good on line tutorials that you could take a look at to get a handle on using functions in C. Your function is called read_button. When you have (uint8_t button_value) after the name of your function you are indicating that the function is looking for a 8 bit integer to be passed from the calling statement. I don’t think you actually want to pass anything to your function you just want it to return a value indicating if a button was pushed and which button it is.
would indicate there is no value passed to the function. Any variable used in any function must be defined in that function or globally. If you are using button_value in your function you must define it first but not in the name statement as you have done. . In your main, a statement like
will call the read_button function and put the value that was returned from that function into the entered_code variable. A couple of point you should consider What is returned from your function if no button is pushed? What is returned from your function if multiple buttons are pushed at the same time? There will be thousands of calls per second going on if no buttons are pushed. |
September 15, 2012 by sask55 |
I made an error. This is the correct statement to call your function. If you where intending to send a value to the function you could include that variables name or names of several variables to send within the brackets. In your main, a statement like
will call the read_button function and put the value that was returned from that function into the entered_code variable. |
September 16, 2012 by sask55 |
I had a little more time so decided to make a few changes to your code and post it. This is how I would approach the input portion of the code. This is not a working or finished example but more of a sample of what I was trying to say. As it is, there is no beginning or end to the code entry part of this sample. That is to say that it can’t be reset or restarted and no provision is in place to compare the code to stored code. You will need to consider adding some way to have a starting point and an ending point for the code sequence. This could be done by having a set length for the code so that you could make a comparison after the last digit is entered and then looping back to the first digit if the code fails. As I said before I would consider an extra button or two. It is very possible I have made typo or syntax errors you may need to correct. I have no Nerd kit set up right now and have not attempted to compile this code.
I hope I have not discouraged you from continuing with your project. This is just one idea to do what I think you are attempting to do. |
September 17, 2012 by live4the1 |
Below is what I have now. When I press 1-3, I get the output to the screen that I would expect but if I press 4 or 5 as a first digit, I get values -25536 and -15536 respectively. Why is this? The code that I have commented out is my attempt at the compare, but it does not work.
|
September 17, 2012 by live4the1 |
Ok, I think that I found the prob with the compare portion of the code; I left out an = sign. |
September 17, 2012 by sask55 |
The issue is clearly a result of the compiler treating the entered _code variable as a 16bit signed integer and not a 16 bit unsigned integer. Any time the value is above 2^15 or 32768 the most significant bit value is being interpreted as a negative value indicator bit. To tell you the truth I am not sure why that is the case as the variable has been declared as a uint16. I believe issue has to originate from the value returned from the read_button function. I am kind of just guessing here. I would try one of these ideas. Change line 13 to
That should declare the function return as a unsigned integer, I think. Or Change line 82 to
That should force the value of read_button to be interpreted and stored as an unsigned integer. If both of those ideas fail I believe you could just declare entered_code as a unit32_t or int32_t variable. In that case the variable is so large the sign overflow will not come into play. Let me know if any of these ideas work. Perhaps some else will explain this better then I have. Darryl |
September 17, 2012 by Noter |
I belive it displays as a signed integer because that is how the lcd subroutine is declared. Try formatting it as an unsigned integer using the print statement.
|
September 18, 2012 by sask55 |
That is a vary good point. I never even look at the lcd output statments, I have always used fprint_p statment for this type of application. I think lcd_write_int16 is certainly the issue. I think you could forget the other ideas I posted. |
September 18, 2012 by live4the1 |
I give that a try. Thanks ALL! |
September 18, 2012 by live4the1 |
It doesn't seem to like the fprint statement. I got an "incompatible type for argument" error. How do I format it correctly within the structure of my code? |
September 18, 2012 by live4the1 |
Figured it out:
should be
|
September 18, 2012 by live4the1 |
It is working mostly how I would expect it with the below code, with a few exceptions. I have added 2 buttons one of the buttons initiates the compare between the entered_code and ul_code, the other button is suppose to reset the lock and allow you to enter the code again but it does not work. For some reason, it does not seem that the program is going back to the beginning. Also, how do you clear old characters on a line before writing the new characters to the line on the LCD?
|
September 18, 2012 by sask55 |
I had a thought this afternoon as I was doing some field work in a tractor. Plenty of time to think I guess. I think my original idea of how to fill the entered_code variable is a little backwards in a way. It would make more sense to move the previously entered digits to the left when you add a new digit. This simple change will make the code entry display seem more familiar, more like a calculator as the digest shift left with each addional button push. There would be no hint or clue as to how many digests are in the code on the LCD screen, so it would be much less likely someone could guess the code. Any length of code that would fit into the variable size you are using would work, no need to predetermine the code length. There is no need for the exp_value variable. So with that in mind I made a few changes to your code.
I likely missed something, made some typos or syntax errors. I have not attempted to compile this. let us know how it goes. I think I have made a couple of changes that may solve the issues you where having. without trying it I am not sure. You may have to print a empty space to the LCD to clear a line if old charactors remain as a problem. Of cource you can always stick with the original idea if you like that better. I dont think your for loop with i is doing what I think you are trying to have it do. |
September 18, 2012 by Noter |
It might be easier if you used strings instead of a number. Then you could do any number of digits for the code without concern if it would fit in any integer. For example:
Or something like that. Check out the string functions here. |
September 19, 2012 by live4the1 |
Darryl, I made the change that you suggested and it works well. Below is the modified code. One thing that I would like to change is, I would like to clear the code from the screen after the enter/unlock button is pressed. The problem that I run into is if I just send code to the LCD to blank out the lines, it seems that the code that is in the while loop reprints the code. If I set the entered code to = 0 at the end of my enter/unlock button if statement, it tells me that I have entered a wrong code. I understand why these don't work but what is the work around for that? I totally understand the "time to think while on the tractor". I drive over an hour to work each day and back so I do a lot of thinking in the car. Noter, thanks for your input as well. I really enjoy seeing the many different ways of doing things.
|
September 19, 2012 by Ralphxyz |
live4the1, could you outline just how your project is working now? It looks like you now have 7 buttons. Ralph |
September 19, 2012 by live4the1 |
Hello Ralph, Right now, I do have 7 buttons, five of the buttons are for entering the code, one is for initializing the comparison between the entered code and the hard coded unlock code, and the last button is to reset the lock to the locked status. I have the LCD hooked up as well as a LED for output. At this point, I enter the code and it shows up on the LCD, then I hit the button to submit the code for comparison. If the code is correct, the LCD prints "Unlocked" on line one, "Correct" is printed on line four, the LED comes on, and the correct code is still present on the screen. I can then hit the reset button and everything resets. If I enter an incorrect code and hit the submit button, the LCD displays "Wrong Code" on line four and resets the code value to 0. What I would like for it to do is after I submit the code, the correct code would be removed from the display. As it is, you have to hit the reset to remove the entered code. I hope that was clear and answered your question. Thanks, Lynn |
September 19, 2012 by sask55 |
It should work set the enter_code to 0 after the code is determined to be correct. I think the problem is that when you press the enter/unlock button everything happens so fast that the code loops around and because you are still holding the button down 0 is the incorrect code on the second pass. You will need to come up with a method to control the button pushes so that they don’t run multiple times for each push. You could consider a delay and hope the user has let go before the delay ends. A better solutions may be to use a while loop. You could add a while loop to stop the main loop while the user is pushing the enter/unlock button. Your code in lines 99 to 109 would go something like this.
Also you should either change the size that you are declaring your variables to 32 bit (line 74 & 75) or change the value of the number in line 86 to something that will not overflow a 16 bit variable. This will depend on how many digits you would like to restrict the code to, 5 digest for 16 bit or 9 digits for 32 bit. |
September 19, 2012 by live4the1 |
Darryl, that did the trick! Thanks! Lynn |
September 19, 2012 by live4the1 |
Wow, I thought this step would be the easy step... I want to create an alarm situation if the wrong code is entered 3 times but with the below code it goes into alarm mode after the first wrong entry. I even tried to display how many wrong attempts were made but it shows 2 after the first attempt. What have I done this time???? I declared wrong_code as uint8_t = 0 before the while.
|
September 19, 2012 by sask55 |
I think that your issue is actually resulting from the same source as the last problem. Keep it mind that the MCU will be polling the buttons thousands of time per second when it is not required to do much else. When a button is pushed it is very likely you are going to get multiple passes of the code either from button bounce or just from the main loop repeatedly running the code as it loops time after time. You will have to take steps to deal with those issues. In this case each time the main loop come around it polls the status of each button one by one.. If it is determined that the enter/unlock button is pushed it checks to see if the code is correct. As long as the button is pushed the MCU will continue to get the same results for both of the if statements. The code in the else block will execute time after time as long as the button is pushed and the code does not match. The wrong_code++ statement in that else block may actually execute hundreds or thousands of time while the button is pushed. The same type of solution used to control the button push when the code is correct can be used if the code is wrong. Man! This gets kind of confusing with the word code haveing two different meanings here. I hope you know what I am staying. Darryl |
September 19, 2012 by JimFrederickson |
Hello Lynn, Often time things that seem simple are not as simple as they seem... Perhaps what I am going to add is "too complex", but getting bad input into the microprocessor can wreak havoc in your ability to debug your program. From my viewpoint there are three things that need to be changed in your code, and maybe thought process. 1 - Separate the keyboard debouncing/validation code from the "User Interface" code. (Basically change your "read_button" function to do all of the hardware reading for the buttons and debounce the keypresses. That way your "User Interface" portion will only run on debounced/validated keypresses.) 2 - Add keyboard debouncing/validation code. 3 - Get rid of the "delay_ms ()" calls. While this is "useful and easy" it also makes your program, essentially, stop/stutter at that point until the delay is complete. For this program it doesn't really matter, but in the future if you have more complex programs you will want your "Main Loop" to continue along and not "stutter"... I also think that working around the use of "delay_ms()" calls will put your mind in a better frame for more complex programs later on as well. Your "User Interface" and the "Debouncing/Validation of Keypresses" are two distinct processes. The "Debouncing/Validation of Keypresses" is, essentially, a random occurrence. (At least as far as your "User Interface" is concerned.) Your "User Interface" doesn't care how a key is "debounced/validated", it only cares that it is and what the key is. Simply checking the status of a port, as your code initially did/does, will work if there are "hardware debouncing circuits" for each key. As pointed out in other responses the microprocessor is ALWAYS running around trying to do things, and it is checking for keypresses much faster than your fingers are moving. (Well if the "sleep" capability of the microprocessor is used, then that will stop the microprocessor code from ALWAYS running...) Like so many things now-a-days it is easier, more flexible, to do things in software rather than hardware. (Although it is harder for the programmer... YOU in this case :) ) When I read keyboard/switch inputs I use the following algorithm: 1 - I get the current state of the switch. (Whether it's pressed or not) 2 - If the current state is the same as the "currentstate" I had stored the last time I accumulate the count in "currentstatecount" otherwise I store the new "currentstate" and resent the "currentstatecount" to 0. 3 - If the "currentstatecount" reaches some value then the "currentstate" is considered to be the current valid state. (Basically it is not noise associated with the key being pressed or released.) 4 - If the current valid state is "KEYISPRESSED" and the "lastvalidstate" was "KEYISPRESSED" then nothing happens. (That is because you already debounced and validated that particular keypress and you are just seeing it again, because the key has not yet been released by the user.) 5 - If the current valid state is "KEYISPRESSED" and the "lastvalidstate" was "KEYISNOTPRESSED" then the "lastvalidstate" is changed to "KEYISPRESSED" and a keypress is stored in "validchar". So "validchar" is my keyboard buffer and when I process that I reset that to be 0 to show the buffer is now empty. I use arrays to manage the keys, and I usually group things for one task in a structure just so it helps me visualize and my code ends up to be more self-documenting. The "transtochar" allows me to change the characters that my buttons returns for my program to use. Because most of my programs do several things I can redefine what is returned for a specific keypress which I find useful. i.e. mykeyboard.validchar = mykeyboard.transtochar[scancode]; So "scancode" is the physical button ID and "validchar" is character code I process in the program.
(Hopefully you will find this of use) |
September 20, 2012 by Noter |
Here is a thread with some different approaches to debouncing buttons - http://www.nerdkits.com/forum/thread/537/. The last post refers to a solution implemented in hardware with a resistor and capacitor. That looks particularly interesting and I may have to give it a try next time I am faced with debouncing a button. |
September 20, 2012 by live4the1 |
This is what I did to take care of the problem but I am interested in learning more about debounce. I thought that is what the delay_ms was doing? Jim, how would I incorporate the debounce method that you mentioned in the read_button function in my code as well as the other 2 buttons? Would I need to create functions for those buttons also?
|
September 20, 2012 by sask55 |
There are always a number of approaches that can be taken for most coding. Denouncing the input from a button is no exception. The approach you have been using is to simply have the MCU wait or delay, doing nothing useful for a short period of time, to allow the button to settle down and hopefully get a good result. Another more robust method (see Noter link) is to basically do repeated checks of the status of a button, then only accept a status change if there are a number of consecutive checks that match each other. This approach does leave the MCU available to do other things not sit in delay loops which is often an important consideration but not really for you current application. Anyway the basic idea for building the input code now is very nearly what was originally suggested be pcbolt at the beginning of this thread. I just did not recognize it at the time. |
September 20, 2012 by JimFrederickson |
Hello Lynn, I modified the last code snippet that you posted to include what I was talking about before. I didn't compile it, but I don't think there are too many issues... Some NOTES FIRST: 1 - If 2 buttons are pressed at "exactly the same time" the button processed first will be lost. (I have had situations where this would be a problem and in those cases there needs to be a "button press queue", but generally that is not an issue, and it adds more complications that I don't think are necessary here.) 2 - This only validates "button presses" NOT "button releases". If you are trying to implement a "standard shift key", for instance, then a button release would be important as well. 3 - I changed part that you had to display the "currently entered code" in the "while statement". Before the "currently entered code" would be displayed over and over regardless of whether there was a change in the code or not. That code is now duplicated, but the end result is it will only display the "currently entered code" when there is a change. Sending characters to the display when there isn't a change takes quite a bit of time. 4 - I didn't understand the "last_button_value" that you had used. That didn't really seem necessary. 5 - I re-ordered the processing of the buttons. Basically you have "2 action buttons" and then the "5 entry buttons". So the "action buttons" are processed first in the "while statement". When an "action button" is processed the "new_button_value" is set to 0 so that no further processing will take place. (Yes "else statement" could be used so that would be unnecessary, but for me i find "if/else statements" can sometimes become too complicated. For me, in this case, I would make a "case statement" to process all of the button values which would also take care of this situation.) 6 - I have the "KEYCOUNT" which is a constant that is used to make sure the button is stable"... I wouldn't change this to anything less than "3", and maybe "7" needs to be more as well. 7 - The "READDELAYCOUNT" is so that the buttons are not just checked "constantly". The idea in "debouncing a button" is to make sure the button is stable, if all of your checking of the button state is done in 1ms is it stable? Also no-one is going to be typing 10,000+ buttons per second so this frees up code cycles that can be used elsewhere. (of course in this case it isn't necessary, but I think it is a good habit to get into.) I, normally, address this using a timer interrupt but this stays closer to what you are used to doing. Some may wonder why the "readdelay" check is in the "read_button" function, since that results in some unnecessary overhead of calling a function 1,000's of times that may not do anything. My explanation is that the choice "to run" is part of the coding for the "read_button" function. That why if I need to use that function in other programs, or in other places in the same program if it is more complicated, the function is more "self-contained" which makes more sense to me. 8 - In the "read_button" function there are more elegant ways of called the "read_button_process", but I thought this would be easier to read. 9 - Programs that have outside interaction are ALWAYS going through a series of states. (Which nearly every program has outside interaction, whether that is a sensor or a person.) Your program, now, has 1 state. "Reading keys to Process". In order to get rid of that "delay_ms(5000)" you would need to create, at least, an additional state. A "Message Wait State" which would take precedence over the "Read Key to Process". (Again, here that does not really matter. In the future though understanding these considerations could important for you.) Ultimately I think that this is a good starting project that should help you get familiar with microcontrollers, and that is useful as well. Good Luck...
|
September 20, 2012 by pcbolt |
Lynn - I don't know if you followed one of the links in the forum thread Noter posted but here it is. On page 2 (under Alternative) there is a simple routine used inside a timer interrupt that I've used with great success. I had to tweak it a bit for the ATmega and when I get back from a business trip, I'll see if I can post the code. There are also some schematics for hardware debouncing on that page which make for good reading. |
September 24, 2012 by pcbolt |
Lynn - Here is the debouncing code I've used before that seems to work pretty well. First I create a few global variables...
Then I set up a timer and button assignments in an initialization routine called from main()...
Next, I can set up a timer interrupt service routine which fires off every 4.44mSec...
What this does is check the input pins for each button every 4.44mSec, if Button1 is pressed "but_1_state" will equal 0. The variable "but_1_test" is shifted over 1 place ORed with the current value of "but_1_state" and ORed with the constant 0xE000. So basically every 4.44mSec, if a button is down, 0's start getting shifted in from right to left. If the button is not pressed, 1's are shifted in from right to left. So the variable "but_1_test" is in one of 3 states. First, all 16 bits are 1 when no button is pressed. Or, when a button is first pressed, a string of 1's and 0's are streaming across by all the signal bouncing going on. Or, when the button has finally made solid contact for 13 cycles of 4.44mSec each, "but_1_test" will fill up with 0's (except for the highest 3 bits) and the "but_1_down" flag will be set to true. The value of 4.44mSec was chosen since this is about the time scale of the bounce signal, and 13 cycles of 4.44mSec or 57mSec is about the same time scale as human reaction time. If you want quicker time you can use a constant of 0xFE00 for 9 cycles. If you find your bouncing signals are longer than 4.44mSec you can change the time prescaler. Finally to check on your button states from the main() code use something like...
I'm sure I didn't explain this too clearly, so if you have any questions just let me know. (BTW the "ticks" variable isn't needed in this example, I just used it for other timing solutions in my code.) |
September 25, 2012 by live4the1 |
Wow, there are so many different complicated ways to do what one would expect to be simple! I need to dig into this stuff until I can more easily wrap my mind around it. It is beginning to sink in though. Thanks for all of the help! What would be some good exercises to really dig into interrupts? |
September 25, 2012 by JimFrederickson |
Probably the simplest exercise with Interrupts is what PCBolt has show in his code example. The code example I showed you didn't use interrupts because you weren't already using them. (Although all of my programs are heavily interrupt driven.) Ultimately if your programs get complicated you will probably need interrupts. Mostly though, programming is ALWAYS an exercise in thought process. you already understand what you see and want you want done, but you need to think about what is it like to see what you see, and do what you do millions of times per second. i.e. If you have an LED attached to a switch and turn the switch on and then off... You will see a flash of light... If you have your little Microcontroller turn the same LED on and off... You will see nothing... If you have your little Microcontroller turn the LED switch on it will have to wait around for some time before turning the switch off in order for you to see it. The code running on this Atmel Microcontroller is just one stream of code. (There are Microcontrollers around that execute multiple streams of code simultaneously, just not this one.) Even when an Interrupt occurs it is really only branching to a new albeit temporary stream of code to take care of an issue. The Microcontroller code does a series of single things really really fast, so to use it can seem like it is doing multiple things. I think one of the best methods is to view your programs as a series of events, conditions, and actions... |
September 25, 2012 by pcbolt |
Lynn - On the tutorial page is a project called "Crystal Real Time Clock" that shows one of the most basic uses of interrupts. It is an easy project to build and has the source code available to study. For a better explanation of interrupts the tutorial called "Interrupts and PS/2 Keyboards" gives a deeper understanding of what interrupts are all about. What I like best about using them is they run almost "outside" your main code and do work "behind the scenes". In the interrupt code above, once the interrupt routines are in place, your main code can simply call the "read_button()" function and not have to worry about delays at all. On a side note; you shouldn't need to change any parameters in the code snippet above, it has been tested to work pretty well. You just need to add more button variables inside the code wherever "but_1_xxx" and "but_2_xxx" are add "but_3_xxx", "but_4_xxx" etc. Heck you could even get creative and add an array of button variables to cycle through. |
September 25, 2012 by missle3944 |
Are you going to want to store the passcode when the atmel is turned off? If then, I can show you some really simple eeprom code, I think its better than the eeprom example in the library. -Dan |
September 26, 2012 by live4the1 |
Dan, since the passcode is hardcoded in my code, I don't have to worry about that do I? I would be interested in seeing your code though because it may come in handy later. Thanks, Lynn |
September 26, 2012 by Ralphxyz |
Of course Dan you could add your eeprom code to the Library. You could make a EEPROM Heading and then links to the existing and to your new code. The more the better. Ralph |
Please log in to post a reply.
Did you know that NerdKits believes in the importance of a mixture of meaningful topics, clear instruction, and engaging projects? Learn more...
|