Portfolio Обо мне Блог

Сейчас я хочу рассказать про то как настроить сборку проекта для микроконтроллеров STM32 в Debian подобных дистрибутивах GNU LINUX, думаю в других дистрибутивах это будет работать также. Для работы с микроконтроллерами нам нужны будут следующие компоненты: CubeMX, GCC tools, VScode. По сути в статье я расскажу как настроить каждый их этих компонентов, а потом как это все соединить вместе.

Тут должна быть часть про то какой VScode крутой, и насколько он лучше любого другого редактора, но мне лень, а вы об этом сможете прочитать и на главной странице данного проекта. От себя могу лишь сказать что в нем нормально работает темная тема, в принципе этого уже достаточно.

Создание среды для разработки

Для начала рассмотрим настройку системы для дальнейшей работы с ней.

CubeMX

Первым делом нужно установить кодогенератор от ST. Он значительно упрощает инициализацию периферии у микроконтроллера.

  1. Качаем отсюда последнюю версию CUBEMX, да требует почту, но зато работает любая.

  2. Создаем папку и распаковываем в нее CubeMX.

mkdir cube
cd cube
unzip ~/Downloads/en.stm32cubemx_v6-1-1.zip
chmod +x ./SetupSTM32CubeMX-6.1.1.linux
./SetupSTM32CubeMX-6.1.1.linux
# далее -> далее -> далее
# проверяем чтобы адрес был внутри home например: /home/zen/STM32CubeMX
# тыкаем на генерацию скрипта удаления.
cd .. && rm -r cube
  1. Можно добавить скрипт в /usr/bin, чтобы появилась возможность запуска из консоли:
sudo touch /usr/bin/cubemx
echo "/home/zen/STM32CubeMX/./jre/bin/java -jar /home/zen/STM32CubeMX/STM32CubeMX" | sudo tee /usr/bin/cubemx
sudo chmod +x /usr/bin/cubemx

Вместо zen подставьте имя своего юзера.

Также из-за того что я использовал нестандартный оконный менеджер (BSPWM), то java работала не корректно, и некотрые окна были пустыми. Решается это просто, необходимо подменить название вашего DE. Для дедиан нужно сделать следующее:

sudo apt install suckless-tools
wmname compiz

Далее перезапускаем java и все работает нормально. Последнюю строчку нужно добавить в автозапуск.

VSCode

Для начала нужно установить VScode, для этого нужно скачть DEB отсюда и раскатать.

Для трушных ребят есть альтернатива, можно использовать не оригинальный VScode а пересобранный с вырезанными закладками от microsoft - codium. Подробнее про это можно прочитать в другой моей статье.

Для решения данной задачи необходимо установить следующие расширения:

  • C/C++ (ms-vscode.cpptools) - продвинутый линтер.
  • Cortex-Debug (marus25.cortex-debug) - дебагер для cortex контроллеров.

Также вы можете заметить в поиске расширение stm32-for-vscode его использовать, я не рекомендую, так как оно модифицирует проект и его больше нельзя будет собрать без этого расширения, а также из консоли.

GCC

К сожалению т.к. мы используем Debian то

sudo apt install -y gcc-arm-none-eabi

не самый лучший вариант, т.к. скорее всего в пакетной базе сильно устаревший пакет, поэтому скачаем компилятор с официального сайта. Тут есть важный нюанс, если у вас в системе уже стоит какой-то компилятор gcc-arm-none-eabi* то удалите его, для избегания конфликтов, если у вас стоит какая то IDE вроде CubeIDE то это не важно т.к. они держат свои версии компиляторов внутри себя. Как только удалили из системы прошлую версию компилятора если она была, качаем последнюю версию для Linux x86_64 Tarball. Для того чтобы установить gcc-arm-none-eabi необходимо сделать следующее:

tar xvjf ~/Downloads/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2
mkdir ~/toolchains
cp -R gcc-arm-none-eabi-10-2020-q4-major ~/toolchains
echo "export PATH=\"\$HOME/toolchains/gcc-arm-none-eabi-10-2020-q4-major/bin:\$PATH\"" >> ~/.bashrc
# echo "export PATH=\"\$HOME/toolchains/gcc-arm-none-eabi-10-2020-q4-major/bin:\$PATH\"" >> ~/.zshrc
rm -r gcc-arm-none-eabi-10-2020-q4-major

Я предлагаю вынести все компиляторы в отдельную папку в home директории чтобы в будущем нам было легко управлять их версиями и менять их в случае необходимости.

Далее можно проверить что все нормально установилось и работает:

arm-none-eabi-gcc --version

Должен появится тот номер версии которую вы ставили.

arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10-2020-q4-major) 10.2.1 20201103 (release)
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Однако вызов none-eabi-gdb может выдать следующую ошибку:

arm-none-eabi-gdb: error while loading shared libraries: libncurses.so.5: cannot open shared object file: No such file or directory

Для решения нужно до установить библиотеку:

sudo apt install libncurses5

OpenOCD

Тут у нас по сути 3 варианта - это OpenOCD, ST-LINK utility и J-Link. У этих вариантов есть свои преимущества и недостатки, и их выбор зависит от софта и железа под которое вы хотите разрабатывать. Наша же цель это настроить блокнот для написания кода, в который включен минимальный необходимый набор функций: брейкпоинты и просмотр переменных (для более сложной отладки, я считаю, лучше использовать специальный софт, думаю об этом я еще напишу). Поэтому возьмем самый простой, свободный и универсальный вариант OpenOCD.

В репозитории он оказался достаточно новый поэтому:

sudo apt install openocd

Если вам необходима более свежая версия ее можно собрать из исходников.

sudo apt install libtool pkg-config texinfo libusb-dev libusb-1.0.0-dev libftdi-dev autoconf libhidapi-dev
git clone https://github.com/ntfreak/openocd
cd openocd/
git checkout v0.11.0        #выберите самую новую стабильную
./bootstrap
./configure
make
sudo make install
cd .. && rm -rf openocd

Далее могут возникнуть проблемы с правами, решение тут.

Для проверки работоспособности нужно сгенерировать кубом любой проект, после чего добавить туда мигание диодом: nano Core/Src/main.c:

while (1)
{
    HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
    HAL_Delay(100);
    /* USER CODE END WHILE */

Далее собираем код в корневой папке:

make

После чего вы должны получить размер получившегося elf файла.

arm-none-eabi-size build/test.elf
   text    data     bss     dec     hex filename
   5188      20    1636    6844    1abc build/test.elf
arm-none-eabi-objcopy -O ihex build/test.elf build/test.hex
arm-none-eabi-objcopy -O binary -S build/test.elf build/test.bin

Это значит что все хорошо и компилятор работает. Однако во время сборки может появится следующая проблема:

Makefile:122: *** пропущен разделитель.  Останов.

Она возникает из-за того, что при перегенерации проекта куб лишний раз добавляет ссылку на один из исходных файлов, при этом не добавляя разделитель. Исправить это очень просто, переходим в Makefile и удаляем указанную в ошибке строку, т.к. она повторяется 2 раза.

Далее необходимо проверить работу OpenOCD, для чего загрузим нашу прошивку на устройство.

Для этого открываем второй терминал и в нем запускаем openocd server.

openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg

Далее в другом терминале, подключаемся к этому серверу и делаем следующее:

telnet localhost 4444
> reset halt
> flash write_image erase /home/zen/Desktop/test/build/test.hex
> reset

После чего диод должен начать мигать. А значит все работает. Обратите внимание на то что путь до hex файла должен быть абсолютный, а не относительный.

Создание нового проекта

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

CubeMX

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

Для текущего примера возьмем любую nucleo и согласимся на генерацию дефолтной настройки для периферии. Далее необходимо выполнить 2 пункта:

  1. Сгенерировать проект с раздельными C/H файлами для периферии, это очень сильно увеличит читаемость кода, т.к. не превращает main.c в свалку генерированного кода, а также можно выбрать копирование в проект только необходимых библиотек а не всего хала.

c_h_generate

  1. Необходимо выбрать как тулчейн Makefile, дабы мы смогли собрать код в консоли без IDE. Также лучше выбрать Application Structure -> Advanced, тогда для файлов библиотек будут сгенерированные отдельные папки.

make

Далее выбираем имя проекта и путь, после чего нажимаем GENERATE CODE, и сворачиваем куб.

VSCODE

Для начала нам нужно открыть папку со сгенерированным проектом в VScode. Это можно сделать так:

code test

Далее я предлагаю создать пустые файлы конфигов через терминал чтобы потом не пришлось тыкать в меню, для чего откроем терминал в vscode (CTRL + ~):

mkdir .vscode
cd .vscode
touch launch.json
touch c_cpp_properties.json
touch tasks.json

Launch

Для начала настроем дебагер, делается это с помощью конфига .vscode/launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Cortex Debug",
            "cwd": "${workspaceRoot}",
            "executable": "build/${workspaceFolderBasename}.elf",
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "openocd",
            "runToMain": true,
            "preLaunchTask": "build",
            "device": "STM32F411RE",
            "svdFile": "${workspaceRoot}/Docs/STM32F411.svd",
            "configFiles": [
                "interface/stlink.cfg",
                "target/stm32f4x.cfg"
            ],

            "swoConfig": {
                "enabled": true,
                "cpuFrequency": 8000000,
                "swoFrequency": 2000000,
                "source": "probe",
                "decoders": [
                    { "type": "console", "label": "ITM", "port": 0 }
                ]
            }
        }
    ]
}

