Electronic – AVR- sending application program to bootloader

atmegaavrbootloader

Now its time to send the application code to the bootloader, that's been programmed into an ATmega32A controller. It has to be sent in hex format, but I have no idea of "How to send it?"

The hex file compiled for an application code from atmel studio is as follows:

    :100000000C942A000C943F000C943F000C943F0089
    :100010000C943F000C943F000C943F000C943F0064
    :100020000C943F000C943F000C943F000C943F0054
    :100030000C943F000C943F000C943F000C943F0044
    :100040000C943F000C943F000C943F000C943F0034
    :100050000C943F0011241FBECFE5D8E0DEBFCDBF1A
    :1000600010E0A0E6B0E0E0E2F2E002C005900D9200
    :10007000A23EB107D9F70E94BD000C940E010C946A
    :100080000000D09A899A88988AB188618AB980B527
    :100090008F7B80BD529880B5866080BD80B5877F9C
    :1000A00080BD80B58F7C80BD89E189B910BC089581
    :1000B0005D9BFECF8CB908950F931F93CF93DF9371
    :1000C000FC0101900020E9F73197E81BF90B51F092
    :1000D000EC018C010E0F1F1F89910E945800C01760
    :1000E000D107D1F7DF91CF911F910F9108955F9BB9
    :1000F000FECF8CB190E008950E9477008335E1F740
    :100100000E9477008935C1F70E9477008335A1F7F7
    :100110000E947700833581F70E947700843561F76C
    :100120000E947700813441F70E947700823521F7E1
    :100130000E947700843501F70895CF93DF93EC0197
    :100140000E945C000E9477008F3421F00E947700AB
    :100150008F34E1F70E9477008B3461F02FE78AE15A
    :1001600096E0215080409040E1F700C00000CE01B1
    :100170000E949D00DF91CF9108950E9441002FE7DA
    :100180008AE196E0215080409040E1F700C00000F5
    :100190000E947C002FE382E49FE021508040904049
    :1001A000E1F700C0000080E690E00E949D008DE62F
    :1001B00090E00E949D002FE78AE196E02150804068
    :1001C0009040E1F700C0000087E790E00E945C00EB
    :1001D0002FE78AE196E0215080409040E1F700C08F
    :1001E000000084E990E00E945C002FE78AE196E03D
    :1001F000215080409040E1F700C000008EEB90E07D
    :100200000E945C002FE78AE196E0215080409040F8
    :10021000E1F700C0000080E090E00895F894FFCF7F
    :1002200041545E4E5754494D453F0D0A0041542BF1
    :1002300043474D490D0A0041545E4950494E4954C7
    :100240003D2261697274656C677072732E636F6DA5
    :10025000220D0A0041545E49504F50454E3D312C0D
    :1002600022544350222C223132322E3136352E3256
    :1002700033302E3137222C373836390D0A004154AD
    :100280005E495053454E443D312C22646576696386
    :10029000652D69642C6770735F64617461220D0A57
    :0202A00000005C
    :00000001FF

As per the standard the first line has data 0C942A000C943F000C943F000C943F00 and checksum 89. Do I have to just send the 16 bytes of data followed by checksum? In some programs, some lines don't even have 16 bytes of data. It only has 10 bytes with checksum at the end, eg: :0A0B4000CDBFED010895F894FFCF3A.

There is no clear examples anywhere. I expect someone out there must have some experience in it to help me.

Best Answer

This will need some further work on your part to integrate into your code but should give you some ideas. The first step in bootloader will be to include headers related to programming of the FLASH:

#include <avr/boot.h>
#include <avr/pgmspace.h>

A few other defines that help define things later and define some response codes at the end of processing each line are:

#define HEX2DEC(x)  (((x < 'A') ? ((x) - 48) : ((x) - 55)))
#define SPM_PAGEMASK ((uint32_t) ~(SPM_PAGESIZE - 1))
enum response_t {RSP_OK, RSP_CHECKSUM_FAIL, RSP_INVALID, RSP_FINISHED};

Then a routine like the following can be used to process the Intel hex format and write to FLASH. It is written for a larger device so handles extended addresses in the hex files so you can remove that for your smaller device to reduce the code size but it won't do any harm to leave in place. You'll also need to add your own UART receive / init code and determine what to do if things time out, I used a watchdog timer in this case.

