Climber's info on AVR programming - User Input

One of the most popular and important ways of getting input from a user is momentary contact switches or buttons. Electrically, the interface couldn't be simpler. Tie one pin to ground and to the other to an I/O pin. Done. As an added precaution I sometimes run the connection through a 400-500 ohm resistor. It's still small enough to be seen by the processor as a low but adds a little safety. If, by accident, the pin the switch is hooked up to is set up for output AND it's been set to high then you would get a short circuit when the button is pressed. That would be bad.

Programming wise it's simple but not quite that simple. You see, mechanical switches BOUNCE. That is, when they are pressed down (or slid, or toggled or whathaveyou) the can make and break contact several times for the first few milliseconds. While that might seem dang fast to us it can result in the wrong number of button presses when we only wanted one.

Of course, you could always add circuitry to eliminate bouncing but one of the reasons to use microcontrollers is to reduce the number of parts in our project not add even more.

I have developed some nice canned C routines that allow my programs to get reliable button presses from the user. These code fragments appeared in my very first real AVR project: a breadboard plugin module specifically to give me four debounced switches to use in other prototypes. It's the small board. The really huge board is a series of circuits that do the same job using discrete logic. I MUCH prefer the little AVR-based one. The other has been put out to pasture. It served me well but I need the real estate back. *sniff*

The way I implement my routine is to have four discrete states for any switch that my code monitors: OFF, ON, BOUNCINGON and BOUNCINGOFF. The BOUNCINGxxx states are the in-between states the switch has when transitioning from ON to OFF and vice-versa. My code doesn't consider a switch to be ON or OFF until a certain period of time has elapsed. I usually define that like so:

#define BOUNCEONTIME  3  // hundredths of a sec after press not bouncing
#define BOUNCEOFFTIME 1  // hundredths of a sec after release not bouncing
#define NUMSWITCHES   4  // number of switches in the project

The BOUNCEONTIME and BOUNCEOFFTIME are, for this example, in hundredths of a second. The code that I took this out of is based on 10 millisecond timing. For your code, use whatever value would come to about 30 milliseconds for BOUNCEONTIME and 10 milliseconds for BOUNCEOFFTIME.

Now, I know what you are thinking. Why would I need a BOUNCEOFFTIME? Well, I found that some of the switches I used would bounce as they were being released. This was a major pain to debug. So, I now take in to account the possibility the switch in use does not turn off instantaneously and completely.

I use a small array to store the switch states:

// these are the four states the switches can be in
enum {OFF, BOUNCINGON, ON, BOUNCINGOFF};
uint8_t switches[NUMSWITCHES];  // start states for the switches

You will note I didn't assign an initial state to the switches array. That's because all static variables in ANSI C are assigned 0 to start (or NULL for strings). I just make sure that the first entry when I enumerate the states is the label of the state that I want start with. In this case, OFF is the first label in the enum statement. The first label in an enum statement is always assigned 0.

When I program on big computers I always assign initial states even when I know the compilers default action assigns an acceptable value on startup. This is because I don't really care about the few extra bytes it takes to do so and I like being able to see at a glance what it's initial value will be without having to look at the associated enum statement. That isn't the case on the AVR. The compiler adds extra code to perform the job of assigning an initial value to a variable even when the value assigned is the same as the startup default. This wastes space. Not a lot of space but still, it's good discipline to pay attention to such things.

I also like to define names for the switches so I don't have to remember their numbers. These particular names are used in the code for my automated Solar Roller Racing Track.

enum {STARTBUTTON, STOPBUTTON, RESETBUTTON, CALIBRATE};

My code also needs to remember how long a switch has been in a transition state before being considered ON or OFF.

uint8_t bouncetimes[NUMSWITCHES];

For both the bouncetimes and switches array the size of the arrays will be equal to the number of switches (duh!).

To implement the full deal I use four functions:

void checkswitches(void) - the "meat" of the code. It loops through all of the switches, checks to see if it is pressed or not and does all the necessary timing to deal with bouncing.

 
//-----------------------------------------------------------------------------
void checkswitches(void)
//
// this here function is to check if the switches are down or not and deal
// with bouncing and the like.
{
  uint8_t sw;

  for (sw=0; sw <=NUMSWITCHES; sw++)  // loop through all four switches
  {
    if (ispressed(sw))  // check if switch pressed
    {
      switch(switches[sw])    // do something depending on current state
      {
        case OFF:             // switch just pressed, wait to stop bouncing
          switches[sw] = BOUNCINGON;
          bouncetimes[sw] = 0;
          break;
        case BOUNCINGON:      // still bouncing, check how long
          if (bouncetimes[sw] == BOUNCEONTIME)   // bouncing finished, now ON
          {
            switches[sw] = ON;
            turnon(sw);
          }
          else
            bouncetimes[sw]++; // still bouncing, keep waiting
          break;
        case ON:
          break;               // button still down. RRAARR HULK NOT CARE!!!!
        case BOUNCINGOFF:
          //bouncetimes[sw]++;
          break;
      }
    }
    else                           // switch is not being pressed
      switch (switches[sw])
      {
        case ON:
          switches[sw] = BOUNCINGOFF;  // switch has just been released
          bouncetimes[sw] = 0;
          break;
        case BOUNCINGOFF:
          if (bouncetimes[sw] == BOUNCEOFFTIME)
          {
            switches[sw] = OFF;
            turnoff(sw);               // switch is now off, do something
          }
          else
            bouncetimes[sw]++;
          break;
        case BOUNCINGON:
        case OFF:
          break;
      }
  }
}

The above function served only to deal with the various states the switch could be. In order to actually DO something it calls turnoff() or turnon(). These functions contain the user code that does the stuff to deal with those annoying humans and their tendency to press buttons. Templates are below. For clarity, I omitted the code that my program uses on the racetrack and just left the switch labels and break statements.

//-----------------------------------------------------------------------------
void turnon(uint8_t sw)
//
// this here function performs the function around turning a switch on
//
{
  switch (sw)
  {
    case STARTBUTTON:
      break;
    case STOPBUTTON:
      break;
    case RESETBUTTON:
      break;
    case CALIBRATE:
      break;
  }
}

//-----------------------------------------------------------------------------
void turnoff(uint8_t sw)
//
// this here function deals with the actions when a button is released
//
{
  switch (sw)
  {
    case STARTBUTTON:
      break;
    case STOPBUTTON:
      break;
    case RESETBUTTON:
      break;
    case CALIBRATE:
      break;
  }
}

Finally, we need a function to determine if a particular button is being pressed or not.

//-------------------------------------------------------------------------
int ispressed(uint8_t sw)
//
// this here function returns true if the specified switch is on or not
// DON'T FORGET!  The switches are active low with pullups.
//
{
  switch (sw)
  {
    case STARTBUTTON: return(!(PINC & _BV(PINC3)));
    case STOPBUTTON:  return(!(PINC & _BV(PINC2)));
    case RESETBUTTON: return(!(PINC & _BV(PINC1)));
    case CALIBRATE:   return(!(PINC & _BV(PINC0)));
  }
  return 0;
}

Of course, you will need to alter the references to PINC/C and whatnot to match the I/O ports your switches are hooked up to.

There you have it. All you need to do now is call checkswitches() every 10 milliseconds and it will take care of the rest. When a button is pressed and has finished bouncing turnon will be called. When a button gets released and has been off for the minimum amount of time turnoff() will get called. Easy as pie.

BONUS MATERIAL. How to do something every 10 milliseconds.

This is your lucky day. Remember the previous link that descibed interrupts. Here is how to do it with an 8 megahertz clock:


uint8_t timercount;

//-----------------------------------------------------------------------------
SIGNAL(SIG_OUTPUT_COMPARE2)
//
// signal called when timer2 counts up to what's in OCR2.  Because of the
// chosen clock speed and the limitations of an 8 bit counter this gets
// called every millisecond and the mainline gets notified every 10
// milliseconds.
{
  timercount++;
  if (timercount == 10)
  {
    timercount = 0;
    interruptoccured = 1;
  }
}

//-----------------------------------------------------------------------------
// INSIDE MAIN FUNCTION

  // set up counter 2 to interrupt the processor every millisecond.
  // with an 8 megahertz system clock 32 prescale, count to 250
  TCCR2 = _BV(WGM21)   // CTC mode
        | _BV(CS20)    // 32 prescale
        | _BV(CS21);
  OCR2 = 250;
  TIMSK = _BV(OCIE2);  // interrupt on compare match for timer 2
  sei();

  while (1)   // spin until interrupt handler says it's been 10 ms
  {
    if (interruptoccured)
    {
      interruptoccured = 0;
      checkswitches();
    }
  }

You could use counter0 to do the same job although you would need to tweek some of the values since a prescale of 32 isn't one of the available options.

You might be wondering why I didn't put all of the code into the interrupt routines. Well, it's a style thing. I like to keep the code inside of an interrupt handler as small as possible. Because interrupts are asynchronous they can be a bear to debug. Keeping them simple keeps the bulk of my debugging in the mainline and support functions. I always code with the need to debug later in mind.


to email Craig send to climber at shaw.ca (replace at with @ and remove spaces).
Return to Craig's Electronics page.
Check out what's new on Craig's pages.
Return to Craig's main page.

Last modified: Aug 18, 2004 - GRRRRRRRRRRRRRRRRR TRYING TO PAINT MY HOUSE BUT IT WON'T STOP RAINING ON THE WEEKENDS! GRRRRRRRRR