[TUT][GCC]Adding CRC and App Length to Hex files

Go To Last Post
6 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is a question that seems to pop up every few months and I've written the following about 3 times, so it's time to put it somewhere more findable.

Downloading new code to a processor using a bootloader can often lead to a problem: How can we be sure that the download worked properly? One way to be sure is to check the CRC (Cyclic Redundancy Code) of the application code against a value that was precomputed before the download. If the two match, we can be relatively sure that the application is okay.

While the code to test CRC is readily available (see AVR-libc Manual: CRC Computations), how does one precompute the value? Plus, how does one add this value to the generated hex file? Finally, how does the boot know how far to run the CRC check before comparing to the final value?

In the example below, I will give the method I used in my ATmega2560 application. For most AVR processors, simple variations of this approach should work fine.

First off, I decided that I would store the app length so I did not need to clear the entire flash - on an ATmega2560 it was more hassle than I wanted to deal with. I also decided that I wanted the length to be just after the interrupt vectors so that it would not change with different versions of libraries or compilers.

Since my flash is 256 Kbytes long, I needed a length of at least 18 bits. Since that's not a readily available variable size, I increased the length to a 32-bit number. Creating a single 32-bit spot in flash is done by doing the following code in main.c:

/* flash code length -- this is loaded in flash by the linker and other scripts */
const uint32_t ProgramLength __attribute__ ((section (".length"))) = 0;

This code depends on a new linker section, .length, and we will need to place it very exactly. To do this, we need to modify the linker script associated with your chip architecture. My ATmega2560 is architecture 6 (see the AVR-Libc Manual page on Using the GNU Tools to get the architecture-to-CPU match). You will find the linker scripts under /lib/ldscripts. Most likely you will use the avr*.x script (to learn what each script is used for, check FAQ #28). Copy your linker script to the project directory; I copied avr6.x and renamed it to linker_script.x.

You need to add the .length section to the script like:

. . .
  /* Internal text space or external memory.  */
  .text :
  {
    *(.vectors)
    KEEP(*(.vectors))
    *(.length)
    /* KEEP(*(.length)) - don't need to keep if it isn't defined */
    /* For data that needs to reside in the lower 64k of progmem.  */
. . .

You will need a custom makefile to do the hex manipulation (there are other ways of doing it, but this is the easiest and most automatic). Start with the MFile template from WinAVR.

To use the above modified linker script, add the following to the LDFLAGS in the makefile:

LDFLAGS += -T linker_script.x

Since we are adding the length immediately after the vectors, that will be 0x000E4 on my mega2560.

In order to add the length to the hex file, we use the srec-cat utility that is shipped with WinAVR. The srec utilities are powerful manipulators of the Intel hex format as well as other formats, but it is the hex format that interests us.

Placing the app length into a hex file is done using the -Little_Endian_Maximum option of srec_cat. Note that we must exclude the 4 bytes of the length to allow srec_cat to fill that space with the length. We will also use this opportunity to fill any blank spots in the hex file with 0xFF. The resulting srec_cat command looks like:

srec_cat $(TARGET).org.hex -Intel -exclude 0x0e4 0x0e8 -Little_Endian_Maximum 0x0e4 -fill 0xff -over \( $(TARGET).app.hex -I \) -Output $(TARGET).max.hex -Intel

All of the -Intel options are to instruct srec_cat to keep the format in Intel Hex format.

Next, we want to append the computed CRC. There are several CRC functions available, but to match up with the CRC code available from AVR-libc we use the XMODEM 16-bit CRC:

srec_cat $(TARGET).max.hex -Intel -Little_Endian_CRC16 -max \( $(TARGET).max.hex -Intel \) -Cyclic_Redundancy_Check_16_XMODEM -Output $(TARGET).crc.hex -Intel

Finally, I take the resulting output and tweak it back to the format that came out of the GCC compiler:

srec_cat $(TARGET).crc.hex -Intel -o $(TARGET).hex -Intel --address_length=2 --line_length=44

All of the above can be combined into the .ELF to .HEX rule in the make file:

# Create final output files (.hex, .eep) from ELF output file.
#
# ------------------------------------------------------------------------
# The following conversion from .elf to .hex has been modified to add two
# things:
#    - a computed CRC value at the end of the app, and
#    - a total length (excluding the CRC) at 0x0e4
# This allows us to run a CRC check on the flash at boot-up and gracefully
# fail if necessary.

%.hex: %.elf
	@echo
	@echo $(MSG_FLASH) $@
	$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@
	mv $@ $(TARGET).org.hex
	srec_cat $(TARGET).org.hex -Intel -exclude 0x0e4 0x0e8 -Little_Endian_Maximum 0x0e4 -fill 0xff -over \( $(TARGET).app.hex -I \) -Output $(TARGET).max.hex -Intel 
	srec_cat $(TARGET).max.hex -Intel -Little_Endian_CRC16 -max \( $(TARGET).max.hex -Intel \) -Cyclic_Redundancy_Check_16_XMODEM -Output $(TARGET).crc.hex -Intel 
	srec_cat $(TARGET).crc.hex -Intel -o $(TARGET).hex -Intel --address_length=2 --line_length=44

At this point you should have a hex file in $(TARGET).hex with the app length at location 0x00E4 through 0x00E8 in little endian format and the CRC appended to the end of the app.

So, how to test it? I have the following code in my boot:

#include 
#include 
#include 
#include 
#include "morepgmspace.h"


uint8_t checkflashcrc( void )
{
    uint32_t lastAddr;
    uint32_t addr;
    uint8_t  byte_u8;
    uint16_t crc_u16;

    /* First get the last address programmed -- this should be immediately
     * after the interrupt vectors.
     */
    lastAddr = pgm_read_dword_far( (uint32_t) 0x000000E4 );

    if ( ( 0 == lastAddr ) || ( lastAddr > 0x03dfff ) )
    {
        /* Address is incorrectly set - it's bad! */
        return( 0 );
    }

    /* Compute the CRC */
    crc_u16 = 0;
    for( addr = 0; addr < lastAddr; addr++)
    {
        byte_u8 = pgm_read_byte_far( addr );
        crc_u16 = _crc_xmodem_update( crc_u16, byte_u8 );
    }

    if( pgm_read_word_far( lastAddr ) != crc_u16 )
    {
        return( 0 ); /* Bad CRC */
    }
    else
    {
        return( 1 ); /* Good CRC */
    }
}

You may only need the normal pgm_read_byte from instead of the pgm_read_byte_far from morepgmspace.h if your flash does not extend past the 64 K byte point. The morepgmspace.h file by Carlos Lamas is planned on being in AVR-libc "eventually" (it is not in WinAVR 20090313), so if it is not in your version do a search on morepgmspace.h and you should find it.

In the main boot code:

    PROGDIR &= ~(1<<PROG_NO);	// Make sure PROG_NO is an INPUT.
    PROGPORT |= (1<<PROG_NO); 	// Enable pull-up on PROG_REF line on PROGPORT.
    initbootuart(); // Initialize UART.

    if ( ! checkflashcrc() )
    {
        sendCRCFail();
        badCRC = 1;
    }

    /* Branch to bootloader or application code? */
    if (  badCRC || !(PROGPIN & (1<<PROG_NO)) )
    {
        /* Wake up the monitor!! */
        sendMonitor();
        sendVersion();
        sendCRLF();

The PROGDIR/PROGPORT/PROGPIN/PROG_NO allows the user to "force" the bootloader to wake up instead of branching to the application.

This bootloader assumes that the BOOTRST fuse has been programmed so that the boot always starts first after a reset. Some AVR processors do not have this ability (most notably, the older and smaller ATmega series (ATmega16); the ATtiny series may not have it either) so you may need to figure out how to perform the CRC check on your processor. Your solution may be as simple as doing a jump to the boot in a routine in the .init0 section. According to the AVR-libc Manual section on Memory Sections, a user-defined function __init() will be executed immediately after reset. So, a simple solution might be:

void __init( void )
{
    asm volatile ("jmp 0xF00" );
}

where you would replace 0xF00 with where your boot starts.

That should be it. I did make some hand edits above to remove cruft uninteresting to this particular topic, so there may be compile issues with the above code.

Stu

Engineering seems to boil down to: Cheap. Fast. Good. Choose two. Sometimes choose only one.

Newbie? Be sure to read the thread Newbie? Start here!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There is:

__data_load_end[1]; /* Defined by the standard linker script. Set to address of last byte of .text+.data section */

Not sure it is large enough for the Mega256?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi.
Is this method applies to the atmel studio 6.0 ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes and no. The fact is that AS6 does not come with a copy of srec_cat built for Windows so you will need to get that separately - the easiest way is probably to download WinAVR.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

http://srecord.sourceforge.net/ has the latest version, 1.62.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've been using Stu's method on a handful of projects and it works quite well. Initially Stu lent me a hand in getting my makefile set up.

For some other projects (that didn't use Stu's CRC/length) I started adding lockbit settings, fuse settings, EEPROM data and device signature in my C code so that all info would be contained in the .elf file. Apparently when I try to add any of these items to Stu's CRC/length trick, AVR Studio 6.1 convulses violently and projectile vomits a hex file that is orders of magnitude larger than it should be. I assume this is probably due to these items being outside the normal program space.

Has anyone encountered this? Any suggestions on how to have them peacefully coexist?

-Thanks-