Собственно идея написать эту статью как памятку себе любимому, ну может ещё кому пригодится пришла в голову год назад, после того как убил немало времени на это нехитрое занятие. Недавно оказалось, что проблема актуальна по сей день. Почему-то ни один из найденных вариантов сам по себе не помогает и данная статься является результатом обработки всей найденной информации. При решении вопроса, больше всего бесило - возьмите мой проект и будет вам счастье, а проекта там уже и нет... Такой подход я плохо переношу, поэтому и сам делать так не буду.

Всё ниже описанное является следствием моего личного опыта, и ни на какую истинность не претендует. Все советы рассчитаны не людей только решившихся на переход с AVR на STM32

Вопросы типа почему Linux, VSCode и прочее, думаю, освещения не требуют. Считаю, что все заинтересованные в вопросе, на эти мелочи давно нашли СВОЙ ответ. Однако отмечу, в Винде всё это тоже работает, проверено, и проекты спокойно переживают миграцию между машинами.

Пожалуй начнём!

Этап установки VSCode опущу, этого добра настролько навалом, что даже чересчур. Скажу только, что минимально нужно поставить модули C/C++ от Microsoft (считаю, что плагин от крупного автора имеет больше шансов на долгую жизнь), Cortex-Debug от marus25 (альтернатив пока нет) и Makefile Tools от Microsoft (анализирует Makefile и сам настраивает IntelliSense, давно ждал такую штуку, было хоть сам пиши).

Самый простой способ получить заготовку проекта - собрать его в CubeMX. Процесс интуитивно понятный, сотни раз описанный в сети, поэтому отвлекаться на него не буду. Отмечу пару важных моментов.

Нужно не забыть в настройках порта разрешить отладочную шину

Очень беспонтовая ошибка. По умолчанию все выводы переводятся на вход и после первой прошивки у Вас отваливается программатор. Так как у всех начинающих программатор - китайский минимальный клон, то никакого сброса для STM32 там нет и что дальше делать не всем понятно.

Решение довольно простое - нужно исправить досадную ошибку и запустить процесс программирования или стирания удерживая кнопку сброса. Когда программа будет выполнять повторы подключения, отпустить сброс. Получиться может не с первого раза, но обязательно получится. Ещё на днях подсказали способ - переключить джамперы загрузки на положение отличное от Flash, что бы не дать запуститься нашей косячной программе и переписать камень из такого положения (сам не пробовал).

Как и у меня в своё время, у большинства начинающих в распоряжении так называемая Синяя Пилюля, поэтому давайте включим выход к которому подключен светодиод (PC13).

Так как светодиод через резистор подключен к питанию, то вывод настроим в режим Open-Drain, хотя от Push-Pull хуже не будет.

Настройку тактировки мне приводить лень, пхать сюда огромную бессмысленную картинку не вижу смысла, а от уменьшенной смысла ещё меньше. К тому же CubeMX предлагает сам настроить все коэффициенты, да и для ручного режима всё более чем интуитивно понятно.

Поэтому завершаем работу с CubeMX задав имя проекта и обязательно переключив *Toolchain/IDE* на *Makefile*

После генерации проекта, в целевом каталоге получаем вот такой набор файлов.

Всё бы хорошо, но для отладки нам необходим SVD-файл, в нём информация для отладчика, необходимая для человеческого отображения регистров периферии, ядра и т.п.

Где взять этот файл? С одной стороны всё довольно быстро ищется, но не всегда это будут свежие версии. Лучше всего взять с сайта ST. Идём на st.com, дальше Products -> Microcontrollers & Microprocessors и ищем там свой проц. В нашем случае STM32 32-bit Arm Cortex MCUs -> STM32 Mainstream MCUs -> STM32F1 Series -> STM32F103

Тут можно найти всю возможную документацию по процессору, но сейчас нас интересует вкладка CAD Resources. На ней находим HW Model, CAD Libraries & SVD -> System View Description. Скачиваем найденый архив. Для ленивых ссылка на страницу. В архиве найдёте несколько svd-файлов. Нас сейчас интересует - STM32F103.svd. Не долго думая кидаем его в папку с проектом.

