Home

About

Me

Project List

Mail me

Last update: 2012-09-06

libAvrUtils

Abstract

avr-gcc comes with a standard C library plus additional, AVR specific types, definitions and functions. When developing AVR applications one will discover that there are tasks that occur repeatedly. Therefore this library provides further helper routines in addition to what is offered by avr-gcc. Examples are an stack for buffering error messages and a simple unit testing framework.
In theory this library supports many (maybe all) ATmega AVRs. However, it has only been tested with the single UART model ATmega8 and the dual UART models ATmega162 and ATmega1284P.

Including the Framework

Download the source from here. Include errorBuffer.h, twiMaster.h, unitTest.h etc. in your application depending on your needs. Also compile the corresponding .c-file and link against errorBuffer.o, twiMaster.o, unitTest.o etc.

Utilities

Macro Collection (macroCollection.h)

MacroDescriptionExample
LO_NIBBLEReturns low nibble (least significant four bits) of a given byte.uint8_t lowNibble = LO_NIBBLE(0xa6); // 0x6
HI_NIBBLEReturns high nibble (most significant four bits) of a given byte.uint8_t highNibble = HI_NIBBLE(0xa6); // 0xa
LOReturns low byte (least significant byte) of a given 16 bit value.uint8_t lowByte = LO(0xa6ff); // 0xff
HIReturns high byte (most significant byte) of a given 16 bit value.uint8_t highByte = HI(0xa6ff); // 0xa6
Available macros

Format Helper (formatHelper.h/.c)

Functions for compact and quick formatting.

FunctionDescriptionExample
byteToHexString()Returns hexadecimal string representation of given byte.
char* result = "Hex value is 0x**.\n";
byteToHexString( 0x5c, result[15] );
printf( result ); // Hex value is 0x5c.
Available functions

Debug Printer (debugPrinter.h/.c)

When I start a new AVR project I typically include a lot of debug-printing which I later remove. Setting up the UART and adding a printing routine is tedious. Therefore I included this helper routines into my collection. They are not very flexible nor omnipotent. The only thing you can do is printing via the UART with a selectable baud rate at the fixed 8N1 format.

The routines do not utilize interrupt handling, it is all busy waiting.

It should work with all single UART models. Some dual UART models are also supported. Further models can be added to the source code quite easily.

FunctionDescriptionExample
initializeUART()Initialize the UART for busy 8N1 printing.See below.
putCharToUART()Print a single character to the UART.See below.
redirectStdOutToUART()Redirects stdout to UART. Allows usage of printf() for debug-printing.See below.
printToUART()Print a string (zero terminated) of characters to the UART.See below.
Available functions

#define F_CPU 8000000UL #define BAUD 38400 #include <util/setbaud.h> // Calculates UBRR/U2X values according to given F_CPU and BAUD #include "debugPrinter.h" // Also includes <stdio.h>. int main( void ) { // Initialization. initializeUART( UBRR_VALUE, USE_2X ); // Print single character. putCharToUART( '>', NULL ); // Print a string. printToUART( "Hello World!\n" ); // Print using printf() from <stdio.h>. redirectStdOutToUART(); // Only required if you want to use printf(). printf( "Hello Universe!\n" ); while ( 1 ) ; }
Debug printer code example

In-Target Unit Testing (unitTest.h/.c)

Code which is not hardware specific and which does not include AVR specific header files can be compiled and tested in the host environment you develop on. A good example might be a routine that just does calculations, e.g. a PID controller function. Code that depends on the hardware or includes AVR header files which cannot be easily mocked away can be tested in an simulator or - if you dont have one - on the target. This approach is supported by this small testing framework.

The framework does:

The following excerpt shows how the unit testing framework is used. The complete program is included in this collection of helper functions (main.c). For a deeper understanding take a look at the source code.

#include "unitTest.h" #include "errorBuffer.h" BEGIN_TEST_SUIT BEGIN_TEST_CASE( ErrorBuffer ) // Create an error buffer and do not forget to initialize it! ErrorBuffer l_buffer; init( &l_buffer ); // Conduct your tests. ASSERT_L_EQUAL( 0, peek( &l_buffer ) ); ASSERT_L_EQUAL( 0, pop( &l_buffer ) ); ASSERT_FALSE( overflow( &l_buffer ) ); ... END_TEST_CASE BEGIN_TEST_CASE( UnitTestingFramework ) ... END_TEST_CASE ... END_TEST_SUIT

Local and Global Variables

Within each test case you can define local variables/constants which are only accessible by the respective test case. Commonly used variables/constants can be defined right after BEGIN_TEST_SUIT or in between test cases.

Drawbacks

The unit testing framework requires quite a bit of memory: Be advised that the program by default links against the minimal version of vfprintf(). As a consequence we save program memory but float and double values will not be printed. They appear as a "?" instead. This should not be a problem since we use uint8_t a lot in this eight bit environment. But if required you can link against the full version of vfprintf(). This will use more memory. An example for that is included in the Makefile. Details can be looked up in the avr-gcc Library Reference.

Error Message Buffer (errorBuffer.h/.c)

The files errorBuffer.h and errorBuffer.c define a simple stack for buffering error messages. The idea is that - in and AVR environment - multiple sources for errors exist. Interrupt service routines and user program are executed concurrently each of them potentially producing errors. Furthermore errors may appear repeatedly.
Instead of storing the error code in a scalar variable - where it can be overwritten - we store it in a buffer.

The buffer is implemented as a stack. That means that the most current error code is retrieved first and the oldest error code is retrieved last. Newer error messages are discarded when the buffer is full. The idea is to keep the older errors because they are probably the root cause while newer error codes are just follow-up errors.

TopicExample
Include error buffer.
#include "errorBuffer.h"
Create error buffer.
ErrorBuffer errBuf;
init( &errBuf );
Store error code.
push( &errBuf, <some uint8_t error code> );
Check if errors occurred.
if ( 0 != peek( &errBuf ) ) {
    // At least one error has occurred, handle errors.
    ...
}
Query (and remove) error from buffer.
uint8_t currErr = pop( &errBuf );
Check if there were more errors than the buffer can handle.
if ( overflow( &errBuf ) ) {
    // Too many errors, react on that.
    ...
}

Also push() lets you know if there was an overflow:

if ( !push( &errBuf, <some uint8_t error code> ) ) {
    // Too many errors, react on that.
    ...
}
Sample use of error buffer

TWI (I2C) Master Driver (twiMaster.h/.c)

twiMaster.h and twiMaster.c form a TWI master implementation. Synchronous (blocking) and asynchronous (non-blocking/event-driven) communication patterns are supported. The framework can also use the AVR's internal pull-up resistors for the SDA and SCL pins. This can reduce effort because the external resistors can be omitted. In my tests with a single TWI master and TWI slave it worked fine. Even though it is eventually the better approach to follow the recommendations and use external pull-up resistors.

Not all AVR models feature a TWI module, e.g. ATmega162.

The following has to be mentioned: This code was created according to Atmel's application note AVR315. It was furthermore influenced by Arduino libraries twi and wire by Nicholas Zambetti.

Synchronous Use

The following code is an example for how to use this library in synchronous communications style. The example reads data from a Nintendo Nunchuk controller. Library calls are in bold.

