Forum: Mikrocontroller und Digitale Elektronik STM32 Bootloader zum Flashen und Applikation anspringen


von Holger T. (holger1979)


Lesenswert?

Hallo,

ich möchte für einen STM32 eine Art Bootloader schreiben, der in einem 
externen Flashspeicher nach einem Update sucht.

Ist ein Update vorhanden, so wird dieses auf den STM geflashed, geprüft 
und bei bestandener Prüfung aus dem externen Flash gelöscht. 
Anschließend wird der STM32 resettet.

Ist kein Update vorhanden, also auch nach dem Flashen des Updates, dann 
soll an die Startadresse des Updates gesprungen werden.


Theoretisch weiß ich was da passieren sollte.
Ich brauche zwei SECTIONS. Die Erste für den Bootloader, die zweite für 
das Programm, dass geupdatet und ausgeführt werden soll.

Das Flashprogramm benötigt die Treiber für den externen Flash und kann 
dann entder Flashen oder Springen.

Zum Springen schalte ich die Interrupts aus, setze ProgramCounter und 
VectorTable und schon läuft das Programm los (theoretisch). Interrupts 
natürlich wieder an.

Eine einzelnes Programm zu schreiben, dieses dann kompilieren (mit der 
neuen Zieladresse), dieses dann mit ST-Link an diese Position zu flashen 
und mit einem Miniprogramm anzuspringen ist nicht das Problem.
Das Problem ist nur, dass ich nicht jedes Mal die ganzen Schritte 
durchmachen möchte, um das Ganze ans Laufen zu bringen.
Ich möchte einfach editieren, compilieren, flashen und debuggen.
Und das mit einer Eclipse-Umgebung mit OpenOCD.

Geht das so einfach oder muss ich Bootloader und Programm getrennt 
voneinander entwickeln und ändere dann nur zum Schluss die Adresse des 
eigentlichen Programms vor dem entgültigen kompilieren?

Gibt es da ein Tutorial oder so?

Und noch eine Frage:
Was ist, wenn ich Funktionen aus dem Bootloader (wie z.B. Flashzugriff) 
aus dem Hauptprogramm verwenden möchte? An die Funktionen komme ich doch 
nach dem Sprung nicht mehr dran, oder? Die sind dem Programm ja 
unbekannt.

Vielen Dank!

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Holger T. schrieb:
> Das Problem ist nur, dass ich nicht jedes Mal die ganzen Schritte
> durchmachen möchte, um das Ganze ans Laufen zu bringen.
> Ich möchte einfach editieren, compilieren, flashen und debuggen.
> Und das mit einer Eclipse-Umgebung mit OpenOCD.

Dann mach doch. Du musst nur verhindern das beim Flashen des neuen 
Programms der Bootloader gelöscht wird, was OpenOCD IIRC auch 
normalerweise nicht tut
wenn da nicht irgendwelche Programmdaten sind.

Du kannst auch schon währen der Entwicklung die neue Startadresse ins 
Linkerskript eintragen. Dann muss nur ein Bootloader(-stub) im Flash an 
Addresse 0 stehen. Man muss natürlich die Pagegrenzen beachten, Flash 
ist ja immer nur abschnittsweise löschbar.

von Holger T. (holger1979)


Lesenswert?

Hallo Jim,

und der Bootloader-Stub muss dann nur den Sprung machen und den Pointer 
verbiegen? Das klingt sinnvoll.

Wie lege ich diesen Stub an?
Der Code muss ja dann anscheinend in das Hauptprogramm rein, jedoch mit 
dem Hinweis, dass der Stub bei 0x08000000 anfängt.

Also in etwa so ?:
extern void bootloader(void) _attribute_ ((section ("Flash")));

von Gerd E. (robberknight)


Lesenswert?

Holger T. schrieb:
> Ist ein Update vorhanden, so wird dieses auf den STM geflashed, geprüft
> und bei bestandener Prüfung aus dem externen Flash gelöscht.
> Anschließend wird der STM32 resettet.
>
> Ist kein Update vorhanden, also auch nach dem Flashen des Updates, dann
> soll an die Startadresse des Updates gesprungen werden.

Und was ist, wenn der Benutzer oder sonstwer im Moment des Löschens des 
externen Flashs abschaltet? Dann ist dort noch ein "halbes" Programm 
vorhanden. Beim nächsten Start des STM32 wird er sein funktionsfähiges 
Programm damit überschreiben und danach nicht mehr sinnvoll 
funktionieren.

Mein Vorschlag daher:

- Dein Programm wird mit Nullen auf Maximalgröße des Flashs des STM32 - 
4 Bytes aufgefüllt
- In die letzten 4 Bytes kommt eine CRC32 über das gesamte Programm
- Der Bootloader prüft die CRC32 des momentan vorhandenen Programms in 
seinem Flash
- Der Bootloader errechnet die CRC32 des externen Flashs und vergleicht 
sie mit der im externen Flash stehenden Prüfsumme

Wenn errechnete Prüfsumme externer Flash != dort stehender Prüfsumme
-> externer Flash unbrauchbar, abbrechen und lokales Programm starten

Wenn Prüfsumme externer Flash != Prüfsumme lokal
-> Programm aus externem Flash laden und ins lokale flashen. Dabei die 
Prüfsumme am Ende als letztes flashen. Wenn Du zur Sicherheit einen 
Vergleich machst, dann die Prüfsumme erst übernehmen, wenn der Vergleich 
erfolgreich.

von Bernd K. (prof7bit)


Lesenswert?

Holger T. schrieb:
> Wie lege ich diesen Stub an?
> Der Code muss ja dann anscheinend in das Hauptprogramm rein, jedoch mit
> dem Hinweis, dass der Stub bei 0x08000000 anfängt.

Nein, Du flashst Deinen Stub an die Stelle an die später der Bootloader 
kommt. Wenn der Bootloader schon fertig ist kannst Du gleich den 
fertigen Bootloader flashen.

Dann flashst (flasht? SP?) Du Dein Programm (welches bereits mit dem 
modifizierten Linkerscript gebaut wurde) an die Stelle an die es später 
zu liegen kommen soll (der gdb wird das automatisch korrekt erledigen 
wenn Du auf "debuggen" klickst, denn in der .elf steht genau drin wo das 
Speicherabbild des Programms anfängt und wo es hingehört).

Der vordere Teil des Flash mit dem Bootloader (oder dem Bootloader-Stub) 
bleibt dabei unberührt. Das Programm lässt sich dabei auch immer noch 
problemlos debuggen.

Das musst Du so zum funktionieren.bekommen (es wird funktionieren), wenn 
Du das hast ist das schonmal die halbe Miete.

: Bearbeitet durch User
von Holger T. (holger1979)


Lesenswert?

Hallo,

vielen Dank für die Antworten.

@Gerd E. :
Eine Checksumme mit vorheriger und nachträglicher Prüfung wird es 
selbstverständlich geben. Habe es nur nicht so wichtig für den Beitrag 
gehaten.   ;)

@Bernd K. :
OK, dann liegt der Stub (der zuvor geflashed wird) auf 0x08000000 und 
das Hauptprogramm dann dort, wo ich das laut Linkerskript haben will.
Wenn ich MPU starte, dann startet die ja immer bei 0x08000000 und 
springt dann dank Stub in das Hauptprogramm. Das ist OK.
Wenn ich jetzt das Hauptprogramm debugge, an welche Startadresse springt 
GDB? Und wenn direkt ins Hautprogramm gesprungen wird, dann kann ich 
eventuell keine Funktionen mehr aus dem Stub nutzen, oder?

Ich frage mich auch noch, wie das mit der InterruptVektortabelle 
aussieht. Sobald ich die Adresse ändere, kann ich doch sämtliche 
Funktionen des Stubs nicht mehr nutzen? Andersrum, wenn ich die Adresse 
nicht ändere, dann kann ich doch keine Funktionen in meinem 
Hauptprogramm aufrufen? Oder wie sieht das aus?

Muss ich dann eigentlich noch einen Stack leeren beim Spung ins 
Hauptprogramm? Ich könnte mir vorstellen, dass noch Variablen des Stub 
im Speicher rumliegen werden.

Wie ist da das beste Vorgehen beim Sprung, wenn ich
1. Einfach nur von Stub nach Hauptprogramm springe und dabei auch den 
Ramspeicher/Stack frei machen will?
2. vom Stub ins Hauptprogramm springe, sodass ich weiterhin alle 
Funktionen und Variablen aus dem Stub verwenden kann?
3. vom Stub ins Hauptprogramm springe, sodass ich ausgewählte Funktionen 
und Variablen aus dem Stub weiter verwenden kann, ansonsten aber 
Ramspeicher und Stack geleert werden?

von Bernd K. (prof7bit)


Lesenswert?

Holger T. schrieb:
> OK, dann liegt der Stub (der zuvor geflashed wird) auf 0x08000000 und
> das Hauptprogramm dann dort, wo ich das laut Linkerskript haben will.
> Wenn ich MPU starte, dann startet die ja immer bei 0x08000000 und
> springt dann dank Stub in das Hauptprogramm. Das ist OK.

