Ich habe hier einen E-Bike-Controller, den ich gern über UART flashen würde. Wäre prinzipiell kein Problem, leider hat der Hersteller den Boot0 Pin nicht auf einen Header gelegt und die Platine komplett vergossen. Also möchte ich einen eigenen Bootloader auf die Startadresse legen und mein eigentliches Programm an eine höhere Adresse. Dazu gibt es einige Beispielprojekte, ich habe dieses auf den STM32F031C6 portiert:
Da ist doch schon ein UART Bootloader eingebaut, man muss ihn "nur" starten. Dein Bootloader müsste nur entscheiden, was gestartet werden soll, Hauptprogramm oder integrierter Bootloader. Das darf maximal 42 Byte kosten.
Wenn der NRST Pin raus geführt ist, kann dein Bootloader das RCC_CSR abfragen. Wenn PIN_RSTF nicht gesetzt ist, wird das Hauptprogramm gestartet, sonst der UART Bootloader. Ohne Reset-Pin muss man irgendwas anderes abfragen, aber das Prinzip bleibt gleich.
Obwohl so ein Programm nur ein paar Zeilen hat, findet man vor allem viele misslungene Versuche. Die BootLoaderUniversalAnsprungFunktion() hat auch einige Anläufe gebraucht, aber sie hat am Ende funktioniert:
Da der integrierte Bootloader nicht verwendbar war (UART-Pins anderweitig besetzt) musste ich einen eigenen schreiben, incl. CRC-Berechnung, Software-UART, der nach dem Reset max. 10ms auf ein bestimmtes Muster wartet und sonst in die Applikation springt. Das ging mit etwas Mühe beim G030 in 2kByte, man muss halt das ganze HAL-Zeugs weglassen ...
Hm, der STM32F031c6 ist ein ARM Cortex M0 Prozessor, der anscheinend das Remapping nicht beherrscht:
[q]Physical remap
Once the boot mode is selected, the application software can modify the memory accessible
in the code area. This modification is performed by programming the MEM_MODE bits in
the SYSCFG configuration register 1 (SYSCFG_CFGR1). Unlike Cortex® M3 and M4, the
M0 CPU does not support the vector table relocation. For application code which is located
in a different address than 0x0800 0000, some additional code must be added in order to be
able to serve the application interrupts. A solution is to relocate by software the vector table
to the internal SRAM:
• Copy the vector table from the flash (mapped at the base of the application load
address) to the base address of the SRAM at 0x2000 0000.
• Remap SRAM at address 0x0000 0000, using SYSCFG configuration register 1.
• Then once an interrupt occurs, the Cortex®-M0 processor fetches the interrupt handler
start address from the relocated vector table in SRAM, then it jumps to execute the
interrupt handler located in the flash. [/q]
Prinzipiell steht da ja, was man machen muß, für mich sind das allerdings bisher böhmische Dörfer, da werde ich mich wohl tiefer reinknien müssen.
Wie weit bist du denn mit der Portierung? Im Original wird doch auch das VTOR benutzt? Die Kopie im RAM ist für die Hardware oder auf Assembler-Ebene kein Problem, aber wie sieht das Linker Script dafür aus???
Welchen Compiler bzw. welche IDE benutzt du für das Hauptprogramm?
Ich glaube, es wird einfacher, wenn es nur ein Programm gibt. Dann liegt die Vectortable ganz normal im Flash auf 0. Das normale Hauptprogramm entscheidet dann, ob es selbst starten soll oder ob es zum eingebauten UART-Bootloader springt.
Diese Entscheidung könnte theoretisch ein Menüpunkt in der normalen Benutzeroberfläche sein. Aber ich würde auf jeden Fall den NRST-Pin o.ä. abfragen. Wenn der Code dafür erstmal funktioniert, sollte der Mechanismus doch genauso unkaputtbar sein wie ein getrennter Bootloader. Es sind ja nur wenige Zeilen im crt0 aka reset_handler aka startup_xxx.s.
SYSCFG->CFGR1|=SYSCFG_CFGR1_MEM_MODE_0;//System Memory auf Adresse 0 remappen
Das ist die Einstellung für System Memory, also für den internen Bootloader, Wenn du den starten willst, musst du nichts kopieren. Es geht auch garnicht, weil ab 0 Flash ist (eben System Memory). Für den Bootloader braucht man auch kein VTOR.
Für RAM ab 0 müssten beide CFGR Bits gesetzt sein. Aber dazu passt die Flash-Adresse der Kopie nicht. 0x08000000 wäre ja dein Bootloader.
Was hast du jetzt genau vor?
Ich arbeite mit der Cube IDE. Ich konnte nicht mal herausfinden wo
.RAMVectorTable definiert ist, aber er kennt es anscheinend..
Nachdem der Name nur für die section gebraucht wird, muss er den nicht kennen. Der Linker legt einfach eine section mit dem Namen an.
Ich möchte einfach nur auf Befehl in den nativen Bootloader springen,
Aus dem normalen Hauptprogramm heraus? Das auf 0x08000000 beginnt?
Dann befindet sich die Vectortable ja automatisch auf der richtigen Adresse und es funktioniert ohne VTOR und du musst nichts kopieren.
Kann ich dann nicht einfach an die Bootloaderadresse 0x1FFFEC00
springen?!
Du musst nicht einmal die genaue Adresse kennen, die steht ja im Flash (System Memory) und das wird mit dem SYSCFG.CFGR nach 0 umgeschaltet. Danach steht die Adresse für den Sprung auf 0x00000004. Das kleine Assemblerprogramm liest die und springt zum Bootloader.
Das kleine
Assemblerprogramm liest die und springt zum Bootloader.
Hm, funktioniert aber nicht. Wenn ich das
1
SysTick->CTRL=0;
2
SysTick->LOAD=0;
3
SysTick->VAL=0;
weglasse, bootet der Prozessor sofort neu in das normale Programm.
Mit den drei Zeilen stoppt zwar die "normale" UART Kommunikation, aber CubeProgrammer kann sich trotzdem nicht mit dem Prozessor verbinden:
06:44:29 : Serial Port COM6 is successfully opened.
06:44:29 : Port configuration: parity = even, baudrate = 115200, data-bit= 8, stop-bit = 1.0, flow-control = off
06:44:31 : Timeout error occured while waiting for acknowledgement.
06:44:31 : Error: Activating device: KO. Please, verify the boot mode configuration and check the serial port configuration. Reset your device then try again...
bootet der Prozessor sofort neu in das normale Programm.
Immerhin, es könnte schlimmer sein, vielleicht fehlt nur eine kleine Kleinigkeit. Aber auch dafür wäre es seht hilfreich, wenn wir hier dein aktuelles Programm anschauen könnten.
Besonders interessant ist natürlich deine Datei mit dem Assemblertext, und main, und die startup Datei. In dem github-Projekt heißt die startup/startup_stm32f100xb.s; weiß jemand, wo Cube den entsprechenden Quelltext versteckt?
Wenn du die nicht findest: die ELF-Datei, die du zum Flashen benutzt hast, wäre ein guter Ersatz.
ich habe das ganze für einen STM32F072 umgesetzt, dazu hier ein paar Code-Schnipsel, ich hoffe sie helfen weiter.
Für die Vektortabelle muss man im Linkerskript (z.B. STM32F072C8TX.ld im CUBE-Projektverzeichnis) eine zusätzliche Section am Anfang des RAM anlegen.
Die muss die erste RAM-Section sein, also vor der .data-Section:
Das muss sowohl im (Boot)Loader als auch in der eigentlichen Applikation gemacht werden, damit die Vektortabelle dort jeweils an der richtigen Stelle liegt.
1
/* zus?tzliche Section f?r Vektortabelle im RAM: */
2
3
.isr_vector_ram(NOLOAD):
4
{
5
.=ALIGN(4);
6
KEEP(*(.isr_vector_ram))/* Startup code */
7
.=ALIGN(4);
8
}>RAM
Die Vektortabelle dann folgendermaßen im RAM definieren (main.c):
1
#define VECTOR_TABLE_SRAM
2
#define VECTOR_TABLE_LENGTH 48 // Länge der Interrupt-Vektortabelle, siehe Startup-Code.
#define BOOTLOADER_FLASH_SIZE_KB 12 // für Bootloader reservierter Speicher. Muss ein Vielfaches der Pagegröße sein!
5
6
#define VECTOR_TABLE_ARRAY_SIZE (VECTOR_TABLE_LENGTH+1) // hinter Vektortabelle noch 1 Platz mehr für Variable für Bootloader-Request
7
8
// Muss mit Angaben im Linkerskript der Applikation übereinstimmen. Linkerskript des Bootloaders idealerweise auch anpassen, damit es keine Überlappung geben kann.
9
#define APPLIKATION_STARTADRESSE (0x08000000 + BOOTLOADER_FLASH_SIZE_KB * 1024) // Platz für Bootloader freihalten
10
11
#ifdef VECTOR_TABLE_SRAM
12
// Die Interrupt-Vektortabelle im RAM. Dort müssen nachher die Vektoren der Applikation hinkopiert werden
Ich habe hinter der Vektortabelle noch einen zusätzlichen Eintrag freigelassen, damit kann das Hauptprogramm dort eine Magic Number reinschreiben und anschließend einen RESET auslösen. Der Bootloader kann dann auf diese Nummer prüfen und ggf. den ST-Bootloader starten. So kann der Bootloader indirekt aus dem Hauptprogramm gestartet werden
1
staticuint32_tcheck_backup_reg_bootloader()
2
{
3
uint32_tret=0;
4
uint32_tbkup_reg;
5
6
// reservierte RAM-Variable laden
7
bkup_reg=VectorTable[VECTOR_TABLE_ARRAY_SIZE-1];
8
9
if(bkup_reg==MAGIC_NUMBER_START_ST_BOOTLOADER)// ST-Bootloader starten, vorher aber Nummber löschen, sonst Endlosschleife.
Allerdings habe ich die nicht funktionierende Funktion noch nicht commited. Kann ich heute Abend mal machen und auch die elf Datei mit hochladen.
@emigraen: das muss ich erst mal sacken lassen 😁 Ich bin gestern über ähnliche Tipps gestolpert, aber mir fehlt da offensichtlich eine Menge Hintergrundwissen. Ich will ja gar keinen custom Bootloader verwenden, wenn es auch mit dem nativen geht...
Ich will ja gar keinen custom Bootloader verwenden,
wenn es auch mit dem nativen geht...
Musst du auch nicht, dann vereinfacht es die Sache deutlich.
Dann brauchst du die Vektortabelle im RAM garnicht, sondern musst einfach direkt aus deinem Hauptprogramm meine Beispielfunktion aufrufen:
1
start_app_or_bootloader(0);
Die entsprechenden Code-Teile zum Umkopieren der Tabelle kannst du dort dann rauslöschen und das Linkerskript muss dann auch nicht angepasst werden.
Der Vorteil an einem kleinen Custom-Bootloader wäre, dass er prüfen könnte ob überhaupt eine Applikation geflasht ist und dann im Notfall immer den ST-Bootloader anspringt. (Er braucht gar keine eigene Flash-Funktion haben, sondern springt nur weiter entweder zur Applikation oder zum ST-Bootloader.)
Die entsprechenden Code-Teile zum Umkopieren der Tabelle kannst du dort
dann rauslöschen und das Linkerskript muss dann auch nicht angepasst
werden
Das habe ich jetzt auch gemacht, natürlich die Bootloaderadresse angepasst, aber das geht auch nicht :-(
Wenn ich im Debugmode die Routine anspringe kommt diese Meldung:
Break at address "0x2050604" with no debug information available, or outside of program code.
Ich habe mit den verschiedensten Kombinationen der vorgeschlagenen Lösungen rum gespielt, darum nicht über die vielen auskommentierten Zeilen wundern.
Ich glaube, es wird einfacher, wenn es nur ein Programm gibt. Dann liegt
die Vectortable ganz normal im Flash auf 0. Das normale Hauptprogramm
entscheidet dann, ob es selbst starten soll oder ob es zum eingebauten
UART-Bootloader springt.
Das ist genau das Gegenteil von einem guten Bootloader. Von so einem sollte das zu ladende "Hauptprogramm" überhaupt nichts wissen müssen (allerhöchstens, dass es etwas weniger Flash zur Verfügung hat als es "normal" für das Target ist, damit beim Linken eine entsprechende Fehlermeldung geworfen werden kann).
Und wenn es nötig ist, das aus dem "Hauptprogramm" den Bootloader zu starten, dann sollte es reichen, den µC einfach von dort aus zu zu resetten. Wenn die Architektur keine Softwarelösung dafür hergibt (eher die Ausnahme), dann halt notfalls über einen GPIO an RESET (mit entsprechender Hilfsschaltung dazwischen, sprich: Monoflop).
Das habe ich jetzt auch gemacht, natürlich die Bootloaderadresse
angepasst, aber das geht auch nicht :-(
Es fehlt (in deinem auskommentierten Code) mindestens das Remapping des System Memory auf Adresse 0 wie in meinem Code oben geschrieben:
1
__DSB();// memory access barrier
2
__HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH();// remap system memory to address 0:
3
__DSB();
Ohne das funktioniert es nicht.
Es kann auch potenziell problematisch sein (sollte mit dem Fehler wsl. nichts zu tun haben), wenn Teile der HW initialisiert sind wenn man zum ST-Bootloader springt. Man weiß nicht, wie genau er initialisiert, also sollte vorher alles zurückgesetzt sein. Am einfachsten lässt sich das sicherstellen, wenn man vor dem Init von Hardware zum Bootloader springt.
Ich mache das so, dass ich eben die Magic Number nach der Vektortabelle ganz am Anfang der main() prüfe und ggf. springe, noch bevor Takt usw. initialisiert wird. Dann kann man nichts vergessen zu de-initialisieren.
Versuch mal testweise, ganz am Anfang der main() zu springen, um solche Probleme auszuschließen.
Break at address "0x2050604" with no debug information available, or
outside of
program code.
0x2050604 ist wohl tatsächlich ungültiger Speicherbereich, siehe Memory Map im Datenblatt. Also springst du irgendwo falsch hin.
Mit Assembler kann ich leider nicht weiterhelfen, aber im C-Code kannst du ja die Sprungadresse (Variable JumpAddress) im Debugger anschauen, bevor der Sprung ausgeführt wird. Dass nach dem Sprung das Debuggen nicht mehr geht, sollte normal sein, da dann ja der ST-Bootloader ausgeführt wird.
https://www.st.com/resource/en/reference_manual/rm0091-stm32f0x1stm32f0x2stm32f0x8-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
Nachtrag: Denkbar wäre auch, dass noch Interrupts aktiviert sind, wenn du nicht alles sauber deinitialisierst. Wenn in der Vektortabelle des ST-Bootloaders dann kein gültiger Handler eingetragen ist, springt er evtl. in ungültigen Bereich. Also noch ein Argument, möglichst früh im Programm dorthin zu springen. (Oder einmal den Umweg über RESET zu gehen.)
du ja die Sprungadresse (Variable JumpAddress) im Debugger anschauen
Die Adresse passt und das remap system memory to address 0 hatte ich auch gemacht. Interrupts deaktivieren auch. Direkt am Anfang der main() auch nicht. Ich glaub ich brauch erst mal ne Pause :-)
Vielleicht probiere ich dann erst mal den BOOT0 PIN auf GND zu legen, ob der STCube Programmer dann die Verbindung hinbekommt...
An den Boot0 Pin ist nicht ranzukommen :-(
Gibt es eigentlich einen Timeout, wie lange der Bootloader auf den Programmer wartet? Und was hat es mit dem Magic Number auf sich, von dem immer die Rede ist?!
except for the STM32F0 and some STM32L0 that have an empty check mechanism in place.
Im reference manual steht:
Empty check
On STM32F04x and STM32F09x devices only, internal empty check flag is implemented to
allow easy programming of virgin devices by the boot loader
Damit sollte der STM32F031C6 diesen empty check nicht haben?!
Edit:
ich habe das o.g. How-To so eingebaut, siehe letztes commit, wenn ich jetzt im Debug-Mode den Prozessor anhalte, tummelt er sich irgendwo im Adressbereich 0x1fffed72, 0x1fffed68, 0x1fffed7c, sollte also eigentlich passen. Aber warum reagiert er nicht auf den Verbindungsversuch vom Cube Programmer?!
Vielleicht bringt mich das auf die richtige Spur?!
Da geht's doch darum, wie man den Bootloader aus dem laufenden Hauptprogramm heraus aufruft. Das meiste davon ist überflüssig, wenn das Hauptprogramm garnicht läuft. Also wenn du diese Abfrage noch vor main() einbaust:
Gibt es eigentlich einen Timeout, wie lange der Bootloader auf den
Programmer wartet?
Der eingebaute wartet beliebig lange, bis zum nächsten Reset.
Und was hat es mit dem Magic Number auf sich, von dem
immer die Rede ist?!
Der entscheidende Punkt: beim Start des Bootloaders müssen die Hardware-Register so aussehen, wie nach einem Reset. Dafür gibt es drei Möglichkeiten:
a) man startet den Bootloader so früh wie möglich, bevor irgendwelche Hardware initialisiert wird
b) man setzt alle einzeln zurück
c) das Hauptprogramm erzeugt selbst einen Hardware-Reset per AIRCR. Nachdem das Programm wieder startet, muss es erkennen, ob das ein normaler Reset war oder der selbst erzeugte. Dafür kann man eine Magic Number ins RAM oder in ein Backup-Register schreiben. Wenn da die magische Bitkombination steht, wird zum Bootloader gesprungen, sonst zum Hauptprogramm. Man kann stattdessen auch das RCC.CSR Register auswerten. Das ist genau dafür da, um verschiedene Reset-Auslöser zu unterscheiden.
Hm, habe noch mal viel gelesen und probiert, hat immer noch nicht geklappt. Ich werde das Ganze als nächste an einem Bluepill-Board ausprobieren um zu schauen, ob es ggf. an einer Eigenart des STM32F031 liegt...
Aaaahhh, ich bin so ein Depp! Die Hardware, die ich nutze, hat UART1 auf PB6/PB7 remapped. Dann kann das natürlich nicht gehen mit dem nativen Bootloader. :-)