NerdKits - electronics education for a digital generation

You are not logged in. [log in]

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
live4the1's Avatar

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
pcbolt's Avatar

@ live4the1

I think you just need to break the problem down into 3 tasks.

  • Getting the input from the pins.
  • Distinguishing which pin was activated.
  • Accumulate the code sequence.

Starting with part 3, you might use something like...

uint16_t input_code = 0, stored_code;
uint8_t valid_input = 0;

stored_code = 53242;
valid_input = get_pin_number(); // need to create this function return 0 if not valid
if (valid_input){
  input_code += valid_input;
  input_code *= 10;
  if (input_code > 11111){
    if (input_code == stored_code) goto_unlock_function();
    else goto_deny_function();
  }
}

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
sask55's Avatar

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
sask55's Avatar

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
live4the1's Avatar

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.

#define F_CPU 14745600

#include <avr/io.h>
#include <inttypes.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "../libnerdkits/delay.h" 
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#include "../libnerdkits/io_328p.h"

int main()
{

    DDRC &= ~(1<<PC1); // set PC1 as input
    DDRC &= ~(1<<PC2); // set PC2 as input
    DDRC &= ~(1<<PC3); // set PC3 as input
    DDRC &= ~(1<<PC4); // set PC4 as input
    DDRC &= ~(1<<PC5); // set PC5 as input

    // turn on the internal resistors for the pins
    PORTC |= (1<<PC1); // turn on internal pull up resistor for PC1
    PORTC |= (1<<PC2); // turn on internal pull up resistor for PC2
    PORTC |= (1<<PC3); // turn on internal pull up resistor for PC3
    PORTC |= (1<<PC4); // turn on internal pull up resistor for PC6
    PORTC |= (1<<PC5); // turn on internal pull up resistor for PC7

    DDRB |= (1<<PB5); // set PB5 as output (LED)

    lcd_init();
        lcd_home();
        FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
        //display initial settings
            lcd_line_one();
            lcd_write_string(PSTR("Locked  "));
            lcd_line_two();
            lcd_write_string(PSTR("Enter Code"));  
            lcd_line_three();

    uint16_t ul_code=54321;
    uint16_t entered_code=00000;
    uint8_t button_value;

    int read_button (uint8_t button_value) {
        if ((PINC & (1<<PC1)) ==0)
        {button_value = 1;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC2)) ==0)
        {button_value = 2;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC3)) ==0)
        {button_value = 3;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC4)) ==0)
        {button_value = 4;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC5)) ==0)
        {button_value = 5;
        delay_ms (20); 
        }
    return button_value;
    }

    while(1){

    button_value = entered_code;

        lcd_line_three();
        lcd_write_string(PSTR("Code: "));
        lcd_write_int16( entered_code );
        lcd_write_string(PSTR("        "));

    }
 }
September 14, 2012
by sask55
sask55's Avatar

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.

  read_button (){

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

   entered_code = read _button;

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
sask55's Avatar

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

  entered_code = read _button();

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
sask55's Avatar

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.

 #define F_CPU 14745600

 #include <avr/io.h>
 #include <inttypes.h>
 #include <avr/pgmspace.h>
 #include <stdio.h>
 #include <avr/interrupt.h>
 #include "../libnerdkits/delay.h" 
 #include "../libnerdkits/lcd.h"
 #include "../libnerdkits/uart.h"
#include "../libnerdkits/io_328p.h"

int read_button () {
uint8_t button_value =0;

    if ((PINC & (1<<PC1)) ==0)
    {button_value = 1;
    delay_ms (20); 
    }

if ((PINC & (1<<PC2)) ==0)
    {button_value = 2;
    delay_ms (20); 
    }

if ((PINC & (1<<PC3)) ==0)
    {button_value = 3;
    delay_ms (20); 
    }

if ((PINC & (1<<PC4)) ==0)
    {button_value = 4;
    delay_ms (20); 
    }

if ((PINC & (1<<PC5)) ==0)
    {button_value = 5;
    delay_ms (20); 
    }
return button_value;
}

int main()
{

DDRC &= ~(1<<PC1); // set PC1 as input
DDRC &= ~(1<<PC2); // set PC2 as input
DDRC &= ~(1<<PC3); // set PC3 as input
DDRC &= ~(1<<PC4); // set PC4 as input
DDRC &= ~(1<<PC5); // set PC5 as input

// turn on the internal resistors for the pins
PORTC |= (1<<PC1); // turn on internal pull up resistor for PC1
PORTC |= (1<<PC2); // turn on internal pull up resistor for PC2
PORTC |= (1<<PC3); // turn on internal pull up resistor for PC3
PORTC |= (1<<PC4); // turn on internal pull up resistor for PC6
PORTC |= (1<<PC5); // turn on internal pull up resistor for PC7

DDRB |= (1<<PB5); // set PB5 as output (LED)

lcd_init();
    lcd_home();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
    //display initial settings
        lcd_line_one();
        lcd_write_string(PSTR("Locked  "));
        lcd_line_two();
        lcd_write_string(PSTR("Enter Code"));  
        lcd_line_three();

uint16_t ul_code=54321;// this nuber is entered in opposite order to code to be entered by user ie 54321 would require the user to enter 12345 to unlock
uint16_t entered_code=0;
uint8_t new_button_value;
uint8_t last_button_value=0;
uint16_t exp_value=1; // this value could be set to allow code to be entered left to right if the code length is predetermined, for example 100,000 for 6 diget code length.

while(1){

    new_button_value = read_button ();  // check if a buton is pushed now
        if (New_button_value == 0) {  // if no button is pushed?
            if (last_button_value > 0){ //was the button just released?
                entered_code = enter_code + (last_button_value * exp_value); // add diget
                last_button_value = 0; //reset last value
                exp_value = exp_value * 10; // increment exp value- Note that by using divide by 10 rater than time by ten the code could be entereed left to right which is more normal. 
                }

        }else{  // a button is pushed 
            Last_button_value = new_buton_value; // reset to the current button push value. 
            }

    lcd_line_three();
    lcd_write_string(PSTR("Code: "));
    lcd_write_int16( entered_code );
    lcd_write_string(PSTR("        "));

}
 }

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
live4the1's Avatar

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.

#define F_CPU 14745600

#include <avr/io.h>
#include <inttypes.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "../libnerdkits/delay.h" 
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#include "../libnerdkits/io_328p.h"

int read_button() {
uint8_t button_value = 0;
    if ((PINC & (1<<PC1)) ==0)
        {button_value = 1;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC2)) ==0)
        {button_value = 2;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC3)) ==0)
        {button_value = 3;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC4)) ==0)
        {button_value = 4;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC5)) ==0)
        {button_value = 5;
        delay_ms (20); 
        }
    return button_value;
    }

int main()
{ 
    DDRC &= ~(1<<PC0);
    DDRC &= ~(1<<PC1); // set PC1 as input
    DDRC &= ~(1<<PC2); // set PC2 as input
    DDRC &= ~(1<<PC3); // set PC3 as input
    DDRC &= ~(1<<PC4); // set PC4 as input
    DDRC &= ~(1<<PC5); // set PC5 as input

    // turn on the internal resistors for the pins
    PORTC |= (1<<PC0);
    PORTC |= (1<<PC1); // turn on internal pull up resistor for PC1
    PORTC |= (1<<PC2); // turn on internal pull up resistor for PC2
    PORTC |= (1<<PC3); // turn on internal pull up resistor for PC3
    PORTC |= (1<<PC4); // turn on internal pull up resistor for PC6
    PORTC |= (1<<PC5); // turn on internal pull up resistor for PC7

    DDRB |= (1<<PB5); // set PB5 as output (LED)

    lcd_init();
        lcd_home();
        FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
        //display initial settings
            lcd_line_one();
            lcd_write_string(PSTR("Locked  "));
            lcd_line_two();
            lcd_write_string(PSTR("Enter Code"));  
            lcd_line_three();
            lcd_line_four();

    uint16_t ul_code=54321;
    uint16_t entered_code=0;
    uint16_t new_button_value = 0;
    uint16_t last_button_value = 0;
    uint16_t exp_value = 10000;
    uint8_t i;

    while(1){
        for (i = 0; i < 5; i++)
        {
        new_button_value = read_button();
            if (new_button_value == 0) {
                if (last_button_value > 0) {
                    entered_code = entered_code + (last_button_value * exp_value);
                    last_button_value = 0;
                    exp_value = exp_value / 10;
                    }

            }else{
                last_button_value = new_button_value;
                }

        lcd_line_three();
        lcd_write_string(PSTR("Code: "));
        lcd_write_int16( entered_code );
        lcd_write_string(PSTR("        "));
        }

        //if ((PINC & (1<<PC0)) == 0){
        //  if (entered_code = ul_code) {
        //      lcd_line_one();
        //      lcd_write_string(PSTR("Unlocked "));
        //      PORTB |= (1<<PB5);
        //  }
        //  else{
        //      lcd_line_four();
        //      lcd_write_string(PSTR("Wrong Code "));
        //      PORTB &= ~(1<<PB5);
        //      }
        //}
    }
 }
September 17, 2012
by live4the1
live4the1's Avatar

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
sask55's Avatar

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

   uint16 read_button() {

That should declare the function return as a unsigned integer, I think.

Or

Change line 82 to

new_button_value = (uint16)(read_button());

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
Noter's Avatar

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.

fprintf_P(lcd_stream, PSTR("Code: %u       "), entered_code);
September 18, 2012
by sask55
sask55's Avatar

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
live4the1's Avatar

I give that a try. Thanks ALL!

September 18, 2012
by live4the1
live4the1's Avatar

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
live4the1's Avatar

Figured it out:

fprintf_P(lcd_stream, PSTR("Code: %u       "), entered_code);

should be

fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);
September 18, 2012
by live4the1
live4the1's Avatar

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?

