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
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.
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")));
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.
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
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?
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
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..?
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?
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
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?
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.
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.