Vs code программирование микроконтроллеров

Настройка VSCode для программирования AVR в Linux

Всем, кто занимается электроникой, так или иначе знакомы контроллеры AVR. Начинающим они знакомы, по большей части, за счёт экосистемы Arduino. В них есть большой плюс — собственный загрузчик, что избавляет от необходимости приобретать недешёвый программатор, во всяком случае сразу. Даже у азиатов самый дешёвый ISP-программатор стоит столько же сколько и клон st-link v2, хотя возможности скромнее. В этой статье я не буду касаться темы выбора операционки, IDE и прочих «религиозных» вопросов. Скажу только, что сам качую между Debian (основная ОС уже много лет) и Win7 (рабочая), поэтому вопрос повторяемости решения весьма важен.

Всё описаное ниже, я вляется результатом личного опыта, а программа написана в демонстрационных целях, специально для статьи.

С чего движняк? А собственно с того же, с чего и всегда. Понадобилось мне внести правку на пару строчек с старючую самоделку, собранную из arduino и палок. Беда в том, что писалась программа в Eclipse, а он с тех пор два раза менялся, плагины переставали работать и сейчас этот же проект опять не собирается. Беда ещё и в том, что простой запуск make в каталоге проекта ничего не даёт — он падает с ошибкой. Почему-то половина параметров, необходимых для сборки прописана в Makefile, а вторая половина передавалась параметрами. Сама сборка разбита на несколько mk-файлов, по три строчки в каждом. Ещё удивили левые дефайны с андефайнами на них же. В общем огорчился я и решил перевести всё сделать по-своему. Возвращаться на Eclipse, в обозримом будущем, не вижу ни малейшего смысла, поэтому пилим под мой любимый сейчас VSCode. Занятие нехитрое и я охотно верю, что Вы можете сделать всё красивее и даже подскажете в комментариях как, но есть люди для которых make — тёмный лес с волками. Думаю начинающим такая заметка пригодится. Рассказывать про PlatformIO, пожалуйста, не надо — это отдельная тема, думаю посмотреть на неё ещё раз в обозримом будущем, но это не точно.

Читайте также:  Программирование роллетных ворот дорхан

Начнём! В моём распоряжении Arduino Nano и Debian bullseye. Для начала необходимо установить пару пакетов: gcc-avr; binutils-avr; make; avrdude. Для VSCode нужно поставить плагины C/C++ от Microsoft и Makefile Tools от Microsoft.

Так как в Adruino нет отладки как таковой, то настройка сводится к правильной подсветке синтаксиса и запуску компиляции.

В каталоге будущего проекта создаём каталоги: .vscode; src и inc. В .vscode создаём файл c_cpp_properties.json вот с таким содержимым

< "configurations": [ < "name": "Habr_2", "includePath": [ "$/inc", "/usr/lib/avr/include" ], "browse": < "path": [] >, "defines": [ "F_CPU 16000000UL", "__AVR_ATmega328P__" ], "compilerPath": "/usr/bin/avr-gcc", "compilerArgs": [], "cStandard": "c11", "cppStandard": "gnu++11", "intelliSenseMode": "$", "configurationProvider": "ms-vscode.makefile-tools" > ], "version": 4 > 

Это минимально необходимая конфигурация, часть настроек сделает Makefile Tools проанализировав Makefile. Всё же сделаю пару уточнений. В includePath прописываются пути поиска заголовочных файлов, в browse — используемые *.c и *.cpp файлы с исходными текстами программы (здесь пусто потому что Makefile Tools сам их найдёт и добавит для текущего проекта), defines — все макроопределения. Теперь пишем супер-программу. В каталоге src создаём файлы:

#include #include "functions.h" int main(void)

и functions.c

#include #include void func(void) < for (;;) < asm("NOP"); PORTB = 1 >

а в каталоге incfunctions.h

#ifndef __FUNCTIONS__ #define __FUNCTIONS__ void func(void); #endif

Сия благородная программа творит великое дело — мигает единственным доступным на плате светодиодом.

Зачем разбил программу на два файла? А просто так, что бы у неопытных товарищей не возник вопрос — как вынести функцию в отдельный файл.

Ну, собственно, приступаем к страшному — созданию Makefile. Занятие не невозможное, достаточно прочитать документ на двести с небольшим страниц, но кто сейчас читает руководства.

# Имя программы и собранного бинарника TARGET = habr_2 # путь к каталогу с GCC AVRCCDIR = /usr/bin/ #само название компилятора, мало ли, вдруг оно когда-нибудь поменяется CC = avr-gcc OBJCOPY = avr-objcopy # каталог в который будет осуществляться сборка, что бы не засерать остальные каталоги BUILD_DIR = build # название контроллера для компилятора MCU = atmega328p #флаги для компилятора OPT = -Os C_FLAGS = -mmcu=$(MCU) $(OPT) -Wall # параметры для AVRDUDE DUDE_MCU = m328p PORT = /dev/ttyUSB0 PORTSPEED = 115200 # DEFINы DEFINES = \ -D__AVR_ATmega328P__ \ -DF_CPU=16000000UL # пути к заголовочным файлам C_INCLUDES = \ -I/usr/lib/avr/include \ -Iinc # файлы программы C_SOURCES = \ src/habr_2.c \ src/functions.c # служебные переменные OBJ_FILES = $(C_SOURCES:.c=.o) ASM_FILES = $(C_SOURCES:.c=.s) OUT_OBJ = $(addprefix $(BUILD_DIR)/, $(notdir $(OBJ_FILES))) # правила для сборки all: $(TARGET).hex $(TARGET).hex: $(TARGET).elf $(AVRCCDIR)$(OBJCOPY) -j .text -j .data -O ihex $(BUILD_DIR)/$< $(BUILD_DIR)/$@ $(TARGET).elf: $(OBJ_FILES) $(ASM_FILES) mkdir -p $(BUILD_DIR) $(AVRCCDIR)$(CC) $(C_FLAGS) $(DEFINES) $(OUT_OBJ) -o $(BUILD_DIR)/$@ %.o: %.c echo $^ $(AVRCCDIR)$(CC) -c $(C_FLAGS) $(DEFINES) $(C_INCLUDES) $< -o $(BUILD_DIR)/$(@F) %.s: %.c echo $^ $(AVRCCDIR)$(CC) -S -g3 $(C_FLAGS) $(DEFINES) $(C_INCLUDES) $< -o $(BUILD_DIR)/$(@F) clean: rm -f $(BUILD_DIR)/* prog: $(TARGET).hex avrdude -p $(DUDE_MCU) -c arduino -P $(PORT) -b $(PORTSPEED) -U flash:w:$(BUILD_DIR)/$(TARGET).hex read_eeprom: avrdude -p $(DUDE_MCU) -c arduino -P $(PORT) -b $(PORTSPEED) -U eeprom:r:eeprom.hex:i write_eeprom: eeprom.hex avrdude -p $(DUDE_MCU) -c arduino -P $(PORT) -b $(PORTSPEED) -U eeprom:w:eeprom.hex

Наверняка, половина обитателей Хабра напишет Makefile в сто раз лучше моего, с удовольствием почитаю конструктивные замечания и рекомендации. Уточню пару моментов в Makefile. Переменная TARGET = habr_2 - название программы, у меня это вторая статья на Хабре, отсюда и название, у вас будет своё значение. Переменная AVRCCDIR = /usr/bin/ - полный путь к каталогу в котором у Вас avr-gcc. Если у Вас Windows, то нужно его поменять на свой каталог. Сильно рассчитывать на PATH не стоит, мало ли где ещё может лежать бинарник с таким же названием, винда всё жеж. В C_SOURCES = \ добавляем по аналогии свои *.c-файлы*. Цель prog: записывает в контроллер вашу программу, а read_eeprom: и write_eeprom: - нужны что бы считать и вернуть EEPROM, мало ли чего Вы там сохраняете. Зачем я собираю ещё и .s файлы, а просто так, иногда интересно, что насобирал компилятор.

