Ich versuche mich aktuell - mithilfe des hiesigen AVR Tutorials - an einem Bootloader. Ich arbeite mit einem Atmega644P.
Das Problem nun: Der Bootloader springt nicht an, bzw. das Programm in der Bootloadersektion des Flashs nicht ausgeführt zu werden. Entsprechend funktionieren auch keine Interrupts der UART Schnittstelle. Ich vermute schlicht, dass ich dem Compiler die Boot Reset Address falsch mitteile und ich bin diesbezüglich auch etwas verwirrt.
In den Linker Options definiere ich mit .text=0x7000 die Boot Reset Address, die gemäss Datenblatt für eine Boot Size von 4096 Words deklariert ist. Wenn ich das .hex File dann aufmache sehe ich, dass das Programm ab Addresse 0xE000 startet. Ist nun 0x7000 die Word Addresse und 0xE000 dann die Byteaddresse? Hab dann auch mal versucht in den Linker Options die Addresse 0x3800 zu definieren, damit das Programm ab Byteaddresse 0x7000 startet.. Allerdings hatte das auch keinen Zweck, das Programm wird einfach nicht richtig ausgeführt... Irgendwo hab ich da nen Knoten.
Hab auch schon gegoogled und hier so manchen Thread durchgeschaut, bin allerdings auf keinen grünen Zweig gekommen.
In den Linker Options definiere ich mit .text=0x7000 die Boot Reset
Address
Nein, damit schiebst du das Programm in den Bootsektor. Definiert wird damit gar nichts. Damit das Programm dort auch nach dem Reset ausgeführt wird, musst du auch den Resetvektor mit den Fuses entsprechend setzen. Das ist damit:
Wenn du schon einen Screenshot zeigst - was sehr begrüssenswert
ist - warum zeigst du dann nicht alle Fuses sondern listest in
kryptischen Hex-Werten deine Konfiguration?
Allerdings hatte das auch keinen Zweck,
das Programm wird einfach nicht richtig ausgeführt...
Woran stellest du das fest? "Geht nicht" ist nicht besonders
aussagekräftig. Deinem Screenshot nach zu urteilen benutzt
du einen Atmel Ice, damit kannst du ja ausführlich debuggen.
Damit solltest du erkennen können wo und wie es nicht
funktioniert.
Wenn du schon einen Screenshot zeigst - was sehr begrüssenswert
ist - warum zeigst du dann nicht alle Fuses sondern listest in
kryptischen Hex-Werten deine Konfiguration?
Da hast du absolut Recht! Angehängt ein neuer Screenshot
Vielleicht setzt du IVSEL nicht innerhalb von 4 CPU-Zyklen?
MCUCR = temp | (1<<IVCE);
000071BF LDI R24,0x55 Load immediate
000071C0 LDI R25,0x00 Load immediate
000071C1 LDD R18,Y+8 Load indirect with displacement
000071C2 ORI R18,0x01 Logical OR with immediate
000071C3 MOVW R30,R24 Copy register pair
000071C4 STD Z+0,R18 Store indirect with displacement
MCUCR = temp | (1<<IVSEL);
000071C5 LDI R24,0x55 Load immediate
000071C6 LDI R25,0x00 Load immediate
000071C7 LDD R18,Y+8 Load indirect with displacement
000071C8 ORI R18,0x02 Logical OR with immediate
000071C9 MOVW R30,R24 Copy register pair
000071CA STD Z+0,R18 Store indirect with displacement
LG, Sebastian
Ich habe es aus dem gängigen Tutorial übernommen. Das IVSEL innert 4 CPU Zyklen gesetzt werden muss war mir klar, dachte aber schneller als direkt nach IVCE kann ich es ja nicht setzen. Gibts da eine effizientere Methode fürs Setzen der Bits?
Allerdings hatte das auch keinen Zweck,
das Programm wird einfach nicht richtig ausgeführt...
Woran stellest du das fest? "Geht nicht" ist nicht besonders
aussagekräftig. Deinem Screenshot nach zu urteilen benutzt
du einen Atmel Ice, damit kannst du ja ausführlich debuggen.
Damit solltest du erkennen können wo und wie es nicht
funktioniert.
"Geht nicht" ist vielleicht der falsche Ausdruck. "Geht nicht wie gewünscht" wäre passender. Beim Debuggen habe ich festgestellt, dass das Programm zwar korrekt anläuft, aber die Zeile
1
uart_puts("Hallo hier ist der Bootloader\n\r");
ist problematisch, denn die while-Schleife wird nie erreicht. Sobald ich den Transmit Interrupt für die UART einschalte, scheint das Programm die Addresse für die entsprechende Serviceroutine nicht zu finden und dann resetted die CPU.
Kommentiere ich die oben genannte Zeile aus, springt das Programm normal in die while-Schleife und wartet dort auf ein Zeichen, läuft also so weit normal. Sobald ich über das Terminal dann allerdings ein Zeichen sende, somit also einen Receive Interrupt auslöse, scheint das Programm an eine undefinierte Stelle zu springen und ist dann irgendwo, nur nicht da wo es sein sollte (siehe Screenshot)
Vielleicht setzt du IVSEL nicht innerhalb von 4 CPU-Zyklen?
Das war auch das Problem, ich habe es gerade gelöst. Einerseits habe ich es genau so gemacht, wie im Datenblatt angegeben. Als ich jedoch das Assembly Code Example mit dem Assembler Code aus meinem Programm verglichen habe, habe ich gesehen, dass sehr wohl mehr als 4 CPU Zyklen vergehen, bis IVSEL gesetzt wird. Das bedeutet die Interrupts landen wieder am Anfang der Flash Sektion, was beim Auslösen ebenjener dann problematisch ist.
Das Problem hierbei war, dass ich zum Debuggen die Optimierung ausgeschaltet habe. Hab dann die Option "Optimize most" angewählt und siehe da, das Programm funktioniert wie gewünscht. Man lernt nie aus.
Wenn ich dein Projekt kompiliere kommt bei mir die Warnung
"F_CPU not defined for <util/delay.h>"
Solltest du auf jeden Fall im Projekt (nicht in den Sources)
definieren (F_CPU=irgendwas Sinnvolles), sonst wird der UART-I/O nicht
funktionieren wie du es erwartest.
Vielleicht sagst du uns auch mal was für eine Test-Hardware
du verwendest?
Das Problem hierbei war, dass ich zum Debuggen die Optimierung
ausgeschaltet habe.
Damit du weiter ohne störende Optimierung debuggen kannst
lagere die Vektor-Umbiegung in eine eigene Funktion aus,
dann kannst du Funktions-lokal die Optimierung so einstellen
wie du es brauchst. Etwa so:
Wenn ich dein Projekt kompiliere kommt bei mir die Warnung
"F_CPU not defined for <util/delay.h>"
Solltest du auf jeden Fall im Projekt (nicht in den Sources)
definieren (F_CPU=irgendwas Sinnvolles), sonst wird der UART-I/O nicht
funktionieren wie du es erwartest.
Vielleicht sagst du uns auch mal was für eine Test-Hardware
du verwendest?
Ich verwende ein eigens designtes Board, das ich universell für Prüfgeräte einsetze.
Konnte das Projekt auch ohne define kompilieren, was mir aber aufgefallen ist beim UART Treiber, dass die Baud Rate falsch berechnet wird. Im Bootloader Tutorial wird für die Baud Rate folgendes definiert:
1
#define BOOT_UART_BAUD_RATE 9600
Die Funktion uart_init schreibt mir dann ins UBRR0 Register den Wert 128, gemäss Datenblatt muss für die Baudrate 9600 aber der Wert 129 rein. Wenn man den define oben anpasst zu
funktionierts dann. Weiss aber nicht, ob das nur mit dem Define der CPU Taktfrequenz zusammenhängt, denn mit UART_BAUD_SELECT_DOUBLE_SPEED funktionierts wieder nicht und es wird ein falscher Wert ins UBRR0 Register geladen.
Damit du weiter ohne störende Optimierung debuggen kannst
lagere die Vektor-Umbiegung in eine eigene Funktion aus,
dann kannst du Funktions-lokal die Optimierung so einstellen
wie du es brauchst.
Interessant, das kenne ich so noch nicht. Habs gerade ausprobiert und funktioniert. Super Lösung.
schneller als direkt nach IVCE kann ich es ja nicht setzen. Gibts da
eine effizientere Methode fürs Setzen der Bits?
Direkt den gewünschten Wert in MCUSR schreiben. Das ist auch ohne Optimierung schnell genug. Nach einem Reset sind die Werte der anderen Bits ja bekannt.
In meinem Bootloader verzichte ich auf UART-Interrupts. Dann braucht es auch keine Vektortabelle und kein IVSEL. Brauchst du Interrupts?
Konnte das Projekt auch ohne define kompilieren, was mir aber
aufgefallen ist beim UART Treiber, dass die Baud Rate falsch berechnet
wird. Im Bootloader Tutorial wird für die Baud Rate folgendes definiert:
#define BOOT_UART_BAUD_RATE 9600
Die Funktion uart_init schreibt mir dann ins UBRR0 Register den Wert
128, gemäss Datenblatt muss für die Baudrate 9600 aber der Wert 129
rein. Wenn man den define oben anpasst zu
#define F_CPU 20000000UL
#define BOOT_UART_BAUD_RATE UART_BAUD_SELECT(9600, F_CPU)
funktionierts dann. Weiss aber nicht, ob das nur mit dem Define der CPU
Taktfrequenz zusammenhängt, denn mit UART_BAUD_SELECT_DOUBLE_SPEED
funktionierts wieder nicht und es wird ein falscher Wert ins UBRR0
Register geladen.
Wie weisst du
sonst wann du das Datenregister auslesen musst wenn die CPU etwas
empfängt?
Durch Lesen des Statusregisters und Prüfen des entsprechenden
Flags. Interrupt Steuerung ist nicht zwingend .... hilft aber
beim Empfangen enorm, beim Senden nicht so sehr.
In meinem Bootloader lese ich nur zu bestimmten Momenten Zeichen vom
UART ein.
Und der Sender weiß, wann er senden darf?
Braucht er nicht wissen. Man muss halt im Empfänger nur jederzeit schnell genug pollen, dann geht das natürlich auch ohne Interrupts. Ist ja nicht so, dass der Bootloader nebenbei noch Pi auf zwei Millionen Stellen genau berechnet oder sowas.
Nö, der hat eine einfache Aufgabe, zu deren Ausführung er nur maximal vier Quellen pollen muss, deren Behandlung jeweils trivial und in maximal einigen zehn Takten zu schaffen ist. Das geht ganz locker auch ohne die Verwendung von Interrupts zu lösen. Das ist, genau genommen, sogar etwas einfacher.
Und es spart (übrigens besonders in C!) ganz erheblich Codesize.
Ich habe auch mal einen Bootloader fuer einen Mega32/64 geschrieben. Und dabei auch keine Interrupts verwendet. Laden auch ueber die serielle Schnittstelle. mit 9600 baud. Da war ueppig Zeit zwischen den Zeichen. Auch mit CRC. Das Limit fuer die Kommunikation war eh das Flash. Das wollte nicht schneller.
Jeder empfangene Block wurde grad ins Flash geschrieben. Die einzelnen Bloecke waren mit CRC gesichert, aber ob alles stimmt sieht man erst am Schluss.
Heutzutage wuerde ich daher ein externes Flash, MagnetRAM oder EEPROM fuer den gesammten Code verwenden. Also erst die Kommunikation, bis vollstaendig, Ueberpruefungen, und dann erst programmieren.
Ich habe auch mal einen Bootloader fuer einen Mega32/64 geschrieben. Und
dabei auch keine Interrupts verwendet. Laden auch ueber die serielle
Schnittstelle. mit 9600 baud. Da war ueppig Zeit zwischen den Zeichen.
Auch mit CRC. Das Limit fuer die Kommunikation war eh das Flash. Das
wollte nicht schneller.
Hmmm. Die Classic-Megas besitzen einen eingebauten Write-Buffer für den Flash. Der ist in wenigen Takten/Byte beschreibbar. Nennenswerte Zwangspausen ergeben sich eigentlich nur in den Momenten, wenn Pages gelöscht werden oder der Writebuffer tatsächlich in's Flash geschrieben wird, also einmal pro Flash-Page.
Dazu kommt noch, dass die Classic-Megas für große Teile des Flash RWW unterstützen. Zumindest für diese Teile können also selbst diese Zwangspausen für die Datenübertragung genutzt werden.
Bei den neuzeitlichen AVR8 ist das leider alles weggespart worden. Kein Pagebuffer mehr und auch kein RWW mehr. Wohl deshalb, weil die meisten Bootloader-Frickler sowieso nicht in der Lage waren, diese Features adäquat zu nutzen...