porting(3avr)

πŸ’– porting β€” Porting From IAR to AVR GCC πŸ’–

okay bestie, gather round β€” this is the porting(3avr) page, and it's literally your survival guide for yanking an AVR app out of IAR and dropping it into the GNU toolchain (AVR GCC). Let's get into it. πŸ¦‹


✨ NAME ✨

porting β€” the whole "how do I move my code from IAR to AVR GCC" thing. That's the vibe. πŸ‘‘


🌸 Introduction 🌸

So C was designed to be a portable language, right? And there are two main flavors of porting activity:

  • porting an app to a different platform (different OS and/or different processor), and
  • porting to a different compiler.

And porting to a different compiler gets so much worse when the app is an embedded system. Like, the C language Standard β€” strangely!! β€” does not specify a standard for declaring and defining Interrupt Service Routines (ISRs). Ma'am that's a choice. Different compilers just do their own thing for defining registers, and some of them use non-standard language constructs. πŸ™ˆ

This chapter describes some methods and pointers on porting an AVR application built with the IAR compiler over to the GNU toolchain (AVR GCC). And btw β€” this may not be an exhaustive list. Just so you know. πŸ’…


πŸŽ€ 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 used in your code. Like, for example:

#include <iom169.h>
Note πŸ“Œ IAR does not always use the same register names or bit names that are used in the AVR datasheet. So the datasheet says one thing, IAR says another, and you're just standing there like 😭.

AVR GCC also has individual IO header files for each processor. BUT β€” plot twist β€” the actual processor type is specified as a command line flag to the compiler. That's the -mmcu=processor flag. (You usually do this in the Makefile, iykyk.) This lets you 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 that separates subdirectories can be used on Windows distributions of the toolchain, and it's the recommended method of including this file. Slash stays, bestie. 🫢

The compiler knows the processor type, and 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 app to another processor type without editing every single file to include the new IO header file. iconic honestly ✨

The AVR toolchain tries to adhere to the exact names of the registers and the names of the bits found in the AVR datasheet. There may be some discrepancies between the register names found in the IAR IO header files and the AVR GCC IO header files. So keep your eyes open. πŸ‘€


πŸ’… Interrupt Service Routines (ISRs) πŸ’…

Like I said up top β€” the C language Standard, strangely, does not specify a standard way of declaring and defining an ISR. So every compiler just invented their own special little way of doing it. Main character behavior, 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. And this macro requires 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 have been changed to match the names of the vectors in IAR. This significantly helps in porting applications from IAR to AVR GCC. We love a compatibility moment. 🫢

⭐ Intrinsic Routines ⭐

IAR has a number of intrinsic routines, such as:

__enable_interrupts() __disable_interrupts() __watchdog_reset()

These intrinsic functions compile to specific AVR opcodes (SEI, CLI, WDR).

There are equivalent macros used in AVR GCC β€” however, they are not located in a single include file. (rude, but okay.)

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's a whole Watchdog Timer API available in AVR GCC that can be found in <avr/wdt.h>. πŸ’–


🧚 Flash Variables 🧚

The C language was not designed for Harvard architecture processors with separate memory spaces. So there are various non-standard ways to define a variable whose data lives in the Program Memory (Flash). No cap, that's just how it is.

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 πŸ“Œ The PROGMEM macro expands to the Variable Attribute of progmem. This macro requires that you include <avr/pgmspace.h>. This is the canonical method for defining a variable in Program Space. Don't skip it. πŸ‘‘

To read back flash data, use the pgm_read_*() macros defined in <avr/pgmspace.h>. All Program Memory handling macros are defined there. πŸ“

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). Make 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 whichever compiler is being used. Then you'd use it like so:

FLASH_DECLARE(int mydata[] = ...);

one macro to rule them all ✨


πŸ¦‹ 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 specifying that the main() function is of type "noreturn". Then you define main() as normal. And heads up β€” the return type for main() is now void. πŸ’€


πŸ”₯ Locking Registers πŸ”₯

The IAR compiler lets a user lock general registers from r15 and down, 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 through the var name "filteredTimeSinceCommutation". That means the compiler can't dispose of it at its own will. It's that kind of commitment. πŸ’œ

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. Locking registers is not recommended in AVR GCC, because it removes this register from the control of the compiler, which may make code generation worse. Use at your own risk. ⚠️ For real, bestie β€” be careful with this one.

and that's the whole page, you absolute legend 🌸 This was porting(3avr) from AVR-LibC β€” Version 2.2.1. Go forth and port, and remember: the vibe changes, the flags never do. slay πŸ’…βœ¨

πŸ“¦ where this page comes from