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

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


  1. uuuuuuuu
    19.11.2021 08:19

    Я тоже долгое время пользовался Eclipse, но makefile делал вручную. И это было не то что бы сложно, скорее неудобно и не универсально. В итоге я сделал toolchain для CMake и использую его для генерации сборочных сценариев и проекта для IDE.


  1. quaer
    19.11.2021 11:21
    +1

    Как же мутно всё... Напиши файлик на одном птичьем языке, потом другой на другом, потом третий на третьем. Если надо перенести на другой комп, снова те же танцы.

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


    1. Druj
      19.11.2021 12:01

      Перенос делается двумя кнопками в гит клиенте, push/pull.

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

      Можно и было сделано, но статья не про это. В статье 70% посвящено настройке редактора и тулчейна, если вам нужно что-то готовое то просто возьмите любую из популярных IDE для мк.


    1. jogick Автор
      19.11.2021 12:49

      В том-то и дело, при таком подходе минимум движений при переезде с компа на комп. Если у Вас пути к программам находятся в PATH, то вообще ничего менять не нужно, даже между Win и Linux, а я так и работаю. В своей первой стать, в комментариях, я писал, что перемещаюсь между рабочей Win7, домашним Debian и PaspberryPi4. Синхронизация проекта идёт через git-репозиторий, доступный со всех трёх машин. Никаких изменений вносить не приходится.

      К тому же, это поначалу кажется всё сложно и непонятно, но после пары проектов, понимаешь, что всё нормально. К тому же это не самый сложный Makefile, бывает как понаворотят... А .c и .h вам всё равно писать, если программируете не всё в одном файле.


      1. quaer
        20.11.2021 10:08

        Ваш текст:

        Понадобилось мне внести правку на пару строчек с старючую самоделку, собранную из arduino и палок. Беда в том, что писалась программа в Eclipse, а он с тех пор два раза менялся, плагины переставали работать и сейчас этот же проект опять не собирается. Беда ещё и в том, что простой запуск make в каталоге проекта ничего не даёт - он падает с ошибкой.

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


        1. jogick Автор
          20.11.2021 10:23
          +2

          Так в приведённой Вами цитате и есть ответ. Содержимое Makefil'а генерировалось плагином и что там будет зависело, видимо, от левой пятки автора плагина.

          Мне надоели эти "радости" - когда автор плагина спрятал от меня довольно простые вещи, типа автоматизировав рутинную работу, при этом наворотив там там невесть чего, а потом перестал поддерживать свой проект. Проще один раз разобраться в базовых вещах и сделать всё самому. При таком подходе, даже вернувшись к проекту через десять лет, даже если IDE перестала существовать, Вы можете всё подправить буквально в блокноте.


    1. alaken
      19.11.2021 22:13

      Я использую platformio, очень удобно, нужную платформу инсталлируешь прямо из интерфейса (AVR, ESPRESIF, STM32)
      https://platformio.org/install/ide?install=vscode