#define F_CPU 14745600

#include <avr/io.h>
#include <inttypes.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "../libnerdkits/delay.h" 
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#include "../libnerdkits/io_328p.h"

int read_button() {
uint8_t button_value = 0;
    if ((PINC & (1<<PC1)) ==0)
        {button_value = 1;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC2)) ==0)
        {button_value = 2;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC3)) ==0)
        {button_value = 3;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC4)) ==0)
        {button_value = 4;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC5)) ==0)
        {button_value = 5;
        delay_ms (20); 
        }
    return button_value;
    }

int main()
{ 
    DDRB &= ~(1<<PB4);
    DDRC &= ~(1<<PC0);
    DDRC &= ~(1<<PC1); // set PC1 as input
    DDRC &= ~(1<<PC2); // set PC2 as input
    DDRC &= ~(1<<PC3); // set PC3 as input
    DDRC &= ~(1<<PC4); // set PC4 as input
    DDRC &= ~(1<<PC5); // set PC5 as input

    // turn on the internal resistors for the pins
    PORTB |= (1<<PB4);
    PORTC |= (1<<PC0);
    PORTC |= (1<<PC1); // turn on internal pull up resistor for PC1
    PORTC |= (1<<PC2); // turn on internal pull up resistor for PC2
    PORTC |= (1<<PC3); // turn on internal pull up resistor for PC3
    PORTC |= (1<<PC4); // turn on internal pull up resistor for PC6
    PORTC |= (1<<PC5); // turn on internal pull up resistor for PC7

    DDRB |= (1<<PB5); // set PB5 as output (LED)

    lcd_init();
        lcd_home();
        FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
        //display initial settings
            lcd_line_one();
            lcd_write_string(PSTR("Locked  "));
            lcd_line_two();
            lcd_write_string(PSTR("Enter Code"));  
            lcd_line_three();
            lcd_line_four();

    uint16_t ul_code=55441;
    uint16_t entered_code=0;
    uint16_t new_button_value = 0;
    uint16_t last_button_value = 0;
    uint16_t exp_value = 10000;
    uint8_t i;

    while(1){
        for (i = 0; i < 5; i++)
        {
        new_button_value = read_button();
            if (new_button_value == 0) {
                if (last_button_value > 0) {
                    entered_code = entered_code + (last_button_value * exp_value);
                    last_button_value = 0;
                    exp_value = exp_value / 10;
                    }

            }else{
                last_button_value = new_button_value;
                }

        lcd_line_three();
        fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);
        }

        if ((PINC & (1<<PC0)) == 0){
            if (entered_code == ul_code) {
                lcd_line_one();
                lcd_write_string(PSTR("Unlocked "));
                PORTB |= (1<<PB5);

            }
            else{
                lcd_line_four();
                lcd_write_string(PSTR("Wrong Code "));
                PORTB &= ~(1<<PB5);
                }

            }
        if ((PINB & (1<<PB4)) == 0){
                lcd_line_one();
                lcd_write_string(PSTR("Locked "));
                PORTB &= ~(1<<PB5);
                i = 0;
                entered_code = 0;
                }
    }
    return 0;
 }
September 18, 2012
by sask55
sask55's Avatar

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.

#define F_CPU 14745600

#include <avr/io.h>
#include <inttypes.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "../libnerdkits/delay.h" 
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#include "../libnerdkits/io_328p.h"

int read_button() {
uint8_t button_value = 0;
if ((PINC & (1<<PC1)) ==0)
    {button_value = 1;
    delay_ms (20); 
    }

if ((PINC & (1<<PC2)) ==0)
    {button_value = 2;
    delay_ms (20); 
    }

if ((PINC & (1<<PC3)) ==0)
    {button_value = 3;
    delay_ms (20); 
    }

if ((PINC & (1<<PC4)) ==0)
    {button_value = 4;
    delay_ms (20); 
    }

if ((PINC & (1<<PC5)) ==0)
    {button_value = 5;
    delay_ms (20); 
    }
return button_value;
}

