Как стать автором
Обновить

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

Время на прочтение9 мин
Количество просмотров9.6K

Всем, кто занимается электроникой, так или иначе знакомы контроллеры 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": [
                "${workspaceRoot}/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": "${default}",
            "configurationProvider": "ms-vscode.makefile-tools"
        }
    ],
    "version": 4
}

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

habr_2.c

#include <avr/io.h>
#include "functions.h"

int main(void)
{
	DDRB = 1 << PB5;
	func();

	return 0;
}

и functions.c

#include <avr/io.h>
#include <util/delay.h>

void func(void)
{
	for (;;)
	{
		asm("NOP");
		PORTB = 1 << PB5;
		_delay_ms(500);
		PORTB &= ~(1 << PB5);
		_delay_ms(500);
	}
}

а в каталоге inc - functions.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, просто что бы в отладке увидеть как она меняется, а то неинтересно получалось, и результат вот.

void func(void)
{
	volatile uint8_t t = 0;

	for (;;)
	{
		asm("NOP");
		t = t ? 0 : 1;
		PORTB = 1 << PB5;
		_delay_ms(500);
		PORTB &= ~(1 << PB5);
		_delay_ms(500);
	}
}

Из-за того, что процессор и программатор другие - в 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 со следующим содержимым

tasks.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 нужна только для настройки поведения терминала и на работу не влияет, её можно вообще не писать. Вы же переписывыаете всё руками, что бы лучше запомнить и прочувствовать, а тупо копипастите?!

launch.json

{
	"version": "0.2.0",
	"configurations": [
		{
			"name": "Debug",
			"type": "cppdbg",
			"request": "launch",
			"program": "${workspaceFolder}/build/habr_2_2.elf",
			"MIMode": "gdb",
			"miDebuggerPath": "/usr/bin/avr-gdb",
			"miDebuggerServerAddress": "localhost:3333",
			"stopAtEntry": true,
			"cwd": "${workspaceFolder}",
			"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, который позволяет запустить симуляцию с отладкой для камней не имеющих отладочных средств, но считаю, что если вы в состоянии для неё описать входные и выходные сигналы, то эту статью вы не стали читать уже после первого абзаца - она не для Вас.

Теги:
Хабы:
Всего голосов 8: ↑8 и ↓0+8
Комментарии8

Публикации

Истории

Ближайшие события

One day offer от ВСК
Дата16 – 17 мая
Время09:00 – 18:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург