Embedded

Настройка тулчейна: STM32CubeMX, arm-none-eabi-gcc, CMake для STM32

Введение

Прежде чем писать код, нужно настроить инструментарий сборки — компилятор для целевой ARM-архитектуры (отличной от архитектуры компьютера разработчика), генератор начальной конфигурации периферии (STM32CubeMX), и интеграцию всего этого с CMake, продолжая подход к сборке проектов из основного цикла статей.

Концепция

arm-none-eabi-gcc — кросс-компилятор, генерирующий машинный код для архитектуры ARM Cortex-M (используемой в STM32), запускаемый на компьютере разработчика с совершенно иной архитектурой (x86_64) — аналогично кросс-компиляции для Raspberry Pi (статья 342), но для микроконтроллера без операционной системы. STM32CubeMX — графический инструмент от производителя (STMicroelectronics), позволяющий визуально настроить периферию конкретного чипа (какие пины на что назначены, частоты тактирования) и сгенерировать начальный, готовый к сборке проект с нужными HAL-библиотеками и файлом линковки (linker script), специфичным для карты памяти конкретного чипа.

Пример кода: Настройка тулчейна

# Установка тулчейна ARM на Linux/macOS (Windows — через установщик с сайта ARM)
sudo apt install gcc-arm-none-eabi gdb-multiarch

arm-none-eabi-gcc --version
# toolchain-stm32.cmake — toolchain-файл CMake для кросс-компиляции под STM32
set(CMAKE_SYSTEM_NAME Generic) # "Generic" — означает отсутствие операционной системы вовсе
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)

set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) # тест компилятора без линковки —
                                                    # обычная проверка через исполняемый файл
                                                    # не сработает без операционной системы и main()

set(CMAKE_EXE_LINKER_FLAGS "-T${CMAKE_SOURCE_DIR}/STM32F407VGTx_FLASH.ld" CACHE INTERNAL "")
# CMakeLists.txt — основная конфигурация проекта STM32
cmake_minimum_required(VERSION 3.20)
project(Stm32BareMetalDemo C CXX ASM)

set(CPU_FLAGS "-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard")
set(CMAKE_C_FLAGS "${CPU_FLAGS} -Wall -Og -g")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CPU_FLAGS} --specs=nano.specs")

add_executable(Stm32BareMetalDemo
    Core/Src/main.c
    Core/Src/stm32f4xx_hal_msp.c
    Core/Src/system_stm32f4xx.c
    Core/Startup/startup_stm32f407vgtx.s  # стартовый код на ассемблере, сгенерированный CubeMX
)

target_include_directories(Stm32BareMetalDemo PRIVATE
    Core/Inc
    Drivers/STM32F4xx_HAL_Driver/Inc
    Drivers/CMSIS/Device/ST/STM32F4xx/Include
    Drivers/CMSIS/Include
)

# Преобразование итогового ELF-файла в .bin/.hex для прошивки в микроконтроллер
add_custom_command(TARGET Stm32BareMetalDemo POST_BUILD
    COMMAND ${CMAKE_OBJCOPY} -O binary
        $<TARGET_FILE:Stm32BareMetalDemo>
        $<TARGET_FILE_DIR:Stm32BareMetalDemo>/Stm32BareMetalDemo.bin
)
# Конфигурация и сборка
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=toolchain-stm32.cmake
cmake --build build

Пояснения к коду

CMAKE_SYSTEM_NAME Generic — ключевая настройка, явно сообщающая CMake об отсутствии операционной системы на целевой платформе, что меняет поведение многих стандартных проверок CMake (например, обычная проверка работоспособности компилятора пытается собрать и запустить простой исполняемый файл, что бессмысленно без ОС и обычного main() с возвратом в командную строку). Флаги -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard точно указывают компилятору конкретную модель процессорного ядра ARM, набор инструкций (Thumb — компактный набор инструкций ARM) и наличие аппаратного блока вычислений с плавающей точкой — несовпадение этих флагов с реальным чипом приведёт либо к ошибке прошивки, либо, что хуже, к неопределённому поведению уже работающей на чипе программы. add_custom_command с objcopy преобразует итоговый ELF-файл (стандартный формат исполняемых файлов, который понимает компилятор и линкер) в плоский бинарный (.bin) формат, непосредственно записываемый во flash-память микроконтроллера программатором.

Пояснения к коду (продолжение про линковщик)

Файл линковки (STM32F407VGTx_FLASH.ld, сгенерированный STM32CubeMX) описывает физическую карту памяти конкретного чипа — где начинается и сколько занимает flash-память для кода, где находится RAM для данных, и куда должен быть помещён стек — без правильного, специфичного для конкретной модели чипа файла линковки, скомпилированная программа будет пытаться разместить код или данные за пределами реальной физической памяти конкретного чипа, что обычно проявляется либо ошибкой линковки, либо неработоспособностью прошитой программы.

Подводные камни

  • Использование файла линковки или стартового кода, сгенерированного для другой модели/ревизии чипа STM32, отличающейся объёмом flash-памяти или RAM от реально используемого чипа — это может привести либо к явной ошибке линковки (переполнение доступной памяти), либо, что значительно хуже, к программе, которая успешно линкуется и прошивается, но проявляет неопределённое, трудно диагностируемое поведение из-за обращения к несуществующим физически адресам памяти; файл линковки и стартовый код всегда должны точно соответствовать реальной используемой модели чипа.
  • Несовпадение флагов FPU/архитектуры (-mfpu, -mfloat-abi) между компиляцией пользовательского кода и предоставленными HAL-библиотеками/драйверами, особенно при смешивании самостоятельно скомпилированного кода с готовыми, предкомпилированными библиотеками — несоответствие этих низкоуровневых флагов ABI (Application Binary Interface) может привести к неправильной передаче чисел с плавающей точкой между функциями, скомпилированными с разными настройками, что крайне трудно диагностировать без явного понимания причины такого несоответствия.
  • Полная ручная настройка проекта STM32 без использования STM32CubeMX для генерации начального стартового кода и файла линковки, особенно для новичков в STM32-разработке — написание корректного стартового кода (инициализация таблицы векторов прерываний, настройка тактирования системы, копирование инициализированных данных из flash в RAM) с нуля требует глубокого понимания архитектуры ARM Cortex-M, и использование сгенерированного CubeMX стартового кода как отправной точки значительно снижает риск тонких ошибок инициализации, которые иначе были бы легко допущены при ручном написании этого кода.
  • Отсутствие проверки совпадения версии тулчейна arm-none-eabi-gcc с версией, для которой тестировались используемые HAL-библиотеки — хотя обычно совместимость достаточно широка, очень старые или очень новые версии компилятора иногда демонстрируют проблемы совместимости с конкретными версиями вендорских HAL-библиотек (предупреждения, превращающиеся в ошибки при более строгих настройках нового компилятора, или изменения в поведении оптимизации) — при возникновении необычных проблем сборки стоит явно проверить, тестировалась ли используемая версия HAL-библиотек с используемой версией компилятора.