Пришло время запустить VSCode!!! Лично я предпочитаю в нужном каталоге дать команду code ., а там каждому своё... Программа спросит, доверяете ли вы авторам этого каталога, придётся сказать, что доверяете. Makefile Tool проанализирует Makefile, о чём выдаст много текста в панели, и настроит IntelliSense. Ещё не так давно приходилось его настраивать вручную, не сложно, но не интересно.

Не забываем сохранить Рабочую Область!!!

Теперь немного дополняем Makefile - добавляем ещё одну цель prog

prog: $(BUILD_DIR)/$(TARGET).elf
    openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c "program build/$(TARGET).elf verify exit reset"

Небольшое пояснение. Скрипты interface/stlink-v2.cfg и target/stm32f1x.cfg позволяют openocd узнать через какой адаптер и с кем он должен работать. Сами скрипты распологаются в каталоге scripts с установленным openocd (для Винды) или в каталоге /usr/share/openocd в Linux. Приведённый пример работает для китайского клона ST-Link v2 и Синей Пилюли. Если у Вас другой программатор или камень, то необходимые файлы найдёте в тех же каталогах.

Собственно зачем нужна эта байда? Иногда нужно записать программу в процессор не запуская отладку - просто записать и посмотреть, что произойдёт. Вот за это и отвечает -c "program build/$(TARGET).elf verify exit reset

Следующим этапом я настраиваю git, но это по вкусу. Ну как настраиваю, создаю файл *.gitignore* ну и инициализацию репозиторий. Пример .gitignore

    DSP/
    Templates/
    Examples/
    NN/
    /build/
    *.log
    *.d
    .mxproject
    *.ioc

по списку поясню только каталог build - в него производится сборка проекта, поэтому чтобы не отвлекаться на него и случайно не нагадить в коммит его и игнорим. Логи любит создвать Makefile Tools, они мне тоже ни к чему.

Если очень чешутся руки, то на данном этапе уже можно в терминале VSCode дать команду `make prog`, соберётся Ваш пустой проект и запишется в процессор. Процесс сборки будет отображаться здесь же в терминале, а об успешной записи Вам сообщит openocd примерно вот так

...
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : clock speed 1000 kHz
Info : STLINK V2J37S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.169781
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f1x.cpu on 3333
Info : Listening on port 3333 for gdb connections
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08000c74 msp: 0x20005000
** Programming Started **
Info : device id = 0x20036410
Info : flash size = 64kbytes
** Programming Finished **
** Verify Started **
** Verified OK **
** Resetting Target **
shutdown command invoked

Желающие почесали руки, продолжаем. В каталоге .vscode проекта нужно создать файл tasks.json примерно такого содержания

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build",
            "type": "shell",
            "group": "build",
            "command": "make",
            "problemMatcher": []
        },
        {
            "label": "Clean",
            "type": "shell",
            "group": "build",
            "command": "make clean",
            "problemMatcher": []
        },
        {
            "label": "Write firmware",
            "type": "shell",
            "command": "make prog",
            "problemMatcher": []
        }
    ]
}

Собственно здесь объявляются три задачи: сборка проекта, очистка рабочего каталога и прошивка контроллера. Можно конечно и здесь расписать все команды с параметрами, раньше так и делал, но потом стало лень дублировать команды, следить что бы они были одинаковыми в Makefile и тут... Ну в общем остановился на таком варианте. Работает же...

Теперь создаём launch.json c вот таким содержимым

{
	"version": "0.2.0",
	"configurations": [
		{
			"name": "openocd",
			"type": "cortex-debug",
			"request": "launch",
			"cwd": "${workspaceRoot}",
			"servertype": "openocd",
			"executable": "./build/Habr.elf",
			"svdFile": "STM32F103.svd",
			"configFiles": [
				"interface/stlink-v2.cfg",
				"target/stm32f1x.cfg"
			],
			"preLaunchTask": "Build"
		}
	]
}

