Nut/ATmega256x

From Nutwiki
Jump to: navigation, search

ATmega256x Development Rules

I decided to put some explanation here, because porting our application to 256x AVR variant was really painful. I tried to get some info in avr-gcc list, but they are probably using Free RTOS and no bootloader with a requirement to call functions in upper half with eicall instruction.

avr-gcc Limitation

The avr-gcc compiler generates eicall instructions without seeding the eind register. This is because avr-gcc still works with 16bit pointers. Eicall is generated e.g. for indirect calls through arrays or device structures. So in fact eicall instruction with current avr-gcc cannot call a function in upper half flash. With this in mind, we found all functions, that were addressed by eicall calls and we placed them in "lower" sections (in source, with attribute). These sections are then linked first to fit in lower half. Little modification of linker script is required.

Newer avr-gcc (do not forget to switch on various optimizations to stay with a small code image) is using a trampolines area. eicall target addresses point to a place in lower half, where far jmps reside. This has an advantage, that it works for (and also from) both flash halves. Disadvantage of this is an additional jmp instruction that has to be executed. Placing modules into a lower section manually is a tedious job, but produces fastest code.

Nut/OS Conflicts

All C constructs, where a function pointer is dereferenced, generates eicall. Targeting functions has to be placed in lower sections as the compiler/linker does not generate a real far call with setting eind register. In our application, where TCP, UART, NIC RTL and TWI interfaces are used, we forced following functions in lower sections:

_sputb                    AvrUsartGetFlowControl      AvrUsartSetSpeed        NutEtherOutput         UsartClose
AvrInterrupt5Ctl          AvrUsartGetParity           AvrUsartSetStatus       NutEventTimeout        UsartInit
AvrTimer2OvfIrqCtl        AvrUsartGetSpeed            AvrUsartSetStopBits     NutIdle                UsartIOCtl
AvrTwiIrqCtl              AvrUsartGetStatus           AvrUsartTxComplete      NutTcpDeviceIOCtl      UsartOpen
AvrUart0RxIrqCtl          AvrUsartGetStopBits         AvrUsartTxEmpty         NutTcpDeviceRead       UsartRead
AvrUart0TxDataIrqCtl      AvrUsartInit                AvrUsartTxStart         NutTcpDeviceWrite      UsartSize
AvrUart1RxIrqCtl          AvrUsartRxComplete          NicInit                 NutTcpDeviceWrite_P    UsartWrite
AvrUart1TxDataIrqCtl      AvrUsartRxStart             NicInterrupt            NutTcpSm               UsartWrite_P
AvrUart1TxIrqCtl          AvrUsartSetClockMode        NicOutput               NutThreadEntry
AvrUsartDeinit            AvrUsartSetDataBits         NicRx                   NutThreadWake
AvrUsartGetClockMode      AvrUsartSetFlowControl      NutDhcpClient           NutTimerIntr
AvrUsartGetDataBits       AvrUsartSetParity           NutEtherInput           TwInterrupt


Bootloader Specials

If you are using a bootloader which has a code, that generates eicall, you have to seed eind to 1. Example of such a code:

fn[sect_id](address, buf, len);		//fn is an array of fn pointers

If you are using this bootloader from an application - purpose can be to share bootloader mechanism and to feed it with data, received e.g. via http. Then bootloader is called iteratively as packets are coming in from a TCP connection.

As I explained above, in bootloader, we need to have eind set to 1. The only solution, we found, is to disable interrupts during a time, when eind == 1. This is because, when an interrupt occurs, it cannot use eicall to reach lower half of flash. For which interrupts this is true ? For all. This is because eicall is generated by CallHandler code (see below for details).


Using a fn pointer in CallHandler generates eicall.

typedef struct {
   u_long ir_count;
   void *ir_arg;
   void (*ir_handler) (void *);
} IRQ_HANDLER;
#define NUTSIGNAL(signame,handler)	\
SIGNAL(signame)		\
{ CallHandler (&handler);  }
IRQ_HANDLER sig_INTERRUPT5;
...
NUTSIGNAL(SIG_INTERRUPT5, sig_INTERRUPT5)
...
void CallHandler(IRQ_HANDLER * irh)
{
   irh->ir_count++;
   if (irh->ir_handler)
       (irh->ir_handler) (irh->ir_arg);		//-> eicall
}