AVR: envía el programa de aplicación al cargador de arranque

Ahora es el momento de enviar el código de la aplicación al gestor de arranque, que ha sido programado en un controlador ATmega32A. Tiene que enviarse en formato hexadecimal, pero no tengo idea de "¿Cómo enviarlo?"

El archivo hexadecimal compilado para un código de aplicación de atmel studio es el siguiente:

    :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

Según el estándar, la primera línea tiene data 0C942A000C943F000C943F000C943F00y checksum 89. ¿Tengo que enviar solo los 16 bytes de datos seguidos de la suma de verificación? En algunos programas, algunas líneas ni siquiera tienen 16 bytes de datos. Solo tiene 10 bytes con suma de verificación al final, por ejemplo: :0A0B4000CDBFED010895F894FFCF3A.

No hay ejemplos claros en ninguna parte. Espero que alguien por ahí debe tener algo de experiencia para ayudarme.

Es posible que desee cambiar esto a incrustado.SE
Hola Dzarda, ¿podrías por favor elaborar
No tengo tiempo para escribir una respuesta completa en este momento, pero es un formato hexadecimal de Intel y normalmente lo más fácil es enviar todo y decodificar en el gestor de arranque. Entonces puede usar un programa de terminal normal para enviarlo.
Hola PeterJ, estoy seguro de que puedo decodificarlo. Pero, ¿tengo que incluir la suma de verificación en cada final de línea? Y cómo rellenar si alguna línea tiene menos datos

Respuestas (3)

Esto necesitará más trabajo de su parte para integrarlo en su código, pero debería darle algunas ideas. El primer paso en el gestor de arranque será incluir encabezados relacionados con la programación de FLASH:

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

Algunas otras definiciones que ayudan a definir las cosas más adelante y definen algunos códigos de respuesta al final del procesamiento de cada línea son:

#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};

Luego, se puede usar una rutina como la siguiente para procesar el formato hexadecimal de Intel y escribir en FLASH. Está escrito para un dispositivo más grande, por lo que maneja direcciones extendidas en los archivos hexadecimales para que pueda eliminar eso para su dispositivo más pequeño para reducir el tamaño del código, pero no hará ningún daño dejarlo en su lugar. También deberá agregar su propio código UART de recepción/inicio y determinar qué hacer si se agota el tiempo de espera. En este caso, utilicé un temporizador de vigilancia.

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;
}

Lee una línea hexadecimal completa y verifica la suma de comprobación, así que compruébelo con el formato hexadecimal de Intel para ver cómo funciona en detalle antes de usarlo. A partir de ahí, la idea general es que cuando llega a un nuevo límite de página FLASH, borra, pero de lo contrario continúa y escribe los datos.

Cuando se ingresa al cargador de arranque, este es el código principal que estaba usando para llamarlo, pero una vez más, deberá averiguar qué hacer cuando ocurre una falla. En mi caso, estaba usando un software de PC personalizado, así que envié algunas cadenas para indicar el estado, pero para una carga de terminal, es posible que solo desee establecer un indicador de EEPROM dependiendo de si tuvo éxito y debe iniciarlo o mostrar un mensaje amigable para humanos y llame al gestor de arranque nuevamente:

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
        ;
}

De todos modos, eso debería darle un buen comienzo y el código anterior se ha probado bastante bien con un firmware bastante complejo. Solo necesitará un poco de trabajo para escribir el UART que falta y otro código de inicialización para determinar cuándo se debe llamar al cargador de arranque para su aplicación. También puede necesitar algunos ajustes para un ATmega32A, pero creo que el enfoque debería funcionar bastante bien en todos los dispositivos AVR.

Hola, PeterJ, cuando algunas páginas que no sean la última página tienen un código de longitud corta como ": 02072000FFCF09: 1007220041545E4950494E49543D220000000000A8", obtengo que 2 bytes se desplazan en cada página hasta el final.
@ usuario2943851, probablemente no tendré la oportunidad hoy, pero ¿quería publicar el archivo en, digamos, pastebin y responder aquí con un enlace y lo revisaré y veré qué puedo detectar?
En este momento, estoy usando SRecord para convertir Intel Hex de longitud variable a Intel Hex de longitud fija. Sin embargo, sería bueno aprender a analizar el formato hexadecimal original de Intel. Así que tratando duro

En cuanto al formato HEX:

Como se sugiere en el comentario, lea sobre el formato Intel HEX. http://en.wikipedia.org/wiki/Intel_HEX

Básicamente, lo que dice es que cada línea corresponde a un bloque de datos. El formato de la línea es el siguiente:

:XYZC

Dónde:

  • X es 1 byte que especifica la longitud de la carga útil
  • Y es 1 byte que especifica el tipo de carga útil *
  • Z es la carga útil de longitud X bytes
  • C es 1 byte de la suma de comprobación de la línea

*

'00' Registro de datos
'01' Registro de fin de archivo
'02' Dirección de segmento extendida
'03' Registro de dirección de segmento de inicio
'04' Registro de dirección lineal extendida
'05' Registro de dirección lineal de inicio

Consulte http://microsym.com/editor/assets/intelhex.pdf para ver la especificación completa.


Sobre el método de transmisión:

También se menciona en el comentario: es mucho mejor enviar el archivo completo tal como está. De esa manera, no necesita un programa especializado en el lado de la PC, solo una terminal que se comunica con su dispositivo. Luego analice el texto en el chip.


Cómo escribirlo:

El método más fácil es comenzar a leer las notas de aplicación de Atmel:

Todas las operaciones de autoprogramación se realizan utilizando la instrucción SPM

Esto se puede encontrar aquí: http://www.atmel.com/images/doc1644.pdf

El AVR109 es un cargador de arranque famoso para dispositivos Mega. Puede encontrar mucha información aquí.

Ya analizaré y tomaré solo los datos. Mi pregunta ahora es "¿Cómo escribirlo?", ¿Qué hacer con la suma de verificación?
Espera, voy a editar la respuesta.

Hay herramientas para esas cosas. Lo primero y más importante: realmente no desea que el controlador descubra dónde colocar qué, ya que eso requeriría analizar el archivo en el controlador.

En su lugar, convierta el archivo en un archivo binario (que se puede escribir 1:1 para flashear), luego deje que el controlador lo flashee en la dirección de la aplicación (debe averiguarlo antes). Veo un posible programa: su aplicación está vinculada a la dirección 0x0; esto significa que si el gestor de arranque está en esa dirección específica, no funcionará (deberá vincularlo a una dirección diferente). Si el cargador de arranque reside en alguna otra dirección (preferiblemente al final del flash), está listo para comenzar.

Encuentre un script que se convierta a binario y haga todo lo demás aquí: http://1drv.ms/1hj9eU0

Está destinado a otra familia Atmel (AVR32), pero debería hacerse una idea. Si tienes preguntas, házmelo saber.

¿No entiendo lo que quieres decir con 1:1 para flashear?
Cuando transmita ese archivo a su dispositivo, no necesitará escribir los caracteres ASCII 'C' y '9' en su dispositivo, pero deberá escribir el byte 0xc9 en esa ubicación de memoria. Para crear un binario de este tipo a partir de un archivo hexadecimal, deberá convertir el hexadecimal en un archivo binario "puro" (esto es lo que hace srec_cat del archivo zip).