Страшная штука. Собственно "name" может иметь любое значение, оно влияет только на отображение. Поле "executable" должно иметь значение файла в который собирается ваша программа, с указанием пути от каталога проекта. У Вас оно своё, поэтому вместо Habr должно быть то, чему у вас равно TARGET = в Makefile. В "svdFile" прописываем svd файл нашего процессора. Если вы его положили не в корень проекта, то нужно указать путь от каталога проекта. В "configFiles" указываем те самые скрипты, которые мы добавляли в Makefile для прошивки контроллера. И последний момент "preLaunchTask": "Build" - запуск задачи сборки программы из tasks.json, что бы было чего прошивать и отлаживать.

А теперь момент без которого всё это добро не работает - необходимо сообщить программе, где лежат бинарники openocd и gdb, причём не пути к каталогам с ними, а именно сами программы. Сделать это нужно в глобальном файле settings.json, пока так стабильно работает. Так как сам этот файл слегка закопан, к тому же, в Linux и Windows находится в разных местах, то я для себя нашёл простой способ его открыть. Идём Файл -> Настройки -> Параметры, откроется вкладка с настройками. Далее Текстовый редактор -> Шрифт и ищем надпись Изменить в settings.json. В открывшийся файл нужно добавить следующие строчки

"cortex-debug.armToolchainPath": "usr/bin",
"cortex-debug.openocdPath": "/usr/bin/openocd",
"cortex-debug.gdbPath": "/usr/bin/gdb-multiarch",

"cortex-debug.armToolchainPath" - просто путь к каталогу в котором ваш *arm-none-eabi-gcc*, вот тут просто каталог

"cortex-debug.openocdPath" - полный путь к бинарнику openocd, для винды будет заканчиваться на .exe

"cortex-debug.gdbPath" - полный путь к бинарнику gdb котрым Вы будете пользоваться

Ну и финишная прямая.

В файле main.c чуть-чуть меняем функцию main() на вот такое

int main(void)
{
  uint32_t tempTick;
 
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();

  __ASM("NOP");
  while (1)
  {
    tempTick = HAL_GetTick();
    while (HAL_GetTick() - tempTick < 500);
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 1);
    tempTick = HAL_GetTick();
    while (HAL_GetTick() - tempTick < 500);
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 0);
  }
}

Честно говоря страшнее кода придумать с ходу не смог. Хочу просто показать, что ЭТО ВСЁ может жить.

Теперь на __ASM("NOP"); ставим точку останова, переходим "Запуск и Отладка" и запускаем всё это добро, нажав на зелёный треугольничек.

В Терминале побежали надписи процесса компиляции и через пару секунд всё остановится на вот такой картинке

ПОЗДРАВЛЯЮ, ВЫ В ОТЛАДКЕ!!!

Можете просто нажать F5 или значок проигрывания и наслаждаться мигающим светодиодом!

На этом у меня всё! Можно было бы ещё рассказать про настройку IntelliSense, но во-первых такого добра и так навалом, а во-вторых проблема уже частично решается плагином Makefile Tools, хоть пока и неидеально, но для начала хватает.

