На хабре уже есть немало информации об отладке МК в VSCode на Linux (тыц, тыц), также было написано как настроить тулчейн для работы под Windows в QT Creator, Eclipse, etc.
Пришло и моё время написать похожую статью, но для VS Code и под Widnows.
Инициализация проекта будет проводиться с помощью STM32CubeMX. Сборкой будет управлять CMake с тулчейном stm32-cmake. В качестве компилятора используется ARM GNU Toolchain. Тестовым стендом является NUCLEO-F446ZE.
Источниками вдохновения послужили:
Репозиторий stm32-template
Видео EbeddedGeek
Видео Matej Blagšič
Предисловие окончено, приступаем к настройке.
Установка необходимых утилит
Для удобства установки будем пользоваться пакетным менеджером Scoop.
Для его установки достаточно прописать в powershell следующие команды:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser;
irm get.scoop.sh | iex
Добавим необходимые репозитории
scoop bucket add main
scoop bucket add extras
И, наконец, сами программы:
scoop install meson
scoop install ninja
scoop install cmake
scoop install llvm
scoop install gcc-arm-none-eabi
scoop install stlink
scoop install openocd
scoop install git
meson, ninja, cmake, llvm и gcc-arm-none-eabi используются для конфигурации и сборки проекта, stlink и openocd являются gdb-серверами, git необходим для подключения различных тулчейнов.
P.S.
Если у вас уже есть что-то из этого и вы можете вызвать его через консоль (т.е. программа добавлена в path) то советую либо убрать её из скрипта либо удалить у себя, и установить через scoop.
Настройка VS Code
Для работы потребуются установить в VS Code следующие расширения:
-
Также рекомендую Doxygen Documentation
Инициализация проекта
Открываем CubeMX и создаем проект для нашей платы. Всю периферию оставляем настроенной по умолчанию.
В параметрах проекта (Project Manajer) выбираем Make в качестве тулчейна
В параметрах генератора кода указываем подключение бибилотек только в виде ссылок
Настройка системы сборки
Открываем папку проекта в VS Code и вызываем терминал командой Ctr+~
Скачиваем stm32-cmake
git clone --recurse-submodules -j8 https://github.com/ObKo/stm32-cmake.git
Также потребуются файлы .clang-format
, .clang-tidy
, fetch_svd.cmake
, и CMakeLists.txt
из репозитория stm32-template. Для удоства клонируем его в соседнюю директорию.
git clone https://github.com/Dooez/stm32-template.git ../stm32-template
.clang-format
, .clang-tidy
необходимы LLVM, а fetch_svd.cmake
используется для поиска файла описания регистров конкретного микроконтроллера.
Отредактируем CMakeLists.txt
под наш проект.
Изменим переменную MCU на STM32F446ZE
set(MCU STM32F446ZE)
По умолчанию "кубик" инициализирует на плате NUCLEO-F446ZE USART3, USB_OTG_FS и несколько GPIO. Добавим библиотеки в проект, для этого необходимо для сборки прописать команду target_link_libraries
. Также добавим библиотеку CMSIS и, для уменьшения размеров прошивки, Newlib Nano и NoSys
target_link_libraries(${PROJECT_NAME}
HAL::STM32::${MCU_FAMILY}::RCC
HAL::STM32::${MCU_FAMILY}::GPIO
HAL::STM32::${MCU_FAMILY}::UART
HAL::STM32::${MCU_FAMILY}::CORTEX
HAL::STM32::${MCU_FAMILY}::LL_USB
HAL::STM32::${MCU_FAMILY}::PCD
HAL::STM32::${MCU_FAMILY}::PCDEx
CMSIS::STM32::${MCU_MODEL}
STM32::Nano
STM32::NoSys
)
Чтобы CMake мог увидеть файлы, сгенерированные кубиком, необходимо добавить их в Include Path
и явно указать исполняемые c/cpp
файлы.
add_executable(${PROJECT_NAME}
Core/Src/main.c
Core/Src/stm32f4xx_it.c
)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/Core/Inc
${CMAKE_CURRENT_SOURCE_DIR}/Core/Src
)
main.c содержит, собственно, функцию main()
, а в stm32f4xx_it.c
находится функция, которая считает количество срабатываний SysTick
, без которой не будут работать такие функции как HAL_Delay()
Также для уменьшения размера исполняемого файла, добоавим следующие директивы компилятора:
target_compile_options(${PROJECT_NAME} PUBLIC -Os -fno-exceptions -fno-rtti)
Настройка проекта под VS Code
Нажимаем сочетание клавиш Ctrl+Shift+P
и в появившейся строке находим Preferences: Open Workspace Settings (JSON)
В создавшемся файле .vscode/settings.json
указаны параметры для расширений и корректного отображения кода. Пишем:
{
"cmake.generator": "Ninja",
"cmake.configureEnvironment": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "on"
},
"C_Cpp.default.intelliSenseMode": "gcc-arm",
"cortex-debug.gdbPath": "arm-none-eabi-gdb",
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"
}
Далее по тому же сочетанию находим Tasks: Configure Task и выбираем cmake build
В создавшийся файл .vscode/tasks.json
добавляем задания для прошивки и очистки памяти микроконотроллера с помощью st-flash. Итоговый файл tasks.json
выглядит следующим образом:
{
"version": "2.0.0",
"tasks": [
{
"type": "cmake",
"label": "CMake: build",
"command": "build",
"targets": [
"ALL_BUILD"
],
"problemMatcher": [],
"group": "build"
},
{
"type": "shell",
"label": "flash",
"command": "st-flash",
"args": [
"--reset",
"write",
"${input:workspaceFolderForwardSlash}/build/${workspaceFolderBasename}.bin",
"0x8000000"
],
"options": {
"cwd": "${workspaceFolder}/build"
},
"dependsOn": "CMake: build",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Builds project and flashes firmware."
},
{
"type": "shell",
"label": "erase",
"command": "st-flash",
"args": [
"--connect-under-reset",
"erase"
],
"detail": "mass erase of chip"
}
],
"inputs": [
{
"id": "workspaceFolderForwardSlash",
"type": "command",
"command": "extension.commandvariable.transform",
"args": {
"text": "${workspaceFolder}",
"find": "\\\\",
"replace": "/",
"flags": "g"
}
}
]
}
Также при желании можно добавить команду для прошивки с помощью OpenOCD
Для STM32F4 она выглядит следующим образом
{
"type": "shell",
"label": "flash-openocd",
"command": "openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c 'program ${input:workspaceFolderForwardSlash}/build/${workspaceFolderBasename}.bin verify reset exit' ",
"dependsOn": "CMake: build",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Builds project, connects to the openOCD server and flashes new firmware."
}
Далее необходимо сконфигурировать расширение CMake для VS Code
Нажимаем сочетание клавиш Ctrl+Shift+P
и в появившейся строке находим CMake: Configure
и выбираем конфигурацию под arm-none-eabi
После конфигурации автоматически сгенерируется файл .vscode/launch.json
, рассмотрим его поподробнее:
{
"configurations" :
[
{
"cwd" : "${workspaceRoot}",
"device" : "STM32F446ZE",
"executable" : "${workspaceRoot}/build/${workspaceFolderBasename}.elf",
"name" : "Cortex Debug (generated)",
"preLaunchTask" : "CMake: build",
"preRestartCommands" : [ "load", "enable breakpoint", "monitor reset" ],
"request" : "launch",
"runToEntryPoint" : "main",
"servertype" : "stutil",
"showDevDebugOutput" : "raw",
"svdFile" : "${workspaceRoot}/build/_deps/st-svd-archive-src/STM32F4_svd_V1.8/STM32F446.svd",
"type" : "cortex-debug"
}
],
"version" : "0.2.0"
}
svdFile
– путь до файла, который необходим, чтобы просматривать регистры периферии МК
Картинка
"preLaunchTask": CMake: build
– компилирует проект перед прошивкой МК.
preRestartCommands
– отправляет команды через GDB при нажатии на кнопку перезапуска отладки
Скрипт fetch_svd.cmake
по умолчанию использует в качетсве GDB-сервера stutils. Примеры конфигурации под OpenOCD и JLink можно посмотреть на вики cortex-debug в приложенных ссылках.
Переходим к коду (наконец-то)
Не мудрствуя лукаво, пойдём мигать светодиодом. (и ещё немного поиграемся с выделением памяти). Изменим main()
следующим образом
#include "stdlib.h"
uint8_t* data;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
data = new uint8_t[16];
uint8_t const_data[16];
for(int i = 0; i < 16; i++){
data[i] = i+1;
const_data[i] = i+1;
}
/* USER CODE END 2 */
/* Infinite loop */
while (1)
{
HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
HAL_Delay(50);
}
}
Компиляция проекта осуществляется нажатием клавиши F7
либо сочетанием Ctrl+Shift+B
. Так как ранее мы в launch.json указали сборку перед прошивкой, то нам будет достаточно нажать F5
и перейти сразу к отладке. Рассмотрим интерфейс:
Первая кнопка осуществляет программный сброс (software reset) устройства
Вторая запускает код (горячая клавиша F5
)
Третья, четвёртая и пятая – "шаг" вперёд к следующей функции, "шаг" вперёд к следующей инструкции (т.е. с погружением) и выполнение код до выхода из функции.
Шестая клавиша осуществляет пересборку проекта и перезапуск прошивки.
А седьмая останавливает отладку.
Окно слева содержит следующие разделы:
Cortex Registers – регистры процессора
Cortex Peripherals – регистры периферии (например, там можно смотреть и изменять состоянием регистров GPIO и мигать светодиодом с помощью мышки, хехе)
Breakpoints – список выставленных прерываний. Отмечу, что у разных микроконтроллеров и отладчиков допустимо различное число брейкпоинтов (Например, у ST-Link V2.1 их всего 6)
В CallStack можно посмотреть очередь вызова (вплоть до main, что логично)
Раздел Variables позволяет просматривать как локально объявленные переменные, так и глобальные, например
uwTick
, показывающую количество милисекунд от момента запуска МКВ Memory View можно посмотреть в любой доступный раздел памяти МК
Рассмотрим возможности Watch Window (и заодно сравним его с Keil MDK)
Массив const_data
был объявлен статически, и его можно посмотреть просто по названию, тут всё как везде
А теперь попробуем посмотреть содержимое динамически выделенного массива:
Здесь, так же как и везде, дебаггер отобразит лишь первый элемент (в кавычках можно увидеть содержимое до первого \0
). Однако, в отличие от, например, Keil MDK, мы можем явно указать, как именно следует воспринимать данный указатель:
Такая возможность часто бывает необходима не только для динамически выделенных массивов, но и, например, при передаче в функцию указателя на какой-либо буфер.
Также мы можем переопределить этот указатель написав, например, такой запрос:*(uint16_t*)data@8
Тогда в Watch Window будет показано отображение массива типа short
, а не uchar
Комментарии (27)
juramehanik
28.01.2023 22:45+2Спасибо, есть пара интересностей, но столько утилит и фреймворков чтоб светодиодом комфортнее помигать....
Куб и так генерирует мейкфайл, которому достаточно указать путь для gcc в tasks.json , хотелось бы увидеть пример кода или проекта, где имеет смысл наворачивать столько фарша помимо мейка. Или щас в ойти прям увольняют пожизненно, если не дай бох руками мейкфайл править?
lazbaphilipp Автор
29.01.2023 09:04Не понял про увольнение.
Я пробовал собирать и мейкфайлом из кубика в том числе. Никаких проблем с этим не было.
Насчёт фарша - тут из отличий от "обычного" проекта только cmake. Естественно, он не обязателен для отладки в VSCode, однако он даёт определённый набор преимуществ в плане подключения библиотек и автонастройки расширения c/c++
juramehanik
29.01.2023 10:41Ничем обидеть не хотел, просто очень часто слышу мол использование чистого make это плохой подход старых дедов и вообще плохо плохо как goto. Просто при этом не приводятся примеры проектов на на bare metall где наворачивание cmake оправдано.
lazbaphilipp Автор
29.01.2023 12:15+1Ничего против make в чистом виде не имею, но если и пушка и ядра бесплатные, то почему бы не использовать то, что даёт больше возможностей.
Однако я могу привести пример (пусть и только в виде описания). В одном из проектов у меня есть несколько очень похожих по структуре и функционалу устройств. Бо́льшая часть кода у них одинаковая, различие лишь в одном файле - парсере команд. При использовании CMake я могу держать парсер обоих устройств в одном проекте и собирать сразу два исполняемых файла без дублирования кода.
В упомянутых в статье видео EbeddedGeek показаны настройки VSCode для работы с чистым Makefile. Также в рунете есть статья по модификации его для совместной компиляции c и cpp файлов.
В целом, всё неплохо, кубик не удаляет и не переписывает пользовательские строки в мейкфайле(только что проверил), однако нужно настраивать c_cpp_properties и launch ручками и искать svd файл(что тоже не проблема, в целом, так как это делается один раз при создании проекта).И ещё небольшая поправка к первому вашему комментарию: сейчас установщик ARM GNU Toolchain прописывает путь до компилятора в
$Path
так что достаточно написать имя компилятора.
DSarovsky
29.01.2023 10:41Если задача только собрать проект (и тесты, например, запустить), то cmake скрывает необходимость установки фреймворков, так как умеет с github-а подтянуть, остается лишь компилятор поставить.
kenny5660
29.01.2023 05:15+4Для VsCode и STM32 есть плагин PlatfromIO, по факту бесплатный аналог VisualGDB, о котором говорили выше. Но PlatfromIO по моему мнению обладает куда большим функционалом, статический анализатор, анализ использованной памяти, удаленная отладка через tcp/ip, запуск Unit тестов, как на мк так и нативно на ПК (только аппаратно-независимый код).
lazbaphilipp Автор
29.01.2023 09:07В PlatformIO платная отладка. Статический анализатор есть и без него, кстати. Он появляется при подключении пакета расширений C/C++
DSarovsky
29.01.2023 10:25+2Только удаленная отладка платная, локально все хорошо (что логично ведь это простой openocd). Единственное, что пока в Platformio напрягает - это несвежесть компилятора, что решается путем скачивания и замены файлов в одной директории.
P.S. Чтобы два раза не вставать: использованный ObKo/stm32-cmake нормально из коробки подгружает библиотеки при их отсутствии? Мне не удалось, пришлось править в одном месте явный вызов MakeAvailable подставить.
lazbaphilipp Автор
29.01.2023 10:51Что имеется в виду под отсутствием?
Отсутствие в папке проекта? Я в своём примере их и так не включал. Я проверил его работу на двух семействах - F1 и F4, пока проблем не было, всё скачивалось.
Если же вопрос про отсутствие библиотек в репозитории, откуда он их подгружает, то тут не могу ничего сказать.DSarovsky
29.01.2023 11:15Не могу назвать себя спецом в cmake, но насколько понял из исходников, сценарий там такой примерно:
Если указаны STM32_CUBE_${FAMILY}_PATH или STM32_CMSIS_${FAMILY}_PATH, то взять оттуда.
Если не указаны, то считать, что кубовские библиотеки лежат по пути /opt/STM32Cube${FAMILY}
В utilities.cmake есть функции stm32_fetch_XXX, подтягивающие CMSIS/HAL с github, но я так и не нашел, где они вызываются и вставил руками.
Вопрос такой в итоге: без установленных заранее CMSIS/HAL у вас всё нормально собралось?
lazbaphilipp Автор
29.01.2023 11:52Понял.
Для проверки я удалил репозиторий CubeMX из системы, однако отмечу, что, как можно увидеть в CMakeLists, переменные эти у меня не объявлены. Папки/opt/
в Windows тоже нет.Результат такой: в момент конфигурации у меня ненадолго возрастает до максимума нагрузка на сеть, если подпапка
/build
отсутствует в проекте. Данные подтягиваются, проект собирается.
lazbaphilipp Автор
29.01.2023 10:38Перепроверил. Похоже, что я неправ – и отладка, и весь IDE в целом бесплатны.
Меня смутили некоторые особенности его работы, однако в остальном - прекрасный инструмент
Avlaak
30.01.2023 00:23По своему опыту понял, что для меня пока что ничего кроме Keil не подходит под серьёзные проекты, даже допотопность Keil нивелируется компилятором, в сравнении с ARM Compiler 6 все конкурирующие компиляторы проигрывают, это начинаешь понимать только когда проект большой и сложный, когда вопрос памяти и скорости работы стоит во главе угла. Пробовал связку VS code + Embedded IDE (https://em-ide.com/) с подключением ARM Compiler, но всё равно есть проблемы со скоростью отладки, хотя как у редактора кода у VS code нет конкурентов сейчас.
lazbaphilipp Автор
30.01.2023 00:57Я сравнил объем кода, генерируемого armclang v6.17 (из кейла 5.38а самого последнего) и arm-none-eabi-gcc в тулчейне с newlib nano.
И тут такой момент: при оптимизации с флагом -O0 действительно код из кейла меньше где-то на 0.7 КБ, однако при оптимизации -Oz + LTO код из кейла оказывается больше тоже почти на килобайт.
Скорость работы я не проверял, надо собрать оба варианта с -Os и запустить какой-нибудь алгоритм достаточно сложный
mctMaks
30.01.2023 12:18вы сейчас сравниваете LLVM (ака clang) и GCC, отсюда и разница в объемах результирующего кода. Для интереса, сравните выхлоп 5 версии компилятора (он на основе gcc как раз) с arm-none-eabi-gcc. Разница должна быть минимальной в таком кейсе.
хотя кейлы как раз 6 версию своего компилятора активно продвигают по типу он "быстрее\компактней"
lazbaphilipp Автор
30.01.2023 12:23ARM сейчас вообще кейл поддерживают просто потому что, а продвигают они свою Web IDE.
Да, действительно, я сравниваю два разных по принципу алгоритма, однако именно в этом и суть. Зачем сравнивать одинаковое?
Изначально в моём ответе был упомянут и ARMCC, главная проблема которого в том, что он очень очень очень медленно работает. Ну и ещё он не поддерживает даже c++11, как я понял по опыту. Но прошивки на его выходе получаются действительно компактными. Возможно, за счёт того, что код, который он всё же может обработать, это си и си с классами.
mctMaks
30.01.2023 14:20если я правильно помню, адекватная поддержка С++ у кейла появилась именно в armclang.
Да, действительно, я сравниваю два разных по принципу алгоритма, однако именно в этом и суть. Зачем сравнивать одинаковое?
из текста показалось что сравниваете проприетарный компилятор и свободно распространяемый. в данном случае имело смысл сравнивать одинаковое. Теперь когда стало понятно что сравнение именно технологий, то да.
Изначально в моём ответе был упомянут и ARMCC, главная проблема которого в том, что он очень очень очень медленно работает
Давно ушел от кейла, однако пробовал решение от Segger (их собственная сборка на основе armclang) которое выдает более компактную прошивку, по сравнению с тем же gcc. Разница на одном и том же проекте составила более 2кБ. Основное отличие при просмотре ассемблерного кода (у меня по крайней мере) в использовании одной инструкции вместо двух при загрузке int32 в регистры, однако скорости это не добавляет, так как инструкция все равно выполняется за 2 такта.
lazbaphilipp Автор
30.01.2023 15:18Почему два такта? У Cortex-M 32битное АЛУ, но 16 битная шина?
mctMaks
31.01.2023 10:51Хороший вопрос, не задумывался. Исходил из описания инструкций отсюда:
Заметил что по ассемблеру инструкций меньше, а счетчик тактов инкрементируется одинаково. Надо у Дзозефа Ю почитать за этот момент
lazbaphilipp Автор
31.01.2023 10:58Про какие команды речь? Судя по тому, что написано в референсе по ссылке, все Load/Store команды выполняются не меньше двух тактов. Полагаю, что segger используют другие флаги для компилятора, и оптимизируют где-то ещё.
mctMaks
31.01.2023 11:53+1оптимизация одинаковая стоит.
GCC выдавал две инструкции (не помню какие именно, возможно использовался MOV два раза, что эквивалентно одному LDR), а CLANG - одну.
По тактам время исполнения получилось одинаковым. Кода получаем меньше (число инструкций то другое), а время исполнения при этом не меняется.
Будет пауза на работе, попробую повторить эксперимент
devprodest
30.01.2023 23:19Кстати и сам clang нормально собирает под arm bare metal, только нужно ему библиотеки сишные подсунуть во время линковки хоть даже от гцц или же собрать себе из исходников ллвмные.
lazbaphilipp Автор
31.01.2023 11:00Ну да, транслятор из IR кода LLVM в ARM уже есть давно.
В общем-то говоря, можно использовать тот метод компиляции, которые считаете удобным. На процесс отладки это в большинстве случаев не влияет.
BARSRAB
Ээээ, а зачем танцы с бубном и VSCode, когда можно работать прямо в VisualStudio с VisualGDB?..
GennPen
Например, потому что VisualGDB платный.
lazbaphilipp Автор
Кроме того, VSCode весит сильно меньше Visual Studio и имеет довольно много приятностей из расширений.