... int main( void ) { ... initializeTwiMaster( USE_PULLUPS, F_CPU, TWI_BR_400k ); ... uint8_t HANDSHAKE1[] = {0xf0, 0x55}; twiSend( 0x52, HANDSHAKE1, 2 ); _delay_ms( 1 ); uint8_t HANDSHAKE2[] = {0xfb, 0x00}; twiSend( 0x52, HANDSHAKE2, 2 ); _delay_ms( 1 ); while( true ) { _delay_ms( 250 ); // Just to slow down so that we can follow on screen. uint8_t result[6] = {0,0,0,0,0,0}; twiReceive( 0x52, result, 6 ); char msg[40]; sprintf( msg, "%x, %x, %x, %x, %x, %x\n", result[0], result[1], result[2], result[3], result[4], result[5] ); printToUart(msg); uint8_t ZERO[] = {0x00}; twiSend( 0x52, ZERO, 1 ); } }

Asynchronous Use

The following code is an example for how to use this library in asynchronous communications style. The example again reads data from a Nintendo Nunchuk controller. This allows for easy comparison of both examples. Library calls are in bold.

At the first glance the asynchronous example looks more complicated than the synchronous one. The advantage is that the TWI processing can happen in the background. The main program may in paralel perform other tasks or idle sleep in order to save power. Another advantage is that things happen exactly when TWI events occur.

... void whenSent( void ) { _delay_ms( 1 ); // Delay required! initiateTwiReceive( 0x52, 6 ); } void whenReceived( const uint8_t* p_buffer, const uint8_t p_size ) { char msg[40]; sprintf( msg, "%d: %x, %x, %x, %x, %x, %x\n", p_size, p_buffer[0], p_buffer[1], p_buffer[2], p_buffer[3], p_buffer[4], p_buffer[5] ); printToUart(msg); // This is not required, it is only intended to slow down so that we can follow the // output on screen. _delay_ms( 250 ); uint8_t ZERO[] = {0x00}; initiateTwiSend( 0x52, ZERO, 1 ); } int main( void ) { ... initializeTwiMaster( USE_PULLUPS, F_CPU, TWI_BR_400k ); // We do the handshake with the Nunchuk in synchronous style (it is simpler and occurs // only once. uint8_t HANDSHAKE1[] = {0xf0, 0x55}; twiSend( 0x52, HANDSHAKE1, 2 ); _delay_ms( 1 ); uint8_t HANDSHAKE2[] = {0xfb, 0x00}; twiSend( 0x52, HANDSHAKE2, 2 ); _delay_ms( 1 ); // Now register the event handlers for asynchronous processing. registerTwiSentHandler( whenSent ); registerTwiReceivedHandler( whenReceived ); // We need to initially call this event handler to kick of an infinit/background loop. whenSent(); // In the main program we just sleep. Everything happens in the background (ISR). while( true ) { set_sleep_mode( SLEEP_MODE_IDLE ); sleep_mode(); } }

Prerequisites

Screenshots/Pictures

Testing result output...
Please note how it looks if a test case fails because a condition is not met. Furthermore note the "?" characters. The program is linked against the minimal version of vfprintf() in order to save memory. Also pay attention to the number of failed test cases and failed conditions. A test case fails if only one condition fails. The test case is then immediately aborted.

Test case ErrorBuffer
Test case UnitTestingFramework
Test case FalseFails
        main.c (100): True.
Test case TrueFails
        main.c (104): False.
Test case CpEqualFails
        main.c (108): Expected a but was b.
Test case NotCpEqualFails
        main.c (112): Both were a.
Test case LEqualFails
        main.c (116): Expected 123 but was 256.
Test case NotLEqualFails
        main.c (120): Both were 666.
Test case DEqualFails
        main.c (124): Expected ? but was ?.
Test case NotDEqualFails
        main.c (128): Both were ?.
Test case NullFails
        main.c (133): Not NULL.
Test case NotNullFails
        main.c (138): NULL.

12 test cases
        2 passed
        10 failed
55 conditions
        45 passed
        10 failed
Complete unit testing protocol

Version History

VersionDateChange
1.0.02012-07-19Initial version (error buffer and unit testing framework).
1.1.02012-08-30TWI master added.
1.2.02012-09-06Format helper, debug printer and macro collection added.

Copyright Statement

libAvrUtils Copyright (C) 2012 Holger Zahnleiter

This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. The program and its source code are published under the GNU General Public License (GPL). See http://www.gnu.org/licenses/gpl-3.0.txt for details.

Credits

Creating this library would not have been possible without the achievements of the following persons/organisations:

References/Links

avr-gcc Library Reference avr-gcc Library Reference (stdio).
http://www.mikrocontroller.net/ A great (german) resource of Atmel AVR tutorials and general microcontroller know how.
RoboterNETZ Another great (german) resource of microcontroller know how.
AVR freaks English speakers should visit this website.
CrossPack CrossPack for AVR Development for Mac OS X.
AVR-GCC-Tutorial German avr-gcc tutorial from mikrocontroller.net.
http://www.nongnu.org/avr-libc/ The AVR C library (for use with avr-gcc).
http://www.nongnu.org/avrdude/ AVRDUDE, a tool for flashing AVRs.
ATmega8 product site Atmel's website featuring ATmega8. The most current datasheet can be downloaded from there.
Application Note AVR315 Using the TWI module as I2C master.

Downloads

Source code (libAvrUtils-1.2.0-src.zip) for ATmega8 (avr-gcc).
Pre-compiled test program for ATmega8 (testDebugPrinter.hex)
Pre-compiled test program for ATmega8 (testErrorBuffer.hex)
Pre-compiled test program for ATmega8 (testFormatHelper.hex)
Pre-compiled test program for ATmega8 (testMacroCollection.hex)
Atmel ATmega8 Datasheet
AVR315: Using the TWI module as I2C master

Disclaimer

The information on this web site and the documents downloadable from here have been carefully reviewed and is believed to be reliable, but I do not assume any liability arising out of the application or use of any documents, programs or circuit described herein.
Furthermore I want to declare that I'm not responsible in any way for the content of other web pages, books and other sources I'm refering to.