Efficient C Coding For AVR
Efficient C Coding For AVR
1
This generates the following assembly code: Support for 16/32-bit Variables
The AVR instruction set includes special instructions for
LD R16,-Z; Pre-decrement Z pointer and load data
handling 16-bit numbers. This includes Add/Subtract
ST X+,R16; Store data and post increment
Immediate Values to Word (ADIW, SBIW). Arithmetic oper-
The four pointer addressing modes and examples are ations and comparison of 16-bit numbers are completed
shown below. All pointer operations are single-word with two instructions in two clock cycles. 32-bit arithmetic
instructions that execute in two clock cycles. operations and comparison are ready in four instructions
1. Indirect addressing: For addressing of arrays and and four cycles. This is more efficient than most 16-bit
pointer variables: processors!
*pointer = 0x00;
2. Indirect addressing with displacement: Allows C Code for AVR
accesses to all elements in a structure by pointing
to the first element and add displacement without
having to change the pointer value. Also used for Initializing the Stack Pointer
accessing variables on the software stack and array After power up or RESET the stack pointer needs to be set
accesses. up before any function is called. The linker command file
3. Indirect addressing with post-increment: For effi- determines the placement and size of the stack pointer.
cient addressing of arrays and pointer variables with The configuration of memory sizes and stack pointer setup
increment after access: is explained in application note AVR032 “Modifying linker
*pointer++ = 0xFF; command files”
4. Indirect addressing with pre-decrement: For effi-
cient addressing of arrays and pointer variables with Accessing I/O Memory Locations
decrement before access: The AVR I/O memory is easily accessed in C. All registers
*--pointer = 0xFF in the I/O memory are declared in a header file usually
The pointers are also used to access the flash program named “ioxxxx.h”, where xxxx is the AVR part number. The
memory. In addition to indirect addressing with pointers, code below shows examples of accessing I/O location. The
the data memory can also be accessed by direct address- assembly code generated for each line is shown below
ing. This gives access to the entire data memory in a two- each C code line.
word instruction.
#include <io8515.h> /* Include header file with symbolic names */
2 AVR035
AVR035
//?0008:
}
/* Set and clear bits in I/O registers can also be declared as macros */
/* Example of usage*/
if(CHECKBIT(PORTD,PIND1)) /* Test if PIN 1 is set*/
{
CLEARBIT(PORTD,PIND1); /* Clear PIN 1 on PORTD*/
}
if(!(CHECKBIT(PORTD,PIND1))) /* Test if PIN 1 is cleared*/
{
SETBIT(PORTD,PIND1); /* Set PIN 1 on PORTD*/
}
3
Accessing Memory Mapped I/O
Some AVR devices include an external data memory inter- I/O. The following examples show how to declare, write and
face. This interface can be used to access external RAM, read memory mapped I/O:
EEPROM, or it can be used to access memory mapped
#include <io8515.h>
4 AVR035
AVR035
5
Example
The feature will be illustrated through an example. In this
example, assume the following setup of the EEPROM:
1. A character array (100 bytes)
2. An integer (2 bytes)
3. Two unsigned characters (2 bytes each)
typedef struct
{
char cArray[100]; /* The character array */
int iNumber; /* The integer */
unsigned char uMinorVersion; /* The first unsigned character */
unsigned char uMajorVersion; /* The second unsigned character */
} TypeEEPROMStruct; /* Just a type name */
The #define directive contains a macro to be used in the C the EEPROM data contents, this pointer, needs to be
program to get the addresses of the structure variables. It changed (this will also require a change in the EEPROM
contains a constant pointer (0x0000). In order to displace linker file, see below).
6 AVR035
AVR035
7
if(i!=0x100) /* Check towards initialization */
error(); /* If not as expected -> error */
EEAR = EEPROMADR(uMinorVersion); /* Set up address to 4th element */
EECR |=1; /* Start EEPROM Read */
if(EEDR != 0x10) /* Check towards initialization */
error(); /* If not as expected -> error */
EEAR = EEPROMADR(uMajorVersion); /* Set up address to 3rd element */
EECR |=1; /* Start EEPROM Read */
if(EEDR != 0xFF) /* Check towards initialization */
error(); /* If not as expected -> error */
for (;;)
; /* Do nothing (success) */
}
The program can be compiled and executed in AVR Studio. into the error() routine. The EEPROM is loaded with a hex
The “eeprom.hex” file must be loaded into the EEPROM file by using the File-> Up/Download memories function
memory before the program is executed or it will go right after the program has been loaded.
Data Types
As the AVR is an 8-bit microcontroller, use of 16 and 32-bit sary. The following example shows the code size for a loop
variables should be limited to where it is absolutely neces- counter for an 8-bit and 16-bit local variable:
8-bit Counter
unsigned char count8 = 5; /* Declare a varible, assign a value */
// LDI R16,5 ;Init variable
do /* Start a loop */
{
}while(--count8); /* Decrement loop counter and check for zero */
// ?0004:DEC R16 ; Decrement
// BRNE ?0004 ; Branch if not equal
16-bit Counter
unsigned int count16 = 6; /* Declare a variable, assign a value */
// LDI R24,LOW(6) ;Init variable, low byte
// LDI R25,0 ;Init variable, high byte
do /* Start a loop */
{
}while(--count16); /* Decrement loop counter and check for zero */
// ?0004:SBIW R24,LWRD(1) ; Subtract 16-bit value
// BRNE ?0004 ; Branch if not equal
8 AVR035
AVR035
}
Note that the LDS and STS (Load and Store direct from/to give more efficient code than global variables if the variable
SRAM) are used to access the variables in SRAM. These is accessed more than once inside the function.
are two-word instructions that execute in two cycles. To limit the use of global variables, functions can be called
with parameters and return a value which are commonly
Table 2. Code Size and Execution Time for Variables
used in C. Up to two parameters of simple data types (char,
Variable Code Size(bytes) Execution int, float, double) are passed between functions in the reg-
Time(cycles) isters R16 - R23. More than two parameters and complex
Global 10 5 data types (arrays, structs) are either placed on the soft-
ware stack or passed between functions as pointers to
Local 2 1 SRAM locations.
When global variables are required they should be col-
A local static variable is loaded into a working register at lected in structures whenever appropriate. This makes it
the start of the function and stored back to its SRAM loca- possible for the C compiler to address them indirectly. The
tion at the end of the function. Static variables will therefore following example shows the code generation for global
variable versus global structures.
typedef struct
{
char sec;
}t;
t global /* Declare a global structure*/
char min;
9
void C_task main(void)
{
t *time = &global;
// LDI R30,LOW(global) ; Init Z pointer
// LDI R31,(global >> 8) ; Init Z high byte
if (++time->sec == 60)
{
// LDD R16,Z+2 ; Load with displacement
// INC R16; Increment
// STD Z+2,R16 ; Store with displacement
// CPI R16,LOW(60) ; Compare
// BRNE ?0005 ; Branch if not equal
}
if ( ++min == 60)
{
// LDS R16,LWRD(min) ; Load direct from SRAM
// INC R16 ; Increment
// STS LWRD(min),R16 ; Store direct to SRAM
// CPI R16,LOW(60) ; Compare
// BRNE ?0005 ; Branch if not equal
}
}
When accessing the global variables as structures the will be the same, but if the structure consists of 2 bytes or
compiler is using the Z-pointer and the LDD and STD more it will be more efficient to access the global variables
(Load/store with displacement) instructions to access the in a structure.
data. When the global variables are accessed without Unused locations in the I/O memory can be utilized for stor-
structures the compiler use LDS and STS (Load/store ing global variables when certain peripherals are not used.
direct to SRAM). The difference in code size and will be: As example, If the UART is not used the UART Baud Rate
Register (UBRR) is available, and if the EEPROM is not
Table 3. Code Size for Global Variables
used both the EEPROM data register (EEDR) and the
Variable Code Size(bytes) EEPROM Address Register (EEAR) will be available to
store global variables.
Structure 10
The I/O memory is accessed very efficiently, and locations
Non-structure 14
below 0x1F in the I/O memory are especially suited since
they are bit-accessible.
This does not include initialization of the Z-pointer (4 bytes)
for the global structure. To access one byte the code size
/* Define bit macros, note that they are similar to the I/O macros*/
10 AVR035
AVR035
11
Accessing Flash Memory
A common way to define a constant is:
const char max = 127;
This constant is copied from flash memory to SRAM at To save SRAM space the constant can be saved in flash
startup and remains in the SRAM for the rest of the pro- and loaded when it is needed:
gram execution. This is considered to be waste of SRAM.
flash char max = 127;
flash char string[] = "This string is stored in flash";
void main(void)
{
char flash *flashpointer; ; Declare flash pointer
Control Flow
Loops
Eternal loops are most efficiently constructed using for( ; ;) { }:
for( ; ;)
{
/* This is an eternal loop*/
}
// ?0001:RJMP ?0001 ; Jump to label
do{ }while(expression) loops generally generates more effi- following example shows the code generated for a do{ }
cient code than while{ } and for{expr1; expr2; expr3). The while loop:
char counter = 100; /* Declare loop counter variable*/
// LDI R16,100 ; Init variable
do
{
} while(--counter); /* Decrement counter and test for zero*/
?0004:DEC R16 ; Decrement
// BRNE ?0004 ; Branch if not equal
Pre-decrement variables as loop counter gives usually the more efficient because branches are depending on the
most efficient code. Pre-decrement and post-increment is flags after decrement.
12 AVR035
AVR035
read_and_convert:
// IN R16,LOW(22) ; Read I/O memory
// SUBI R16,LOW(184) ; Add 48 to value
// RET ; Return
The code with macro assemble into this code:
main:
// IN R16,LOW(22) ; Read I/O memory
// SUBI R16,LOW(184) ; Add 48 to value
// OUT LOW(12),R16 ; Write I/O memory
Table 4. Code Size and Execution Time for Macros and Functions.
Variable Code Size(bytes) Execution Time(cycles)
Function 10 10
Macro 6 3
13
Eighteen Hints to Reduce Code Size on a module by module basis to investigate what
gives the best result;
1. Compile with full size optimization;
18. Optimize C_startup to not initialize unused seg-
2. Use local variables whenever possible; ments(i.e. IDATA0 or IDATA1 if all variables are tiny
3. Use the smallest applicable data type. Use or small);
unsigned if applicable;
4. If a non-local variable is only referenced within one
function, it should be declared static;
Five Hints to Reduce RAM
5. Collect non-local data in structures whenever natu- Requirements
ral. This increases the possibility of indirect 1. All constants and literals should be placed in flash
addressing without pointer reload; by using the flash keyword;
6. Use pointers with offset or declare structures to 2. Avoid using global variables if the variables are local
access memory mapped I/O; in nature. This also saves code space. Local vari-
7. Use for(;;) { } for eternal loops; ables are allocated from the stack dynamically and
8. Use do { } while(expression) if applicable; are removed when the function goes out of scope;
9. Use descending loop counters and pre-decrement if 3. If using large functions with variables with a limited
applicable; lifetime within the function, the use of subscopes
can be beneficial;
10. Access I/O memory directly (i.e. do not use
pointers); 4. Get good estimates of the sizes of the software
stack and return stack (linker file);
11. If the _EEPUT/_EEGET macros are being used,
replace the EECR = ; with EECR |= ; 5. Do not waste space for the IDATA0 and UDATA0
segments unless you are using tiny variables (linker
12. Use bit masks on unsigned chars or unsigned ints file);
instead of bitfields;
13. Declare main as C_task if not called from anywhere
in the program; Checklist for Debugging Programs
14. Use macros instead of functions for tasks that gen- 1. Ensure that the CSTACK segment is sufficiently
erates less than 2-3 lines assembly code; large;
15. Reduce the size of the interrupt vector segment 2. Ensure that the RSTACK segment is sufficiently
(INTVEC) to what is actually needed by the applica- large;
tion. Alternatively, concatenate all the CODE 3. Ensure that the external memory interface is
segments into one declaration and it will be done enabled if it should be enabled and disabled if it
automatically; should be disabled;
16. Code reuse is intra-modular. Collect several func- 4. If a regular function and an interrupt routine are
tions in one module (i.e. in one file) to increase code communicating through a global variable, make
reuse factor; sure this variable is declared volatile to ensure that
17. In some cases, full speed optimization results in it is reread from RAM each time it is checked;
lower code size than full size optimization. Compile
14 AVR035
Atmel Headquarters Atmel Operations
Corporate Headquarters Atmel Colorado Springs
2325 Orchard Parkway 1150 E. Cheyenne Mtn. Blvd.
San Jose, CA 95131 Colorado Springs, CO 80906
TEL (408) 441-0311 TEL (719) 576-3300
FAX (408) 487-2600 FAX (719) 540-1759
Europe Atmel Rousset
Atmel U.K., Ltd. Zone Industrielle
Coliseum Business Centre 13106 Rousset Cedex
Riverside Way France
Camberley, Surrey GU15 3YL TEL (33) 4-4253-6000
England FAX (33) 4-4253-6001
TEL (44) 1276-686-677
FAX (44) 1276-686-697
Asia
Atmel Asia, Ltd.
Room 1219
Chinachem Golden Plaza
77 Mody Road Tsimhatsui
East Kowloon
Hong Kong
TEL (852) 2721-9778
FAX (852) 2722-1369
Japan
Atmel Japan K.K.
9F, Tonetsu Shinkawa Bldg.
1-24-8 Shinkawa
Chuo-ku, Tokyo 104-0033
Japan
TEL (81) 3-3523-3551
FAX (81) 3-3523-7581
Fax-on-Demand
North America:
1-(800) 292-8635
International:
1-(408) 441-0732
e-mail
[email protected]
Web Site
http://www.atmel.com
BBS
1-(408) 436-4309