Bare Metal Micro

AVR Digital I/O

4. Outputs

Using digital I/O pins as outputs gives us the ability to do things like blink a status LED or turn on a relay. Configuring and using pins as outputs is actually very easy to do.

There are two main steps for using digital outputs:

  1. Set the direction of the pin to output
  2. Set the value of the pin

To set the direction of the pin to be output, you will use the Data Direction Register. Each port has a data direction register that is used to set the direction of each of its pins. A value of 1 in the data direction register indicates that the pin is an output. The following example shows setting pin PB5 to be an output. Read the Wikipedia page on bit manipulation to get a refresher on setting bits in C.

1
2
// Configure PB5 as an output using the Data Direction Register B (DDRB)
DDRB |= (1 << DDB5)

To clean things up a bit we can use the _BV macro which converts a bit number into a byte value.

1
2
// Configure PB5 as an output using the Data Direction Register B (DDRB)
DDRB |= _BV(DDB5);

To set the value of the pin, you will use the Data Register. Each port has a data register that is used to set the value of each of its pins. A value of 1 in the port data register outputs a high value on the pin. A value of 0 in the port data register outputs a low value on the pin. The following example shows setting the PB5 pin to a high value.

1
2
// Set PB5 high using the Port B Data Register (PORTB)
PORTB |= _BV(PORTB5);

A full working example of applying what we have covered so far is listed below. This example configures PB5 as an output and then repeatedly sets the pin high then low. If you run this example on an Arduino Uno you will see the LED that is connected to Arduino pin 13 blinking. This is because the Arduino Uno pin 13 is connected to pin PB5 of the ATmega328P.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    // Configure PB5 as an output using the Port B Data Direction Register (DDRB)
    DDRB |= _BV(DDB5);

    // Loop forever
    while (1)
    {
        // Set PB5 high using the Port B Data Register (PORTB)
        PORTB |= _BV(PORTB5);

        // Wait 500ms
        _delay_ms(500);

        // Set PB5 low using the Port B Data Register (PORTB)
        PORTB &= ~_BV(PORTB5);

        // Wait 500ms
        _delay_ms(500);
    }
}

One final interesting feature is the ability to toggle the value of an output pin. This can be done by using the Port Input Pin Register. As we will see in the next section, the port input pin register is primarily used to read the value of a pin, however, if you write a 1 to the port input pin register, the corresponding output will toggle. The following example shows using the port input pin register to toggle the PB5 pin.

1
2
// Toggle PB5 using the Port B Input Pin Register (PINB)
PINB = _BV(PINB5);

Using this method, we can optimize the full example from above as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    // Configure PB5 as an output using the Port B Data Direction Register (DDRB)
    DDRB |= _BV(DDB5);

    // Loop forever
    while (1)
    {
        // Toggle PB5 using the Port B Input Pin Register (PINB)
        PINB = _BV(PINB5);

        // Wait 500ms
        _delay_ms(500);
    }
}