Elastic Sheep

Because elasticdog was already taken

Elastic Sheep header image 2

Adding switches

October 5th, 2009 · 1 Comment · Project Wiggum

In this post I will add two switches to control the selection of one led among six. One of the button will increase the selection, the other one will decrease it.

Connecting the switches

I connect the switches on the PC0 and PC1 inputs and on the ground. I don’t need any external pull-up resistor because the ATMEGA168 already provides them internally.

Connection of push-button switches to the ATMEGA168

Configuring the inputs pins

To configure the direction of a port pin, you have to modify the DDRx register bits, 0 for an input, 1 for an output. Then PORTx can be used to enable an internal pull-up.

  /* Setup PC0 and PC1 as inputs with pull-up */
  DDRC = 0x00;
  PORTC = _BV(DDC0) | _BV(DDC1);

You should note that the above code is not the cleaniest. It is ok if you do all your initializations in one place at the start of your program and so setup all the pins at the same time. In the other case it is better to modify only the bits that you need and left the others untouched. The next sample code just do that.

  /* Setup PC0 and PC1 as inputs with pull-up */
  DDRC &= ~(_BV(DDC0) | _BV(DDC1));
  PORTC |= _BV(DDC0) | _BV(DDC1);

The use of predefined constants to address bit offsets is also preferable. It requires at little more work initially but it is easier to read than hexadecimal codes if you decide to build your circuit again in a few months.

Reading an input pin state

To read the state of an input you simply read the PINx register and mask it with the pin bit offset you need:

  // The simple way
  // = 0b00000010 if PC1 is high
  uint8_t state_pc1 = PINC & _BV(DDC1));
  if (state_pc1)
  {
    // ...
  }
 
  // More complicated but a better way
  // = 0b00000001 if PC1 is high
  uint8_t state_pc0 = (PINC & _BV(DDC1)) >> DDC1;
  if (state_pc1 == 1)
  {
    // ...
  }

The simple way of reading the PC1 status returns a bit-field where the bit 1 is set. This value is not false because it is greater than 0, but it can not be compared to a TRUE constant because it depends on the bit you are reading.

The “more complicated” way reduces the bit-field to a binary value, 0 or 1, which is independent of the tested pin.

What is this _BV all about ?

The _BV macro is provided by the avr-libc library to ease the manipulation of bit offsets. It provides a value where the Nth bit is set. Then a file specific to each AVR variant (iomx8.h for the ATMEGA168) provides the bit offset constants for each ports.

// Extracted from str_defs.h
#define _BV(bit) (1 << (bit))
 
// Extracted from iomx8.h
#define DDC6    6
#define DDC5    5
#define DDC4    4
#define DDC3    3
#define DDC2    2
#define DDC1    1
#define DDC0    0

The following statements are equivalent. The last one is for me the most informative one.

  x = 0b00000100;
  x = 0x04;
  x = 1 << 2;
  x = 1 << DDC2;
  x = _BV(DDC2);

The test program

My test program reads the state of the switches I have connected on PC0 and PC1 and increase or decrease the index of the selected led. The leds_update function switch off all the leds except the selected one.

  /* Init the switches state */
  prev_state_sw0 = 0;
  prev_state_sw1 = 0;
 
  /* Main loop */
  for(;;)
  {
    /* Read the current switch state */
    state_sw0 = (PINC & _BV(DDC0));
    state_sw1 = (PINC & _BV(DDC1));
 
    if (state_sw0 && !prev_state_sw0)
    {
      /* The switch 0 has been pressed */
      if (selection < 5)
      {
        selection++;
        leds_update(selection);
      }
    }
 
    if (state_sw1 && !prev_state_sw1)
    {
      /* The switch 1 has been pressed */
      if (selection > 0)
      {
        selection--;
        leds_update(selection);
      }
    }
 
    prev_state_sw0 = state_sw0;
    prev_state_sw1 = state_sw1;
 
    /* Wait for 20ms */
    delay_ms(20);
  }

To detect the press of a button, you need to store the previous state of it. When the state has changed (current != previous) and the current state is greater than 0, then you can trigger an action. If you want to trigger an action on button release, you have to wait for a state change and a current state equal to 0.

In my test program I don’t do any debouncing. The fact that I only scan the pin state with a 20ms interval seems to be sufficient to avoid pin state artifacts (for proper debouncing, here is a good guide from Jack Ganssle).

Here is the test video:

In the next post, I will show how to connect a buzzer to play tones.

Full source code

test_switches.zip

Tags: ·

One Comment so far ↓

Leave a Comment