So ist es.

> Wenn ich jetzt das Hauptprogramm debugge, an welche Startadresse springt
> GDB? Und wenn direkt ins Hautprogramm gesprungen wird, dann kann ich
> eventuell keine Funktionen mehr aus dem Stub nutzen, oder?

Der GDB wird einen Reset auslösen und es startet dann im 
Bootloader(-Stub), dann springt es kurz danach von dort an den 
(verschobenen) Start der zu debuggenden Anwendung und wenn Du dort 
irgendwo einen Breakpoint hast (oder auch wenn Du irgendwann mal auf 
Pause klickst) hält es ganz normal dort an und lässt sich ganz normal 
debuggen.

> Ich frage mich auch noch, wie das mit der InterruptVektortabelle
> aussieht.

Der Bootloader muss kurz bevor er an den Start der Anwendung springt der 
CPU mittels des SCB->VTOR Registers mitteilen wo ab jetzt die 
Vektortabelle steht, ich mach das so daß ich zuerst SCB->VTOR auf den 
neuen Wert setze und dann kommt ein kleiner Schnipsel Inline-ASM der 
emuliert was bei einem Reset geschehen würde, also den initialen 
Stackpointer und den initialen Programmzähler von dort liest und dann 
zuerst das SP-Register neu setzt und dann das PC-Register neu setzt und 
in dem Moment in dem ich das PC-Register gesetzt habe (was ja einem 
Sprung gleichkommt) gehts in der Anwendung ganz normal los mit dem 
dortigen Reset-Vektor.

> Sobald ich die Adresse ändere, kann ich doch sämtliche
> Funktionen des Stubs nicht mehr nutzen?

Der Bootloader muss alle seine Interrupts deaktivieren (sofern er 
überhaupt welche benutzt hat) bevor er die Vektortabelle umbiegt.

> Andersrum, wenn ich die Adresse
> nicht ändere, dann kann ich doch keine Funktionen in meinem
> Hauptprogramm aufrufen? Oder wie sieht das aus?

Es geht da nur um Interrupts. Die Vektortabelle des Bootloaders zeigt 
auf die Interrupt-Handler-Funktionen im Bootloader, sofern welche 
verwendet werden. Die Vektortabelle des Hauptprogramms zeigt auf Handler 
des Hauptprogramms.

Wenn die Vektortabelle umgebogen wurde und dann irgendwann später wird 
ein Interrupt ausgelöst dann schaut er an der Position in der neuen 
Tabelle nach und der Eintrag wird irgendwo in den Code der Anwendung 
zeigen.

> Muss ich dann eigentlich noch einen Stack leeren beim Spung ins
> Hauptprogramm?

Siehe oben: Du emulierst das was die CPU auch tun würde wäre es ein 
normaler Reset, nur halt eben mit der neuen Tabelle am neuen Anfang: Die 
ersten beiden long-worte in der Tabelle sind der initiale Stackpointer 
und der initiale Programmzähler (also die Startadresse) so wie sie der 
Linker für nötig befunden und da reingeschrieben hat.Die CPU würde diese 
Werte lesen, in die SP und PC Register laden und dann loslaufen lassen.

Du nimmst nun also das erste uint32_t aus der Tabelle (der Anwendung) 
und schreibst es in den Stackpointer, dann nimmst du das zweite uint32_t 
und schreibst es in den Programmzähler und schwupps in dem Moment (bei 
der nächsten Instruktion) läuft Dein Programm so los als hätte es einen 
Reset gegeben.

: Bearbeitet durch User
von dunno.. (Gast)


Lesenswert?

Holger T. schrieb:
> Ist ein Update vorhanden, so wird dieses auf den STM geflashed, geprüft
> und bei bestandener Prüfung aus dem externen Flash gelöscht.
> Anschließend wird der STM32 resettet.

Wieso löschst du denn jedes mal den externen flash? Und, wie kommt der 
überhaupt ans System?

Ist das ein dongle? Was machst du mit dem dongle wenn du 2 Geräte 
updaten willst, zwischendurch neu flashen? Oder wird der flash eh nur 
eingelötet..?

Falls dongle, würde ich lieber die Version im flash mitcodieren, und 
halt flashen wenn nicht identisch mit der im uc. So kannst du mit dem 
dongle beliebig viele einheiten updaten..?

von Holger T. (holger1979)


Lesenswert?

Hallo Bernd K,

vielen Dank für die ausführliche Ausführung.
Jetzt ist Einiges klarer geworden.

