На хабре уже есть немало информации об отладке МК в 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 следующие расширения:
C/C++ Extension Pack
CMake
CMake Tools
Cortex-Debug
Memory View
Tasks
Command Variable
Также рекомендую 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