Ниже приведу описание параметры:

  • name - имя которое будет отображаться в окне дебагера.
  • cwd - папка с проектом.
  • executable - местоположение elf файла, нужно для отладки по строкам.
  • request - тип конфигурации.
  • type - тип конфигурации (расширение для конфигурации).
  • servertype - сервер отладки.
  • runToMain - дебагер ставит брекпоинт на первой команде после main.
  • preLaunchTask - запуск команды (для сборки), перед отладкой, описанной в tasks.json.
  • device - указать название вашего камня (по сути это не на что не влияет).
  • svdFile - прописать путь до SWD файла (об этом ниже)
  • configFiles - флаги передаваемые openOCD для отладки (в данном случае это файлы описания конфига для программатора и контроллера).
  • swoConfig - включение отладки через SWO, можно удалить этот блок если ее не используете.
  • configFiles - здесь указать программатор и семейство контроллера.

К сожалению блокнот никак не может узнать по каким адресам находится периферия в конкретно взятом контроллере, поэтому необходимо скачать файл SWD с описанием этих адресов с сайта производителя. Я предлагаю создать в проекте папку Docs, куда закинем этот файл и будем кидать даташиты со схемами. Для контроллера используемого мной этот файл можно взять тут.

Task

Далее разтеремся с Task так называются действия которые будут выполнятся при старте отладки контроллера. Для этого необходимо добавить конфигурацию в следующий файл - .vscode/tasks.json:

{
        "version": "2.0.0",
        "tasks": [
            {
                "label": "build",
                "type": "shell",
                "command": "make",
                "args": [
                    "-j9"
                ],
                "group": {
                    "kind": "build",
                    "isDefault": true
                },
                "problemMatcher": []
            },
            {
                "label": "clean",
                "type": "shell",
                "command": "make",
                "args": [
                    "clean"
                ],
                "problemMatcher": []
            }
        ]
}

Тут менять ничего не нужно, но пункты меню опишу:

  • label - это команда которую нужно будет выбрать в Run Task....
  • type - это тип команды (shell - выполняется в командной оболочке).
  • command - команда выполняемая в оболочке.
  • args - параметры этой команды.
  • problemMatcher - эта штука должна парсить вывод компилятора делая его долее читабельным.
  • group - установка данной команды как дефолтной для сборки, позволяет запускать сборку по нажатию CTRL + SHIFT + B.

Далее сохраняем этот файл, и во вкладке RUN выбираем Cortex Debug после чего нам станут доступны стандартные функции отладки.

debug

Linter

Далее необходимо решить проблему с линтером, т.к. он не знает где находятся наши исходные файлы, по этому не может подсказать где ошибка, а также не работает авто подстановка. Для этого нам необходимо изменить файл .vscode/c_cpp_properties.json:

{
    "configurations": [
        {
            "name": "STM32",
            "compilerPath": "arm-none-eabi-gcc",
            "includePath": [
                "${workspaceRoot}/**"
            ],
            "defines": [
                "STM32F411xE",
                "USE_HAL_DRIVER"
            ],
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-arm"
        }
    ],
    "version": 4
}

Описание пунктов:

  • includePath - это папки в которых следует искать хедеры, но для того чтобы работал рекурсивный поиск, необходимо поставить ** в конце адреса.
  • compilerPath - путь до компилятора.
  • intelliSenseMode - тип компилятора.
  • defines - параметры компилятора (подключаемые библиотеки), необходимые параметры можно найти в makefile:
# C defines
C_DEFS =  \
-DUSE_HAL_DRIVER \
-DSTM32F411xE

Также если вдруг когда вы читаете эту статью С++20 стал нормально поддерживаться то поменять c++17 на c++20.

MakeFile

Вот мы и получили полностью функциональную рабочую среду, но почему бы не сделать еще один шаг, и не подправить makefile, раз уже мы зашли так далеко, как не странно наши изменения даже не пострадают в случае изменения проекта в кубе (хотя возможна проблема, где куб сгенерит одну строку 2 раза, что описано выше). Я предлагаю сделать следующие улучшения:

  • Добавить в CFLAGS флаг -std=c11.
  • Также я предлагаю заменить вывод линтера на более информативный, для этого заменить это:
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
    $(CC) $(OBJECTS) $(LDFLAGS) -o $@
    $(SZ) $@

на это:

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
    @echo "\r\n---------------------   SIZE   ----------------------"
    @$(CC) $(OBJECTS) $(LDFLAGS) -o $@ -Wl,--print-memory-usage
    @echo ""
    @$(SZ) $@
    @echo "-----------------------------------------------------"

что выведет нам количество занятой памяти:

---------------------   SIZE   ----------------------
Memory region         Used Size  Region Size  %age Used
             RAM:        1648 B       128 KB      1.26%
           FLASH:        5208 B       512 KB      0.99%

   text    data     bss     dec     hex filename
   5188      20    1636    6844    1abc build/test.elf
-----------------------------------------------------pravilno-gotovim-cubemx

Если просто скопировать строки выше и вставить в vscode то побьются табуляции перед @. Восстановите их чтобы make не ругался.

Для добавления в проект новых .c и .h файлов их необходимо будет указать в makefile в полях C_SOURCES и C_INCLUDES соответственно, причем для последних достаточно будет указать только папку а хедеры будут найдены автоматически.

CODE

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

mkdir App && cd App && mkdir Src Inc
touch Inc/app.h
touch Src/app.c

После чего добовляем в файл app.h следующее:

#ifndef INC_APP_H_
#define INC_APP_H_

#include "main.h"

void app();

#endif /* INC_APP_H_ */

А в app.h следующее:

#include "app.h"

void app()
{
    while (1)
    {

    }
}

Дальше работаем с этим файлом когбудто это наш main.c. Но для того чтобы это заработало нужно сделать еще 2 действия. Первое это вызвать app() из майна:

/* Private includes ----------------------------------*/
/* USER CODE BEGIN Includes */
#include "app.h"
/* USER CODE END Includes */

// ...........

/* USER CODE BEGIN 2 */
app();
/* USER CODE END 2 */

Второе это добавить новые файлы в make:

# C sources
C_SOURCES =  \
App/Src/app.c \
Core/Src/main.c \

...............

# C includes
C_INCLUDES =  \
-ICore/Inc \
-IApp/Inc \

Далеше добавляйте все прочие файлы в папку App, если проект начинает разрастатся то имеет смысл выделить часть связанную с конкретной платой в папку Bsp, а в App оставить только бизнес логику. Тут для вас открывается свобода для творчества по организации проекта, в систему сборки все добавляется аналагичным образом.

GIT

Для начала нужно создать проект на вашем удаленном сервере (your-repo-name), я советую использовать gitlab, тут есть важный нюанс, проект обязательно должен быть пустым, иначе при слиянии локального и удаленного проектов возникнут ошибки. Далее в корне проекта делаем следующее:

git init
touch .gitignore
echo "/build" >> .gitignore
git add -A
git commit -am "first commit"
git remote add origin https://gitlab.com/yourusername/your-repo-name.git
git push -u origin master

При желании можно также добавить файл "README.md" в корень проекта, и добавить туда описание вашего творения, его будет видно на главной странице проекта.

Может показаться, что это слишком большая и сложная инструкция для повторения и гораздо проще использовать KEIL на винде и не заниматься всем этим. Однако в ней нужно будет разобраться только один раз, и дальше просто подставлять json файлы в свой проект, меняя в них пару строк дабы указать название своего контроллера и все (еще из неудобств нужно скачивать отдельно SVD, но это можно не делать если вам не нужны адреса периферии для отладки).

Git с примером проекта - тут.