AVR Port Input Logic

This guide teaches you how to make decisions based on port input conditions.

STK500 Setup

If you’ve been following along in these guides, then your STK500 should be setup like this.

The important things to setup based on the picture above are:

  • ISP6PIN connects to SPROG3 if you are using an ATmega32
  • PORTA connects to LEDS
  • PORTB connects to SWITCHES

Purpose of this Guide

This guide teaches you how to use if statements in C. In this guide we will write a program in C that makes some decisions based on the state of input pins.

Goal of the Program

The goal of this program is the following:

  • If no buttons are pressed, all LEDs are off
  • If SW0 is pressed, then every other LED lights up
  • If SW1 is pressed, the the lower 4 LEDs light up

With these simple rules in mind, we can write our main loop. First we need to talk about bit masks.

Using Masks to Isolate Bits

The easiest way to detect if a bit is set in a byte is to mask out the bits that we are not interested in. You can mask out a value by ANDing the input PIN with a byte value that has only the bits set that you are interested in. Lets say you want to mask out the least significant bit in a byte. If you AND your byte with 00000001, you will effectively ignore all bits other than the least significant bit. See the 1 in the least significant position.

Table 1

Input Value 10010101 This is random data
Mask 00000001 This mask isolates LSB
Logical AND 00000001 Only the LSB is present in the result

Here the bit mask has isolated only the LSB into the result. By changing the mask, you can change which bit you are interested in looking at. To look at the second bit, we use a mask of 00000010 like this:

Table 2

Input Value 10010101 This is random data
Mask 00000010 This mask isolates LSB
Logical AND 00000000 Only the LSB is present in the result

Here you can see that the zero in the second bit position, when ANDed with a 1 in the second bit position creates a zero in the second bit position of the result.

This is useful because in C, any value greater than zero is considered to be true. If you tested the result of Table 1 in an if statement, the result is true, meaning “Yes, bit 0 was set“. If you tested the result of Table 2 in an if statement, the result is false, meaning “No, bit 1 was not set“.

As a general rule, to isolate a bit in a byte, AND the byte with a mask whose only bit that is set is the bit that you are interested in.

Lets look at an example if statement to see how this works.

Example 1

uint8_t value;
value = 0x02;
if(value & (1 << 0)) {
    // do something here
}

Lets see what is going on here. First we create an 8 bit variable called value. Then we assign value to be 0x02, which is 00000010 in binary. Next we test if value & (1 << 0) is true.

00000010This is 0x02
00000001This is the mask (1 << 0), could be written 0x01
00000000Only the masked bit is present in the result

Input Value
Mask Logical AND

As you can see, the result is zero, which is false. The if will fail and the code inside the blocks { } will not execute.

Example 2

uint8_t value;
value = 0x02;
if(value & (1 << 1)) {
    // do something here
}

Lets see what is going on here. First we create an 8 bit variable called value. Then we assign value to be 0x02, which is 00000010 in binary. Next we test if value & (1 << 0) is true.

 

Input Value 00000010 This is 0x02
Mask 00000010 This is the mask (1 << 1), could be written 0x02
Logical AND 00000010 Only the masked bit is present in the result

As you can see, the result is greater than zero, which is true. The if will succeed and the code inside the blocks { } will execute.

The PBx Defines

The guys who wrote WinAVR realized that bitwise input from the ports is something that we would do often. To make our lives easier, they created some DEFINES for us.

#DEFINE PB0 0
#DEFINE PB1 1
#DEFINE PB2 2

While not particularly necessary, they certainly lead to more readable code. If you read in someone else’s program (1 << 3) (a 1 shifted left 3 times, which is equal to 0x08) it is hard to tell what the programmer was thinking when they wrote it. However, if you read (1 << PB3) (a 1 shifted left PB3 times, also equal to 0x08) then it is more obvious that the programmer was referring to PORTB bit 3.

Negative Input Logic

There is just one more little detail when reading in the switches on the STK500. Remember that the switches are negative logic, so before you can test them against your bit mask you need to invert them with a tilde ~. It looks like this in code:

