Я не большой фанат инфраструктуры Arduino. Да, сегодня у нас есть уже больше вариантов – к примеру, pro IDE и Platform IO. Однако от оригинальной IDE у меня всегда случается изжога. И всю её степень я оценил только накануне, когда захотел сделать нечто очень простое: увеличить принимающий буфер последовательного порта ATmega32. В итоге я пришёл к решению, которое может помочь вам и с другими проблемами – так что, даже если вам не нужна конкретно эта возможность, вам, возможно, будет полезно посмотреть, что именно я сделал.

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

Во-первых, вы, наверное, спросите, зачем я использую IDE. Коротко говоря, я его не использую. Однако если вы делаете нечто, чем будут пользоваться другие, его практически невозможно игнорировать. Как бы вы ни настраивали собственную среду разработки, как только ваш код выходит в интернет, кто-нибудь попробует использовать его с IDE. Когда-то я писал о компьютере на базе Z80 за $4. У меня редко бывает время на сборку того, о чём я пишу, однако я очень хотел попробовать собрать этот маленький компьютер. Какое-то время всё лежало в полуразобранном состоянии, а потом мне прислали для него плату. Я её получил, и – как вы уже догадались – всё ещё немножечко полежало в полуразобранном состоянии. Но я, наконец, нашёл время закончить проект и загрузил CP/M.



Единственной проблемой проекта было отсутствие хороших вариантов передачи данных с него на ПК и обратно. Казалось, что лучше всего делать шестнадцатеричные файлы Intel и копировать/вставлять их через терминал. Мне хотелось чего-то получше, и в результате я угодил в кроличью нору субботнего утра. В итоге я придумал способ добавлять собственные пункты меню в Arduino IDE, чтобы править настройки компилятора в зависимости от железа, которое используется в проекте. Такому трюку стоит обучиться, поскольку он может пригодиться и за пределами этой конкретной задачи.

Проблема: ограничение размера буфера последовательного порта Arduino


Не буду загружать вас деталями о том, как заставить плату работать, поскольку вам это будет интересно, только если она у вас есть. Все детали есть в обсуждении на Hackaday.io, если вам это реально нужно. В итоге получалось, что для передачи через XModem буфер последовательного порта Arduino был недостаточно большим для того, чтобы считаь его надёжным. Всё вроде бы работало и с буфером по умолчанию размером в 64 байта, однако XModem отправляет больше данных, и легко представить, как буфер переполняется.

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

Набор инструментов


В проекте маленького компьютера используется реальный чип Z80 и ATMega32A почти для всех вспомогательных функций. Он даёт тактовую частоту, работает последовательным портом, накопителем, и т.д. Однако Arduino IDE не поддерживает напрямую ATMega32A, поэтому для этого придётся установить набор инструментов. Ситуация требовала MightyCore, поэтому я её и использовал.

Библиотеки для последовательного порта настроены при помощи операторов #define, чтобы вы могли исправить размер буфера. По умолчанию, если ничего не настраивать, вы получите значения, основанные на количестве доступной RAM:

#if !defined(SERIAL_TX_BUFFER_SIZE)
#if ((RAMEND - RAMSTART) < 1023)
#define SERIAL_TX_BUFFER_SIZE 16
#else
#define SERIAL_TX_BUFFER_SIZE 64
#endif
#endif
#if !defined(SERIAL_RX_BUFFER_SIZE)
#if ((RAMEND - RAMSTART) < 1023)
#define SERIAL_RX_BUFFER_SIZE 16
#else
#define SERIAL_RX_BUFFER_SIZE 64
#endif
#endif

Вносим изменения


Всё просто, не так ли? Определяем символы перед загрузкой HardwareSerial.h. Опаньки – этот файл грузится в Arduino.h. ИП хочет добавить его к вашей программе и заставляет грузиться первым. Вроде бы некоторые версии IDE проверяли, не включили ли вы его уже, чтобы не включать его повторно, однако версия 1.8.5 этого не делает. Может, я могу передать какие-то настройки компилятору? Не-а. По крайней мере, не через IDE.