int main()
{ 
DDRB &= ~(1<<PB4);  // enter unlock button
DDRC &= ~(1<<PC0);  //clear input lock button
DDRC &= ~(1<<PC1); // set PC1 as input
DDRC &= ~(1<<PC2); // set PC2 as input
DDRC &= ~(1<<PC3); // set PC3 as input
DDRC &= ~(1<<PC4); // set PC4 as input
DDRC &= ~(1<<PC5); // set PC5 as input

// turn on the internal resistors for the pins
PORTB |= (1<<PB4);
PORTC |= (1<<PC0);
PORTC |= (1<<PC1); // turn on internal pull up resistor for PC1
PORTC |= (1<<PC2); // turn on internal pull up resistor for PC2
PORTC |= (1<<PC3); // turn on internal pull up resistor for PC3
PORTC |= (1<<PC4); // turn on internal pull up resistor for PC6
PORTC |= (1<<PC5); // turn on internal pull up resistor for PC7

DDRB |= (1<<PB5); // set PB5 as output (LED)

lcd_init();
    lcd_home();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
    //display initial settings
        lcd_line_one();
        lcd_write_string(PSTR("Locked  "));
        lcd_line_two();
        lcd_write_string(PSTR("Enter Code"));  
        lcd_line_three();
        lcd_line_four();

uint32_t ul_code=55441;
uint32_t entered_code=0;
uint8_t new_button_value = 0;
uint8_t last_button_value = 0;

while(1){

    new_button_value = read_button();
        if (new_button_value == 0) {
            if (last_button_value > 0) {
                entered_code = entered_code * 10 + last_button_value ; // shift left and add new digit
                last_button_value = 0;
                    if (entered_code > 200000000) { //limit size of code to < 32 bit
                        entered_code = 0;  //reset enter_code if it is to long.
                        }

                }

        }else{
            last_button_value = new_button_value;
            }

    lcd_line_three();
    fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);

    if ((PINC & (1<<PC0)) == 0){
    delay_ms (20);
        if (entered_code == ul_code) {
            lcd_line_one();
            lcd_write_string(PSTR("Unlocked "));
            PORTB |= (1<<PB5);

        }
        else{
            lcd_line_four();
            lcd_write_string(PSTR("Wrong Code "));
            entered_code = 0;
            PORTB &= ~(1<<PB5);
            }

        }
    if ((PINB & (1<<PB4)) == 0){
    delay_ms (20);
            lcd_line_one();
            lcd_write_string(PSTR("Locked    "));
            PORTB &= ~(1<<PB5);

            entered_code = 0;
            }
}
return 0;
 }

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
Noter's Avatar

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:

    #define good_code "54321"
    int position = 0;
    char entered_code[21] // allow up to 20 digits in the code
    memset(entered_code, 0, sizeof(entered_code)); // fill the string with terminators

    entered_code[position++] = read_button() + 48; // turn the numeric into a character

    if(strlen(entered_code)==strlen(good_code) {
        if(strcmp(entered_code, good_code)==0) { // it's a match
        // do something
        }
    } else {  // try again
        position = 0;
        memset(entered_code, 0, sizeof(entered_code)); // fill the string with terminators
    }

Or something like that. Check out the string functions here.

September 19, 2012
by live4the1
live4the1's Avatar

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.

#define F_CPU 14745600

#include <avr/io.h>
#include <inttypes.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "../libnerdkits/delay.h" 
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#include "../libnerdkits/io_328p.h"

int read_button() {
uint8_t button_value = 0;
    if ((PINC & (1<<PC1)) ==0)
        {button_value = 1;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC2)) ==0)
        {button_value = 2;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC3)) ==0)
        {button_value = 3;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC4)) ==0)
        {button_value = 4;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC5)) ==0)
        {button_value = 5;
        delay_ms (20); 
        }
    return button_value;
    }

int main()
{ 
    DDRB &= ~(1<<PB4);
    DDRC &= ~(1<<PC0);
    DDRC &= ~(1<<PC1); // set PC1 as input
    DDRC &= ~(1<<PC2); // set PC2 as input
    DDRC &= ~(1<<PC3); // set PC3 as input
    DDRC &= ~(1<<PC4); // set PC4 as input
    DDRC &= ~(1<<PC5); // set PC5 as input

    // turn on the internal resistors for the pins
    PORTB |= (1<<PB4);
    PORTC |= (1<<PC0);
    PORTC |= (1<<PC1); // turn on internal pull up resistor for PC1
    PORTC |= (1<<PC2); // turn on internal pull up resistor for PC2
    PORTC |= (1<<PC3); // turn on internal pull up resistor for PC3
    PORTC |= (1<<PC4); // turn on internal pull up resistor for PC6
    PORTC |= (1<<PC5); // turn on internal pull up resistor for PC7

    DDRB |= (1<<PB5); // set PB5 as output (LED)

    lcd_init();
        lcd_home();
        FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
        //display initial settings
            lcd_line_one();
            lcd_write_string(PSTR("Locked  "));
            lcd_line_two();
            lcd_write_string(PSTR("Enter Code"));  
            lcd_line_three();
            lcd_line_four();

    uint16_t ul_code=55441;
    uint16_t entered_code=0;
    uint16_t new_button_value = 0;
    uint16_t last_button_value = 0;

    while(1){

        new_button_value = read_button();
        if (new_button_value == 0) {
            if (last_button_value > 0) {
                entered_code = entered_code * 10 + last_button_value ; // shift left and add new digit
                last_button_value = 0;
                    if (entered_code > 200000000) { //limit size of code to < 32 bit
                        entered_code = 0;  //reset enter_code if it is to long.
                        }

                }

            }else{
                last_button_value = new_button_value;
                }

        lcd_line_three();
        fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);

        if ((PINC & (1<<PC0)) == 0){
            if (entered_code == ul_code) {
                lcd_line_three();
                lcd_write_string(PSTR("Code:         "));
                lcd_line_one();
                lcd_write_string(PSTR("Unlocked "));
                lcd_line_four();
                lcd_write_string(PSTR("Correct        "));
                PORTB |= (1<<PB5);

            }
            else{
                lcd_line_four();
                lcd_write_string(PSTR("Wrong Code "));
                PORTB &= ~(1<<PB5);
                entered_code = 0;
                }

            }
        if ((PINB & (1<<PB4)) == 0){
                lcd_line_one();
                lcd_write_string(PSTR("Locked   "));
                lcd_line_four();
                lcd_write_string(PSTR("               "));
                PORTB &= ~(1<<PB5);
                entered_code = 0;
                }

    }
    return 0;
 }
September 19, 2012
by Ralphxyz
Ralphxyz's Avatar

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
live4the1's Avatar

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
sask55's Avatar

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.

 if ((PINC & (1<<PC0)) == 0){

            if (entered_code == ul_code) {
                lcd_line_three();
                entered_code = 0;
                fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);
                lcd_line_one();
                lcd_write_string(PSTR("Unlocked "));
                lcd_line_four();
                lcd_write_string(PSTR("Correct        "));
                PORTB |= (1<<PB5);
                delay_ms (20); // set a short delay so that the button bounce will settle down and the while loop will work as expected
                while ((PINC & (1<<PC0)) == 0) {
                }

            }

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
live4the1's Avatar

Darryl, that did the trick! Thanks!

Lynn

September 19, 2012
by live4the1
live4the1's Avatar

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.

if ((PINC & (1<<PC0)) == 0)
        {
            if (entered_code == ul_code) 
            {
               lcd_line_three();
               entered_code = 0;
               fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);
               lcd_line_one();
               lcd_write_string(PSTR("Unlocked "));
               lcd_line_four();
               lcd_write_string(PSTR("Correct        "));
               PORTB |= (1<<PB5);
               delay_ms (20); // set a short delay so that the button bounce will settle down and the while loop will work as expected
               while ((PINC & (1<<PC0)) == 0) {
               }
                wrong_code = 0;
           }
            else{
                lcd_line_four();
                lcd_write_string(PSTR("Wrong Code "));
                lcd_write_int16( wrong_code );
                lcd_write_string(PSTR("        "));
                wrong_code++;
                PORTB &= ~(1<<PB5);
                entered_code = 0;
                }

            if (wrong_code == 3)
            {
                PORTB |= (1<<PB3);
                delay_ms(5000);
                PORTB &= ~(1<<PB3);
                wrong_code = 0;
            }

        }

        if ((PINB & (1<<PB4)) == 0){
                lcd_line_one();
                lcd_write_string(PSTR("Locked   "));
                lcd_line_four();
                lcd_write_string(PSTR("               "));
                PORTB &= ~(1<<PB5);
                entered_code = 0;
                delay_ms(20);
                }
September 19, 2012
by sask55
sask55's Avatar

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
JimFrederickson's Avatar

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.

    //  Define keyboard scan codes

    #define KEYISPRESSED    1
    #define KEYISNOTPRESSED 0
    #define KEYCOUNT    16

    typedef struct {
    uint8_t transtochar[KEYCOUNT];
    uint8_t lastvalidstate[KEYCOUNT];
    uint8_t currentstate[KEYCOUNT];
    uint8_t currentstatecount[KEYCOUNT];
    uint8_t lastvalidchar;
    uint8_t validchar;
    uint8_t lastscanresult;
    } keyboard;

    volatile keyboard mykeyboard;

(Hopefully you will find this of use)

September 20, 2012
by Noter
Noter's Avatar

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
live4the1's Avatar

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?

#define F_CPU 14745600

#include <avr/io.h>
#include <inttypes.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "../libnerdkits/delay.h" 
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#include "../libnerdkits/io_328p.h"

int read_button() {
uint8_t button_value = 0;
    if ((PINC & (1<<PC1)) ==0)
        {button_value = 1;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC2)) ==0)
        {button_value = 2;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC3)) ==0)
        {button_value = 3;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC4)) ==0)
        {button_value = 4;
        delay_ms (20); 
        }

    if ((PINC & (1<<PC5)) ==0)
        {button_value = 5;
        delay_ms (20); 
        }
    return button_value;
    }

int main()
{ 
    DDRB &= ~(1<<PB4);
    DDRC &= ~(1<<PC0);
    DDRC &= ~(1<<PC1); // set PC1 as input
    DDRC &= ~(1<<PC2); // set PC2 as input
    DDRC &= ~(1<<PC3); // set PC3 as input
    DDRC &= ~(1<<PC4); // set PC4 as input
    DDRC &= ~(1<<PC5); // set PC5 as input

    // turn on the internal resistors for the pins
    PORTB |= (1<<PB4);
    PORTC |= (1<<PC0);
    PORTC |= (1<<PC1); // turn on internal pull up resistor for PC1
    PORTC |= (1<<PC2); // turn on internal pull up resistor for PC2
    PORTC |= (1<<PC3); // turn on internal pull up resistor for PC3
    PORTC |= (1<<PC4); // turn on internal pull up resistor for PC6
    PORTC |= (1<<PC5); // turn on internal pull up resistor for PC7

    DDRB |= (1<<PB5); // set PB5 as output (LED)
    DDRB |= (1<<PB3);

    lcd_init();
        lcd_home();
        FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
        //display initial settings
            lcd_line_one();
            lcd_write_string(PSTR("Locked  "));
            lcd_line_two();
            lcd_write_string(PSTR("Enter Code"));  
            lcd_line_three();
            lcd_line_four();

    uint32_t ul_code=55441;
    uint32_t entered_code=0;
    uint16_t new_button_value = 0;
    uint16_t last_button_value = 0;
    uint8_t wrong_code = 0;

    while(1){

        new_button_value = read_button();
        if (new_button_value == 0) {
            if (last_button_value > 0) {
                entered_code = entered_code * 10 + last_button_value ; // shift left and add new digit
                last_button_value = 0;
                    if (entered_code > 200000000) { //limit size of code to < 32 bit
                        entered_code = 0;  //reset enter_code if it is to long.
                        }

                }

            }else{
                last_button_value = new_button_value;
                }

        lcd_line_three();
        fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);

        if ((PINC & (1<<PC0)) == 0)
        {
            if (entered_code == ul_code) 
            {
               lcd_line_three();
               entered_code = 0;
               fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);
               lcd_line_one();
               lcd_write_string(PSTR("Unlocked "));
               lcd_line_four();
               lcd_write_string(PSTR("Correct        "));
               PORTB |= (1<<PB5);
               delay_ms (20); // set a short delay so that the button bounce will settle down and the while loop will work as expected
               while ((PINC & (1<<PC0)) == 0) {
               }
                wrong_code = 0;
           }
            else{
                lcd_line_four();
                lcd_write_string(PSTR("Wrong Code "));
                lcd_write_int16( wrong_code );
                lcd_write_string(PSTR("        "));
                wrong_code++;
                PORTB &= ~(1<<PB5);
                entered_code = 0;
                delay_ms (20); // set a short delay so that the button bounce will settle down and the while loop will work as expected
                while ((PINC & (1<<PC0)) == 0) {
               }
                }

            if (wrong_code == 3)
            {
                PORTB |= (1<<PB3);
                delay_ms(5000);
                PORTB &= ~(1<<PB3);
                wrong_code = 0;
                delay_ms (20); // set a short delay so that the button bounce will settle down and the while loop will work as expected
                while ((PINC & (1<<PC0)) == 0) {
               }
            }

        }

        if ((PINB & (1<<PB4)) == 0){
                lcd_line_one();
                lcd_write_string(PSTR("Locked   "));
                lcd_line_four();
                lcd_write_string(PSTR("               "));
                PORTB &= ~(1<<PB5);
                entered_code = 0;
                delay_ms(20);
                delay_ms (20); // set a short delay so that the button bounce will settle down and the while loop will work as expected
                while ((PINB & (1<<PB4)) == 0) {
               }
                }

    }
    return 0;
 }
