porting(3)
πΈ porting β a.k.a. porting(3), from the avr-libc manual β¨
π NAME π
porting β bestie, we're porting from IAR to AVR GCC and it's gonna be fine, I promise π«Ά
π Introduction π
okay so, C was literally designed to be a portable language, that's its whole thing. but there are two main flavours of porting activities you can get into: porting an app to a whole different platform (different OS and/or different processor), and porting to a different compiler. and the compiler one gets so much messier when your application is an embedded system π like, the C language Standard β strangely!! β does not specify a standard for declaring and defining Interrupt Service Routines (ISRs). not one. so every compiler just does its own little thing for defining registers, and some of them use straight-up non-standard language constructs π
this chapter is gonna walk you through some methods and pointers for porting an AVR application built with the IAR compiler over to the GNU toolchain (AVR GCC). note that this may not be an exhaustive list, so don't come for me π
π Registers π
IO header files contain identifiers for all the register names and bit names for a particular processor. IAR has individual header files for each processor, and you must include them whenever registers are being used in the code. like so:
#include <iom169.h>
Note: IAR does not always use the same register names or bit names that are used in the AVR datasheet. yeah. iconic of them π
AVR GCC also has individual IO header files for each processor. BUT β plot twist β the actual processor type gets specified as a command line flag to the compiler. (Using the -mmcu=processor flag.) this is usually done in the Makefile. which means you get to specify only a single header file for any processor type:
#include <avr/io.h>
Note: the forward slash in the <avr/io.h> file name, the one separating the subdirectories, can be used on Windows distributions of the toolchain and is the recommended method of including this file. slay, cross-platform π
the compiler already knows the processor type, so through that one single header file above, it can pull in and include the correct individual IO header file. the advantage?? you only have to specify one generic header file, and you can easily port your application to another processor type without having to change every single file to include the new IO header file. no cap, that's a massive win π₯
the AVR toolchain tries to adhere to the exact names of the registers and names of the bits found in the AVR datasheet. there may be some descrepencies between the register names found in the IAR IO header files and the AVR GCC IO header files. so like. heads up π«Ά
β Interrupt Service Routines (ISRs) β
as mentioned above, the C language Standard, strangely, does not specify a standard way of declaring and defining an ISR. hence, every compiler seems to have their own special way of doing so. main character energy, honestly π
IAR declares an ISR like so:
#pragma vector=TIMER0_OVF_vect
__interrupt void MotorPWMBottom()
{
// code
}
in AVR GCC, you declare an ISR like so:
ISR(PCINT1_vect)
{
//code
}
AVR GCC uses the ISR macro to define an ISR. this macro requries the header file:
#include <avr/interrupt.h>
the names of the various interrupt vectors are found in the individual processor IO header files that you must include with <avr/io.h>.
Note: the names of the interrupt vectors in AVR GCC has been changed to match the names of the vectors in IAR. this significantly helps in porting applications from IAR to AVR GCC. they did that for us π₯Ήπ
π¦ Intrinsic Routines π¦
IAR has a number of intrinsic routine such as
__enable_interrupts() __disable_interrupts() __watchdog_reset()
these intrinsic functions compile to specific AVR opcodes (SEI, CLI, WDR).
there are equivalent macros that are used in AVR GCC, however they are not located in a single include file. they're scattered, it's giving treasure hunt πΊοΈ
AVR GCC has sei() for __enable_interrupts(), and cli() for __disable_interrupts(). both of these macros are located in <avr/interrupt.h>.
AVR GCC has the macro wdt_reset() in place of __watchdog_reset(). however, there is a whole Watchdog Timer API available in AVR GCC that can be found in <avr/wdt.h>. a whole API. obsessed πΆ
π Flash Variables π
the C language was not designed for Harvard architecture processors with separate memory spaces. which means there are various non-standard ways to define a variable whose data resides in the Program Memory (Flash). we're improvising, bestie π
IAR uses a non-standard keyword to declare a variable in Program Memory:
__flash int mydata[] = ....
AVR GCC uses Variable Attributes to achieve the same effect:
int mydata[] __attribute__((progmem))
Note: see the GCC User Manual for more information about Variable Attributes. π
avr-libc provides a convenience macro for the Variable Attribute:
#include <avr/pgmspace.h>
.
.
.
int mydata[] PROGMEM = ....
Note: thePROGMEMmacro expands to the Variable Attribute ofprogmem. this macro requires that you include<avr/pgmspace.h>. this is the canonical method for defining a variable in Program Space. the canonical one, okay, don't fight it π
to read back flash data, use the pgm_read_*() macros defined in <avr/pgmspace.h>. all Program Memory handling macros are defined there. all of them. one-stop shop β¨
there is also a way to create a method to define variables in Program Memory that is common between the two compilers (IAR and AVR GCC). create a header file that has these definitions:
#if defined(__ICCAVR__) // IAR C Compiler
#define FLASH_DECLARE(x) __flash x
#endif
#if defined(__GNUC__) // GNU Compiler
#define FLASH_DECLARE(x) x __attribute__((__progmem__))
#endif
this code snippet checks for the IAR compiler or for the GCC compiler and defines a macro FLASH_DECLARE(x) that will declare a variable in Program Memory using the appropriate method based on the compiler that is being used. then you would used it like so:
FLASH_DECLARE(int mydata[] = ...);
see?? one macro, both compilers, peace and harmony π
π Non-Returning main() π
to declare main() to be a non-returning function in IAR, it is done like this:
__C_task void main(void)
{
// code
}
to do the equivalent in AVR GCC, do this:
void main(void) __attribute__((noreturn));
void main(void)
{
//...
}
Note: see the GCC User Manual for more information on Function Attributes. π
in AVR GCC, a prototype for main() is required so you can declare the function attribute to specify that the main() function is of type 'noreturn'. then, define main() as normal. note that the return type for main() is now void. it left and it's not coming back πͺπ¨
π Locking Registers π
the IAR compiler allows a user to lock general registers from r15 and down by using compiler options and this keyword syntax:
__regvar __no_init volatile unsigned int filteredTimeSinceCommutation @14;
this line locks r14 for use only when explicitly referenced in your code thorugh the var name 'filteredTimeSinceCommutation'. this means that the compiler cannot dispose of it at its own will. it's yours now, you called dibs π
to do this in AVR GCC, do this:
register unsigned char counter asm("r3");
typically, it should be possible to use r2 through r15 that way.
Note: do not reserve r0 or r1 as these are used internally by the compiler for a temporary register and for a zero value. seriously, hands off r0 and r1 π locking registers is not recommended in AVR GCC as it removes this register from the control of the compiler, which may make code generation worse. use at your own risk. π¬
okay bestie that's the whole page π this was porting(3) from the avr-libc manual, Version 2.1.0, dated Sat Jan 29 2022. go forth and port, you've got this, fr fr π«Άβ¨π
π¦ where this page comes from
- fedora 43: avr-libc 2.2.1-3.fc43 Β· BSD-3-Clause
- fedora 44: avr-libc 2.2.1-5.fc44 Β· BSD-3-Clause
- opensuse-leap 16.0: avr-libc 2.1.0-bp160.1.33 Β· BSD-3-Clause
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms