2 AVR1000b Getting Started with Writing C-Code for AVR® MCUs

This technical brief provides the recommended steps to successfully program the AVR ® microcontrollers (MCUs) and to define coding guidelines to help writing more readable and reusable code.

High-level programming languages have become a necessity due to the imposed short development time and high-quality requirements. They make it easier to maintain and reuse code due to better portability and readability than the low-level instructions specific for each microcontroller architecture.

Programming language alone does not ensure high readability and reusability, but good coding style does. Therefore, the AVR MCU peripherals, header files and drivers are designed according to this presumption.

The most widely used high-level language for AVR microcontrollers is C, so this document will focus on C programming. To ensure compatibility with most AVR C compilers, the code examples in this document are written using ANSI C coding standard.

This document contains code examples developed with the Atmel Studio Integrated Development Environment (IDE). Most code examples are compatible with other IDEs, presented in Section 5: Further Steps.

2.1 Data Sheet Module Structure and Naming Conventions

The first step in writing C-code for a microcontroller is to know and understand what type of information can be found in the data sheet of the device used for programming. The data sheet contains information about the features, the memories, the core and the peripheral modules of the microcontroller, the functional description of the peripheral modules, the peripherals base addresses, the names and addresses of the registers, and other functional and electrical characteristics.

2.1.1 How to Find the Data Sheet

Any documentation related to Microchip products can be found at:

The device data sheets, for the device families of interest in this document, can be found at:

2.1.2 Pin Description

The pin description can be found in any device data sheet. The pinout is contained in the Pinout, Pin Configurations section, or any other name convention, depending on the device. The pinout of the ATmega809/1609/3209/4809 48-pin devices is presented in Figure 2-2 .

The configurable functionalities for each I/O pin are described in the I/O Multiplexing and Considerations section, or the Alternate Port Functions subsection of the I/O Ports section, depending on the device. If an evaluation board is used, such as AVR128DA48 Curiosity Nano, the user needs to know how the microcontroller’s pins are allocated on the specific board. The information is available on the AVR128DA48 Curiosity Nano webpage, in the AVR128DA48 Curiosity Nano Schematics document. Other documents that describe the AVR128DA48 Curiosity Nano board and microcontroller characteristics are available on the same webpage.

2.1.3 Modules Description

An AVR microcontroller is comprised of several building blocks: AVR CPU, SRAM, Flash, EEPROM and several peripheral modules called module types. Throughout this document, all peripheral modules will be referred to as modules.

Newer AVR microcontroller families can have one or more instances of a given module type. All instances have the same features and functions. Some module types are a subset of others and inherit some of their features. The inherited features are fully compatible with the respective module type. For example, the subset module for a timer can have fewer compare and capture channels than a full timer module.

Read-Modify-Write operations are not needed when working with bit masks or -positions if the Reset value of the register is 0x00 , and the register can be configured in a single line.

The desired configuration can be, for example:

This configuration can be implemented by using either the binary, hexadecimal, or decimal value (as presented below) and writing the resulting value directly to the register.

ADC0.CTRLA = 0b00100001; /* binary */ ADC0.CTRLA = 0x21; /* hexadecimal */ ADC0.CTRLA = 33; /* decimal */ 

However, to improve the code readability (and potentially the portability), it is recommended to use the device defines shown in the upcoming sections.

Note: For AVR registers, the Reset value for most bits and bit fields is ‘ 0 ’, but there are exceptions. For example, the USART0 Control C register has a couple of bits with the Reset value ‘ 1 ’. In this case, the user must explicitly set the desired configuration without relying on the fact that the Reset values of the bits are usually ‘ 0 ’.

The USART0 Control C register has a Reset value of 0x03 . In this case, there is a need for a Read-Modify-Write operation to initialize one of the other bits or bit fields without changing the value of the CHSIZE bit field. Figure 2-14 shows this register.

2.3.2.1 Register Initialization Using Bit Masks and Group Configuration Masks

This subsection provides the recommended way to configure the ADC CTRLA register using bit masks and group configuration masks.

ADC0.CTRLA = ADC_ENABLE_bm /* Enable the ADC */ | ADC_CONVMODE_bm /* Select Differential Conversion mode */ | ADC_RESSEL_10BIT_gc; /* 10-bit conversion */ 

Note the absence of the bitwise OR (‘ | ’) on the register side of the assignment. In most cases, device and peripheral routines are written in this way.

The ADC_RESSEL_enum enumeration contains the group configuration mask presented below.

ADC_RESSEL_10BIT_gc = (0x012), /* 10-bit mode */

CAUTION: The above initialization of the register must be done in a single line of C-code. Writing as follows, the group configuration in the second line will clear the bit set in the first line.

