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.

Microcontroller Programming » Code for Momentary Switch to stay on or off

March 26, 2010
by EdsJunk
EdsJunk's Avatar

I can not seem to figure out how to write some code that will allow for a momentary button (like the red one available in the kit) to toggle between on and off when pushed. also would like the MCU to remember if it was off or on when its powered off and back on.

My idea right now is to replace a bunch of switches in my car with a LCD and just a couple momentary switches. so i can turn off accessories, and also being able to switch the "page" on the LCD to give the buttons other functions as well. Thanks again!

March 28, 2010
by EdsJunk
EdsJunk's Avatar

anybody? I have NO CLUE what to do

March 28, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi EdsJunk,

It sounds like you have yourself a neat project in the works I can't wait to see how it progresses.

The easiest way to react to a button press is to hook up the button so that it pulls a pin low, and leaves it floating otherwise. We go into plenty of detail on this subject in our Digital Calipers DRO tutorial. Once you have the button wired up. Reading it is a matter of checking the right register. Say I had my button hooked up on PC0. I could do:

while(PINC & (1<<PC0)){
   // do nothing in while loop.
}
// continue when button is pressed

The statement PINC & (1<<PC0) checks whether the pin is a digital high or low. Since the pullup resistor keeps the pin high while the button is not pressed it is going to wait in the while loop. When the button is pressed PINC & (1<<PC0) will evaluate to 0 (or false) and the while loop will exit allowing the rest of your code to execute.

There are a couple of problems with this approach. If the rest of your code runs very quickly you will loop back around before you can release the button and it might execute more than once per button press. This may or may not be ok. In our DRO example we did not have to worry about that because we were just zeroing our scale, we didn't care if it got zeroed a bunch of times in succession.

This approach also freezes your main loop leaving you unable to do other tasks while waiting for a button push. A different approach would be to use a pin change interrupt on the pin with the button. The interrupt will fire when the button changes the digital state of the pin. See our Interrupts tutorial for more on how to do that. With this approach you will have to be careful of mechanical bouncing on the button when you press it, it might cause the interrupt to fire more than once.

Hope that gets you started. Let us know if you run into any problems.

Humberto

March 28, 2010
by EdsJunk
EdsJunk's Avatar

Well i have it setup right now to read the button presses.. what I want it to do is to remember when i press the button and not return to off. So for example.. if I push the button.. it stays on, if i push it again, it goes off.. right now it stays on if i hold down the button and when I let off it goes back off.. here is my code so far

int main() { // start up the LCD lcd_init(); lcd_home(); lcd_line_one(); lcd_write_string(PSTR(" JeePuter 2.0 ")); lcd_line_three(); lcd_write_string(PSTR(" 1998 Jeep Wrangler")); lcd_line_four(); lcd_write_string(PSTR(" Eddie Zarick "));

delay_ms(5000);

// Set the 6 pins to input mode - Two 3 bit numbers
DDRC &= ~(1<<PC0); // set PC0 as input 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); // turn on internal pull up resistor for PA0 PORTC |= (1<<PC1); // turn on internal pull up resistor for PA1 PORTC |= (1<<PC2); // turn on internal pull up resistor for PA2 PORTC |= (1<<PC3); // turn on internal pull up resistor for PA3 PORTC |= (1<<PC4); // turn on internal pull up resistor for PA4 PORTC |= (1<<PC5); // turn on internal pull up resistor for PA5

// declare the variables to represent each bit, of our two 3 bit numbers uint8_t a1; uint8_t a2; uint8_t a3;

uint8_t b1; uint8_t b2; uint8_t b3;

while(1) {

      // (PINC & (1<<PC0)) returns 8 bit number, 0's except position PA0 which is
      // the bit we want
      // shift it back by PA0 to put the bit we want in the 0th position.

  a1 = (PINC & (1<<PC0)) >> PC0;
  a2 = (PINC & (1<<PC1)) >> PC1;
  a3 = (PINC & (1<<PC2)) >> PC2;
  b1 = (PINC & (1<<PC3)) >> PC3;
  b2 = (PINC & (1<<PC4)) >> PC4;
  b3 = (PINC & (1<<PC5)) >> PC5;

// print message to screen //PAGE 1

if(b3<1)

{

lcd_line_one(); lcd_write_string(PSTR("CB RADIO SCANNER")); lcd_line_two(); if (a1<1) //Sets On or Off depending on pin A1 lcd_write_string(PSTR("ON ")); if (a1>0) lcd_write_string(PSTR("OFF")); lcd_write_string(PSTR(" ")); if (a2<1) lcd_write_string(PSTR(" ON")); if (a2>0) lcd_write_string(PSTR("OFF"));
lcd_line_three(); lcd_write_string(PSTR("POWER O/R INVERTER")); lcd_line_four(); if (a3<1) lcd_write_string(PSTR("ON ")); if (a3>0) lcd_write_string(PSTR("OFF")); lcd_write_string(PSTR(" ")); if (b1<1) lcd_write_string(PSTR(" ON")); if (b1>0) lcd_write_string(PSTR("OFF"));

  //delay_ms(500);

}

if(b3>0)

{

lcd_line_one(); lcd_write_string(PSTR("SCENE L GARAGE DOOR")); lcd_line_two(); if (a1<1) lcd_write_string(PSTR("ON ")); if (a1>0) lcd_write_string(PSTR("OFF")); lcd_write_string(PSTR(" ")); if (a2<1) lcd_write_string(PSTR(" ON")); if (a2>0) lcd_write_string(PSTR("OFF"));
lcd_line_three(); lcd_write_string(PSTR("FOG L LED L")); lcd_line_four(); if (a3<1) lcd_write_string(PSTR("ON ")); if (a3>0) lcd_write_string(PSTR("OFF")); lcd_write_string(PSTR(" ")); if (b1<1) lcd_write_string(PSTR(" ON")); if (b1>0) lcd_write_string(PSTR("OFF"));

  //delay_ms(500);

}

} return 0; }