September 20, 2012
by sask55
sask55's Avatar

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
JimFrederickson's Avatar

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...

    #define KEYISPRESSED    1
    #define KEYISNOTPRESSED 0

    #define KEYCOUNT        7
    #define READDELAYCOUNT  2000

    typedef struct {
    uint16_t readdelay;
    uint8_t transtochar[KEYCOUNT];
    uint8_t lastvalidstate[KEYCOUNT];
    uint8_t currentstate[KEYCOUNT];
    uint8_t currentstatecount[KEYCOUNT];
    uint8_t validchar;
    } button;

    volatile button mybutton;

    void read_button_init() {
    //  Setup button microprocessor hardware and read_button data structures

    uint8_t i;

        DDRB &= ~(1<<PB4);
        DDRC &= ~(1<<PC0);
        DDRC &= ~(1<<PC1); // set PC1 as input
        DDRC &= ~(1<<PC2); // set PC2 as input
        DDRC &= ~(1<<PC3); // set PC3 as input
        DDRC &= ~(1<<PC4); // set PC4 as input
        DDRC &= ~(1<<PC5); // set PC5 as input

        // turn on the internal resistors for the pins
        PORTB |= (1<<PB4);
        PORTC |= (1<<PC0);
        PORTC |= (1<<PC1); // turn on internal pull up resistor for PC1
        PORTC |= (1<<PC2); // turn on internal pull up resistor for PC2
        PORTC |= (1<<PC3); // turn on internal pull up resistor for PC3
        PORTC |= (1<<PC4); // turn on internal pull up resistor for PC6
        PORTC |= (1<<PC5); // turn on internal pull up resistor for PC7

        DDRB |= (1<<PB5); // set PB5 as output (LED)
        DDRB |= (1<<PB3);

    //  Set Keypress to Character Code Translation Table
        mybutton.transtochar[0] = 1;
        mybutton.transtochar[1] = 2;
        mybutton.transtochar[2] = 3;
        mybutton.transtochar[3] = 4;
        mybutton.transtochar[4] = 5;

        mybutton.transtochar[5] = '@';
        mybutton.transtochar[6] = 'R';

    //  Reset all last valid states to 'no press'
        for (i=0; i < KEYCOUNT; i++) {
            mybutton.lastvalidstate[i] = KEYISNOTPRESSED;
            }

    //  Reset all current states to 'no press'
        for (i=0; i < KEYCOUNT; i++) {
            mybutton.currentstate[i] = KEYISNOTPRESSED;
            }

    //  Reset all current state count 0
        for (i=0; i < KEYCOUNT; i++) {
            mybutton.currentstatecount[i] = KEYISNOTPRESSED;
            }

         mybutton.validchar = 0;
         mybutton.readdelay = READDELAYCOUNT;
         }

    int read_button_process(uint8_t scancode, uint8_t scanstate) {
    //  If the 'lastvalidstate' was a keypress, we need to debounce a 'keyrelease'
    //  before we can process that key again
      if (mybutton.lastvalidstate[scancode] == KEYISPRESSED) {
    //  If the key is pressed then clear currentstatecount and exit
         if (scanstate == KEYISPRESSED) {
            mybutton.currentstatecount[scancode] = 0;
            return;
            }

    //  *****
    //  NOTE:  Here I don't use an "else" because the previous "if" contains a "return"
    //  which will end the current function call at that point.
    //  so anything after that "if" is by default an "else".
    //  *****

         mybutton.currentstatecount[scancode]++;

         if (mybutton.currentstatecount[scancode] > KEYCOUNT) {
            mybutton.currentstatecount[scancode] = 0;
            mybutton.lastvalidstate[scancode] = KEYISNOTPRESSED ;  //  Change to 'no key press'
            mybutton.validchar = 0;
            }
        return;
        }

    //  Now we are processing for 'lastvalidstate being 'no key press'

      if (scanstate == KEYISNOTPRESSED) {
         //  if the scanstate for the current scancode is still 'no key press' then exit
         mybutton.currentstatecount[scancode] = 0;
         return;
         }

      mybutton.currentstatecount[scancode]++;

      if (mybutton.currentstatecount[scancode] > KEYCOUNT) {
        //  we have reached our 'debounce count
        //  so we have a current valid 'pressed key state' and need to update accordingly
         mybutton.lastvalidstate[scancode] = KEYISPRESSED;
         mybutton.validchar = mybutton.transtochar[scancode];
         mybutton.lastvalidchar = mybutton.validchar;
         mykeyboard.currentstatecount[scancode] = 0;
         }

        }

    int read_button() {

    uint8_t button_value = 0;

    //  Reads state of all buttons and processes them individually

    //  Check to see if we are reading the button on this particular call
        mybutton.readdelay--;

        if (mybutton.readdelay > 0) {
            return 
            };

        mybutton.readdelay = READDELAYCOUNT;

    //  Process button 0 current state
        if ((PINC & (1<<PC1)) == 0) {
            read_button_process(0, KEYISPRESSED);
            }   
        else {
            read_button_process(0, KEYISNOTPRESSED);
            }

    //  Process button 1 current state
        if ((PINC & (1<<PC2)) == 0) {
            read_button_process(1, KEYISPRESSED);
            }
        else {
            read_button_process(1, KEYISNOTPRESSED);
            }

    //  Process button 2 current state
        if ((PINC & (1<<PC3)) == 0) {
            read_button_process(2, KEYISPRESSED);
            }
        else {
            read_button_process(2, KEYISNOTPRESSED);
            }

    //  Process button 3 current state
        if ((PINC & (1<<PC4)) == 0) {
            read_button_process(3, KEYISPRESSED);
            }
        else {
            read_button_process(3, KEYISNOTPRESSED);
            }

    //  Process button 4 current state
        if ((PINC & (1<<PC5)) == 0) {
            read_button_process(4, KEYISPRESSED);
            }
        else {
            read_button_process(4, KEYISNOTPRESSED);
            }

    //  *****
    //  I would add the appropriate code for the other 2 buttons here as well.
    //  *****

    //  Process button 5
        if ((PINC & (1<<PC0)) ==0) {
            read_button_process(5, KEYISPRESSED);
            }
        else {
            read_button_process(5, KEYISNOTPRESSED);
            }

    //  Process button 6
        if ((PINB & (1<<PB4)) ==0) {
            read_button_process(6, KEYISPRESSED);
            }
        else {
            read_button_process(6, KEYISNOTPRESSED);
            }

        button_value = mybutton.validchar;
        mybutton.validchar = 0;

        return button_value;
        }

    int main()
    {
        read_button_init();

        lcd_init();
        lcd_home();

        FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

    //display initial settings
        lcd_line_one();
        lcd_write_string(PSTR("Locked  "));
        lcd_line_two();
        lcd_write_string(PSTR("Enter Code")); 
        lcd_line_three();
        lcd_line_four();

        uint32_t ul_code=55441;
        uint32_t entered_code=0;
        uint16_t new_button_value = 0;
        uint8_t wrong_code = 0;

        lcd_line_three();
        fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);

        while(1){

            new_button_value = read_button();

    //  button 5?
    //        if ((PINC & (1<<PC0)) == 0)
    //  Process "Enter Keypress", check lock code
            if (new_button_value == "@") {
    //  Since we are processing this keypress make sure no other processing will occur
                    new_button_value = 0;
                if (entered_code == ul_code)
                {
                   lcd_line_three();
                   entered_code = 0;
                   fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);
                   lcd_line_one();
                   lcd_write_string(PSTR("Unlocked "));
                   lcd_line_four();
                   lcd_write_string(PSTR("Correct        "));
                   PORTB |= (1<<PB5);
                    wrong_code = 0;
               }
                else{
                    lcd_line_four();
                    lcd_write_string(PSTR("Wrong Code "));
                    lcd_write_int16( wrong_code );
                    lcd_write_string(PSTR("        "));
                    wrong_code++;
                    PORTB &= ~(1<<PB5);
                    entered_code = 0;
                   }

    //  Code entry failed 3 times
                if (wrong_code == 3)
                {
                    PORTB |= (1<<PB3);
                    delay_ms(5000);
                    PORTB &= ~(1<<PB3);
                    wrong_code = 0;
                }

            }

     //  button 6
    //        if ((PINB & (1<<PB4)) == 0){

    //  Process "Code Entry Reset"
            if (new_button_value == 'R') {
    //  Since we are processing this keypress make sure no other processing will occur
                    new_button_value = 0;
                    lcd_line_one();
                    lcd_write_string(PSTR("Locked   "));
                    lcd_line_four();
                    lcd_write_string(PSTR("               "));
                    PORTB &= ~(1<<PB5);
                    entered_code = 0;
                    lcd_line_three();
                    fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);
                    }

            if (new_button_value != 0) {
                entered_code = entered_code * 10 + (new_button_value ; // shift left and add new digit
                if (entered_code > 200000000) { //limit size of code to < 32 bit
                    entered_code = 0;  //reset enter_code if it is to long.
                    }
                lcd_line_three();
                fprintf_P(&lcd_stream, PSTR("Code: %u       "), entered_code);
                }

        }
        return 0;
     }
