AVR-mUPI  v1.2
A unified application programming interface for megaAVR devices.
Introduction

What is AVR-mUPI?

AVR-mUPI is a software layer that provides a unified interface for application programming on ATMEL megaAVR microcontrollers. AVR-mUPI is presented as a set of header files and static libraries to be included in the application building process.

AVR-mUPI has been developed in C language using the GNU Toolchain for AVR devices provided by ATMEL on a GNU/Linux environment.

What is AVR-mUPI used for?

The main purpose of AVR-mUPI is to virtualize the megaAVR hardware from the user application perspective. This is accomplished by hiding the hardware specific details and variability behind a unified programming interface applicable to all megaAVR devices.

AVR-mUPI handles the following megaAVR hardware issues:

  • Hardware functions implemented in several ways (e.g. ADC triggering, 8/16 bit Timer clock prescalers, access to UCSRC register, etc.).
  • Use of several instances of a hardware function (e.g. Digital Ports, 8/16 bit Timers, USARTs, External Interrupts, etc.).
  • Differences in ISR, register and bit naming (e.g. USART interrupt vectors).
  • Location of a given bit in different registers (e.g. ACME bit on the Analog Comparator, Interrupt flag/enable bits on 8/16 bit Timers, etc.).
  • Differences in pin assignment for special functions (e.g. SPI, TWI, 8/16 bit TimerOutput Compare, etc.).

What are the benefits of using AVR-mUPI?

AVR-mUPI provides the following advantages when developping applications for the megaAVR devices.

  1. A unified interface shared among all megaAVR devices: All the applications developed with AVR-mUPI share the same symbols and functions provided by the AVR-mUPI interfaces. Therefore, there are no differences in the application source code wether it is for one device or another. For instance, the rising edge sense control of external interrupt 0 is configured differently on ATmega8 and on ATmega64:
    // Configure the external interrupt 0 on rising edge...
    // ... on ATmega8,
    MCUCR |= _BV(ISC01) | _BV(ISC00);
    // ... on ATmega64,
    EICRA |= _BV(ISC01) | _BV(ISC00);
    However, when using AVR-mUPI, this action is achieved by calling the same function with the same configuration value for both (all) megaAVR devices:
    // Configure the external interrupt 0 on rising edge with AVR-mUPI.
  2. Increased portability of your applications: The unified application programming interface provided by AVR-mUPI virtualizes the access to the hardware of the megaAVR devices to the user application level. Behind this unified interface, there are dedicated header files and binary libraries that implement each interface specifically for each megaAVR device. At compile time, the appropriate definitions of the interface will be included and its implementation will be automatically linked by using the corresponding device library. Consequently, the source code becomes portable to other megaAVR devices with no need to modify it. The following example shows the implementation of the function MCU_ClearResetFlags() responsible for clearing the reset flags of the megaAVR device. In the definition of this function it is clearly seen the different implementations of the function for each megaAVR device.
    {
    #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega8535__) || \
    defined(mUPI_ATmega8515_162)
    MCUCSR &= ~(_BV(WDRF) | _BV(BORF) | _BV(EXTRF) | _BV(PORF));
    #elif defined(mUPI_ATmega16_32) || defined(mUPI_ATmega64_128)
    MCUCSR &= ~(_BV(JTRF) | _BV(WDRF) | _BV(BORF) | _BV(EXTRF) | _BV(PORF));
    #elif defined(mUPI_ATmegaXX8)
    MCUSR &= ~(_BV(WDRF) | _BV(BORF) | _BV(EXTRF) | _BV(PORF));
    #elif defined(mUPI_ATmegaXX4all) || defined(mUPI_ATmegaXX0) || defined(mUPI_ATmegaXX1)
    MCUSR &= ~(_BV(JTRF) | _BV(WDRF) | _BV(BORF) | _BV(EXTRF) | _BV(PORF));
    #endif
    }
  3. Ability to create libraries of generic source code using built-in peripherals. The megaAVR devices provide multiple instances on some of their built-in peripherals, like 8/16 bit timers, external interrupts, digital ports, usarts, etc. When implementing high level functions based on these type of peripherals (e.g. communication protocols, external device drivers, etc.), the functions shall take into account the differences and subtilities found among the instances of a same type of peripheral (e.g. the name/address of the registers, the location of bits, the name of ISRs, etc). For these peripherals, AVR-mUPI uses identifiers as arguments of the function calls in order to indicate the peripheral instance a function has to be executed for. For instance, the USART interface provides the USART_Identifier type that defines the identifiers for the different instances of the USART peripheral present on the device. This identifier is passed as argument to the USART functions to indicate the desired peripheral instance. In this way, it is possible to design and implement high level functions that are independent from the concrete peripheral instance, and thus, making them generic and suitable to be packed in code libraries. See A generic code library. in the Examples chapter for an example on how to create a generic code library using AVR-mUPI.
  4. Improved source code reading and understanding: The fact of using symbol and function names that convey themselves the meaning of a value or action is always easier to understand than a series of letters and digits. The following example compares the instructions needed to setup timer 1 (Fast PWM 10-bit mode, clock source divided by 1024 and overflow interrupt enabled) using the classic programming style with respect to using AVR-mUPI:
    // Configure timer 1 in Fast PWM 10-bit mode, clock source divided by 1024 and overflow interrupt enabled...
    // ... using the classic programming style,
    TIMSK = (1 << TOIE1);
    TCCR1A = (1 << WGM11) | (1 << WGM10);
    TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10);
    // ... using AVR-mUPI.
    TIMER16_EnableInterrupt(TIMER16_I_1, TIMER16_II_Overflow);
    TIMER16_Start(TIMER16_I_1, TIMER16_CS_DIV1024);
    The equivalent source code using AVR-mUPI is easier to read and understand.