enum response_t process_line()
{
    char c, line_buffer[128], data_buffer[64];
    uint8_t line_len = 0, data_len = 0, data_count, line_type, line_pos, data;
    uint8_t addrh, addrl, checksum, recv_checksum;
    uint16_t addr, extended_addr = 0, i;
    static uint32_t full_addr, last_addr = 0xFFFFFFFF;

    c = uart_getc();
    while (c != '\r')
    {
        if (c == ':')
            line_len = 0;
        else if (c == '\n')
            ;
        else if (c == '\0')
            ;
        else if (line_len < sizeof(line_buffer))
            line_buffer[line_len++] = c;
        c = uart_getc();
    }
    if (line_len < 2)
        return RSP_INVALID;
    data_count = (HEX2DEC(line_buffer[0]) << 4) + HEX2DEC(line_buffer[1]);
    if (line_len != data_count * 2 + 10)
        return RSP_INVALID;
    addrh =  (HEX2DEC(line_buffer[2]) << 4) + HEX2DEC(line_buffer[3]);
    addrl =  (HEX2DEC(line_buffer[4]) << 4) + HEX2DEC(line_buffer[5]);
    addr = (addrh << 8) + addrl;
    line_type = (HEX2DEC(line_buffer[6]) << 4) + HEX2DEC(line_buffer[7]);
    line_pos = 8;
    checksum = data_count + addrh + addrl + line_type;
    for (i=0; i < data_count; i++)
    {
        data = (HEX2DEC(line_buffer[line_pos]) << 4) + HEX2DEC(line_buffer[line_pos + 1]);
        line_pos += 2;
        data_buffer[data_len++] = data;
        checksum += data;
    }
    checksum = 0xFF - checksum + 1;
    recv_checksum = (HEX2DEC(line_buffer[line_pos]) << 4) + HEX2DEC(line_buffer[line_pos + 1]);
    if (checksum != recv_checksum)
        return RSP_CHECKSUM_FAIL;
    if (line_type == 1)
    {
        if (last_addr != 0xFFFFFFFF)
        {
            boot_page_write (last_addr & SPM_PAGEMASK);
            boot_spm_busy_wait();
        }
        return RSP_FINISHED;
    }
    else if ((line_type == 2) || (line_type == 4))
        extended_addr = (data_buffer[0] << 8) + data_buffer[1];
    else if (line_type == 0)
    {
        full_addr = ((uint32_t) extended_addr << 16) + addr;
        if ((full_addr & SPM_PAGEMASK) != (last_addr & SPM_PAGEMASK))
        {
            if (last_addr != 0xFFFFFFFF)
            {
                boot_page_write (last_addr & SPM_PAGEMASK);
                boot_spm_busy_wait();
            }
            boot_page_erase (full_addr);
            boot_spm_busy_wait ();
        }
        for (i=0; i < data_len; i+=2)
        {
            uint16_t w = data_buffer[i] + ((uint16_t) data_buffer[i + 1] << 8);
            boot_page_fill (full_addr + i, w);
        }
        last_addr = full_addr;
    }
    return RSP_OK;
}

It reads an entire hex line and verifies the checksum so check it against the Intel hex format to see how it works in detail before using. From there the general idea is that when it hits a new FLASH page boundary it does an erase but otherwise goes ahead and writes the data.

When the bootloader is entered this is the main code I was using to call it, but once again you'll need to work out what do to when a failure occurs. In my case I was using some custom PC software so sent back a few strings to indicate the status, but for a terminal upload you may just want to set an EEPROM flag depending on whether it was successful and you should boot to it or to show a human friendly message and call the bootloader again:

void enter_loader()
{
    enum response_t response = RSP_OK;

    uart_puts("+OK/\r\n");
    while (response != RSP_FINISHED)
    {
        response = process_line();
        if (response == RSP_OK)
            uart_puts("+OK/");
        else if (response != RSP_FINISHED)
            uart_puts("+FAIL/");
        wdt_reset();
    }   
    boot_rww_enable ();
    while (1) // Use watchdog to reboot
        ;
}

Anyway that should give you a good start and the above code has been pretty well tested with some fairly complex firmware. You'll just need a bit of work to write the missing UART and other initialisation code to determine when the bootloader should be called for your application. It may also need some tweaking for a ATmega32A but I think the approach should work pretty well across AVR devices.