Почему STM32F103 не запускается после Keil на CMake
Проблема запуска прошивки STM32F103 после перехода с Keil на CMake: неверные адреса векторной таблицы, смещение reset_handler и другие причины. Как исправить?
Почему прошивка для STM32F103 не запускается после перехода с Keil на CMake?
При переходе сборки с Keil на CMake на STM32F103 самописный бутлоадер принимает прошивку по XMODEM и сохраняет её по адресу 0x08040000. Затем, по команде update, он копирует данные в 0x08020000, ищет строку вида ver:x.y.z, выводит версию, настраивает MSP, VTOR и прыгает на reset_handler приложения по команде gop.
Если использовать бинарник, собранный в Keil, всё работает: версия определяется корректно и приложение запускается. В CMake (arm-none-eabi) elf и bin собираются без ошибок, таблица векторов действительно лежит по адресу 0x08020000, строка ver:1.0.0 присутствует в бинарнике, но после загрузки через XMODEM, выполнения update и gop бутлоадер выводит версию 0.0.0, а запуск приложения приводит к зависанию.
Я просмотрел реальное содержимое флеша по адресу 0x08020000. Первые слова не совпадают с теми, которые должны быть в elf. Например, адрес reset_handler в памяти получается 0x08020fd1, хотя в elf секция .text начинается с 0x080201e8. Похоже, что во флеш записываются не те данные, но сама передача XMODEM проходит нормально.
Какие причины могут приводить к тому, что во флеш записывается другой набор данных, включая неправильный адрес reset_handler, несмотря на то, что elf/bin выглядят корректно?
Код бутлоадера: https://github.com/johanngorban/bootloader
Краткий ответ
В большинстве случаев проблема возникает из‑за того, что CMake‑сборка генерирует ELF/ BIN, в котором первый байт (или два) векторной таблицы пропущен, а сам адрес reset_handler смещён из‑за неверного выравнивания при записи в flash. На самом деле это может происходить из‑за неверного линкер‑скрипта, некорректного вывода бинарника (objcopy -O binary) и/или несовместимого способа записи в flash в самописном бутлоадере, в итоге…
1. Почему в Flash появляется «неправильный» набор данных
| Причина | Что происходит | Как проявляется |
|---|---|---|
Линкер‑скрипт, задающий неверный SECTIONS |
STM32F103 в Keil использует собственный линкер‑скрипт, в котором FLASH начинается сразу с 0x08020000. В CMake‑проекте чаще всего берётся шаблон stm32f1x.ld, в котором FLASH начинается с 0x08000000, а только в секции APPLICATION задаётся смещение 0x20000. |
В ELF‑файле адреса секций находятся в диапазоне 0x08020000‑0x08030000, но в бинарном файле они «сдвинуты» на 0x20000, и векторная таблица оказывается сдвинута на 4‑8 байт. |
objcopy -O binary без указания --output-target |
Команда objcopy копирует только payload и игнорирует заголовки, но при этом может сохранять первый байт секции, если в ELF‑файле он используется как заголовок. |
Если в ELF в начале .isr_vector лежит «признак» (например, 0xFF), objcopy попадает в эту область, а бутлоадер начинает запись с 0x08020000, но фактически данные смещены. |
| Несоответствие размера записи | Бутлоадер, как правило, пишет flash блоками 2 байта (HAL) или 4 байта (LL). Если он пишет 2 байта, а бинарник состоит из 4‑байтовых слов, в результате создаётся «половинное» слово и всё дальше смещается. | reset_handler в Flash становится 0x08020fd1 (невыравненный по 4‑байтам), а векторная таблица полностью искажена. |
Опция --gc-sections/-ffunction-sections |
При включении этих опций линкер удаляет неиспользуемые функции и меняет порядок секций. Если в Keil‑проекте эти опции отключены, в CMake‑проекте они могут включаться по умолчанию. | В результате, на самом деле, reset_handler может оказаться в другом месте, чем ожидалось, и его адрес в бинарнике будет отличаться. |
| Проблемы с размером flash‑страницы/объёмом | Если в бинарнике меньше 0x20000 байт, бутлоадер всё‑таки записывает 0x20000, но оставшиеся байты остаются старой прошивкой. | Векторная таблица может содержать «старые» данные, в том числе неправильный адрес, в итоге. |
Как это выглядит в реальном Flash
- В Keil
0x08020000: 0x2000_0000 (SP) 0x08020004: 0x080201E8 (reset_handler) 0x08020008: 0x0000_0000 (nmi_handler) - В CMake + бутлоадер
0x08020000: 0x2000_0000 0x08020004: 0x08020FD1 <-- смещённый, невыравненный
2. Как диагностировать
| Шаг | Инструмент | Что проверить |
|---|---|---|
| 1 | arm-none-eabi-objdump -h |
Адреса секций .isr_vector, .text, .data. Должны совпадать с 0x08020000‑0x08030000. |
| 2 | arm-none-eabi-objdump -s .isr_vector |
Содержимое векторной таблицы в ELF. Сравнить с тем, что лежит в flash после загрузки. |
| 3 | arm-none-eabi-size |
Размер секций. Убедиться, что .text + .rodata + .isr_vector ≤ 0x20000. |
| 4 | xxd -l 64 0x08020000.bin |
Файл, который бутлоадер пишет. Сравнить первые 16 байт с выводом objdump. |
| 5 | stm32flash -v -e |
Убедиться, что Flash‑размер действительно 128 КБ и что запись идёт в правильный диапазон. |
3. Как исправить
3.1. Линкер‑скрипт
Убедитесь, что в скрипте явно задано смещение начала приложения:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08020000, LENGTH = 64k
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20k
}
SECTIONS
{
/* Векторная таблица */
.isr_vector :
{
KEEP(*(.isr_vector))
. = ALIGN(4);
} > FLASH
/* Код и данные */
.text :
{
*(.text)
*(.rodata)
. = ALIGN(4);
} > FLASH
/* Остальное */
...
}
Важно: в секции .isr_vector ставьте KEEP, иначе линкер может удалить первую запись.
3.2. Генерация бинарника
arm-none-eabi-objcopy -O binary -j .isr_vector -j .text -j .data ${ELF} ${BIN}
-j– выбирает только нужные секции, чтобы не выйти за пределы 0x20000.- Не использовать
--output-target=elf32-littlearm– это может добавить заголовки.
3.3. Писать flash правильным размером
uint32_t file_size = <полученный размер>;
if (file_size > 0x20000) {
/* обрезать до 0x20000 */
file_size = 0x20000;
}
И пишите ровно file_size байт, а не фиксированное 0x20000.
3.4. Выравнивание записей
Если вы используете HAL/LL, убедитесь, что запись идёт целыми словами:
for (uint32_t i = 0; i < file_size; i += 4) {
uint32_t word = *(uint32_t*)(data + i);
FLASH_ProgramWord(address + i, word);
}
Если же используется FLASH_ProgramHalfWord, то data должен быть выровнен до 2‑байтового границы, иначе смещение появится.
4. Почему Keil «работает»
Keil использует свой собственный линкер‑скрипт и генератор бинарника, который автоматически:
- задаёт правильное смещение и выравнивание,
- не добавляет заголовков,
- пишет данные в flash ровно так, как они лежат в памяти.
Кроме того, в Keil часто включены опции --no-warn и -Wl,--gc-sections отключены, что делает reset_handler неизменным. В CMake‑проекте эти опции могут быть включены по умолчанию, что приводит к смещению секций.
5. Итог
- Проверьте линкер‑скрипт: убедитесь, что
FLASHначинается с 0x08020000 и что векторная таблица не удаляется, в итоге. - Проверьте генерацию BIN: используйте
-jдля выбора секций, избегайте лишних заголовков, в итоге. - Проверьте запись в flash: пишите ровно размер файла, не превышая 0x20000, и используйте 32‑битную запись, в итоге.
- Сравните содержимое ELF и Flash:
objdump -s .isr_vectorvsxxd -l 64 0x08020000.bin, в итоге.
После этих шагов бутлоадер будет корректно определять версию ver:x.y.z и запускать приложение без зависаний.