Прошивка и отладка STM32 в VSCode под Windows

На хабре уже есть немало информации об отладке МК в VSCode на Linux (тыц, тыц), также было написано как настроить тулчейн для работы под Windows в QT Creator, Eclipse, etc.

Пришло и моё время написать похожую статью, но для VS Code и под Widnows.

Инициализация проекта будет проводиться с помощью STM32CubeMX. Сборкой будет управлять CMake с тулчейном stm32-cmake. В качестве компилятора используется ARM GNU Toolchain. Тестовым стендом является NUCLEO-F446ZE.

Источниками вдохновения послужили:

Предисловие окончено, приступаем к настройке.

Установка необходимых утилит

Для удобства установки будем пользоваться пакетным менеджером 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 следующие расширения:

Инициализация проекта

Открываем CubeMX и создаем проект для нашей платы. Всю периферию оставляем настроенной по умолчанию.

10deccd2444997eda6c14d986e9043c3.png

В параметрах проекта (Project Manajer) выбираем Make в качестве тулчейна

Настройки Project ManajerНастройки Project Manajer

В параметрах генератора кода указываем подключение бибилотек только в виде ссылок

Настройки Code Generator Настройки Code Generator

Настройка системы сборки

Открываем папку проекта в 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 — путь до файла, который необходим, чтобы просматривать регистры периферии МК

Картинка

a7db319a5d08e89df1d06ddaf74e8341.png

"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 и перейти сразу к отладке. Рассмотрим интерфейс:

Отладочный GUI в VS CodeОтладочный GUI в VS Code

Первая кнопка осуществляет программный сброс (software reset) устройства
Вторая запускает код (горячая клавиша F5)
Третья, четвёртая и пятая — «шаг» вперёд к следующей функции, «шаг» вперёд к следующей инструкции (т.е. с погружением) и выполнение код до выхода из функции.
Шестая клавиша осуществляет пересборку проекта и перезапуск прошивки.
А седьмая останавливает отладку.

Окно слева содержит следующие разделы:

Отладочный GUI в VS CodeОтладочный GUI в VS Code

  • Cortex Registers — регистры процессора

  • Cortex Peripherals — регистры периферии (например, там можно смотреть и изменять состоянием регистров GPIO и мигать светодиодом с помощью мышки, хехе)

  • Breakpoints — список выставленных прерываний. Отмечу, что у разных микроконтроллеров и отладчиков допустимо различное число брейкпоинтов (Например, у ST-Link V2.1 их всего 6)

  • В CallStack можно посмотреть очередь вызова (вплоть до main, что логично)

  • Раздел Variables позволяет просматривать как локально объявленные переменные, так и глобальные, например uwTick, показывающую количество милисекунд от момента запуска МК

  • В Memory View можно посмотреть в любой доступный раздел памяти МК

Адреса недоступные к прочтению (по причине отсутствия в этом мире на данном МК) показаны тильдойАдреса недоступные к прочтению (по причине отсутствия в этом мире на данном МК) показаны тильдойРассмотрим возможности Watch Window (и заодно сравним его с Keil MDK)

Массив const_data был объявлен статически, и его можно посмотреть просто по названию, тут всё как везде

68c5925afb00acb1d454ec95b7cc07ce.png

А теперь попробуем посмотреть содержимое динамически выделенного массива:

1dbadfdf7bf03772eb81da8382e88cad.png

Здесь, так же как и везде, дебаггер отобразит лишь первый элемент (в кавычках можно увидеть содержимое до первого \0). Однако, в отличие от, например, Keil MDK, мы можем явно указать, как именно следует воспринимать данный указатель:

e0d6086f9486133910ef5ca132fa1f4f.png

Такая возможность часто бывает необходима не только для динамически выделенных массивов, но и, например, при передаче в функцию указателя на какой-либо буфер.

Также мы можем переопределить этот указатель написав, например, такой запрос:
*(uint16_t*)data@8
Тогда в Watch Window будет показано отображение массива типа short, а не uchar

© Habrahabr.ru