Jetzt ist es an der Zeit, den Anwendungscode an den Bootloader zu senden, der in einen ATmega32A-Controller programmiert wurde. Es muss im Hex-Format gesendet werden, aber ich habe keine Ahnung von "Wie soll ich es senden?"
Die für einen Anwendungscode von Atmel Studio kompilierte Hex-Datei lautet wie folgt:
: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
Standardmäßig enthält die erste Zeile Daten 0C942A000C943F000C943F000C943F00
und Prüfsumme 89
. Muss ich nur die 16 Datenbytes gefolgt von der Prüfsumme senden? In manchen Programmen haben einige Zeilen nicht einmal 16 Byte Daten. Es hat nur 10 Bytes mit Prüfsumme am Ende, zB: :0A0B4000CDBFED010895F894FFCF3A
.
Es gibt nirgendwo klare Beispiele. Ich nehme an, jemand da draußen muss etwas Erfahrung darin haben, um mir zu helfen.
Dies erfordert einige weitere Arbeit Ihrerseits, um es in Ihren Code zu integrieren, sollte Ihnen aber einige Ideen geben. Der erste Schritt im Bootloader besteht darin, Header in Bezug auf die Programmierung des FLASH einzufügen:
#include <avr/boot.h>
#include <avr/pgmspace.h>
Ein paar andere Definitionen, die helfen, Dinge später zu definieren und einige Antwortcodes am Ende der Verarbeitung jeder Zeile zu definieren, sind:
#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};
Dann kann eine Routine wie die folgende verwendet werden, um das Intel-Hex-Format zu verarbeiten und in FLASH zu schreiben. Es ist für ein größeres Gerät geschrieben, behandelt also erweiterte Adressen in den Hex-Dateien, sodass Sie diese für Ihr kleineres Gerät entfernen können, um die Codegröße zu reduzieren, aber es schadet nicht, es an Ort und Stelle zu lassen. Sie müssen auch Ihren eigenen UART-Empfangs-/Init-Code hinzufügen und bestimmen, was zu tun ist, wenn die Dinge ablaufen, ich habe in diesem Fall einen Watchdog-Timer verwendet.
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;
}
Es liest eine ganze Hex-Zeile und verifiziert die Prüfsumme, also vergleichen Sie es mit dem Intel-Hex-Format, um zu sehen, wie es im Detail funktioniert, bevor Sie es verwenden. Von dort ist die allgemeine Idee, dass, wenn es auf eine neue FLASH-Seitengrenze trifft, es löscht, aber ansonsten fortfährt und die Daten schreibt.
Wenn der Bootloader eingegeben wird, ist dies der Hauptcode, den ich verwendet habe, um ihn aufzurufen, aber noch einmal müssen Sie herausfinden, was zu tun ist, wenn ein Fehler auftritt. In meinem Fall habe ich eine benutzerdefinierte PC-Software verwendet, also einige Zeichenfolgen zurückgesendet, um den Status anzuzeigen, aber für einen Terminal-Upload möchten Sie möglicherweise nur ein EEPROM-Flag setzen, je nachdem, ob es erfolgreich war und Sie damit booten oder anzeigen sollten eine benutzerfreundliche Nachricht und rufen Sie den Bootloader erneut auf:
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
;
}
Wie auch immer, das sollte Ihnen einen guten Start geben und der obige Code wurde ziemlich gut mit einiger ziemlich komplexer Firmware getestet. Sie brauchen nur ein wenig Arbeit, um den fehlenden UART und anderen Initialisierungscode zu schreiben, um zu bestimmen, wann der Bootloader für Ihre Anwendung aufgerufen werden soll. Für einen ATmega32A sind möglicherweise auch einige Anpassungen erforderlich, aber ich denke, der Ansatz sollte auf allen AVR-Geräten ziemlich gut funktionieren.
Lesen Sie, wie im Kommentar vorgeschlagen, das Intel HEX-Format. http://en.wikipedia.org/wiki/Intel_HEX
Was es sagt, ist im Grunde, dass jede Zeile einem Datenblock entspricht. Das Format der Zeile ist wie folgt:
:XYZC
Wo:
*
'00' Datenaufzeichnung
'01' Dateiendeaufzeichnung
'02' Erweiterte Segmentadresse
'03' Startsegmentadressenaufzeichnung
'04' Erweiterte lineare Adressaufzeichnung
'05' Startlineare Adressaufzeichnung
Siehe http://microsym.com/editor/assets/intelhex.pdf für die gesamte Spezifikation.
Es wird auch im Kommentar erwähnt - Sie sind viel besser dran, die gesamte Datei so zu senden, wie sie ist. Auf diese Weise benötigen Sie kein spezielles Programm auf der PC-Seite, sondern nur ein Terminal, das mit Ihrem Gerät kommuniziert. Analysieren Sie dann den Text auf dem Chip.
Die einfachste Methode ist, die Anwendungshinweise von Atmel zu lesen:
Alle Selbstprogrammierungsoperationen werden unter Verwendung des SPM-Befehls ausgeführt
Diese finden Sie hier: http://www.atmel.com/images/doc1644.pdf
Der AVR109 ist ein berühmter Bootloader für Mega-Geräte. Hier finden Sie viele Informationen.
Es gibt Werkzeuge für solche Dinge. Zuallererst: Sie möchten den Controller nicht wirklich herausfinden lassen, wo er was ablegen soll, da dies das Parsen der Datei auf dem Controller erfordern würde.
Konvertieren Sie die Datei stattdessen in eine Binärdatei (die 1:1 in Flash geschrieben werden kann) und lassen Sie sie dann vom Controller auf die Anwendungsadresse flashen (das müssen Sie vorher herausfinden). Ein mögliches Programm, das ich sehe: Ihre Anwendung ist mit der Adresse 0x0 verknüpft - das bedeutet, dass der Bootloader, wenn er sich an dieser bestimmten Adresse befindet, nicht funktioniert (Sie müssen ihn mit einer anderen Adresse verknüpfen). Wenn sich der Bootloader an einer anderen Adresse befindet (vorzugsweise am Ende des Flashs), können Sie loslegen.
Bitte finden Sie hier ein Skript, das in Binärdateien konvertiert und alles andere erledigt: http://1drv.ms/1hj9eU0
Es ist für eine andere Atmel-Familie (AVR32) gedacht, aber Sie sollten die Idee bekommen. Wenn Sie Fragen haben, lassen Sie es mich wissen.
Dzarda
gzix
PeterJ
gzix