September 20, 2012
by pcbolt
pcbolt's Avatar

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
pcbolt's Avatar

Lynn -

Here is the debouncing code I've used before that seems to work pretty well. First I create a few global variables...

volatile uint16_t but_1_test, but_2_test, ticks;
volatile uint8_t but_1_down, but_2_down;

Then I set up a timer and button assignments in an initialization routine called from main()...

void init_button(){

    but_1_test = but_2_test = ticks = 0;
    but_1_down = but_2_down = 0;

    DDRC &= ~(1<<PC2); // set PC2 as input - Button1
    DDRC &= ~(1<<PC3); // set PC3 as input - Button2

    //turn on pullup resistors
    PORTC |= (1<<PC2);
    PORTC |= (1<<PC3);

    TCCR0A = 0x00;  // Normal timer operation - count from 0 to 255
    TCCR0B |= (1<<CS02);  // Prescaler of 256 -> 14,745,600 / 256 = 57,600 Hz
    // 57,600 / 256 = 225 hz = every 4.44 mSec an overflow interrupt occurs
    TIMSK0 |= (1<<TOIE0);   // enable timer interrupt

    return;
}

Next, I can set up a timer interrupt service routine which fires off every 4.44mSec...

ISR(TIMER0_OVF_vect) {

    uint8_t but_1_state = (PINC & (1<<PC2)) >> PC2;
    uint8_t but_2_state = (PINC & (1<<PC3)) >> PC3;
    but_1_test = (but_1_test << 1) | but_1_state | 0xe000;
    but_2_test = (but_2_test << 1) | but_2_state | 0xe000;
    but_1_down = 0;
    but_2_down = 0;
    if (but_1_test == 0xe000) but_1_down = 1;
    if (but_2_test == 0xe000) but_2_down = 1;

    ticks++;

    return;

}

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...

uint8_t read_button(){

    if (but_1_down) return 1;
    if (but_2_down) return 2;
    return 0;

}

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
live4the1's Avatar

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
JimFrederickson's Avatar

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
pcbolt's Avatar

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
missle3944's Avatar

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
live4the1's Avatar

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
Ralphxyz's Avatar

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

Post a Reply

Please log in to post a reply.

Did you know that NerdKits has a TV commercial, seen on MythBusters and the Science Channel? Learn more...