Eine detaillierte Erläuterung des STM32 -Speicherzuordnungs- und Bootloading -Vorgangs.
Während alle folgenden Beispiele für STM32F446 gelten, gelten die Grundprinzipien für die meisten MCUs.
STM32CUBEMX erzeugt STM32F446RETX_FLASH.LD, die wir als Referenz für alle unsere Erkundungen betrachten werden.
Der Linker nimmt die vom Compiler erzeugten Objektdateien auf und generiert die endgültige Kompilierungsausgabe, die in unserem Fall das .LEL -Binary ist. Der Linker verwendet immer eine Linker -Skriptdatei. Auch wenn Sie keines angeben, wird ein Standardskript verwendet.
Es ist wichtig zu beachten, dass das Linker -Skript den Speicher nur basierend auf den MCU -Spezifikationen beschreibt und keine Hardware -Speicher -Adressierung verändert.
Wir können nicht über den Bootloaderprozess sprechen, ohne die Speicherstruktur zu verstehen. Tatsächlich ist es der Zweck des Bootloaders, den Speicher in einem Zustand bereit zu haben, main() unserer Anwendung auszuführen.
Programmspeicher, Datenspeicher, Register und E/A -Ports in STM32F4 sind im selben linearen Adressraum organisiert.
...
+ -- -- 0x2001FFFF -- -- +
| |
| RAM |
| |
+ -- -- 0x20000000 -- -- +
| ... |
+ -- -- 0x1FFF7A0F -- -- +
| |
| System |
| |
+ -- -- 0x1FFF0000 -- -- +
| ... |
+ -- -- 0x081FFFFF -- -- +
| |
| Flash |
| |
+ -- -- 0x08000000 -- -- +
| ... |
+ -- -- 0x001FFFFF -- -- +
| |
| Alias |
| |
+ -- -- 0x00000000 -- -- + Wo Alias -Speicher auf Blitz-, System- oder RAM -Speicher hinweist, abhängig vom BOOT0 . Standardmäßig ist es Blitz.
Zwei Räume sind für uns vorerst von Interesse:
In der Praxis könnte die funktionale Teilung jedoch variieren. Beispielsweise müssen Sie möglicherweise Binärdaten in RAM laden oder umgekehrt.
Die Speicherstruktur wird in der Linkerdatei widerspiegelt. Flash beginnt bei ORIGIN = 0x8000000 und RAM bei ORIGIN = 0x20000000 .
// linker file
MEMORY
{
RAM ( xrw ) : ORIGIN = 0x20000000 , LENGTH = 128 K
FLASH ( rx ) : ORIGIN = 0x8000000 , LENGTH = 512 K
}Beachten Sie, dass dies nur eine Standardimplementierung ist. Sie können Flash und RAM problemlos teilen und Ihre eigenen Blöcke oder Abschnitte hinzufügen, sobald sie die MCU -Speicherspezifikation einhalten.
Der Speicher wird dann in die Linkerdatei in Abschnitte aufgeteilt, wobei jeweils ihre Adresse und das Ziel (z. B. Flash oder RAM) über die Adresse und das Ziel verfügt.
// linker file
SECTIONS
{
...
. text :
{
...
} > FLASH
...
}Um zu erkunden .
arm-none-eabi-objdump -t stm32-boot-explained.elf | sortDas Programm erzeugt die Ausgabe, wie aus den Details im Handbuch beschrieben:
Address Flag_Bits Section Size NameDer kompilierte Programmcode geht in diesen Abschnitt im Flash -Speicher ein.
Der Abschnitt startet von der ORIGIN des Flashs und _etext zeigt auf die letzte Adresse des Abschnitts.
08000000 l d . text 00000000 . text
08000 c00 g . text 00000000 _etextWir können den Ort der Funktionen sehen, die wir in unserer Bootloader -Datei verwenden:
080005 c8 g F . text 00000048 __libc_init_array
08000610 g F . text 000000d 8 main
080006e8 g F . text 00000064 Reset_Handler
08000b c4 g F . text 00000024 SystemInit Der Abschnitt befindet sich im RAM -Speicher und enthält alle Variablen mit definierten Werten. Der Adressbereich erstreckt sich von _sdata bis _edata .
20000000 g . data 00000000 _sdata
20000010 g . data 00000000 _edataÜberprüfen Sie, welche Variablen von Main.c in diesem Abschnitt enthalten sind:
20000000 l O . data 00000004 static_data_int
20000008 g O . data 00000008 data_double Die tatsächlichen Werte müssen durch ein Bootloader -Skript gefüllt werden, indem Daten aus dem Flash -Speicher unter der Adresse _sidata kopiert werden:
08000 c08 g * ABS * 00000000 _sidata Blockieren Sie den Abschnitt "Startsymbol RAM -Daten" für alle Variablen ohne zugewiesenen Wert. Der Bootloader kümmert sich um die Einstellung der Daten dieses Blocks auf 0 .
Der Speicherbereich erstreckt sich über _sbss bis _ebss .
20000010 g . bss 00000000 _sbss
20000044 g . bss 00000000 _ebssAlle Variablen von Main.c ohne expliziten Wert befinden sich in diesem Abschnitt:
2000002 c l O . bss 00000004 static_bss_int
20000030 g O . bss 00000008 bss_double
20000038 g O . bss 00000008 bss_my_struct
20000040 g O . bss 00000004 bss_my_unionLassen Sie uns bei GDB achten:
(gdb) p bss_double
$1 = 0
(gdb) p & bss_double
$2 = (double * ) 0x20000030 < bss_double > Sobald Sie der Variablen einen gewissen Wert zuweisen, ändert sich die Adresse nicht und bleibt immer noch in .bss :
(gdb) p bss_double
$3 = 86
(gdb) p & bss_double
$4 = (double * ) 0x20000030 < bss_double > Alle RAM -Speicher über _end und bis _estack dem Heav- und Stapelspeicher gewidmet ist.
20000048 g . _user_heap_stack 00000000 _end
20020000 g . isr_vector 00000000 _estack _estack -Adresse wird als ORIGIN(RAM) + LENGTH(RAM) berechnet. So dass für 128 KB RAM:
_estack = 0x20000000 + 128 * 1024 # dec
= 0x20000000 + 0x20000 # hex
= 0x20020000 Stack ist eine LIFO -Struktur, die bei _estack beginnt und nach unten wächst. Die minimale Stapelgröße wird in der Linkerdatei als _Min_Stack_Size definiert. Der Stack -Speicher wird automatisch befreit.
+ -- -- 0x20020000 -- -- + < -- _estack
| |
| Stack |
| |
+ - - 0x2001FC00 - - + < -- - _Min_Stack_Size
| |
+ -- -- 0x200 sssss -- -- + < -- $msp register
| |
| |
| Free space |
| |
| |
+ -- -- 0x200 hhhhh -- -- + < -- Actual heap end
| |
| Heap |
| |
+ - - 0x20000248 - - + < -- + _Min_Heap_Size
| |
+ -- -- 0x20000048 -- -- + < -- _end Heap wiederum beginnt von _end und wächst nach oben bis zu _estack - _Min_Stack_Size wenn er von malloc angefordert wird.
Wir haben eine in Main definierte stack_int -Variable. Überprüfen Sie die Adresse mit GDB , nachdem sie initialisiert wurde:
(gdb) p & stack_int
$1 = (unsigned short * ) 0x2001ffd6
(gdb) p $msp
$2 = (void * ) 0x2001ffd0 Was mit unseren Erwartungen übereinstimmt, wenn die Variable über dem $msp liegt.
Es ist erwähnenswert, dass die Standard -Implementierung der C -Bibliothek, die häufig in eingebetteten C -Anwendungen verwendet wird, Newlib ist. Diese Bibliothek erfordert die Implementierung bestimmter systemspezifischer Funktionen. STM32Cubemx generiert die syscalls.c -Datei mit den erforderlichen Standardimplementierungen.
Es ist auch der Fall für sbrk Call, der den Programmdatenraum erhöht. malloc nutzt diese Funktion, um mehr Heap -Speicher zuzuweisen. Möglicherweise finden Sie eine Implementierung, die von STM32Cubemx in sysmem.c generiert wird. Es ermöglicht dem Haufen einfach, von _end bis _estack - _Min_Stack_Size zu wachsen.
Wenn wir nun den MCUS -Speicher verstehen, stellen wir eine Verbindung zu unserem Programm mit GDB her. Folgendes sehen wir als erste Ausgabe:
...
Reading symbols from ./stm32-boot-explained.elf...
Remote debugging using localhost:61234
Reset_Handler () at %PATH%/bootloader.c:25
25 void Reset_Handler () {
(gdb) info registers
...
pc 0x80006e8 0x80006e8 < Reset_Handler >
... Reset_Handler wurde irgendwie als Startpunkt für unsere Anwendung identifiziert.
Möglicherweise haben Sie festgestellt, dass in der Linker -Datei eine ENTRY vorhanden ist:
// linker file
ENTRY ( Reset_Handler ) Tatsächlich speichert es einen Verweis auf die Funktionsadresse Reset_Handler im .elf -Datei -Header:
arm-none-eabi-objdump -t -f stm32-boot-explained.elf | grep " start address "
start address 0x080006e9 In unserer Symboltabelle ist Reset_Handler im Abschnitt Flash .text vorhanden, genau wie alle anderen Funktionen:
080006e8 g F . text 00000064 Reset_HandlerAdressen sind Seiten ausgerichtet, weshalb es eine Möglichkeit für eine Nichtübereinstimmung zwischen dem .elf -Kopfzeilen und der tatsächlichen Adresse besteht.
Obwohl diese Informationen hauptsächlich vom Linker verwendet werden, um die Existenz des Einstiegspunktsymbols im Code zu überprüfen, hat sie für die MCU keine praktische Bedeutung.
Gemäß der STM32-Spezifikation holt die CPU den Top-of-Stack _estack Wert von der Adresse 0x00000000 und beginnt dann die Codeausführung aus dem Startspeicher ab 0x00000004 .
+ -- -- 0x001FFFFF -- -- +
| |
| Alias |
| |
+ -- -- 0x00000000 -- -- + Genau hier ist der oben erwähnte Alias -Speicher definiert. Bei der Standardkonfiguration, wenn der Pin -Wert BOOT0 = 0 ab 0x8000000 zum Flash -Speicherblock ist.
Andere Optionen basierend auf dem BOOT0 und BOOT1 sind der Systemspeicher mit einem eingebetteten Bootloader oder RAM -Speicher. Der eingebettete Bootloader ist während der Produktion und aus dem Umfang dieses Handbuchs nach ST programmiert.
Aber Reset_Handler hat eine Adresse 0x08000524 , die nicht genau der Beginn des Flash -Speichers ist. Wie findet die MCU dann die Bootstrap -Methode?
Hier kommt der Vektortisch ins Spiel.
08000000 g O . isr_vector 000001 c4 Vector_Table MCU behandelt den Beginn des Gedächtnisses als Vektoretabelle, die Zeiger auf verschiedene Interrupt -Service -Routinen und wesentliche Startfunktionen enthält, einschließlich des Reset_Handler . Wenden Sie sich an die Spezifikation, um die genaue Tabellenstruktur anzuzeigen, die MCU von 0x00000000 laden wird. Die tatsächliche Tabelle muss vom Bootloader gefüllt werden.
| Adresse | Name |
|---|---|
| 0x00000000 | Reserviert |
| 0x00000004 | Handler zurücksetzen |
| 0x00000008 | Nicht maskierbarer Interrupt |
| 0x00000012 | Harter Fehler |
| ... | Andere Interrupts |
Dieser Reset_Handler ist eine Bootloaderfunktion, die für viele Anwendungen verwendet werden kann, von Sicherheitsspezifischen Aufgaben bis hin zu Firmware Auto-Updating. Hier untersuchen wir die grundlegende Standardimplementierung, um die Interaktion mit dem Speicher der MCU zu verstehen.
Standardmäßig ist die Methode Reset_Handler in der von STM23Cubemx bereitgestellten ASM -Datei startup_stm32f436xx.s ASM definiert. Die tatsächliche Bootloader.c -Implementierung in diesem Projekt ist in C zur Klarheit geschrieben.
Beachten Sie, dass im Linker -Skript definierte Variablen im C -Code zugegriffen werden können:
extern uint32_t _estack ;So dass es leicht möglich ist, die ASM -Version zu replizieren.
Der minimale Belastungsprozess kann dann in die folgenden Schritte aufgeteilt werden:
SystemInit() -Funktion).data -Segments vom Flash zu RAM.bss__libc_init_array() ).main() an)Für die Schritte Nr. 1 und Nr. 4, STM32Cubemx, bietet Sie Funktionen implementiert. Sie können die Details in System_STM32F4XX.C überprüfen.
Das Projekt verfügt über einen minimalen Satz von Dateien, die zum Starten des STM32 erforderlich sind. Möglicherweise möchten Sie es selbst ausprobieren, um die Ausgabe von Arm-None-Aebi-Objdump zu überprüfen und mit GDB durchzugehen.
ARM GNU Toolchain ist erforderlich, um das Projekt zu erstellen.
Es wird empfohlen, ein All-in-One STM32CubeCLT-Befehlstools-Paket mit ARM-None-ABI-GCC , STM32_Programmer_Cli und ST-Link_Gdbserver -Tools zu installieren.
Erstellen Sie das Projekt mit CMake :
mkdir build ; cd build
cmake ../ -DPROGRAMMER_CLI=/opt/ST/STM32CubeCLT_1.15.1/STM32CubeProgrammer/bin
-DGDB_SERVER=/opt/ST/STM32CubeCLT_1.15.1/STLink-gdb-server/bin
make VERBOSE=1Beachten Sie, dass der letzte Befehl die Verknüpfungsstufe ist. Wenn wir alle anderen Compiler -Flags ausziehen, könnte der Befehl wie Folgendes aussehen. Hier wird der Linker angewiesen, unser Linker -Skript zu verwenden.
arm-none-eabi-gcc
...
-T " %SCRIPT_DIR%/STM32F446RETx_FLASH.ld "
...
" %OBJ_DIR%/%OBJECT_NAME%.c.obj "
...
-o stm32-boot-explained.elfSTM32_Programmer_Cli ist für SWD Procotol vorkonfiguriert.
make flash Nehmen Sie eine Notiz zum Programmiererausgang, der die 0x08000000 -Adresse als Ausgangspunkt verwendet:
...
Memory Programming ...
Opening and parsing file: stm32-boot-explained.elf
File : stm32-boot-explained.elf
Size : 1,46 KB
Address : 0x08000000
...Es ist eigentlich die Startadresse des Flash -Speichers, den wir bereits kennen.
Es gibt ein benutzerdefiniertes Ziel, das vorkonfiguriert ist, um ST-Link_gdbserver auszuführen:
# start ST-Link gdb server
make gdb-server
# connect with gdb debugger
gdb -ex ' target remote localhost:61234 ' ./stm32-boot-explained.elf