Комментарии (35)


  1. GennPen
    26.10.2021 01:43
    +1

    Решение довольно простое - нужно исправить досадную ошибку и запустить процесс программирования или стирания удерживая кнопку сброса. Когда программа будет выполнять повторы подключения, отпустить сброс. 

    Все гораздо проще. Перед подачей питания на контроллер (либо нажатием кнопки сброса) нужно BOOT0 подтянуть к VCC. Делается это соответствующей перемычкой или зажатием соответствующей кнопки. Контроллер переводится в режим DFU и его можно шить хоть через SWD, хоть через UART, хоть через USB (в F1 не поддерживается)


    1. VelocidadAbsurda
      26.10.2021 02:12

      А можно один раз в китайском "свистке" вывести наружу сигнал сброса (нога 18 контроллера) и забыть об этих мучениях навсегда.


      1. GennPen
        26.10.2021 08:49

        Не вижу смысла в подключении аппаратного ресета, когда программный нормально работает. А данный способ подъема нужен за очень редким исключением, когда забываешь первым делом включить SWD-интерфейс.


    1. lamerok
      26.10.2021 06:19

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

      -f ../scripts/board/st_xnucleo_f4.cfg -c "reset_config none separate" -c "init" -c "reset halt"

      -c "reset_config none separate" — означает, что сброс производится через SWD без использования отдельной ножки сброса (а у меня как раз такой китайский отладчик)


      1. VelocidadAbsurda
        26.10.2021 12:38

        Проблема в том, что самим пинам SWD программно могут быть назначены другие функции (по ошибке, как в примере, либо намеренно). В таком случае для установления связи по SWD нужно держать ядро в сбросе (тогда пины будут исполнять функцию по умолчанию - SWD). Для случая «по ошибке», как написали выше, действительно достаточно замкнуть nRST любым подручным средством. Однако для проекта с сознательно переназначаемым SWD это уже будут постоянные мучения и проще один раз допаять один провод в самом отладчике.


  1. stephanthe
    26.10.2021 08:26
    +1

    Плагин PlatformIO к VSCode не рассматривали? Я поставил его, а от Куба полностью отказался.


    1. jogick Автор
      26.10.2021 08:31
      +6

      Я смотрел на PlatformIO, но решил отказаться. Очень не люблю когда на двадцать строк кода добавляется пол-мегабайта служебных файлов. И от меня прячется основная часть работы. По этой причине я в своё время отказался от AVR CodeVision, потом от AVR Studio, потом от Eclipse. Сам PlatformIO показался громоздким и избыточным. Опять же этомоё мнение и никого ни к чему не призываю.


      1. stephanthe
        26.10.2021 08:59
        +1

        Мне кажется, что куб прячет гораздо больше и добавляет в проект несколько мегабайт файлов. Причём я если при генерации проекта выбирал везде LL, и копировать только используемые файлы, он мне все равно в папку проекта кроме CMSIS накидывает и HAL, и main.c создаёт довольно не слабый. Может я конечно кубик готовить не умею. Я использую CMSIS, и всю инициализации пишу сам, а платформио библиотеки в проект не копирует, и вся папка проекта у меня килобайт на 60 выходит.


        1. remzalp
          26.10.2021 09:15

          А компилированный проект при этом тяжелый или сравнительно лёгкий?
          Кубик про запас накидывает всё необходимое


          1. jogick Автор
            26.10.2021 09:18

            Если посмотрите Makefile, то там подключаются только те файлы исходников, которые используются, а дальше всё на совести gcc.



          1. stephanthe
            26.10.2021 10:57

            #include "stm32f4xx.h"
            #define BLUE (15)
            #define ORANGE (13)
             
            int main()
            {
            RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN | RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOCEN;
            // для выбора порта прерывания обязательно включить тактирование контроллера системного конфигурирования
            RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
             
            GPIOD->MODER |= GPIO_MODER_MODER13_0 | GPIO_MODER_MODER15_0;
            GPIOC->PUPDR |= GPIO_PUPDR_PUPDR6_0; // подтягивающий резистор к питанию
             
            EXTI->RTSR |= EXTI_RTSR_TR0; // прерывание по 0 линии по повышению (rising)
            EXTI->FTSR |= EXTI_FTSR_TR6; // прерывание по 6 линии по падению (falling)
            EXTI->IMR |= EXTI_IMR_MR0 | EXTI_IMR_MR6; // снимаем маскирование прерываний по линиям 0 и 6
            SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI0_PA; // прерывание по 0 линии с порта A
            SYSCFG->EXTICR[1] |= SYSCFG_EXTICR2_EXTI6_PC; // прерывание по 6 линии с порта C
             
            NVIC_SetPriority(EXTI0_IRQn, 15);
            NVIC_SetPriority(EXTI9_5_IRQn, 10);
            NVIC_EnableIRQ(EXTI0_IRQn);
            NVIC_EnableIRQ(EXTI9_5_IRQn);
             
            for(;;){
            asm("NOP");
            } 
            }
             
            void EXTI0_IRQHandler(void)
            {
            EXTI->PR = EXTI_RTSR_TR0;
            GPIOD->ODR ^= (1 << BLUE);
            }
            void EXTI9_5_IRQHandler(void)
            {
            EXTI->PR = EXTI_RTSR_TR6;
            GPIOD->ODR ^= (1 << ORANGE);
            }

            Мне не с чем сравнивать, я начинаю только. Сейчас разбираюсь с прерываниями. Два светодиода, две кнопки, два прерывания. Код в платформио выглядит так. Файл bin 884 байта.


            1. remzalp
              26.10.2021 11:34
              +1

              Бинарник вполне компактный. Минимальный размер. Можете еще утрамбовать, но там уже придётся руками адреса прописывать, плюс в ассемблер удариться.


              1. stephanthe
                26.10.2021 11:58

                Спасибо за совет, но думаю мне пока рано. Первый месяц в микроконтроллеры ударился. Но может через какое-то время и до ассемблера доберусь. Адреса вручную есть смысл? Ведь все определения в CMSIS задефайнены, и вроде на размер влиять не должны. Разве что функции работы с прерываниями заменить на запись в регистры.


                1. remzalp
                  26.10.2021 12:25
                  +1

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


              1. VelocidadAbsurda
                27.10.2021 02:05

                Как вы оценили минимальность? Сам вот этот видимый код выше со включённой оптимизацией оттранслируется во что-то порядка 200 байт. А остальное — раздутые startup, SystemInit и прочие чудеса, заботливо добавляемые Кубом до вызова main, о существовании которых немало начинающих и не подозревает, т.к. их код туда не ссылается.


        1. jogick Автор
          26.10.2021 09:16

          Я тоже выкидываю из Кубовского проекта всё безбожно, меня от него интересуют только свежие библиотеки и начальная инициализация тактировки, HAL выпиливаю начисто.


      1. DSarovsky
        26.10.2021 17:12

        Мне кажется, странно предъявлять претензий к PlatformIO по поводу разрастания проекта и тут же использовать HAL, который тоже тащит кучу всего, причем прямиком в прошивку.


  1. AKP1
    26.10.2021 09:06
    +1

    Пробовали STM32CubeIDE? Или не рассматривали так как основано на eclipse? Просто всё-равно использовали CubeMX для конфигурации, а в обозначенной IDE он встроен


    1. jogick Автор
      26.10.2021 09:13

      STM32CubeIDE я смотрел когда тот ещё был совсем маленьким и запустить проект на Eclipse было проще чем на STM32CubeIDE. К тому же тогда его не было для Linux. Сейчас для меня основным набором являются Си для микроконтроллеров (STM8, STM32 AVR) и Python для служебных утилит. Если учесть что я перемещаюсь между Window, Debian и Raspberry (да, я использую RPI 4 8 Gb как нормальную машину) то полная кросплатформенность для меня важна.


      1. remzalp
        26.10.2021 12:26

        Уже пару лет как вполне хорошо с CubeID, посмотрите еще раз.


        1. count_enable
          26.10.2021 14:02
          +1

          Уже пару лет как всё очень плохо с STM32CubeIDE.

          Оно медленное. Оно очень плохо работает с Makefile-проектами. Оно постоянно качает гигабайты обновлений.

          Вы скачали CubeIDE и решили попрограммировать. Выбираете процессор или плату и оно начинает качать библиотеки - так мегабайт по 700. Поменяли семейство? Опять качать ВСЁ. Невозможно выбрать компоненты, оно скачает весь BSP, примеры, библиотеки для фат, фриртос, аудио, сенсорных экранов и ещё 100500 компоненотов.

          А через пару недель прилетает обновление и пожалуйте качать заново библиотеки для старых проектов. С STM32CubeIDE не бывает так что его просто можно запустить и начать программировать. Впрочем, у других производителей ситуация не лучше, а наверно ещё хуже. При таком качестве бесплатного софта не удивительно что IAR прямиком из 1998 года продают за большие деньги и он народу нравится.


          1. Punk_Joker
            26.10.2021 17:13
            +1

            IAR комуто нравиться? Не знал этого.


            1. count_enable
              26.10.2021 19:04
              +2

              Интерфейс топорный, плюшек типа автокомплита и интеграции с гитом нету, но работает очень стабильно, быстро, и имеет кучу плагинов для отладки (например для ОСРВ). И конечно же нету добровольно-принудительных обновлений посреди рабочего дня и всплывающих "помощников". Он для тех кому ехать, а не шашечки.


              1. St_325
                27.10.2021 13:37

                Есть там автодополнение. А к дизайну быстро привыкаешь


            1. jogick Автор
              26.10.2021 21:38

              Не знаю как IAR, а вот с Keil'а пытаемся слезть на описанную в статье связку. Сейчас занимаемся корректировкой рабочего проекта.


            1. Gerrero
              27.10.2021 10:08
              +1

              Там вроде свой компилятор какой-то крутой плюс очень много всяких плюшек в виде плагинов.Очень мощная штука для серьезных вещей. Есдинственный "минус" - интерфейс.


        1. milssky
          26.10.2021 15:49

          А CubeIDE научилось в нормальный автокомплит не по горячим клавишам?


      1. grmile
        26.10.2021 21:22

        По RPI как впечатления? Не тормозит? Намного дольше собирает?


        1. jogick Автор
          26.10.2021 21:28

          Смотря с чем сравнивать. Первая сборка идёт дольше, но потом пересобираются только изменённые файлы, а это один - два. Если учесть, что большую часть времени тупишь над котодом или смотришь результат в режиме отладки, то если время полной сборки будет пятнадцать секунд, а не десять - я переживу.

          Всё остальное работает как и на большой машине.


          1. grmile
            27.10.2021 09:09

            Если сравнивать с настольником или лептопом. Когда сборка занимает десять секунд то разница небольшаяб у меня проект большой, сборка в один поток 5-6 минут, хотелось бы прикинуть во сколько раз дольше это будет на малине.


  1. d_suslov
    26.10.2021 16:29

    Идём Файл -> Настройки -> Параметры, откроется вкладка с настройками. Далее Текстовый редактор -> Шрифт и ищем надпись Изменить в settings.json.

    Можно проще перейти в файл с настройками: ctrl+shift+P -> Open settings (JSON)


  1. RollerBob
    26.10.2021 21:28

    А ещё есть прекрасное расширение для VSCode STM32-for-vscode, которое из подготовленного кубом проекта и мэйкфайла конфигурирует task.json, settings.json, launch.json, c_cpp_properties.json. А также может сам скачать и настроить openocd и arm-none-eabi-gcc. Короче, сделать всё что описано в статье :)


    1. jogick Автор
      26.10.2021 21:36

      Не спорю, но зачем мне ещё один плагин, если я всё это могу и сам сделать за пять минут.

      Что Вы будете делать, когда автору плагина надоест его поддерживать, а вы знать не знаете о той ерунде которую он делал? Уже не раз столкнулся с такой ерундой на Eclipse, когда программист решил не переписывать плагин под новую версию среды.


      1. order227
        27.10.2021 00:51

        Если плагин с открытым кодом, то проблем никаких нет - делаешь форк и спишь спокойно. Если код закрыт, то в топку


        1. jogick Автор
          27.10.2021 05:38

          Лично у меня не на столько много лишнего времени, что бы поддерживать форк чужой программы. Особенно, если учесть, что она мне не нужна.

          И сейчас достаточно работы

          Вы же не будете выпускать свой хлеб, только потому что ваша любимая пекарня решила закрыться