March 29, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi EdsJunk,

You are doing great so far. Just getting a reading from your button is certainly a huge step using buttons effectively as an input. In order to move ahead with what you want to do though you have to understand the structure of your code, and think of the type of structure you want to have. As your code currently stands, it is zooming through in the big while loop, reading the state of your PC5 pin (you are reading others but only taking this one into consideration) every time. If the pin is low, you do something, and if not you don't, and this loop keeps running over and over as fast as it can. Looking at it this way, its easy to see why your LCD reflects the current state of the button and doesn't "remember" a button press.

This is actually a fairly deep concept, when you start talking about whether your programs merely reflect the current state of the world, or actually hold their own state, and will behave differently depending on what that internal state is. Since the outside circuit will be exactly the same from one button press to the next, but you want something different to happen every time you press the button, you need to hold some internal state to be able to do what you want.

Luckily, this is as easy as just introducing a variable that holds information in your code. You could do something like:

 //somewhere before the while(1) loop
 uint8_t button_state = 0; //define a variable to hold our button state

 //////////////
 ///other code
 //////////////

 ////// now inside the while(1) loop

 b3 = (PINC & (1<<PC5)) >> PC5; //read PC5

 if(b3<1){ //button is pressed
      button_state = !button_state; //flip the button state
      delay_ms(500);
 }

 if(button_state){
    // do state = 1 stuff
 } else {
    // do state = 0 stuff
 }

Notice that I use the ! operator to flip button_state between a logical true and a logical false depending on whether the button is pressed or not. The code then acts on the state of the variable, not the current state of the pin.

Now this has one major issue. If the while loop executes very quickly, you will have only a few milliseconds to press the button and let go, or else the button state will flip a whole bunch of times. That is the reason for the delay_ms(), this will give you half a second to release the button before the code continues. This is a very naive way of solving this problem, but it works.

I hope all this makes sense, and gets you going in the right direction.

Humberto

January 22, 2011
by SpaceGhost
SpaceGhost's Avatar

I know this is an old topic, but I think it is pretty interesting. I did it like this:

// PIN DEFINITIONS:

// PC5 --AUX switch "push - on, push - off" (pin 28) closing to GND

// PC0 -- +VDC output for AUX (or LED) control

 int main() {

  DDRC |= (1<<PC0);   // Set PC0 (pin 23) as sourcing output for LED

  DDRC &= ~(1<<PC5); // set PC5 (pin 28) as pushbutton input

  PORTC |= (1<<PC5); // turn on internal pull up resistor (pin 24)

// declare the variable to represent pushbutton input
  uint8_t on_off;
  uint8_t i;

    for(i = 0; i <= 1; i++){

       PORTC &= ~(1<<PC0);  // Turn off LED-        
       }

    i=0; // start with LED OFF

while(1) {

   on_off = (PINC & (1<<PC5)) >> PC5;

   if (on_off == 0)

    {   i = i + 1;    //if button pressed, increase count by one
        if (i >= 2 ) //set 1 as highest allowed count, and if i is equal or greater than 2,
        i = 0;       //count loops back to zero
        delay_ms(200);
    }

   if ( i == 0 )

    {   PORTC &= ~(1<<PC0); // LED off

    }

   if ( i == 1 )

    {   PORTC |= (1<<PC0); // LED ON

    }

}

   return 0;

}