Also vorhandene Funktionen aus dem Bootloader kann ich ganz normal vom 
Hauptprogramm aufrufen (ausgenommen Interrupt Handler)?

Wie emuliere ich den CPU Reset? Was passiert da alles? Hast Du da ein 
Beispiel? Am Ende des emulierten Resets also dann Stackpointer und 
Programmzähler mit den Werten aus der Vektortabelle des Hauptprogramms 
belegen. Frage: Wo steht der Start der Vektortabelle?

von Holger T. (holger1979)


Lesenswert?

Hallo dunno,

den Flash lösche ich nach erfolgreichem Updaten, damit der Bootloader 
den flash nur einmal macht.

Der Flash sitzt auf der Platine und ist per SPI angebunden.
Das Flash wird von außen beschrieben und sobald der Bootloader ein 
update entdeckt wird dies in den internen Flash kopiert und verwendet.

Die Programmversion wird zuvor auch noch geprüft.  ;)

Viele Grüße

von Holger T. (holger1979)


Lesenswert?

Kurze Frage für zwischendurch:

Könnte ich statt mit ASM den Reset zu emulieren auch einfach 
Stackpointer und Programcounter setzen und dann NVIC-SystemReset() 
aufrufen?

von Jim M. (turboj)


Lesenswert?

Holger T. schrieb:
> Stackpointer und Programcounter setzen und dann NVIC-SystemReset()
> aufrufen?

Nach NVIC_SystemReset() startet Dein Bootloader neu. Du wolltest doch 
aber das Hauptprogramm ausführen.

von Bernd K. (prof7bit)


Lesenswert?

Holger T. schrieb:
> Wie emuliere ich den CPU Reset? Was passiert da alles? Hast Du da ein
> Beispiel? Am Ende des emulierten Resets also dann Stackpointer und
> Programmzähler mit den Werten aus der Vektortabelle des Hauptprogramms
> belegen. Frage: Wo steht der Start der Vektortabelle?

So sieht das bei mir aus.
1
#define KILOBYTE                                1024UL
2
#define SECTOR_SIZE                             (1  * KILOBYTE)
3
#define CHIP_TOTAL_FLASH                        (32 * SECTOR_SIZE)
4
5
#define START_APP                               (4  * SECTOR_SIZE)
6
7
8
9
/**
10
 * Prepare everything for running the user
11
 * application and jump to its entry point.
12
 */
13
void jump_app(void) {
14
    SCB->VTOR = START_APP;
15
    asm(" ldr r1, [%0,#0]       \n\t"
16
        " ldr r2, [%0,#4]       \n\t"
17
        " mov sp, r1            \n\t"
18
        " mov pc, r2            \n\t"
19
        ::"r"(START_APP)
20
    );
21
}

Und bevor Du das aufrufst schaltest Du noch ale Deine interrupts wieder 
ab und wenn Du irgendwelche Peripheriegeräte komplett und gefährlich 
verkonfiguriert hast die vielleicht auch wieder auf default 
initialisieren und/oder abschalten bevor Du in die App springst.

Der eigentliche Sprung ist das mov pc, r2 in dem obigen Codeschnipsel.

Das ist ein Cortex-M0 von Freescale bei dem das Flash bei 0x00000000 
anfängt, die Anwendung lade ich nach 0x00001000 (also 4kB nach hinten 
verschoben, bei 0x00000000 sitzt mein Bootloader). Die Vektortabelle 
beginnt immer ganz am Angang des Programms. Normalerweise ist das auch 
adresse 0 und VTOR steht im Normalbetrieb auch auf 0.

START_APP ist bei mir 0x00001000 (also 4k nach hinten geschoben) und 
genau da fängt auch die Vektortabelle der verschobenen Anwendung an, von 
dort lade ich die 8 Bytes für SP und PC.

Bei STM32 ist das gesamte Flash wohl irgendwie unorthodoxerweise nach 
0x80000000 gemappt, er würde also normalerweise nach dem Reset die 
Vektortabelle bei 0x80000000 erwarten und die verschobene Anwendung 
liegt dann bei 0x80000000 plus irgendwas.

Du brauchst also einen entsprechend anderen geeigneten Wert für 
START_APP.

: Bearbeitet durch User
von Holger T. (holger1979)


Lesenswert?

Vielen Dank!

Das hilft mir weiter.

Flash von 32 auf 512 setzen,
start von 4 auf x

und die ldr-Zeilen müssten dann so aussehen:

ldr r1, 0x08000000
ldr r2, 0x08000004

Interrups werde ich im Bootloader keine verwenden.

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.