Hallo,
verstehe ich es richtig, dass durch die Boot Pins veranlasst wird, dass
der System Memory in 0x1fff0000 - 0x1fff77ff auf die Adresse 0x00000000
gemappt wird?
Heißt das, ich kann meinen Bootloader wie ein gewöhnliches Programm
schreiben und muss es nur an die Position 0x1fff0000 schreiben und aus
Sicht des µC ist es das normale Programm an Adresse 0x00? Inkl.
Interrupt Vectoren?
Dann muss ich, wenn der Bootloader beendet wird, dem µC ja auch
mitteilen, dass er jetzt an Adresse 0x00 ohne Bootpin starten soll oder?
Führe ich dann den Code ab Adresse 0x08000000 aus, inkl. verbiegen der
Interrupt Vectoren? Oder resete ich quasi irgendwie den µC, dass jetzt
aber nicht der System Memory gemappt wird? Ich glaube über SYSCFG kann
ich im Betrieb das Mapping wieder ändern?
im NVIC gibt es ja ein Register zum mappen der IRQ. Die Positionierung
des Bootloaders im Flash mache ich bestimmt mit einem Linkerkommando
oder gleich das Linker-Script?
Mein Prozessor ist ein STM32F413VGT6.
Danke für eure Hilfe!
Ich habe mal einen Bootloader für stm32f411 (mit CubeMX und gcc)
geschrieben. In der CubeMX repository befindet sich ein Beispiel für
einen Bootloader.
bei mir (Ubuntu) wars zu finden unter:
~/STM32Cube/Repository/STM32Cube_FW_F4_V1.16.0/Projects/STM324xG_EVAL/Ap
plications/LwIP/LwIP_IAP
Das war aber tückisch, habe mehrere Tage debuggen müssen, um
herauszufinden was am User Programm dann zu beachten ist.
Bootloader wird als normales Programm geschrieben. Startadresse
0x8000000.
zu ladendes Anwendungsprogramm: Startadresse bei mir 0x8010000
dann den linkerfile entsprechend anpassen
und jetzt kommt das merkwürdige:
im sourcefile system_stm32f4xx.c, das von CubeMX generiert wird, musst
Du noch einen offset anpassen
Zeile suchen:
#define VECT_TAB_OFFSET 0x00
jetzt umdefinieren in 0x10000 (bei mir startete das User programm bei
0x8010000, musst Du entsprechend anpassen)
Wenn Du das nicht machst laufen keine interrupts mehr, nur blinky geht.
Das Ganze wird in einer UM17xx von STM beschrieben. Was die sich bei STM
dabei gedacht haben ist mir allerdings schleierhaft. Startadresse des
Anwendungsprogramms als offset (!) im Anwendungsprogramm hardcoded in
der source notwendig. Portabel ist anders.
Das Thema hatten wir doch schon mal.
Es wäre besser das dem CubeMX Schöpfer mitzuteilen.
Weiß nicht mehr genau wo, aber der hat in einem Beitrag auf Anfragen und
Kritik zu CubeMX reagiert und die Vorschläge in der nächsten Version
eingebracht.
pegel schrieb:> Es wäre besser das dem CubeMX Schöpfer mitzuteilen.
Oh, das wird dann aber ein längerer Erguss.
Bin recht enttäuscht von CubeMX. Idee ist ja gut, aber performance . . .
Wenn ich den vorprogrammierten Bootloader nicht verändern kann verstehe
ich es so, dass ich meinen Bootloader ganz normal auf den Prozessor
lade.
Für diesen sind z.B. die ersten 16kByte reserviert. Das eigentliche
Programm startet erst ab der Adresse 0x4000.
Wenn der Bootloader, welcher dann ja immer zuerst startet ab Adresse
0x00, entscheidet, dass das Hauptprogramm gestartet werden soll, springt
dieser zur Adresse 0x4000.
Das Hauptprogramm muss dann nur noch die IRQ Tabelle umbiegen. Geht
diese Vorangehensweise?
Ja, so geht das schon. Die Linkerfiles müssen halt zusätzlich passen,
für Bootloader und Applikation.
Interrupts vor dem Sprung abschalten und den Watchdog zumindest länger
stellen hat mir geholfen.
Im Prinzip ja.
Bootloader und Programm müssen auf die Adressen 0x08000000 bzw.
0x08004000 gelinkt werden müssen. Die Adressen 0x0 und 0x4 werden nur
unmittelbar nach dem Reset von der CPU benutzt um SP und PC zu
initialisieren. 0x4000 wird nicht benutzt.
Der Bootloader muss nicht zu 0x4000 und auch nicht zu 0x08004000
springen, sondern zu der Adresse, die in 0x08004004 steht.
Der Stack Pointer muss mit dem Inhalt von 0x08004000 geladen werden. Das
kann der Bootloader oder die crt0 des Hauptprogramms machen. Wenn du
eine vorgefertigte crt0 benutzt, solltest du das heraus finden, weil,
wenn die das macht, kannst du den Bootloader zu 100% in C schreiben.
Die Vektortabelle des Hauptprogramms kann irgendwo stehen, sie muss nur
auf 512-Byte aligned sein (oder 1024- oder 2048-Byte, je nach NVIC). Das
Hauptprogramm könnte sogar die vom Bootloader mit benutzen wenn der
keine Interrupts braucht.
stromverdichter schrieb:> Interrupts vor dem Sprung abschalten und den Watchdog zumindest länger> stellen hat mir geholfen.
würde ich schärfer formulieren. (Alle Einträge in der IVC bekommen ja
praktisch neue Behandlungsroutinen)
Alles, was Du vor dem Sprung an Hardware initialisiert hast, wieder
deinitialisieren. Dann erst springen.
http://www.springer.com/de/book/9783658148492 ,
runterscrollen auf Zusatmaterial, herunterladen, Kapitel 9.
Beschreibung der Internals im Buch.
Man kann den internen BL für sich mitnutzen, sehe ich aber äusserst
selten. Normalerweise ist der gelieferte Bootloader resident und per
Jumper aktivierbar, um im Notfall (zerschossener Applikationsbootloader)
eine Notfallebene zu haben sowie der Produktion eine Hilfe zum Flashen
des Urimages zu geben. Ist äusserst praktisch!
Frohe Feiertage!
Nils N. schrieb:> Dann muss ich, wenn der Bootloader beendet wird, dem µC ja auch> mitteilen, dass er jetzt an Adresse 0x00 ohne Bootpin starten soll oder?>> Führe ich dann den Code ab Adresse 0x08000000 aus, inkl. verbiegen der> Interrupt Vectoren?
Deinen normalen Code kannst du getrost ab Adresse 0 beginnen lassen.
Alles was im eingebauten User-Flash (ab 0x08000000) steht, ist auch ab
Adresse 0 les- und ausführbar.
Lediglich dann, wenn man den eingebauten Bootlader benutzen will und
deshalb die "BOOT"-Pin(s) beim Reset entsprechend setzt, wird der
Bootlader ab Adresse 0 eingeblendet. Dann kann logischermaßen auf den
Flash nur über den Adreßbereich ab 0x08000000 zugegriffen werden.
Aber im Normalfall brauchst du dich um den Bereich ab 0x08000000 nicht
zu kümmern. Deinen Code kannst du getrost ab 0 linken.
W.S.
W.S. schrieb:> Deinen normalen Code kannst du getrost ab Adresse 0 beginnen lassen.> Alles was im eingebauten User-Flash (ab 0x08000000) steht, ist auch ab> Adresse 0 les- und ausführbar.> Aber im Normalfall brauchst du dich um den Bereich ab 0x08000000 nicht> zu kümmern. Deinen Code kannst du getrost ab 0 linken.
Was wäre der Vorteil vom Code ab 0? Normalerweise will man auf Adressen
unterhalb von 0x08000000 garnicht zugreifen und Zugriffe darauf per MPU
verbieten.
Der eingebaute Bootloader hat zwei entscheidende Vorteile: das Flashen
funktioniert mit jedem Chip, das Programm auf dem PC muss nicht die
verschiedenen Flash-Algorithmen kennen. Und man kann ggf. den
SWD-Stecker weglassen und die Pins für etwas nützliches nutzen.
Also, ich bleibe bei 0x08000000...
Ich nochmal. Wenn ich mit folgendem Code ein Offset von 10 für die IRQ
Tabelle vorgebe und dann an die Adresse 0x0800000A mein Programm flashe,
wird dieses nicht korrekt ausgeführt. Auch wenn ich gar keine Interrupts
verwende.
Was mache ich da falsch? Ich mache vorher einen Chiperase, es sollte
dann ja überall vorher 0xFF stehen.
IRQ
Nils N. schrieb:> Ich nochmal. Wenn ich mit folgendem Code ein Offset von 10 für die> IRQ> Tabelle vorgebe und dann an die Adresse 0x0800000A mein Programm flashe,> wird dieses nicht korrekt ausgeführt. Auch wenn ich gar keine Interrupts> verwende.>> Was mache ich da falsch? Ich mache vorher einen Chiperase, es sollte> dann ja überall vorher 0xFF stehen.
Der Offset braucht immer ein bestimmtes alignment. Hängt vom konkreten
chip ab.
Sprich Offset von 10 geht nicht. Beim STM32F411 sind es z.B. 512 byte
alignment. Also Offset muss durch 512 teilbar sein.
Ok, das sehe ich auch grade, dass der Wert "1" 128 Wörtern entspricht,
also der Adresse 0x08000100.
Aber auch ohne Benutzung der Interrupts wird mein Code nicht mehr
ausgeführt, wenn ich diesen an 0x08000100 schreibe und vorher den Flash
lösche. Es handelt sich um ein simples LED-Hello World.
Nils N. schrieb:> Ok, das sehe ich auch grade, dass der Wert "1" 128 Wörtern> entspricht,> also der Adresse 0x08000100.>> Aber auch ohne Benutzung der Interrupts wird mein Code nicht mehr> ausgeführt, wenn ich diesen an 0x08000100 schreibe und vorher den Flash> lösche. Es handelt sich um ein simples LED-Hello World.
Ok,
raffe gerade nicht, was Du gerade probierst.
Wenn der STM aus dem reset kommt erwartet er eine Sprungtabelle ab
Adresse 0x8000000. Das ist die Sprungtabelle Deines bootloaders.
Dein Anwendungsprogramm liegt beispielweise bei Adresse 0x8010000. Dort
brauchst Du die Sprungtabelle Deines Anwendungsprogramms. Wenn Du das
lädst, darfst Du den bootloader natürlich nicht löschen. Also keinen
chip erase machen. Sondern nur den Teil des flash löschen, den dein
Anwendungsprogramm braucht. Den Teil sollte Dein bootloader Programm
bewerkstelligen.
Ich versuche ledichlich erstmal ein Programm an eine andere Adresse zu
schreiben, manuell ohne irgendeinen Bootloader.
LED-Hello World Programm an Adresse 0x08000100 schreiben.
Der STM32 bootet und mappt 0x08000100 an Adresse 0x00000000. Dann kommt
an Adresse 0x00 bis 0xff nur "nop" und dann das Programm. Ich benutze da
noch gar keine Interrupts, aber bereits das LED blinken wird nicht
ausgeführt.
Nils N. schrieb:> LED-Hello World Programm an Adresse 0x08000100 schreiben.
Das alleine reicht nicht. Damit es funktioniert muss es auch
für diese Basisadresse compiliert bzw gelinkt sein.
STM Apprentice schrieb:> Nils N. schrieb:>> LED-Hello World Programm an Adresse 0x08000100 schreiben.>> Das alleine reicht nicht. Damit es funktioniert muss es auch> für diese Basisadresse compiliert bzw gelinkt sein.
das reicht auch noch nicht.
An den ersten Stellen ist nämlich kein ausführbarer Code, sondern eine
Tabelle. Die fängt an mit dem Inhalt des Stackpointers, dann der Adresse
des ausführbaren Programms, dann interrupt Vektoren.
Ich befürchte, TO muss noch einiges lesen.
Wenn die CPU aus dem Reset kommt, erwartet sie vernünftige Werte an der
Stelle 0x8000000. Es sei denn, man benutzt die internen Bootloader des
STM
Also:
In den Systemspeicher (also an die Stelle des internen Bootloaders)
kannst du nicht schreiben, da es ein Nur-Lese-Speicher (sprich ROM) ist.
Der interne Bootloader ist für alle STM32-Controller fest
einprogrammiert.
Einen eigenen Bootloader kompilierst du vorzugsweise für die übliche
Flash-Startaddresse 0x08000000. In diesem Bootloader machst du zunächst
deine ganze Boot-Magie (z.B. Systemcheck, Application-Image verifizieren
o.ä.), sofern notwendig. In diesem Bootloader definierst du bereits den
Offset zur Startaddresse der Vektortabelle deines Anwendungsprogramms
(z.B. 0x08004000). Wenn ich mich recht erinnere, wird VECT_TAB_OFFSET
relativ zur Basisaddresse angeben, hier also 0x4000. Beachten musst du
hierbei, dass die Vektortabelle immer an einer bestimmten Bytegrenze
ausgerichtet sein muss (je nach Controller bei Vielfachen von 512, 1024
oder 2048 Byte; das musst du im Datenblatt nachsehen). Bevor nun der
Sprung zum Anwendungsprogramm erfolgt, musst du sämtliche initialisierte
Peripherie deinitialisieren sowie alle Interrupts abschalten. Das
Anwendungsprogramm soll ja ein System vorfinden, so als ob vorher nie
was anderes gelaufen ist (quasi ein vorgegaukelter Kaltstart).
Dein Anwendungsprogramm entwickelst du nun, wie du es sonst auch gewohnt
bist, mit einer Änderung: in deinen Linker-Einstellungen musst du dein
Programm nun bei der vorher festgelegten Startaddresse 0x08004000
starten lassen und natürlich die verfügbare Flash-Größe um diese 0x4000
Bytes reduzieren.
Nun noch den Bootloader an die Addresse 0x08000000 und dein
Anwendungsprogramm fortan an 0x08004000 flashen und fertig ist die
Laube. Natürlich solltest du beim Chip Erase darauf achten nicht die
Sektoren mit deinem Bootloader mitzulöschen ;)
Gruß
A.. P. schrieb:> Dein Anwendungsprogramm entwickelst du nun, wie du es sonst auch gewohnt> bist, mit einer Änderung: in deinen Linker-Einstellungen musst du dein> Programm nun bei der vorher festgelegten Startaddresse 0x08004000> starten lassen und natürlich die verfügbare Flash-Größe um diese 0x4000> Bytes reduzieren.
Wenn ich mein LED Blink Programm, welches noch komplett ohne Interrupts
arbeitet über das Linkerscript von 0x8000000 auf 0x8000100
umpositioniere arbeitet es nicht, sprich: kein LED blinken. Auch nicht
beim vorherigen Chip erase.
Im generierten Hex sehe ich auch, dass nun korrekt alles an 0x8000100
gelinkt wurde.
Sollte der Prozessor bei einem vorherigen Chiperase nicht bis 0x8000100
durchgehen und dann mein korrekt gelinktes Programm ausführen, z.B. wenn
der Bootloader noch gar nicht programmiert ist?
Meinn Programm ist klein und simpel (noch für einen STM32F407, weil ich
dafür ein Discoveryboard hoer habe):
1
#include"stm32f4xx.h"
2
3
#define T_OUTPUT 0x01
4
#define MODE(a,b) ((b)<<(2*a))
5
#define SET(a) (1<<(a))
6
7
#define F_CPU 16000000
8
9
volatilestaticuint16_ttemp=0;
10
11
intmain(void){
12
//SCB->VTOR = (1UL<<9);
13
14
RCC->APB1ENR|=RCC_APB1ENR_TIM6EN;
15
RCC->AHB1ENR|=RCC_AHB1ENR_GPIODEN;
16
17
GPIOD->MODER|=MODE(12,T_OUTPUT);
18
GPIOD->MODER|=MODE(13,T_OUTPUT);
19
GPIOD->MODER|=MODE(14,T_OUTPUT);
20
GPIOD->MODER|=MODE(15,T_OUTPUT);
21
22
TIM6->PSC=256;
23
TIM6->ARR=F_CPU/256;
24
TIM6->CR1|=TIM_CR1_CEN;
25
26
while(1){
27
if(TIM6->SR&TIM_SR_UIF){
28
TIM6->SR&=~TIM_SR_UIF;
29
GPIOD->ODR^=SET(14);
30
GPIOD->ODR^=SET(15);
31
}
32
}
33
}
Ich habe auch mal ein zweites Programm zusätzlich reinprogrammiert als
Bootloader Ersatz, welches einfach nur an die Adresse 0x8000100 springen
soll. Das geht leider auch nicht:
>>Sollte der Prozessor bei einem vorherigen Chiperase nicht bis 0x8000100>>durchgehen und dann mein korrekt gelinktes Programm ausführen, z.B. wenn>>der Bootloader noch gar nicht programmiert ist?
So ein Verhalten kenne ich nur vom AVR. Da ist zufällig(?) 0xff ein NOP.
Das ist beim ARM komplett anders. Der erwartet ab 0x8000000 eine
Tabelle, keinen ausführbaren Code.
Bei Deinem Beispiel ab 0x8000000 stehen erstmal 0xff macht der
folgendes:
er lädt 0xffffffff in sein Stackpointer (aber da ist gar kein RAM)
dann lädt er 0xffffffff in ein Register und springt an diese Adresse
0xffffffff, (aber da ist gar kein Code.)
So springe ich aus dem bootloader in das applikationsprogramm und das
funzt
/snip
/* Jump to user application */
JumpAddress = *(__IO uint32_t*) (MY_PROGRAM_START_ADDRESS + 4);
Jump_To_Application = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) MY_PROGRAM_START_ADDRESS);
Jump_To_Application();
/* do nothing */
while(1);
/snap
Der Code holt sich aus dem Applikationsprogramm die Adresse für den
Stackpointer und die Einsprungpunkt für das Programm und springt dann
dahin.
Falls Dein Applikationsprogramm den VTOR nicht setzt, musst Du den auch
noch vor dem JumpTo_Application() setzen.