Отдельно стоит упомянуть настройки программы программатора - avrdude. Лично мне известная только эта программа для записи контроллеров AVR, умеет работать со всеми известными мне программаторами, даже не поддерживаемыми Atmel Studio. Когда-то прикручивал avrdude к студии через внешние тулзы, что бы прошивать контроллеры с помощью самодельного AVR910. В данном примере прошивка контроллера идёт при помощи загрузчика уже находящегося в памяти контроллера, об этом говорит ключ -c arduino . Протокол общения у него на все процессоры один, а вот скорость может быть как 115200 так 57600. Если вдруг прошивка не пошла, попробуйте поменять PORTSPEED . Ещё момент с портом - в зависимости от того какой преобразователь USB → UART использовал производитель, а так же наличия других аналогичных устройств в системе, может отличаться имя порта, на котором висит ваша плата. Лично у меня были платы которые появлялись как *ttyACM0*. Соответственно нужное значение необходимо прописать в `PORT = /dev/ttyUSB0`

Ещё один неприятный момент кроется в том, что необходимо для одного контроллера прописывать разные имена в параметрах разных программ. Для компилятора это ключ MCU = atmega328p и макроопределение -D__AVR_ATmega328P__ , а для программатора DUDE_MCU = m328p .

Почти всё. Нужно в терминале VSCode дать команду make и через пару секунд получить в каталоге build файл habr_2.hex. Теперь можно подключить вашу Adruino и дать команду make prog. Побегут буковки и через пару секунд Вы увидите что-то пита такого

. Reading | ################################################## | 100% 0.03s

avrdude: verifying . avrdude: 186 bytes of flash verified

avrdude: safemode: Fuses OK (E:00, H:00, L:00)

avrdude done. Thank you.

Перезапуск и ваш светодиод мигает.

Можно написать tasks.json для запуска сборки и прошивки контроллера, но сейчас лень.

Действие второе

Думал на этом и закончить статью и опубликовать, но чувство прекрасного не позволило. Вспомнил свой AVR Dragon, ведь в камнях посерьёзнее есть нормальная отладка и она нужна на нормальных задачах, значит - без разбора отладки тема не будет достаточно раскрыта. В наличии оказался ATMega16A у которого уже есть JTAG - значит нужно подключать. Собрал вот такую ерунду.

На зелёном проводе припаян SMD-светодиод с резистором.

Необходимо доустановить в системе пакеты avarice и avr-gdb. Первый общается с процессором через программатор и даёт gdb-интерфейс на сетевом порту, а второй собственно программа отладчик, через неё VSCode подключается к интерфейсу отладки. Если где неправ, в комментариях поправьте пожалуйста.

Тестовая программа та же, что и в первом случае, только в функцию func() добавил переменную t, просто что бы в отладке увидеть как она меняется, а то неинтересно получалось, и результат вот.

Из-за того, что процессор и программатор другие - в Makefile небольшие изменения.