Попробовал я много всего. Хотелось, конечно, просто изменить основные библиотеки. Но это нехорошо. Позже вам могут потребоваться настройки по умолчанию. Если обновить набор инструментов, то все обновления потеряются. Мне хотелось избежать этого. Кто-то в интернете посоветовал сделать копию файлов платформы и изменять их. Не идеальное решение.

Проверяем предположения при помощи


Мне было видно, что то, что я делал, не срабатывает, поскольку я временно вставлял операторы #if и #error в HardwareSerial.cpp. К примеру:

#if SERIAL_RX_BUFFER_SIZE==256
#error 256
#endif

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

Компромисс: добавление настроек в меню на уровне платы


Я очень хотел придумать способ изменить настройки только в конкретном проекте, и таким образом задать размер последовательного буфера. Мне это не удалось. Зато мне удалось изменить boards.txt от Mighty Core. Да, мне придётся следить за тем, чтобы обновления не затёрли мои правки, однако они простые, и если чего-то не хватает в файле, это будет очевидно.



Очевидно это будет потому, что я создал меню для IDE, которое появляется только при использовании ATMega32 для Mighty Core. В меню можно выбрать один из предустановленных размеров буфера.

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

  1. Сказать IDE, что у вас есть новый пункт меню, и описать, как он выглядит.
  2. Новый пункт должен менять настройки компилятора.
  3. Поскольку существующая система тоже меняет настройки компилятора, нужно убедиться в том, что они не испортятся.

Первый шаг делается легко. Мой файл boards.txt лежал в ~/.arduino15/packages/MightyCore/hardware/avr/2.0.5/boards.txt. Почти в самом верху там есть список пунктов меню, и в конец я добавил свои:

# Menu options
menu.clock=Clock
menu.BOD=BOD
menu.LTO=Compiler LTO
menu.variant=Variant
menu.pinout=Pinout
menu.bootloader=Bootloader
menu.SerialBuf=Serial Port Buffers (RX/TX)

Затем я сдвинул строки вниз и добавил своё меню перед существующей настройкой LTO для ATMega32:

32.menu.SerialBuf.disabled=Default
32.menu.SerialBuf.disabled.compilerSB.c.extra_flags=
32.menu.SerialBuf.disabled.compilerSB.cpp.extra_flags=
 
32.menu.SerialBuf.SB64=64/64
32.menu.SerialBuf.SB64.compilerSB.c.extra_flags=-DSERIAL_RX_BUFFER_SIZE=64 -DSERIAL_TX_BUFFER_SIZE=64
32.menu.SerialBuf.SB64.compilerSB.cpp.extra_flags=-DSERIAL_RX_BUFFER_SIZE=64 -DSERIAL_TX_BUFFER_SIZE=64
 
32.menu.SerialBuf.SB128=128/128
32.menu.SerialBuf.SB128.compilerSB.c.extra_flags=-DSERIAL_RX_BUFFER_SIZE=128 -DSERIAL_TX_BUFFER_SIZE=128
32.menu.SerialBuf.SB128.compilerSB.cpp.extra_flags=-DSERIAL_RX_BUFFER_SIZE=128 -DSERIAL_TX_BUFFER_SIZE=128
 
32.menu.SerialBuf.SB12864=128/64
32.menu.SerialBuf.SB12864.compilerSB.c.extra_flags=-DSERIAL_RX_BUFFER_SIZE=128 -DSERIAL_TX_BUFFER_SIZE=64
32.menu.SerialBuf.SB12864.compilerSB.cpp.extra_flags=-DSERIAL_RX_BUFFER_SIZE=128 -DSERIAL_TX_BUFFER_SIZE=64
 
32.menu.SerialBuf.SB256=256/256
32.menu.SerialBuf.SB256.compilerSB.c.extra_flags=-DSERIAL_RX_BUFFER_SIZE=256 -DSERIAL_TX_BUFFER_SIZE=256
32.menu.SerialBuf.SB256.compilerSB.cpp.extra_flags=-DSERIAL_RX_BUFFER_SIZE=256 -DSERIAL_TX_BUFFER_SIZE=256
 
