Forum: Mikrocontroller und Digitale Elektronik Bootloader Atmega644P


von Eros S. (dretox)


Angehängte Dateien:

Lesenswert?

Guten Tag

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.

Was mache ich falsch?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

hast du die Fuses auch dementsprechend gesetzt?

von Thomas E. (thomase)


Lesenswert?

Eros S. schrieb:
> 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:

Veit D. schrieb:
> hast du die Fuses auch dementsprechend gesetzt?

gemeint.

von Eros S. (dretox)


Angehängte Dateien:

Lesenswert?

Fuses sind folgendermassen gesetzt:

Extended: 0xFF
High: 0x90
Low: 0xFF

: Bearbeitet durch User
von Sebastian W. (wangnick)


Lesenswert?

Eros S. schrieb:
> Was mache ich falsch?

Vielleicht setzt du IVSEL nicht innerhalb von 4 CPU-Zyklen?
1
  MCUCR = temp | (1<<IVCE);
2
000071BF  LDI R24,0x55    Load immediate 
3
000071C0  LDI R25,0x00    Load immediate 
4
000071C1  LDD R18,Y+8    Load indirect with displacement 
5
000071C2  ORI R18,0x01    Logical OR with immediate 
6
000071C3  MOVW R30,R24    Copy register pair 
7
000071C4  STD Z+0,R18    Store indirect with displacement 
8
  MCUCR = temp | (1<<IVSEL);
9
000071C5  LDI R24,0x55    Load immediate 
10
000071C6  LDI R25,0x00    Load immediate 
11
000071C7  LDD R18,Y+8    Load indirect with displacement 
12
000071C8  ORI R18,0x02    Logical OR with immediate 
13
000071C9  MOVW R30,R24    Copy register pair 
14
000071CA  STD Z+0,R18    Store indirect with displacement

LG, Sebastian

von Wastl (hartundweichware)


Lesenswert?

Eros S. schrieb:
> Fuses sind folgendermassen gesetzt:

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?

von S. L. (sldt)


Lesenswert?

>Der Bootloader springt nicht an

Woraus schließen Sie das - aus (dem Fehlen) der UART-Meldung "Hallo 
..."?
  Ich hätte erstmal zu Beginn des Bootloaders eine LED geschaltet.

von Wastl (hartundweichware)


Lesenswert?

Eros S. schrieb:
> 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.

von Eros S. (dretox)


Angehängte Dateien:

Lesenswert?

Wastl schrieb:
> 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

Sebastian W. schrieb:
> Eros S. schrieb:
>> Was mache ich falsch?
>
> 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?

Wastl schrieb:
> Eros S. schrieb:
>> 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)

von Eros S. (dretox)


Angehängte Dateien:

Lesenswert?

Sebastian W. schrieb:
> 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.

Danke an alle Beteiligten für die Tipps.

: Bearbeitet durch User
von Wastl (hartundweichware)


Lesenswert?

Eros S. schrieb:
> Atmel644_Bootloader.7z (35,4 KB)

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?

von Wastl (hartundweichware)


Lesenswert?

Eros S. schrieb:
> 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:
1
#pragma GCC push_options
2
#pragma GCC optimize ("O3")
3
void Set_IVTable (void)
4
{
5
  unsigned char temp;
6
  /* Interrupt Vektoren verbiegen */
7
  char sregtemp = SREG;
8
  cli();
9
  temp = MCUCR;
10
  MCUCR = temp | (1<<IVCE);
11
  MCUCR = temp | (1<<IVSEL);
12
  SREG = sregtemp;
13
}
14
#pragma GCC pop_options

von Eros S. (dretox)


Lesenswert?

Wastl schrieb:
> Eros S. schrieb:
>> Atmel644_Bootloader.7z (35,4 KB)
>
> 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
1
#define F_CPU        20000000UL
2
#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.

von Eros S. (dretox)


Lesenswert?

Wastl schrieb:
> 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.

von Wastl (hartundweichware)


Lesenswert?

Eros S. schrieb:
> Konnte das Projekt auch ohne define kompilieren

Weil dann ohne weitere Meldung ein Default-Wert gesetzt wird
der natürlich nicht deinen Erwartungen entspricht.

Ich schreib's nochmal:

Wastl schrieb:
> Solltest du auf jeden Fall im Projekt (nicht in den Sources)
> definieren

von Sebastian W. (wangnick)


Lesenswert?

Eros S. schrieb:
> 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?

LG, Sebastian

von Alexander S. (alesi)


Lesenswert?

Eros S. schrieb:
> 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.

Beitrag "UART Baudrate clever runden"

von Eros S. (dretox)


Lesenswert?

Sebastian W. schrieb:

> In meinem Bootloader verzichte ich auf UART-Interrupts. Dann braucht es
> auch keine Vektortabelle und kein IVSEL. Brauchst du Interrupts?

Den Receive Interrupt halte ich schon für sehr sinnvoll. Wie weisst du 
sonst wann du das Datenregister auslesen musst wenn die CPU etwas 
empfängt?

von Eros S. (dretox)


Lesenswert?

Wastl schrieb:
> Ich schreib's nochmal:
>
> Solltest du auf jeden Fall im Projekt (nicht in den Sources)
> definieren

Hab ich gemacht :)

von Sebastian W. (wangnick)


Lesenswert?

Eros S. schrieb:
> Den Receive Interrupt halte ich schon für sehr sinnvoll. Wie weisst du
> sonst wann du das Datenregister auslesen musst wenn die CPU etwas
> empfängt?

Das sagt mir RXC0 in UCSR0A. In meinem Bootloader lese ich nur zu 
bestimmten Momenten Zeichen vom UART ein.

LG, Sebastian

von Wastl (hartundweichware)


Lesenswert?

Eros S. schrieb:
> 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.

Eros S. schrieb:
> Hab ich gemacht :)

Deine nicht vorhandene Beratungsresistenz ehrt dich sehr und
unterscheidet dich deutlich von anderen Leuten (Betonköpfen).

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Sebastian W. schrieb:
> In meinem Bootloader lese ich nur zu bestimmten Momenten Zeichen vom
> UART ein.
Und der Sender weiß, wann er senden darf?

Ich würde das einfach "wie allgemein üblich" mit einem RX-Fifo machen.

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Lothar M. schrieb:

>> 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.

von Thomas E. (thomase)


Lesenswert?

Eros S. schrieb:
> Den Receive Interrupt halte ich schon für sehr sinnvoll. Wie weisst du
> sonst wann du das Datenregister auslesen musst wenn die CPU etwas
> empfängt?

Das Flag kann man auch pollen.

von Purzel H. (hacky)


Lesenswert?

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.

: Bearbeitet durch User
von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Purzel H. schrieb:

> 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...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.