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. Let's 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 second bit |
Logical AND | 00000000 | Only the second bit 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.
Let's 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
}
Let's 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 in hex, or 2 |
Mask | 00000001 | This is the mask (1 \<\< 0), could be written 0x01 |
Logical AND | 00000000 | Only the masked bit is present in the result |
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
}
Let's 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. Let's 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.