Actually, I did it like this first to test and debug the program with the LCD:

// PIN DEFINITIONS:

// PC5 --AUX switch "push - on, push - off" (pin 28) closing to GND

// PC0 -- +VDC output for AUX (or LED) control

 int main() {
    // fire up the LCD
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
    lcd_home();

  DDRC |= (1<<PC0);   // Set PC0 (pin 23) as sourcing output for LED

  DDRC &= ~(1<<PC5); // set PC5 (pin 28) as pushbutton input

  PORTC |= (1<<PC5); // turn on internal pull up resistor (pin 24)

// declare the variable to represent pushbutton input
  uint8_t on_off;
  uint8_t i;

    for(i = 0; i <= 1; i++){

       PORTC &= ~(1<<PC0);  // Turn off LED-        
       }

    i=0; // start with LED OFF

while(1) {

   on_off = (PINC & (1<<PC5)) >> PC5;

   if (on_off == 0)

    {   i = i + 1;    //if button pressed, increase count by one
        if (i >= 2 ) //set 1 as highest count, and if i is equal or greater than 2,
        i = 0;       //count loops back to zero
        delay_ms(200);
    }

   if ( i == 0 )

    {   PORTC &= ~(1<<PC0); // LED off
        lcd_line_three();
        fprintf_P(&lcd_stream, PSTR("OFF"));        
    }

   if ( i == 1 )

    {   PORTC |= (1<<PC0); // LED ON
        lcd_line_three();
        fprintf_P(&lcd_stream, PSTR("ON "));        
    }

    {
        lcd_line_one();
        fprintf_P(&lcd_stream, PSTR("Switch Position-%i "), i); 
    }

}

   return 0;

}

It's a modified version of the counter that Rick had helped me with. This one-switch "push on - push off" program seems to work pretty good. Counter only counts in a 0-1 loop, and basically does something if the count equals 0, and something else if the count equals 1.

The 200 ms. delay seems to work pretty good to debounce the switch. I tried it with and without. Big difference.

I think it would be neat to incorporate something like this with a transistor switch on the output to turn on and off the LCD backlight of a final project... Or for other stuff not yet thought of.

I have one question though - could the variable (uint8_t i) "i" be changed to something else like another letter? Is there another way I could declare that variable that would also work?

January 23, 2011
by SpaceGhost
SpaceGhost's Avatar

Answering my own question - yes, I found that I can change the "i" variable to something else (I changed it to a j). I pretty much figured that I could, seems kinda like a dumb question now.

I think something like this makes a dandy little switching "circuit"... By raising the count limit and adding extra if statements, I think it could make a nice one-button "selector switch"... Or could be used to turn on things in a sequence...

Adding another switch for directional selection, and another switch for "enter" for a "menu selection" switch also comes to mind... Hmmm.

This is just another reason that I like counter circuits so much - so much more that you can do with them besides count things!

January 23, 2011
by SpaceGhost
SpaceGhost's Avatar

Actually, after looking at the code I posted, I think this way might be better -

 int main() {
    // fire up the LCD
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
    lcd_home();

  DDRC |= (1<<PC0);   // Set PC0 (pin 23) as sourcing output for LED

  DDRC &= ~(1<<PC5); // set PC5 (pin 28) as pushbutton input

  PORTC |= (1<<PC5); // turn on internal pull up resistor (pin 28)

// declare the variable to represent pushbutton input
  uint8_t on_off;
  uint8_t j;

    for(j = 0; j <= 1; j++){

       PORTC &= ~(1<<PC0);  // Turn off LED-        
       }

    j=0; // start with LED OFF

while(1) {

   on_off = (PINC & (1<<PC5)) >> PC5;

   if (on_off == 0)

    {   j = j + 1;      //if button pressed, increase count by one
        if (j >= 2 )    //set 1 as highest allowed count, and if i is equal or greater than 2,
        j = 0;           //count loops back to zero
        delay_ms(200);   // switch "debounce"
    }

   if ( j == 0 )

    {   PORTC &= ~(1<<PC0); // LED off
        lcd_line_three();
        fprintf_P(&lcd_stream, PSTR("OFF"));
        lcd_line_one();
        fprintf_P(&lcd_stream, PSTR("Switch Position-0")); 
    }

   if ( j == 1 )

    {   PORTC |= (1<<PC0); // LED ON
        lcd_line_three();
        fprintf_P(&lcd_stream, PSTR("ON "));
        lcd_line_one();
        fprintf_P(&lcd_stream, PSTR("Switch Position-1"));
    }

}

   return 0;

}

Dave

Post a Reply

Please log in to post a reply.

Did you know that many systems in nature can be described by a first order response? Learn more...