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 0C942A000C943F000C943F000C943F00
y 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.
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.
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:
*
'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.
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.
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í.
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.
Dzarda
gzix
PedroJ
gzix