ADC0.CTRLA = ADC_ENABLE_bm; ADC0.CTRLA = ADC_CONVMODE_bm; ADC0.CTRLA = ADC_RESSEL_10BIT_gc;

The correct way of writing this code using three code lines is presented below.

ADC0.CTRLA = ADC_ENABLE_bm; ADC0.CTRLA |= ADC_CONVMODE_bm; ADC0.CTRLA |= ADC_RESSEL_10BIT_gc;

Note: Bit masks can only set bits in a single line of code, so any configurations which require bits to be set to ‘ 0 ’ can be left out since they are correctly configured by their Reset value.

2.3.2.2 Register Initialization Using Bit Positions

The same configuration, as presented above, can be done by using the bit position macros, as follows.

ADC0.CTRLA = (1 /* Enable the ADC */ | (1 /* Select Differential Conversion mode */ | (1 /* 10-bit conversion */ | (0 
Note: The (0 << ADC_RESSEL0_bp) line is added simply for readability, but it can be removed.
ADC0.CTRLA = (1 /* Enable the ADC */ | (1 /* Select Differential Conversion mode */ | (1 /* 10-bit conversion */ 

Other position masks that can be used to configure bit fields are group position masks. They can be used as presented below. The desired configuration value must be shifted with the bit field position (group position).

ADC0.CTRLA = (1 /* Enable the ADC */ | (1 /* Select Differential Conversion mode */ | (0x01 /* 10-bit conversion */

2.3.3 Change Register Bit Field Configurations

This section covers considerations when updating a register bit field, using various header file defines. The RXMODE bit field will be used as an example, where an update is made compared to the initialization presented below.

USART0.CTRLB = USART_RXEN_bm /* Receiver Enable */ | USART_TXEN_bm /* Transmitter Enable */ | USART_RXMODE_LINAUTO_gc; /* LIN Constrained Auto-Baud mode */

The following subsections will provide code examples to change the Receiver Mode (RXMODE) configuration to Generic Auto-Baud (GENAUTO) mode. The available configurations for this bit field, as they are in the device data sheet, are presented in the table below.

The configuration settings for all supported devices are presented at Configuration Settings Reference → 8-bit AVR MCUs, as shown in Figure 2-17 .

The configuration pragmas can be used as presented below.

#pragma config =

The following example shows how to disable the Watchdog Timer and the CRC and to configure the start-up time to be 8 ms, using configuration pragmas.

/* Disable Watchdog Timer */ #pragma config PERIOD = PERIOD_OFF /* Disable CRC and set the Reset Pin Configuration to GPIO mode */ #pragma config CRCSRC = CRCSRC_NOCRC, RSTPINCFG = RSTPINCFG_GPIO /* Start-up Time select: 8 ms */ #pragma config SUT = SUT_8MS

2.3.6.2 Configuring Fuses Using AVR ® LibC

To configure the fuses using Atmel Studio, the fuse.h header file must be included, as presented below.

#include  /* Required header file */

The following example shows how to disable the Watchdog Timer using the Watchdog Configuration (WDTCFG) fuse register, disable the CRC and set the Reset Pin Configuration to GPIO mode using the System Configuration 0 (SYSCFG0) register, and configure the start-up time to 8 ms using the System Configuration 1 (SYSCFG1) register.

FUSES = < /* Disable Watchdog Timer */ .WDTCFG = PERIOD_OFF_gc, /* Disable CRC and set the Reset Pin Configuration to GPIO mode */ .SYSCFG0 = CRCSRC_NOCRC_gc | RSTPINCFG_GPIO_gc, /* Start-up Time select: 8 ms */ .SYSCFG1 = SUT_8MS_gc >;

CAUTION: If not initialized by the user, the fuses will be initialized with default values (‘ 0 ’) when using AVR LibC. If there are fuses not initialized which must be different from ‘ 0 ’, the device may not work as expected.

2.3.7 Function Calls Using Module Pointers

When writing drivers for module types that have multiple instances, the fact that all instances have the same register memory map can be utilized to make the driver reusable for all instances of the module type. If the driver takes a pointer argument pointing to the relevant module instance, the driver can be used for all modules of this type. This represents a great advantage when considering portability. Moreover, the written code may be portable between devices of the same family. Details on the compatibility between devices from the same family are provided in the data sheet’s series Overview section, and some differences are shown in Figure 2-18 .

2.4.4 PORT Example

This subsection contains an example on how to configure a PORT to turn on an LED when pressing a user button. To identify which pins of the microcontroller are routed to the user LED and to the user button, the used board schematic is necessary. For the AVR128DA48 Curiosity Nano board used for this example, the LED is connected to the sixth pin of PORTC, PC6, and the button is connected to the seventh pin of PORTC, PC7. To make sure this is the correct configuration of the pins, the user must check the board schematic.

The code below shows how to write code for turning on and off an LED, using bit position macros.

Turn LED On when Button Pressed Using Bit Positions

/* Direction configuration of the pins */ /* Read-Modify-Write: Software */ PORTC.DIR |= (1 /* Output for the user LED */ PORTC.DIR &= ~(1 /* Input for the user button */ PORTC.PIN7CTRL |= (1 /* The pull-up configuration */ while (1) < if(PORTC.IN & (1 /* Check the button state */ < /* The button is released */ PORTC.OUT |= (1 /* Turn off the LED */ > else < /* The button is pressed */ PORTC.OUT &= ~(1 /* Turn on the LED */ > >
Note: This is the coding style used by Atmel START for peripheral configuration.

The code below provides the same functionality but using the bit masks.

Turn LED On when Button Pressed Using Bit Masks

/* Direction configuration of the pins */ /* Read-Modify-Write: Software */ PORTC.DIR |= PIN6_bm; /* Output for the user LED */ PORTC.DIR &= ~ PIN7_bm; /* Input for the user button */ PORTC.PIN7CTRL |= PORT_PULLUPEN_bm; /* The pull-up configuration */ while (1) < if(PORTC.IN & PIN7_bm) /* Check the button state */ < * The button is released */ PORTC.OUT |= PIN6_bm; /* Turn off the LED */ > else < /* The button is pressed */ PORTC.OUT &= ~(PIN6_bm); /* Turn on the LED */ > >
Note: This is the coding style used by MCC when generating code for AVR.

Also, the SET and CLR registers can be used to set and clear the pin value without Read-Modify-Write operations, as shown in the code below. This allows performing the setting and the clearing of the desired value using an atomic instruction, instead of reading the register value first. The main advantage is that this action cannot be interrupted. Only one instruction is executed instead of three (Read-Modify-Write).

Turn LED On when Button Pressed Using SET and CLR Registers

PORTC.DIRSET = PIN6_bm; /* Output for the user LED */ PORTC.DIRCLR = PIN7_bm; /* Input for the user button */ PORTC.PIN7CTRL |= PORT_PULLUPEN_bm; /* The pull-up configuration */ while (1) < /* Read-Modify-Write: Hardware */ if(PORTC.IN & PIN7_bm) /* Check the button state */ < /* The button is released */ PORTC.OUTSET = PIN6_bm; /* Turn off the LED */ > else < /* The button is pressed */ PORTC.OUTCLR = PIN6_bm; /* Turn on the LED */ > >

Another way to configure the direction and set/clear the value of the output pin is by using virtual ports, as presented in the code below.

Turn LED On when Button Pressed Using Virtual Ports

/* Direction configuration of the pins */ /* Read-Modify-Write: VPORT registers */ VPORTC.DIR |= PIN6_bm; /* Output for the user LED */ VPORTC.DIR &= ~PIN7_bm; /* Input for the user button */ PORTC.PIN7CTRL |= PORT_PULLUPEN_bm; /* The pull-up configuration */ while (1) < if(VPORTC.IN & PIN7_bm) /* Check the button state */ < /* The button is released */ VPORTC.OUT |= PIN6_bm; /* Turn off the LED */ > else < /* The button is pressed */ VPORTC.OUT &= ~PIN6_bm; /* Turn on the LED */ > >

2.4.5 ADC Example

This section presents a simple configuration example for the ADC0 module on an AVR128DA48 device from the AVR DA series of devices. The information needed to program this controller can be found in the device data sheet, as mentioned in Section 1: Data Sheet Structure and Naming Conventions.

To fully configure the ADC, the user can follow the recommended initialization steps that can be found in the Functional Description subsection from the ADC – Analog-to-Digital Converter section of the data sheet.

After the ADC prescaler is configured, the ADC module is enabled, and a conversion is started. In the code below, the configurations are made using the bit position macros.

Configure ADC Using Bit Positions

/* ADC register configuration using bit position macros */ ADC0.CTRLC |= (1 1 /* Configuring the prescaler bits. Configuration: DIV8 */ ADC0.CTRLA |= (1/* Enabling the ADC */ ADC0.COMMAND |= (1/* Starting a conversion */ 

Alternatively, the ADC can be configured using the bit masks, as in the code below.

Configure ADC Using Bit Masks

/* ADC register configuration using bit masks macros */ ADC0.CTRLC |= ADC_PRESC0_bm | ADC_PRESC1_bm; /* Configuring the prescaler bits. Configuration: DIV8 */ ADC0.CTRLA |= ADC_ENABLE_bm; /* Enabling the ADC */ ADC0.COMMAND = ADC_STCONV_bm; /* Starting a conversion */ 

To change the prescaler configuration, a group configuration mask can be used. The original configuration must be cleared first, as shown in the code below.

Change the ADC Prescaler Configuration Using a Group Configuration Mask

/* Changing an ADC prescaler configuration, ensuring to clear the original configuration */ ADC0.CTRLC = (ADC0.CTRLC & ~ADC_PRESC_gm) | ADC_PRESC_DIV4_gc;

2.5 Further Steps

This section has the purpose to direct the user to the IDE installation guides and instructions, and the available application notes.

2.5.1 Application Notes and Technical Briefs Description

This series of technical briefs utilizes the same principles recommended in this document, covering all peripherals for the megaAVR ® 0-series family of AVR microcontrollers. Each technical brief starts with a summary of the use cases covered. Each use case is then developed, showing how to use the data sheet to configure the peripheral in the required configuration.

For example, the Getting Started with ADC technical brief provides an overview of the peripheral and a description on how to use the peripheral in several use cases in different operation modes: single conversion, free-running, sample accumulator, etc.

2.5.2 Relevant Videos for Bare Metal AVR ® Development

The following videos are particularly relevant for this technical brief.

The following is a 28-part video series, which builds up functionality using the data sheet and device header files as primary programming references.

2.5.3 MPLAB ® XC8 Compiler

The XC compilers are comprehensive solutions for the software development of any suitable project. The MPLAB XC8 compiler supports all 8-bit PIC ® and AVR microcontrollers (2) , and it is available as free, unrestricted-use download. A pro license is also available. By using a pro license, the user will obtain a more efficient code. Additionally, a certified XC8 Functional Safety license is now available.

When combined with the MPLAB X IDE, the front-end provides editing errors and breakpoints, that match corresponding lines in the source code, and single-stepping through C source code to inspect variables and structures at critical points.

More information on Microchip’s MPLAB X IDE can be found on the user guides page, by searching for “MPLAB X IDE User’s Guide”.

2.5.4 IDE (MPLAB ® X, Atmel Studio, IAR) – Getting Started

To program AVR microcontrollers, either the MPLAB X, Atmel Studio, or IAR Embedded Workbench IDEs can be used.

MPLAB X Integrated Development Environment (IDE) is an expandable and highly configurable software program. It incorporates powerful tools to help the user discover, configure, develop, debug and qualify embedded designs for most of Microchip’s microcontrollers and digital signal controllers. MPLAB X IDE offers support for AVR MCUs.

All the information needed to be familiar with Atmel Studio, including hands-on and video tutorials, is provided in this user’s guide: Getting Started with Atmel Studio 7. Additionally, information on all the project configurations needed to develop a project (fuse programming, oscillator calibration, interface setting, etc) can be found in the Atmel Studio 7 user’s guide. To find and develop more examples for any board, configure drivers, find example projects, and easily configure system clock settings, the online code configuration tool Atmel START can be used. For more details, refer to the Atmel START User Guide.

The IAR Embedded Workbench for AVR with all the features is described in the AN3419 - Getting Started with IAR Embedded Workbench for AVR application note.

The microcontroller families tinyAVR ® 0/1-series, megaAVR 0-series, and AVR DA have also a series of Getting Started with dedicated guides available online, with information on how to create a project and what starter kit to use. Examples of these guides are listed below:

2.6 Conclusion

This document introduces the user to a preferred coding style for programming the AVR microcontrollers. After going through this document, the user knows what type of information the data sheet is providing, and also the macro definitions, the variable declarations, and the data type definitions provided by the header files. The goals are to use an easily maintainable, portable and readable coding style, to get familiar with the naming conventions for the AVR registers and bits, and to get ready for the further steps in developing a project using these microcontrollers.

This document covers information on specific data sheets, information description, naming conventions, how to write C-code for AVR microcontrollers, alternative ways of writing the code, and, finally, the next steps in developing a project.

Using the suggested methods to write C-code is not mandatory, but the advantages presented here can be considered. The larger the project and the more features the device has, the bigger the advantage.

2.7 References

  1. ATmega4808/4809 Data Sheet
  2. MPLAB ® XC Compilers
  3. AVR Libc Library Reference
  4. AVR ® Devices in MPLAB ® XC8
  5. MPLAB ® XC8 C Compiler User’s Guide for AVR ® MCU
  6. Fundamentals of the C Programming Language
  7. Fundamentals of C Programming - Enumerations