Подробное объяснение процесса отображения памяти и загрузки STM32.
В то время как все примеры ниже предназначены для STM32F446, основные принципы применимы к большинству MCU.
STM32Cubemx создает STM32F446RETX_FLASH.LD, на который мы рассмотрим в качестве ссылки для всех наших исследований.
Линкер берет объектные файлы, созданные компилятором, и генерирует окончательный вывод компиляции, который в нашем случае является бинарным . Линкер всегда использует файл сценария линкера; Даже если вы не указываете его, используется скрипт по умолчанию.
Важно отметить, что сценарий линкера описывает только память, основанную на спецификациях MCU и не изменяет какую -либо аппаратную адаптирование памяти.
Мы не можем говорить о процессе загрузчика без понимания структуры памяти. На самом деле это цель загрузчика, чтобы получить память в состоянии, готовом выполнить метод нашего приложения main() .
Память программы, память данных, регистры и порты ввода -вывода в STM32F4 организованы в одном и том же линейном адресном пространстве.
...
+ -- -- 0x2001FFFF -- -- +
| |
| RAM |
| |
+ -- -- 0x20000000 -- -- +
| ... |
+ -- -- 0x1FFF7A0F -- -- +
| |
| System |
| |
+ -- -- 0x1FFF0000 -- -- +
| ... |
+ -- -- 0x081FFFFF -- -- +
| |
| Flash |
| |
+ -- -- 0x08000000 -- -- +
| ... |
+ -- -- 0x001FFFFF -- -- +
| |
| Alias |
| |
+ -- -- 0x00000000 -- -- + Где память псевдонима указывает на вспышку, систему или оперативную память в зависимости от контакта BOOT0 . По умолчанию это Flash.
На данный момент для нас больше всего интересно:
Однако на практике функциональное разделение может варьироваться. Например, вам может потребоваться загрузить бинарные данные программы в ОЗУ или наоборот.
Структура памяти отражена в файле линкера. Flash начинается с ORIGIN = 0x8000000 и оперативной памяти At ORIGIN = 0x20000000 .
// linker file
MEMORY
{
RAM ( xrw ) : ORIGIN = 0x20000000 , LENGTH = 128 K
FLASH ( rx ) : ORIGIN = 0x8000000 , LENGTH = 512 K
}Обратите внимание, что это просто реализация по умолчанию, вы можете легко разделить Flash и RAM и добавить свои собственные блоки или разделы, как только они придерживаются спецификации памяти MCU.
Затем память разделена в файле линкера на разделы, каждый из которых имеет свой адрес и назначение (например, Flash или RAM).
// linker file
SECTIONS
{
...
. text :
{
...
} > FLASH
...
}Чтобы изучить таблицу символов .
arm-none-eabi-objdump -t stm32-boot-explained.elf | sortПрограмма производит вывод, как описано в деталях в руководстве:
Address Flag_Bits Section Size NameСкомпилированный код программирования входит в этот раздел в флэш -памяти.
Раздел начинается с адреса ORIGIN Flash, и _etext указывает на последний адрес раздела.
08000000 l d . text 00000000 . text
08000 c00 g . text 00000000 _etextМы можем увидеть местоположение функций, которое мы используем в нашем файле загрузчика:
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 Раздел находится в памяти оперативной памяти и содержит все переменные с определенными значениями. Диапазон адресов охватывает от _sdata до _edata .
20000000 g . data 00000000 _sdata
20000010 g . data 00000000 _edataДавайте проверим, какие переменные от main.c находятся в этом разделе:
20000000 l O . data 00000004 static_data_int
20000008 g O . data 00000008 data_double Фактические значения должны быть заполнены сценарием загрузчика путем копирования данных из флэш -памяти по адресу _sidata :
08000 c08 g * ABS * 00000000 _sidata Запуск Символа Символ Символ Секция ОЗУ для всех переменных без назначенного значения. Bootloader позаботится о настройке данных этого блока на 0 .
Диапазон памяти охватывает от _sbss до _ebss .
20000010 g . bss 00000000 _sbss
20000044 g . bss 00000000 _ebssВсе переменные от main.c без явного значения находятся в этом разделе:
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_unionДавайте проверим с GDB :
(gdb) p bss_double
$1 = 0
(gdb) p & bss_double
$2 = (double * ) 0x20000030 < bss_double > Как только вы назначите какое -то значение переменной, ее адрес не изменяется, и он все еще остается в .bss :
(gdb) p bss_double
$3 = 86
(gdb) p & bss_double
$4 = (double * ) 0x20000030 < bss_double > Вся память с оперативной памятью выше _end и до тех пор, пока _estack не будет посвящена памяти кучи и стека.
20000048 g . _user_heap_stack 00000000 _end
20020000 g . isr_vector 00000000 _estack _estack Адрес рассчитывается как ORIGIN(RAM) + LENGTH(RAM) . Так что для 128 КБ ОЗУ:
_estack = 0x20000000 + 128 * 1024 # dec
= 0x20000000 + 0x20000 # hex
= 0x20020000 Stack - это структура LIFO, которая начинается с _estack и растет вниз. Минимальный размер стека определяется в файле линкера как _Min_Stack_Size . Память стека автоматически освобождена.
+ -- -- 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 Куча, в свою очередь, начинается с _end и растет вверх до _estack - _Min_Stack_Size по запросу malloc .
У нас есть переменная stack_int , определенная в Main.c. Давайте проверим адрес с GDB после того, как он будет инициализирован:
(gdb) p & stack_int
$1 = (unsigned short * ) 0x2001ffd6
(gdb) p $msp
$2 = (void * ) 0x2001ffd0 Который соответствует нашим ожиданиям, когда переменная выше $msp .
Стоит отметить, что стандартная реализация библиотеки C, обычно используемая в приложениях C встроенных C, является Newlib. Эта библиотека требует реализации определенных системных функций. STM32Cubemx генерирует файл syscalls.c с необходимыми реализациями по умолчанию.
Это также относится к вызову sbrk , который увеличивает пространство данных программы. malloc использует эту функцию, чтобы выделить больше памяти кучи. Вы можете найти реализацию, сгенерированную STM32Cubemx в sysmem.c. Это просто позволяет куче расти с _end до _estack - _Min_Stack_Size .
Теперь, когда мы понимаем память MCUS, давайте подключимся к нашему программку с помощью GDB , вот что мы видим как первый вывод:
...
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 был каким -то образом идентифицирован как загрузочная точка для нашего приложения.
Возможно, вы заметили, что в файле линкера есть инструкция ENTRY :
// linker file
ENTRY ( Reset_Handler ) На самом деле, он сохраняет ссылку на адрес функции Reset_Handler в заголовке файла .elf :
arm-none-eabi-objdump -t -f stm32-boot-explained.elf | grep " start address "
start address 0x080006e9 Глядя на нашу таблицу символов, Reset_Handler присутствует в разделе Flash .text , как и все другие функции:
080006e8 g F . text 00000064 Reset_HandlerАдреса выровнены, поэтому есть возможность для несоответствия между заголовком .elf и фактическим адресом.
Хотя эта информация в основном используется линкером для проверки существования символа точки входа в коде, она не имеет практического значения для MCU.
Согласно спецификации STM32, ЦП извлекает значение в верхней части сток _estack с адреса 0x00000000 , затем начинает выполнение кода из загрузочной памяти, начиная с 0x00000004 .
+ -- -- 0x001FFFFF -- -- +
| |
| Alias |
| |
+ -- -- 0x00000000 -- -- + Именно здесь определяется упомянутая выше память псевдонима. С конфигурацией по умолчанию, когда значение PIN BOOT0 = 0 , он псевдоним для блока флэш -памяти, начиная с 0x8000000 .
Другие параметры, основанные на BOOT0 и BOOT1 включают системную память со встроенным загрузчиком или памятью RAM. Встроенный загрузчик запрограммируется ST во время производства и из -за масштаба этого руководства.
Но Reset_Handler имеет адрес 0x08000524 , который не является началом флэш -памяти, как тогда MCU находит метод начальной загрузки?
Вот где векторный стол вступает в игру.
08000000 g O . isr_vector 000001 c4 Vector_Table MCU рассматривает начало памяти как векторную таблицу, которая содержит указатели на различные процедуры службы прерывания и основные функции запуска, включая Reset_Handler . Проконсультируйтесь с спецификацией, чтобы увидеть точную структуру таблицы, которую MCU рассчитывает на загрузку с 0x00000000 . Фактическая таблица должна быть заполнена загрузчиком.
| Адрес | Имя |
|---|---|
| 0x00000000 | Сдержанный |
| 0x00000004 | Сброс обработчика |
| 0x00000008 | Не маскируемое прерывание |
| 0x00000012 | Жесткая вина |
| ... | Другие прерывания |
Этот Reset_Handler -это функция загрузчика, которую можно использовать для многих приложений, от задач, специфичных для безопасности до автоматического обновления прошивки. Здесь мы рассмотрим базовую реализацию по умолчанию, чтобы понять его взаимодействие с памятью MCU.
По умолчанию метод Reset_Handler определяется в файле startup_stm32f436xx.s ASM, предоставленного STM23Cubemx. Фактическая реализация Bootloader.c в этом проекте записана в C для ясности.
Обратите внимание, что переменные, определенные в сценарии линкера, можно получить в коде C:
extern uint32_t _estack ;Так что можно легко воспроизвести версию ASM.
Минимальный процесс загрузки может быть разделен на следующие шаги:
SystemInit() ).data от Flash до Ram.bss__libc_init_array() функция)main() )Для шагов № 1 и #4, STM32Cubemx предоставляет реализации функций, вы можете проверить подробности в System_stm32f4xx.c.
Проект имеет минимальный набор файлов, необходимых для загрузки STM32. Вы можете попробовать сами, чтобы проверить вывод ARM-None-Eabi-objdump и пройти через GDB .
Arm GNU Toolchain необходим для создания проекта.
Рекомендуется установить пакет инструментов команд STM32CUBECLT с All-in-One STM32CUBECLT с инструментами ARM-None-EABI-GCC , STM32_PROGRARMER_CLI и ST-LINK_GDBSERVER .
Создайте проект с помощью 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=1Обратите внимание, что последняя команда - этап связывания. Если мы разделим все другие флаги компилятора, команда может выглядеть следующим образом. Именно здесь линкеру указано использовать наш сценарий линкера.
arm-none-eabi-gcc
...
-T " %SCRIPT_DIR%/STM32F446RETx_FLASH.ld "
...
" %OBJ_DIR%/%OBJECT_NAME%.c.obj "
...
-o stm32-boot-explained.elfSTM32_Programmer_Cli предварительно настроен для SWD Procotol, просто запустите:
make flash Вспомните вывод программиста, который использует адрес 0x08000000 в качестве отправной точки:
...
Memory Programming ...
Opening and parsing file: stm32-boot-explained.elf
File : stm32-boot-explained.elf
Size : 1,46 KB
Address : 0x08000000
...На самом деле это начальный адрес флэш -памяти, который мы уже знаем.
Есть пользовательская целевая предварительно сфигурирована для запуска ST-Link_gdbserver :
# start ST-Link gdb server
make gdb-server
# connect with gdb debugger
gdb -ex ' target remote localhost:61234 ' ./stm32-boot-explained.elf