if (~PINB & (1 << PB0)) {

This line does the following actions:

  • Read the value at PINB
  • Negates the value of PINB
  • Shifts a 1 PB0 times to the left
  • Logically ANDs the shifted one with the negated PINB value

The end result is that this if will only be true if the switch tied to PB0 is pressed.

To test for PB1 is almost identical:

if (~PINB & (1 << PB1)) {

Here we are isolating if the switch tied to PB1 is pressed.

The bit_is_set and bit_is_clear macros

Those crazy WinAVR programmers have one more trick up their sleeves for you. They realized that all of this negating, bit shifting and ANDing can get tedious, so they wrote 2 macros to make our programming lives much easier. Here they are, in simplified form:

#define bit_is_set(value, bit) ((value) & _BV(bit))
#define bit_is_clear(value, bit) (~value) & _BV(bit)))

Notice that these defines leverage on the previously discussed _BV macro. Recall that the _BV() macro does a bit shift for you. Using these defines to test if a switch is pressed is easy.

if (bit_is_clear(PINB, PB0)) {

is exactly the same as

if (~PINB & (1 << PB0)) {

You can use either one that you choose in your applications, or even switch back and forth as you see fit.

Accomplish The Goal

It is time to accomplish the goal that we set out to do at the beginning of this guide. Lets test some switch inputs, and turn on some lights accordingly using both the bit-shift-anded-with-not-method, and the bit_is_clear() macro. We will put both version of code here for you to study the differences.

Here’s the bit-shift-anded-with-not-method.

// ********************************************************************************
// Includes
// ********************************************************************************
#include <avr/io.h>
#include <stdbool.h>

// ********************************************************************************
// Main
// ********************************************************************************
int main(void) {
     // configure PORTA as output
    DDRA = 0xFF;
    // configure PORTB as input
    DDRB = 0;
    // make sure PORTB is high impedance and will not source
    PORTB = 0;
    // main loop
    while (true) {
        if (~PINB & (1 << PB0)) {
            // turn on every other light on PORTA, inverse logic
            PORTA = 0xAA;

        } else if (~PINB & (1 << PB1)) {
            // turn on the lower 4 lights light on PORTA, inverse logic
            PORTA = 0xF0;

        } else {
            // default case, all lights off on PORTA, inverse logic
            PORTA = 0xFF;
        }
    }
}

You can download the complete source code here.

And here is the bit_is_clear() macro method.

// ********************************************************************************
// Includes
// ********************************************************************************
#include <avr/io.h>
#include <stdbool.h>

// ********************************************************************************
// Main
// ********************************************************************************
int main(void) {
    // configure PORTA as output
    DDRA = 0xFF;
    // configure PORTB as input
    DDRB = 0;
    // make sure PORTB is high impedance and will not source
    PORTB = 0;
    // main loop
    while (true) {
        if (bit_is_clear(PINB,PB0)) {
        // turn on every other light on PORTA, inverse logic
        PORTA = 0xAA;
    } else if (bit_is_clear(PINB,PB1)) {
        // turn on the lower 4 lights light on PORTA, inverse logic
        PORTA = 0xF0;
    } else {
        // default case, all lights off on PORTA, inverse logic
        PORTA = 0xFF;
    }
}

You can download the complete source code here.

Both of these program do exactly the same thing, and both compile to 184 bytes.

Summary

The following topics were covered in this guide:

  • Bit shifting with (1 << x) syntax
  • Bit masking by using a bit pattern and the AND operator &
  • Testing negative logic with the ~ operator
  • The _BV() bit shifting macros for self documenting code
  • The PB0 defines for self documenting code
  • The bit_is_set() and bit_is_clear() macros for, you guessed it, self documenting code

Now that you have all of these topics under your belt, it’s time to move on to some more difficult things that we can do with the AVR, such as Initializing the USART and Serial Communications.

Or head back to our index of AVR Guides here.

Leave a Reply

Your email address will not be published. Required fields are marked *