3 Blink an LED

This section demonstrates how to turn an LED on and off alternatively.

Although this use case is very simple, it is widely used in real life applications, and it helps in understanding how to achieve some of the very basic functions of the microcontroller. These functions are:

  • Setting a pin direction
  • Setting the output value of a pin

Setting a Pin Direction

The directions of the pins are stored in the PORTn.DIR register. For example, the directions for the pins of PORTA are stored in PORTA.DIR. Each bit in the register controls the direction of the corresponding pin, so PORTA.DIR bit 0 controls pin A0 (also called PA0). For a bit value of ‘1’, the pin is configured as output, while for ‘0’, it is configured as input.

In most cases, it is only necessary to write one bit at a time. For this purpose, there are eight bitmasks (PINx_bm) defined in the <avr/io.h> header file, one for each bit. These macros represent bitmasks that have a ‘1’ bit on x position and ‘0’ bits in rest. For example, PIN5_bm is 0b00100000. Using these bitmasks in conjunction with logic operations makes it possible to only write the corresponding pin and make sure the others are unchanged.

Figure 3-1. Bit 5 Data Direction Register

To set (write to ‘1’) pin x, use:

PORTn.DIR = PORTn.DIR | PINx_bm;

This because a logic OR operation with ‘1’ will always result in ‘1’, while logic OR with ‘0’ will leave the value unchanged (1|1 = 1, 1|0 = 1, 0|0 = 0). Hence, the bitmask only has ‘1’ on the position to be set.

To clear (write to ‘0’) pin x, use:

PORTn.DIR = PORTn.DIR & ~PINx_bm;

This because a logic AND operation with ‘0’ will always result in a ‘0’, while logic AND operation with ‘1’ will leave the value unchanged (1&1 = 1, 1&0 = 0, 0&0 = 0). Hence, the bitmask only has ‘0’ on the position to clear.

Note:
  1. The same approach, to only modify a single bit, can be used for all other registers as described in previous paragraphs.
  2. Logic operations with more bitmasks can be chained in an expression such as PORTA.DIR = PORTA.DIR | PIN0_bm | PIN1_bm.
  3. PINx_bp are similar macros that define the position of the bit in the register. For example, PIN2_bp has the value 2. The bitmask macros can be obtained using these macros and the shift operation.

Setting the Output Value of a Pin

The PORTn.OUT register controls the output values, so that ‘0’ means the pin will be pulled to GND and ‘1’ means it will be pulled to VDD.

Note: The meaning of these values, ‘0’ and ‘1’, can be inverted using the PORTn.PINxCTRL register. See the PORTn.PINxCTRL register section in the data sheet to understand its functionality.
Figure 3-2. Bit 5 Output Value Register

As previously described, the same approach to only modify a single bit can be applied to write to the PORTn.OUT register.

The following code toggles pin PB5 each 500 ms. An LED is attached to that pin on the ATmega 4809 Xplained Pro board.

#define F_CPU 3333333 
#include <avr/io.h>
#include <avr/delay.h>

int main(void)
{
	
    PORTB.DIR |= PIN5_bm;
	
    while (1) 
    {
 		PORTB.OUT |= PIN5_bm;
 		_delay_ms(500);
         PORTB.OUT &= ~PIN5_bm;
 		_delay_ms(500);
    }
}
Note:
  1. To use the delay functions, F_CPU must be defined before including avr/delay.h. F_CPU must match the CPU frequency.
  2. The PORTn.OUTTGL register can also be used to toggle a pin.
  3. As an optimization, PORTn.OUTSET/PORTn.OUTCLR and PORTn.DIRSET/PORTn.DIRCLR registers can also be used to set the pin direction and output value. Their benefit is that instead of the read-modify-write operation, only a write operation is used.

An MCC generated code example for AVR128DA48 with the same functionality as the one described in this section can be found here: