Эта статья — краткий гайд о том, как с нуля завести STM32 под CLion, без шелухи в виде HAL и STM32CubeMX.

Предыстория

Несмотря на то, что примерно год назад STMicroelectronics представили свою IDE на базе Eclipse и CDT, она по-прежнему содержит в себе ряд минусов, которые, по большей части, перекрывает CLion.

На Хабре ранее уже была статья, посвященная запуску связки CLion + STM32, но она была старая, а новые версии CLion несколько изменились.

В интернете достаточно много публикаций на тему того, как запустить связку CLion + STM32CubeMX, но практически нигде нет ничего о том, как сделать все своими руками - может быть я очень плохо искал.

Я надеюсь, что данная статья будет актуальна не только для серии микроконтроллеров от STMicroelectronics, но и от других производителей, использующих те же серии ядер.

Мотивация

Как уже было сказано выше, STM32CubeIDE - среда разработки от STMicroelectronics - содержит в себе ряд минусов, унаследованных от Eclipse и CDT в целом. Среди них: отсутсвие автодополнения кода, отсутствие поддержки синтаксиса выше, чем C++14, отсутствие возможности выбрать самый последний тулчейн с сайта ARM и многое другое. Отдельно стоит отметить стабильность работы Eclipse и STM32CubeIDE в частности - она оставляет желать лучшего. У меня неоднократно случалось так, что я был вынужден полностью сносить IDE и ставить ее заново потому, что при обновлении происходила внутренняя ошибка.

Пару ремарок

Все примеры установок и т.д. будут приведены для macOS, потому что у меня именно она. Также я считаю что с этой системой (по сравнению с Linux и Windows) намного больше подводных камней и неочевидностей.

В качестве примера рассматривается проект с поддержкой C++. В моем конкретном случае это C++20. CLion поддерживает версии начиная от C++98 до C++23. Есть поддержка C90, C99, C11.

CLion

Собственно IDE ради которой все затевается. Можно скачать на сайте JetBrains и для начала попробовать месячный триал. Для счастливых обладателей студенческого билета есть возможность получить вообще весь софт JetBrains в полной комплектации сроком на год.

OpenOCD

Инструмент для удаленной отладки на целевом (target) устройстве. Скачать можно также с официального сайта. Либо набрать в консоли (для macOS) что-то такое:

brew install openocd
What is brew?

Это менеджер пакетов для macOS. Примерно то же самое что и операция apt-get в Ubuntu. Подробнее про то, что это и как это установить тут.

Toolchain

Это набор инструментария для работы с целевым (target) устройством. Включает в себя компиляторы, линковщики, библиотеки и прочее. Загрузить можно с сайта ARM. На момент написания статьи последняя версия, доступная к загрузке, gcc-arm-none-eabi-10.3-2021.07. Для macOS есть два варианта установки: простой архив, и .pkg файл, который установит все сам. Стоит отметить, что начиная с macOS Catalina нужно нехило так повоевать с системой чтобы установить что-то от стороннего разработчика. Помимо этого надо будет вручную добавить путь до компилятора в переменную $PATH, именно там CLion и будет искать путь до компиляторов. Если не хочется тратить время на это, то можно скачать свежую, но не самую последнюю версию так:

brew install --cask gcc-arm-embedded

На момент написания статьи последняя версия в репозиториях brew - 10.2.

Начинаем!

В первую очередь стоит отметить, что на macOS нормально работает только OpenOCD, в то время как инструменты встроенные в CLion корректно работать отказываются.

Итак, если у нас не установлен brew, скачиваем и устанавливаем его с официального сайта. Инструкция по установке находится там же. После установки открываем терминал и пишем:

brew install openocd

А затем, после установки OpenOCD

brew install --cask gcc-arm-embedded

На этом работа с терминалом окончена. Теперь скачиваем и устанавливаем, если еще не сделали этого CLion. Открываем...

Выбираем "New Project". Также можно открыть уже существующий проект, либо взять проект с Git или иной системы контроля версий.

Выбираем рабочую папку проекта и поддерживаемый стандарт языка.

После создания проекта CLion либо сам попросит вас указать путь до компилятора, либо это нужно будет сделать вручную.

Settings->Preferences->Build, Execution, Deployment->Toolchains. В появившейся вкладке слева список, над ним иконка "+", выбираем System. Далее нам предложат ввести имя конфигурации, пути до make и компиляторов. Если вы использовали brew для установки тулчейна, то пути у вас будут выглядеть вот так:

После нажатия "Ok" или "Apply" в правой нижней части нас ожидает ошибка примерно такого рода:

Error
CMake Error at /Applications/CLion.app/Contents/bin/cmake/mac/share/cmake-3.20/Modules/CMakeTestCCompiler.cmake:66 (message):
  The C compiler

    "/usr/local/bin/arm-none-eabi-gcc"

  is not able to compile a simple test program.

Ничего страшного в этом нет, просто CMake пытается собрать свой внутренний тест для хостовой машины (компьютер на котором мы собираем проект) с использованием указанных компиляторов.

Чтобы избавиться от ошибки и в целом как-то повлиять на процесс компиляции, открываем файл CMakeLists.txt в папке проекта и начинаем редачить... Ниже приведен пример уже сконфигурированного CMakeLists.txt файла, вам необходимо лишь выставить параметры в соответствии со своим проектом.

CMakeLists.txt
#Имя системы под которую осуществляется сборка и ее версия
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_VERSION 1)

#Минимальная вресия CMake необходимая для компиляции проекта
cmake_minimum_required(VERSION 3.20)

#Ниже указаны имена компиляторов и утилит тулчейна
#CXX - компиялтор C++
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_AR arm-none-eabi-ar)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
set(SIZE arm-none-eabi-size)

#Из-за того, что мы собираем проект под микроконтроллер, а не под хост
#нужно сообщить об этом CMAKE
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

#Имя проекта и используемые языки
project(test_stm32f0 C CXX ASM)

#Имя ядра микроконтроллера
set(CMAKE_SYSTEM_PROCESSOR cortex-m0)
#Расширение скомпилированного файла
set(CMAKE_EXECUTABLE_SUFFIX ".elf")

#Стандарты C++ и C максимально поддерживаемые в этом проекте
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_C_STANDARD 11)

#Имя файла линкера под ваш МК
set(LINKER_SCRIPT_NAME STM32F072RBTX_FLASH)
#А так же путь до него. В моем случе скрипт лежит в папке startup которая находится в корне проекта
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/startup/${LINKER_SCRIPT_NAME}.ld)

# Далее идут флаги компиляции для каждого компилятора (Си, Си++ и Ассемблер)
# Так как для своих нужд я переносил проект с STM32CubeIDE, все флаги перешли оттуда же
# Опции компиляции можно посмотреть на офф сайте https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html
# Рассмотрим некоторые флаги:
# -mcpu - ядро МК
# -g - степень оптимизации
# -std - стандарт языка, gcc для C, g++ (или c++) для C++
# -O - степень (или тип) оптимизации. -O0 - без оптимзации,
# -O1, -O2, -O3 оптимизации от наименьшей к наибольшей, чем выше цифра, тем сильнее оптимизация
# стоит учитывать что чем выше степень оптимизации, тем сложнее работать в режиме дебага
# -Os - оптимизация по размеру, пытается скомпилировать минимальный размер
# -Ofast - оптимизация по скорости испольнения кода
# -Og - оптимизация для дебага
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=${CMAKE_SYSTEM_PROCESSOR} -g3 -std=gnu++${CMAKE_CXX_STANDARD} -Os -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-exceptions -fno-rtti -fno-threadsafe-statics -fno-use-cxa-atexit -Wall -std=gnu++2a -fstack-usage --specs=nano.specs -mfloat-abi=soft -mthumb")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=${CMAKE_SYSTEM_PROCESSOR} -std=gnu${CMAKE_C_STANDARD} -g3 -Os -ffunction-sections -fdata-sections -fno-strict-aliasing -Wall -fstack-usage --specs=nano.specs -mfloat-abi=soft -mthumb")
set(CMAKE_EXE_LINKER_FLAGS "-mcpu=${CMAKE_SYSTEM_PROCESSOR} -g3 -T ${LINKER_SCRIPT} --specs=nosys.specs -Wl,-Map=${PROJECT_NAME}.map  -Wl,--gc-sections -static --specs=nano.specs -mfloat-abi=soft -mthumb -Wl,--start-group -lc -lm -lstdc++ -lsupc++ -Wl,--end-group")
set(CMAKE_ASM_FLAGS "-mcpu=${CMAKE_SYSTEM_PROCESSOR} -g3 -c -x assembler-with-cpp --specs=nano.specs -mfloat-abi=soft -mthumb")

#Говорим применить такой то компилятор с таким-то линковщиком и т.д.
set(CMAKE_C_LINK_EXECUTABLE "<CMAKE_C_COMPILER> <LINK_FLAGS> -o <TARGET> <OBJECTS>")
set(CMAKE_CXX_LINK_EXECUTABLE "<CMAKE_CXX_COMPILER> <LINK_FLAGS> -o <TARGET> <OBJECTS>")

#Имя и путь к стартап файлу
set(STARTUP_FILE_NAME startup_stm32f072rbtx)
set(STARTUP_LOCATION "${CMAKE_SOURCE_DIR}/startup/${STARTUP_FILE_NAME}.s")

#Пути по которым лежат инклуды, '.' означает корень проекта
include_directories(.)
include_directories(cmsis)

#Глобальный дефайн, нужен для CMSIS
add_definitions(-DSTM32F072xB)

#имена .cpp, .c и .s файлов
set(SOURCE_FILES main.cpp syscalls.c cmsis/system_stm32f0xx.c)
#имена хедеров
set(INCLUDE_FILES cmsis/cmsis_compiler.h cmsis/cmsis_gcc.h cmsis/cmsis_version.h cmsis/core_cm0.h cmsis/stm32f072xb.h cmsis/stm32f0xx.h cmsis/system_stm32f0xx.h)

#На этом моменте происходит компиляция и линковка проекта в .elf
add_executable(${PROJECT_NAME} ${STARTUP_LOCATION} ${INCLUDE_FILES} ${SOURCE_FILES})

#Превращаем .elf в .hex
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_OBJCOPY} ARGS -Oihex ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex)
#Превращаем .elf в .bin
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_OBJCOPY} ARGS -Obinary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin)
#Вывод в консоль данных о размере секций .bss, .data и т.д., а так же всего проекта
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${SIZE} ARGS --format=berkeley ${PROJECT_NAME}.elf)

Чтобы адаптировать этот файл под свой проект, необходимо изменить строчки

#Имя ядра микроконтроллера
set(CMAKE_SYSTEM_PROCESSOR cortex-m0)

#Имя файла линкера под ваш МК
set(LINKER_SCRIPT_NAME STM32F072RBTX_FLASH)
#А так же путь до него. В моем случе скрипт лежит в папке startup которая находится в корне проекта
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/startup/${LINKER_SCRIPT_NAME}.ld)

#Имя и путь к стартап файлу
set(STARTUP_FILE_NAME startup_stm32f072rbtx)
set(STARTUP_LOCATION "${CMAKE_SOURCE_DIR}/startup/${STARTUP_FILE_NAME}.s")

#Глобальный дефайн, нужен для CMSIS
add_definitions(-DSTM32F072xB)

И опциональные. По желанию...

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_C_STANDARD 11)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=${CMAKE_SYSTEM_PROCESSOR} -g3 -std=gnu++${CMAKE_CXX_STANDARD} -Os -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-exceptions -fno-rtti -fno-threadsafe-statics -fno-use-cxa-atexit -Wall -std=gnu++2a -fstack-usage --specs=nano.specs -mfloat-abi=soft -mthumb")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=${CMAKE_SYSTEM_PROCESSOR} -std=gnu${CMAKE_C_STANDARD} -g3 -Os -ffunction-sections -fdata-sections -fno-strict-aliasing -Wall -fstack-usage --specs=nano.specs -mfloat-abi=soft -mthumb")
set(CMAKE_EXE_LINKER_FLAGS "-mcpu=${CMAKE_SYSTEM_PROCESSOR} -g3 -T ${LINKER_SCRIPT} --specs=nosys.specs -Wl,-Map=${PROJECT_NAME}.map  -Wl,--gc-sections -static --specs=nano.specs -mfloat-abi=soft -mthumb -Wl,--start-group -lc -lm -lstdc++ -lsupc++ -Wl,--end-group")
set(CMAKE_ASM_FLAGS "-mcpu=${CMAKE_SYSTEM_PROCESSOR} -g3 -c -x assembler-with-cpp --specs=nano.specs -mfloat-abi=soft -mthumb")

include_directories(.)
include_directories(cmsis)

Ух, много получилось, но самое сложное позади! Осталось добавить конфигурацию для компиляции и отладки.

Добавляется все точно так же, как и в случае с путями для компиляторов.

Собственно это все. На этом проект должен компилироваться и запускаться, однако осталась одна мелочь, очень важная. При отладке хотелось бы иметь возможность смотреть в регистры МК. Для этого при первой сессии отладки нужно указать .svd файл. Их под множество платформ можно взять тут.

Заключение

Надеюсь я ничего не забыл, и у вас тоже получится успешно собрать проект в CLion. Статью я старался писать с упором на новичков и, надеюсь, я им хоть немного помог. Полный проект, рассматриваемый в статье, доступен тут. Там же добавленна библиотека CMSIS и пример моргания светодиодом. Если что, плата NUCLEO-F072RB.

Парочку вопросов к остальным

Отдельно хочется задать вопрос к другим пользователям:

  1. Как перекрасить цвет вывода OpenOCD в консоль? Она выводится зловещим красным цветом, и кажется что это какая-то ошибка, хотя все нормально...

  2. Есть ли какой-то плагин по типу того, который есть в STM32CubeIDE, отображающий текущее оставшееся место в RAM и ROM (в графическом представлении)?