. MCU = atmega16a . DUDE_MCU = m16 . clean: rm -f $(BUILD_DIR)/* prog: $(TARGET).hex avrdude -p $(DUDE_MCU) -c dragon_jtag -U flash:w:$(BUILD_DIR)/$(TARGET).hex read_eeprom: avrdude -p $(DUDE_MCU) -c dragon_jtag -U eeprom:r:eeprom.hex:i write_eeprom: eeprom.hex avrdude -p $(DUDE_MCU) -c dragon_jtag -U eeprom:w:eeprom.hex

В секциях программирования поменялось значение для ключа -c, у меня - dragon_jtag, у Вас что-то своё. Что бы узнать как вам обозначить свой программатор и процессор достаточно вызвать avarice, avrdude и остальных с ключём --help и чуть-чуть потратить времени на прочтение.

В каталог .vscode добавляем tasks.json и launch.json со следующим содержимым

< "version": "2.0.0", "tasks": [ < "label": "Build", "type": "shell", "group": "build", "command": "make", "problemMatcher": [] >, < "label": "Clean", "type": "shell", "group": "build", "command": "make", "args": ["clean"], "problemMatcher": [] >, < "label": "Write firmware", "type": "shell", "group": "build", "command": "make", "args": ["prog"], "problemMatcher": [] >, < "label": "Debug server", "presentation": < "echo": true, "reveal": "always", "panel": "shared", "focus": true, "showReuseMessage": false, "clear": false >, "isBackground": true, "command": "avarice", "args": [ "--dragon", "-R", "-P", "atmega16", "--file", "/build/habr_2_2.elf", ":3333" ], "problemMatcher":[] > ] >

Задачи Build, Clean и Write firmware интереса не вызывают - это просто вызов соответствующих целей в Makefile. Вот Debug server стоит рассмотреть. Программа avarice не имеет режима демона, при запуске она ожидает подключения клиента и завершается после его отключения. Поэтому её необходимо запускать каждый раз перед началом отладки через preLaunchTask, но так как VSCode ждёт сигнала завершения задачи, то необходимо отправлять задачу в фон. За это отвечает параметр "isBackground": true. В command прописана сама avarice, параметры её запуска довольно просты: --dragon - мой программатор; -R - использование аппаратного сброса (и без него работает, но пусть будет); atmega16 - наш процессор; /build/habr_2_2.elf - выходной файл компиляции (см. Makefile); :3333 - сетевой порт, на котором ожидаем клиента (решил указать привычный порт, используемый openocd). Группа параметров presentation нужна только для настройки поведения терминала и на работу не влияет, её можно вообще не писать. Вы же переписывыаете всё руками, что бы лучше запомнить и прочувствовать, а тупо копипастите?!

< "version": "0.2.0", "configurations": [ < "name": "Debug", "type": "cppdbg", "request": "launch", "program": "$/build/habr_2_2.elf", "MIMode": "gdb", "miDebuggerPath": "/usr/bin/avr-gdb", "miDebuggerServerAddress": "localhost:3333", "stopAtEntry": true, "cwd": "$", "environment": [], "externalConsole": false, "preLaunchTask": "Debug server" > ] >

По большому счёту всё очевидно, но всё же уточню пару мест: program - наш собранный .elf (у Вас будет своё название); miDebuggerPath - полный путь к программе отладчику (если будете настраивать в Windows учтите этот момент); miDebuggerServerAddress - адрес и сетевой порт на котором вас ждёт avarice; stopAtEntry - остановится на входе в main() (это по вкусу); "preLaunchTask": "Debug server" - предварительный запуск задачи старта сервера отладки.

Чего не получилось настроить - автоматической прошивки перед запуском отладки. В Debian bullseye, avarice собран без поддержки программирования и сам рекомендует avrdude. Пробовал создавать групповую задачу, пробовал выносить запуск avarice в Makefile и т. п., результат один - идёт прошивка, затем запуск avarice и всё застряёт, можно ждать час - толку ноль. Поэтому процессор приходится отдельно прошивать, а потом запускать отладку.

Всё готово, поджигай!

Для начала соберём проект. Для этого вызовем задачу сборки, можно руками в терминале дать make, можно нажать Ctrl + Shift + B и из списка выбрать Build. У меня эта комбинация не срабатывает (есть даже официальное предупреждение о таком поведении) и пришлось её поменять на Alt + Shift + B. Если всё прошло без ошибок, то прошиваем контроллер - запустив задачу Write firmware или командой make prog. Должен весело замигать светодиод.

Теперь поставим точку останова на строке t = t ? 0 : 1; , думаю разберётесь как - не маленькие. Идём в "Запуск и Отладка" и нажимаем "Начать отладку"

Запускаем отладку и через несколько секунд в основном окне видим такое

Программа остановилась на первой строке функции main() (помните "stopAtEntry": true в launch.json), а в терминале что-то типа этого

.

Waiting for connection on port 3333.

Connection opened by host 127.0.0.1, port 45102.

Нажимаем "Продолжить" или F5, что бы запустить программу и через пару секунд окно меняется на вот такое

ещё раз жмём *"Продолжить"* и окно становится вот таким

При дальнейших запусках светодиод мигает один раз, всё останавливается, а переменная t меняет своё значение между нулём и единицей.

На этом у меня всё. Творческих Вам успехов.

P. S. Я знаю про simavr, который позволяет запустить симуляцию с отладкой для камней не имеющих отладочных средств, но считаю, что если вы в состоянии для неё описать входные и выходные сигналы, то эту статью вы не стали читать уже после первого абзаца - она не для Вас.

Источник

Оцените статью