32.menu.SerialBuf.SB25664=256/64
32.menu.SerialBuf.SB25664.compilerSB.c.extra_flags=-DSERIAL_RX_BUFFER_SIZE=256 -DSERIAL_TX_BUFFER_SIZE=64
32.menu.SerialBuf.SB25664.compilerSB.cpp.extra_flags=-DSERIAL_RX_BUFFER_SIZE=256 -DSERIAL_TX_BUFFER_SIZE=64
 
32.menu.SerialBuf.SB25632=256/32
32.menu.SerialBuf.SB25632.compilerSB.c.extra_flags=-DSERIAL_RX_BUFFER_SIZE=256 -DSERIAL_TX_BUFFER_SIZE=32
32.menu.SerialBuf.SB25632.compilerSB.cpp.extra_flags=-DSERIAL_RX_BUFFER_SIZE=256 -DSERIAL_TX_BUFFER_SIZE=32

Структура меню


Видно, что объект 32.menu группирует все элементы вместе для данного процессора. Дальше идёт наш ключ меню (SerialBuf). После этого идут уникальные ключи для каждого пункта меню. Их важно не использовать повторно. Если, к примеру, у вас будет два ключа SB64, то работать будет только один из них.

Если к этому ключу приписать знак равно, можно назначить текст для данного пункта меню. К примеру, «Default» или «64/64». Также можно добавить к ключу свойство, и оно будет назначено, когда пункт активируют.

Если, допустим, выбрать 256/256, то компилятор выставляет свойство compilerSB.c.extra_flags. Почему я придумал такое название свойства, вы поймёте чуть позже.

Мирное сосуществование


Свойства compilerSB.c.extra_flags нет. Правильно оно называется compiler.c.extra_flags. Однако настройка Mighty Core LTO использует такой же ключ. Поэтому было важно, чтобы новое меню появлялось первым, а также устанавливало ненастоящее свойство. Потом требуется подправить код LTO:

# Compiler link time optimization
32.menu.LTO.Os=LTO disabled
32.menu.LTO.Os.compiler.c.extra_flags={compilerSB.c.extra_flags}
32.menu.LTO.Os.compiler.c.elf.extra_flags=
32.menu.LTO.Os.compiler.cpp.extra_flags={compilerSB.cpp.extra_flags}
32.menu.LTO.Os.ltoarcmd=avr-ar
 
32.menu.LTO.Os_flto=LTO enabled
32.menu.LTO.Os_flto.compiler.c.extra_flags={compilerSB.c.extra_flags} -Wextra -flto -g
32.menu.LTO.Os_flto.compiler.c.elf.extra_flags=-w -flto -g
32.menu.LTO.Os_flto.compiler.cpp.extra_flags={compilerSB.cpp.extra_flags} -Wextra -flto -g
32.menu.LTO.Os_flto.ltoarcmd=avr-gcc-ar

Основное изменение состоит в том, что каждый набор флагов добавляется в заданное в настройках меню. Таким образом все флаги добавляются в правильное свойство, compiler.c.extra_flags.

Я настроил перехват ошибок для всех случаев, чтобы гарантировать, что всё назначится верно.

Настройка под себя


Вы, конечно же, можете изменить параметры, если вам нужно что-то другое. Также этот трюк можно использовать для задания других параметров до того, как управление получает файл Arduino.h. Есть документация, описывающая то, как настраивать разные параметры платформы, включая и boards.txt.

Возможно, было бы лучше создать свой отдельный файл boards.txt с той же самой информацией, но тогда мне потребовалось бы брать с собой и остаток Mighty Core. Вместо этого я просто сохранил копию этого файла под именем boards.txt.custom, и если моё меню исчезнет, я просто сравню его содержимое с boards.txt, чтобы посмотреть, что там поменялось.

Естественно, если вам не нужно заниматься поддержкой людей, использующих IDE, то на это можно просто забить. Лучше использовать Pro IDE, даже несмотря на некоторые его недостатки. Ну и всегда можно обратиться к Platform.io.