Who can be interested in using AVR-mUPI?

AVR-mUPI is a general purpose library. It is not dedicated to any specific application domain. Thus, anyone can benefit from the features it provides, from professional products to hobbyist or educative projects. Those applications needing portability between different megaAVR devices (code libraries or operating systems, for instance) can take full advantage of AVR-mUPI capabilities.

What are the pros and cons of using AVR-mUPI?

Application model

AVR-mUPI does not impose any particular design or implementation model to user applications (e.g. use of interrupt service routines, buffered input/output, concurrency, etc.). The interface methods implement simple functions concerning hardware access having a certain degree of variability between devices. Some input/output methods using polling mechanism have been included for the sake of complementing the provided interfaces. The developer is free to use them or to use alternative methods instead, of course.

Documentation

This User Manual explains in detail how to install, configure, build and use AVR-mUPI, with some illustrative examples. The definition and implementation of the interfaces provided by AVR-mUPI have been thoroughly documented in the Reference Manual. Each element documented in the Reference Manual, point to the file where the element is defined or declared. All AVR-mUPI code has been integrated in the documentation and is available for browsing in the Source Files Browser. Finally, a set of practical examples can be found in the Examples chapter. They show how to use the AVR-mUPI interfaces to create real working applications.

Hardware platform

AVR-mUPI is not tied to a particular hardware platform. You can use it to develop embedded applications running on any board based on megaAVR microcontrollers.

Performance

The use of a virtualization layer in a user application introduces an additional load in terms of execution time and memory usage. For this reason, the implementation of AVR-mUPI has been carried out trying to minimize execution time and FLASH memory usage. Concerning the use of SRAM memory, AVR-mUPI has been designed in order to have no internal state. That means, AVR-mUPI does not use global nor static variables stored in SRAM.

In any case, the application designer shall always assess if it is worth using the AVR-mUPI interfaces or not depending on the performance and ressource requirements of the system under development.

Portability

AVR-mUPI allows for improved portability of source code among different devices. However, in many cases, the full portability between two devices is only possible when hardware functions are present on both devices. AVR-mUPI does not implement by software any hardware function not present on a given device. This limitation in portability can appear when trying to reuse source code with devices ...

  • with different number of instances of a built-in peripheral (e.g. USARTs, 8 and 16 bit timers, ports, etc.).
  • with or without some core system function or built-in peripheral (e.g. System clock prescaler, power reduction, external memory, ADC, TWI, etc.).
  • with different operating modes for the same hardware function (e.g. ADC trigger mode, TIMER8 operation mode, MCU sleep mode, etc.).

In most of these cases, the lack of portability causes that some symbols (functions, enums, macros, etc.) are not going to be defined, producing errors when compiling the source code. In other few cases, the source code will compile without errors. However, the interface implementing the access to the hardware function will not take any action at run time. In any case, refer to the description of the interfaces in the Reference Manual for specific details concerning portability.

Reliability

Although AVR-mUPI is a home-made project carried out during the author's spare time, it has been developed with one main objective: to be a reliable product. Experience shows that an unreliable system ends up to be useless or, in the worst case, it can become even harmful. Therefore, a big effort has been done in order to verify it thoroughly.

AVR-mUPI has been verified using a test management system developed by the same author of this library. The verification process has consisted in the automatic execution of a collection of more than a hundred unit tests covering all the interfaces. A few unit tests have been executed on the host computer (static analysis of generated code) and the rest on dedicated hardware platforms. The tests executed on the host computer have been applied to all the mcu devices supported in AVR-mUPI. The tests executed on hardware platforms have been applied to a representative subset of the supported mcu devices:

  • ATmega8A
  • ATmega16A
  • ATmega32A
  • ATmega8535
  • ATmega8515
  • ATmega162
  • ATmega64A
  • ATmega128A
  • ATmega165P
  • ATmega168A
  • ATmega169P
  • ATmega328P
  • ATmega329PA
  • ATmega644A
  • ATmega645A
  • ATmega645P
  • ATmega649A
  • ATmega1284
  • ATmega1284P
  • ATmega1281