Программирование

Почему 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. Линкер‑скрипт

Убедитесь, что в скрипте явно задано смещение начала приложения:

ld
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. Генерация бинарника

bash
arm-none-eabi-objcopy -O binary -j .isr_vector -j .text -j .data ${ELF} ${BIN}
  • -j – выбирает только нужные секции, чтобы не выйти за пределы 0x20000.
  • Не использовать --output-target=elf32-littlearm – это может добавить заголовки.

3.3. Писать flash правильным размером

c
uint32_t file_size = <полученный размер>;
if (file_size > 0x20000) {
    /* обрезать до 0x20000 */
    file_size = 0x20000;
}

И пишите ровно file_size байт, а не фиксированное 0x20000.

3.4. Выравнивание записей

Если вы используете HAL/LL, убедитесь, что запись идёт целыми словами:

c
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_vector vs xxd -l 64 0x08020000.bin, в итоге.

После этих шагов бутлоадер будет корректно определять версию ver:x.y.z и запускать приложение без зависаний.

Авторы
Проверено модерацией
Модерация