Прошивка 8-битных МК по одному проводу: как устроен бутлоадер в vm5277

В прошлой статье я начал цикл публикаций о своём open-source-проекте vm5277 — инструментарии для программирования 8-битных микроконтроллеров.
vm5277 — это универсальный инструментарий для embedded-разработки, который связывает мир высокоуровневого ООП (язык j8b с Java-подобным синтаксисом) с нативной компиляцией под 8-битные микроконтроллеры (AVR, PIC, STM8).
На текущий момент основой платформой для разработки является AVR, в частности широко известный ATmega328p-чип используемый в Arduino UNO
Я заложил в проект ряд преимуществ, которые в сумме меняют подход к процессу разработки, снижая порог входа и трудозатраты.
Сегодня я разберу, как в vm5277 устроена прошивка МК по одному (или двум) проводам, почему я заменил стандартные механизмы собственным бутлоадером и что это даёт на практике.
Это решение упрощает прошивку, коммуникацию с программой и отладку, реализуя бесшовное взаимодействие с микроконтроллером на всём цикле разработки.
Сразу обозначу вводные, чтобы дальше не было разночтений: я инженер-системотехник, поэтому все решения ниже - про практику, а не про чистую теорию.
Проблема: почему классические способы прошивки неудобны
Исходя из практических реализаций, с которыми я сталкивался, перевод чипа в режим прошивки обычно основан на одном из трёх механизмов:
Аппаратное управление пином RESET. Этот вывод жестко задействован как минимум в классических интерфейсах JTAG и ISP.
Автоматическое управление пином RESET через встроенный чип на плате. Яркий пример - связка с USB-UART мостом (например, CH340), где используется линия DTR. При открытии COM-порта на ПК этот пин дёргает линию сброса и приводит к аппаратному рестарту микроконтроллера.
Программный сброс по команде. Реализация, при которой работающая программа сама ожидает определенную инструкцию по интерфейсу связи, после чего программно перезагружает микроконтроллер или передает управление бутлоадеру.
Все эти подходы имеют существенные недостатки, которые для меня оказались критичными:
Дефицит пинов: Аппаратные интерфейсы программирования (ISP/JTAG) хороши тем, что работают напрямую «в кремнии» и вообще не занимают драгоценную FLASH-память. Однако они требуют подключения множества пинов МК, что становится непозволительной роскошью для «мелких» чипов с небольшим количеством ног.
Ложные сбросы и монополия на интерфейс: Автоматический аппаратный сброс (как в платах Arduino) приводит к перезагрузке МК при каждом открытии порта на компьютере, из-за чего невозможно просто подключиться к работающей плате для снятия логов. Кроме того, использование UART для прошивки создает жесткий конфликт ресурсов: если этот порт задействован под опрос датчиков или внешних устройств, становится невозможно одновременно обновлять прошивку или вести отладку без физического отключения периферии от линий RX/TX.
Поэтому я пошёл по другому пути: решил пожертвовать скромной частью FLASH-памяти ради размещения собственного бутлоадера.
В итоге я применил концепт, который дает сразу ряд преимуществ в сравнении с классическими механизмами:
Универсальность: Единый интерфейс и протокол прошивки для всех поддерживаемых в vm5277 платформ и микроконтроллеров.
Экономия ног: Однопиновый полудуплексный режим работы (при этом классический двухпиновый полнодуплекс тоже поддерживается).
Абсолютная гибкость: Возможность назначить под прошивку абсолютно любой GPIO-пин (в том числе использовать вывод RESET).
Мягкий запуск: Поддержка как аппаратной, так и чисто программной перезагрузки в bootloader.
Шеринг пина: Пин прошивки можно дополнительно использовать для ввода-вывода (вывода логов) прямо в прикладной программе.
Удешевлении платы: Отсутствие необходимости разводить USB-UART преобразователь на PCB конечного устройства.
Свобода для периферии: Аппаратный UART может быть полностью свободным как во время прошивки, так и во время работы приложения.
Адаптивность: Автоматическое определение скорости (BAUDRATE) на основе частоты тактования чипа.
Кроме того, поскольку я полностью контролирую код бутлоадера, это позволило заложить в него ряд промышленных фич, критически важных для реальных проектов:
UID чипа: В тело загрузчика при генерации зашивается уникальный идентификатор, содержащий ID разработчика и конкретного кристалла.
Версионность: Контроль версий прошивки на уровне загрузчика.
Криптозащита (Anti-cloning): Возможность расшифровки бинарника «на лету» в процессе прошивки. Это усложняет кражу интеллектуальной собственности - перехват в канале связи и дизассемблирование, если вы передаете файл прошивки для обновления конечному пользователю.
Кроме того, поскольку я создаю полноценную экосистему, а не просто разрозненный набор утилит, это позволило реализовать следующие системные фичи:
Бесшовная интеграция в процесс разработки: В IntelliJ IDEA вы можете буквально одной кнопкой собрать проект, прошить его и в этой же консоли мгновенно начать принимать логи с МК или отправлять команды обратно, не переключая окна и не совершая никаких дополнительных действий.
Минимум конфигурации: Утилита прошивки автоматически определяют платформу, конкретный чип и режим работы. От разработчика требуется лишь один раз указать тактовую частоту чипа в настройках IDEA проекта.
Масштабируемость (в планах): Архитектура протокола технически позволяет выполнять одновременную параллельную прошивку сразу нескольких устройств.
Переиспользование кода бутлоадера: Часть критически важных функций загрузчика (например, те же процедуры работы с базовым вводом-выводом) могут вызываться напрямую из прикладной прошивки через механизм API. Это исключает дублирование ассемблерного кода и немного экономит память.
Также стоит добавить:
Жесткая синхронизация Baudrate с тактовой частотой: В отличие от классических UART-решений (где из-за дробных коэффициентов деления возникают микросекундные погрешности, фатальные для программного полудуплекса), в моем бутлоадере длительность каждого бита жестко привязана к тактам МК. Чем выше частота ядра, тем выше скорость на линии, причем со 100% точностью. Например, на частоте 16 МГц скорость составляет ровно 230400 бод. Нулевой джиттер гарантирует эталонную стабильность, а утилита на ПК (j8bf) сама на лету пересчитывает и адаптирует скорость UART-порта под текущую частоту МК.
Две конфигурации под разные задачи:
Полнофункциональная (размером 1024 байта / 512 слов) — включает в себя UID и все расширенные фичи, оставаясь при этом очень компактной для своего класса.
Усеченная (размером всего 512 байт / 256 слов) — создана специально для младших чипов с общим объемом Flash-памяти в 2 КБ. Она содержит только базовый функционал прошивки, позволяя экономить каждый байт на экстремально ограниченных кристаллах.
Единственное аппаратное требование — вам понадобится внешний USB-UART преобразователь, который всегда найдётся под рукой у любого embedded-разработчика.
Для работы в однопиновом режиме нужно просто соединить линии RX и TX этого преобразователя друг с другом.
Безусловно, никто не мешает вам использовать и стандартные механизмы прошивки микроконтроллера (например, классический программатор).
Однако в этом случае вы потеряете бесшовную интеграцию и доступ ко всем описанным выше фичам экосистемы vm5277.
Когда заходит речь о прошивке той же Arduino, все привыкли к классической схеме с использованием линий RX и TX. Из-за этого единственный аппаратный UART приходится делить между нуждами загрузчика и периферией, что сильно усложняет одновременное подключение датчиков и отладку.
Как только софт на ПК открывает serial-порт, встроенный USB-UART преобразователь дергает линию сброса МК, и проснувшийся загрузчик переходит в режим ожидания прошивки.
Но что делать, если вы проектируете собственную плату, аппаратный UART уже намертво занят периферией, а на чипе остался всего один свободный пин?
В экосистеме vm5277 эта проблема решена радикально: прошивка и интерактивный ввод-вывод происходят либо по двум любым GPIO-пинам, либо вообще по одному-единственному проводу — и всё это без задействования аппаратного RESET.
Изначально я реализовал только однопиновый режим. Двухпиновый вариант появился позже, как осознанное расширение для удобства работы с готовыми платами Arduino, где USB-UART мост уже жестко распаян на TX/RX.
Я не претендую на абсолютную уникальность однопроводного интерфейса — в тех же чипах AVR от Atmel/Microchip есть фирменный протокол debugWIRE.
Но, во-первых, он является закрытым проприетарным решением, требующим фирменного (и недешевого) программатора.
Во-вторых, для его работы пин RESET должен быть аппаратно заблокирован под нужды отладки.
Ну и в-третьих, в моей долгой инженерной практике debugWIRE регулярно демонстрировал нестабильность и сбои передачи данных (что, скорее всего, связано с сыростью софта на стороне хоста).
Мой же протокол работает через большинство копеечных USB-UART «свистков».
Ниже я подробно разберу, как устроен этот механизм изнутри и как повторить его в своих проектах.
Аппаратный UART требует раздельных линий для приёма и передачи. Чтобы ужать весь обмен в один провод, я перевёл интерфейс в полудуплексный режим: в каждый момент времени линия работает либо только на приём, либо только на передачу.
Со стороны хоста (ПК) для этого не нужно городить сложные схемы — в большинстве случаев достаточно просто соединить вместе линии TX и RX USB-UART преобразователя (например, популярной CH340) и подключить эту общую точку к выбранному пину микроконтроллера.
В самом бутлоадере bldr.asm реализован полностью программный UART. Вся магия обмена работает на жестких таймингах, которые намертво завязаны на текущую тактовую частоту ядра МК.
Но здесь возникает логичный вопрос: если физического провода RESET нет, как гарантированно заставить чип бросить выполнение основного кода и перейти в режим прошивки?
В экосистеме vm5277 эта задача решена разделением логики на два независимых сценария, что особенно актуально для кастомных плат, где автоматический аппаратный сброс МК не предусмотрен в принципе.
Сценарий 1. «Холодный старт»
Если микроконтроллер только что включили по питанию (или сбросили физической кнопкой RESET), управление гарантированно получает бутлоадер.
Чтобы это работало «в железе», при первоначальной конфигурации чипа необходимо выставить фьюз-бит BOOTRST, который перенаправляет вектор сброса на адрес загрузчика.
Дальнейшая логика работы на МК выглядит следующим образом:
Инициализация пина: Бутлоадер настраивает выбранный GPIO-пин для связи (его номер и порт жестко задаются на этапе компиляции загрузчика).
Ожидание хоста: Загрузчик замирает и слушает линию в течение ровно 1 секунды. Задача флешера на ПК — успеть за это окно заявить о себе, например отправив стартовый запрос информации о чипе (REQ_BLDR_INFO).
Установление связи: Если в течение секунды хост прислал данные, то бутлоадер отправляет в ответ и ожидает последующих команд.
Переход в приложение: Если за отведенную секунду в порту тишина, бутлоадер корректно завершает работу и передает управление на адрес 0x0000 для запуска основной прошивки.
Обработка «чистого» кристалла: Если в памяти устройства еще нет прикладной программы (это определяется по незаполненным первым байтам Flash-памяти, содержащим 0xFFFF), бутлоадер игнорирует секундный таймаут и остается в режиме ожидания команд бесконечно.
Штатный выход: Выход из режима программирования после успешной заливки кода осуществляется по специальной финализирующей команде от флешера с ПК.
Сценарий 2. «Горячий старт» — Единый STDIO-слой
Самое интересное начинается, когда контроллер уже вовсю выполняет основную программу «в поле».
В ассемблерном ядре vm5277 реализован полноценный функционал ввода-вывода STDIO (System.out(), System.in()), использующий для связи те же самые один или два GPIO-пина, на которых сидит загрузчик.
Пока плата работает в штатном режиме, она транслирует отладочные логи в консоль ПК и принимает ввод от пользователя.
Когда разработчик в IntelliJ IDEA нажимает кнопку «Скомпилировать и прошить», запускается алгоритм перехвата управления:
Отправка сигнала: Флешер j8bf отправляет в пин связи специальный маркерный байт.
Перехват на МК: Постоянно работающий в фоне STDIO-слой ядра МК на лету принимает данные из линии и распознаёт этот триггер.
Прыжок в загрузчик: Ядро понимает, что хост запрашивает обновление ПО. Оно корректно останавливает выполнение пользовательского кода и выполняет прямой программный переход (jmp) на начальный адрес области бутлоадера во Flash.
Вход в режим прошивки: Бутлоадер просыпается, принимает от флешера пакет инициализации и мгновенно переходит в режим ожидания команд на перезапись памяти.
Запуск нового кода: После успешного завершения прошивки флешер подаёт финальную команду, загрузчик передает управление на адрес 0x0000, и МК начинает выполнять уже свежую версию приложения.
В результате мы получаем по-настоящему бесшовный цикл разработки.
Больше не нужно судорожно перетыкать провода, нажимать физические кнопки на плате или мириться с тем, что устройство улетает в жесткий рестарт каждый раз при случайном открытии терминала на ПК (известная болезнь всех плат Arduino).
Один-единственный провод используется и для интерактивного вывода логов, и для заливки свежего бинарника.
А главное — этот подход стабильно работает на самых разных аппаратных конфигурациях и кастомных PCB, не требуя разводки лишних микросхем и дорожек.
Также хочется подчеркнуть ещё одно фундаментальное преимущество, заложенное в архитектуру vm5277 — полноценную отладку!
И речь сейчас идет не о банальном выводе текстовых логов в консоль. Этот базовый инструмент в экосистеме уже реализован и отлично помогает мне отлаживать как высокоуровневый J8B-код, так и нативный ассемблер ядра.
Я говорю о создании полноценного интерактивного механизма внутрисхемной отладки программ — с полноценными точками останова (breakpoints), пошаговым выполнением и чтением значений переменных или полей классов «на лету».
Это одна из моих приоритетных задач на будущее, и её концепт уже полностью сформирован.
Что примечательно, данный функционал будет базироваться на том же самом единственном пине связи, который сейчас используется для прошивки и ввода-вывода. Реализация отладки не затронет код самого бутлоадера, но будет компилироваться в виде компактного отладочного бэкэнда внутри прикладного бинарника приложения.
При этом важно учитывать, что все описанные преимущества являются кроссплатформенными — они заложены в саму архитектуру экосистемы (за редким исключением, когда вмешиваются жесткие аппаратные ограничения конкретного кристалла).
Безусловно, у такой гибкости есть своя цена, и она высока: поддержка каждой новой платформы требует написания собственного бэкенд-кодогенератора в компиляторе и нативного системного ядра с RTOS на чистом ассемблере целевого процессора.
Однако этот барьер преодолевается один раз при переносе экосистемы, после чего прикладной разработчик получает одинаково высокий уровень комфорта независимо от того, какой бренд МК распаян на его плате.
Итак, как всё-таки в экосистеме vm5277 прошить микроконтроллер?
Давайте для начала рассмотрим консольную Java-утилиту j8bmb. Этот компонент спроектирован строго для сборки и формирования файла прошивки бутлоадера под конкретный целевой МК.
Вот вывод её встроенной справки:
j8bmb
MkBoot bootloader firmware generator for vm5277 Embedded Toolkit
Version: 0.3.2 | License: Apache-2.0
-------------------------------------------
This tool is part of the open-source vm5277 project for 8-bit microcontrollers (AVR, PIC, STM8, etc.)
Provided AS IS without warranties. Author is not responsible for any consequences of use.
Project home: https://github.com/w5277c/vm5277
Contact: w5277c@gmail.com | konstantin@5277.ru
Usage: mkboot <platform>:<mcu> <mcu port> [options]
<mcu port> Bootloader communication pin configuration (required):
Single-wire: 'PB0' (bidirectional, uses RESET if supports GPIO)
Two-wire: 'PB0/PB1' (RX/TX separate)
Options:
-k, --key <hexdata> Firmware decryption key (32 bytes, randomly generated if not specified)
-s, --secure <asmfile> Assembly file for bootloader with decryption function, can be used with -k
Decryption requires hardware support for protected bootloader sections
-i, --uids <file> UIDs data file (default: uids.db)
-u, --uid <hexdata> Chip unique identifier (8 bytes)
-a, --auth <hexdata> <password>
Vendor unique identifier (2 bytes) and password
-m, --uidmode <mode> UID generation method:
global - request to server (requires -a)
local - use local file uids.db (default, can be overridden with -i)
param - set manually (requires -u)
-o, --overwrite Overwrite existing UID in local database
-l, --list Generate assembly listing file 'bldr.lst'
-v, --version Display version
-h, --help Show this help
Examples:
j8bmb avr:atmega328p pb5 - for single-wire connection port
j8bmb avr:atmega328p pd0/pd1 - for two-wire connection port
j8bmb avr:atmega328p pb5 -k fe73...89 -s bldrsecure.asm -m local
j8bmb avr:attiny85 pb5 -u 2d849f626c42ff33
Не думаю, что стоит детально описывать здесь работу j8bmb (все подробности при желании можно изучить в документации в репозитории проекта на GitHub), поэтому пройдусь кратко по самому главному.
Утилита генерирует прошивку исходя из ключевых параметров, часть из которых (например, тот же UID) записывается непосредственно в тело бутлоадера.
Я заложил в неё и некоторый функционал на будущее — к примеру, глобальную интеграцию с сервером для хранения UID (сейчас для альфа-версии это просто не актуально).
Но самое главное в передаваемых параметрах — это конфигурация порта связи. Вы указываете конкретный одно- или двухпиновый интерфейс для прошивки и ввода-вывода, который и будет слушать загрузчик на «железном» уровне.
На выходе утилита генерирует стандартный hex-файл (в рамках платформы AVR).
На данном этапе, поскольку это первоначальная инициализация чипа, его необходимо один раз вручную залить в микроконтроллер стандартными инструментами (например, через avrdude и классический программатор).
Также вам понадобится подготовить сам микроконтроллер — правильно сконфигурировать его фьюзы (fuse bits).
Помимо стандартных настроек (источник тактового сигнала, частота и прочее), необходимо выставить биты, отвечающие за старт МК из области бутлоадера (BOOTRST), и выбрать соответствующий размер секции загрузчика (BOOTSZ).
Для полнофункциональной версии бутлоадера (1024 байта / 512 слов) в случае с ATmega328p нужно выделить секцию размером 512 слов, а для усеченной (512 байт / 256 слов) — секцию в 256 слов.
⚠️ ВНИМАНИЕ! Если в качестве линии связи вы решите использовать пин RESET (что актуально для МК с малым количеством ног, вроде ATtiny85), не забудьте после заливки hex-файла бутлоадера активировать фьюз RSTDISBL.
Будьте предельно осторожны: манипуляции с этим битом отключают возможность аппаратного сброса и стандартного ISP-программирования. Ошибка на этом этапе гарантированно приведет к «окирпичиванию» микроконтроллера. Для его последующего оживления вам потребуется собирать или покупать высоковольтный программатор (HVPP/HVSP).
К сожалению, операции с фьюзами приходится выполнять вручную через сторонний софт и программатор.
И здесь важно подчеркнуть аппаратный нюанс: через сам бутлоадер прошить фьюзы физически невозможно, так как архитектура МК запрещает их изменение изнутри работающей программы.
Фьюзы настраиваются исключительно «снаружи».
В будущем я планирую внедрить автоматический расчет этих битов прямо в j8bmb — утилита будет сама генерировать и выводить в консоль готовые CLI-команды для того же avrdude. Разработчику останется лишь скопировать строку, подключить программатор и в один клик сконфигурировать чип перед заливкой загрузчика.
Обращаю ваше внимание: для работы практически всех утилит тулкита необходим полноценный релиз самого vm5277.
Это связано с тесной интеграцией всех компонентов проекта в единую экосистему.
Например, той же j8bmb требуется знать множество специфических параметров целевого МК.
Эти данные она берёт напрямую из директории defs (и других смежных папок) тулкита, где хранятся базовые заголовочные файлы для микроконтроллеров.
На этом буду считать, что с подготовкой чипа и прошивкой самого бутлоадера я успешно закончил.
Теперь я рассмотрю ещё одну утилиту, которую в процессе работы приходится использовать существенно чаще — j8bf. Это прошивальщик (флешер) уже собранного проекта. Именно он берёт на себя львиную часть UX-взаимодействия с микроконтроллером в ходе повседневной разработки.
Вот вывод встроенной справки флешера:
Flasher: firmware flasher for vm5277 Embedded Toolkit
Version: 0.4.1 | License: Apache-2.0
-------------------------------------------
This tool is part of the open-source project for 8-bit microcontrollers (AVR, PIC, STM8, etc.)
Provided AS IS without warranties. Author is not responsible for any consequences of use.
Project home: https://github.com/w5277c/vm5277
Contact: w5277c@gmail.com | konstantin@5277.ru
Usage: j8bf <platform>:<mcu> <freq> <input.hex> [options]
j8bf detect <freq> [options]
Commands:
detect <freq> Auto-detect connected device
Scans serial ports to identify MCU type and returns
target in format <platform>:<mcu> (e.g., avr:atmega168p)
Use this to discover unknown device before flashing
Options:
-S, --serial <device> Serial device name (default: auto-scan)
Examples: /dev/ttyUSB0, COM3, /dev/ttyACM0
-s, --smart Enable smart flash (only write changed pages)
-e --encrypted Firmware is encrypted
-C --clear-cache Clear firmware cache for this device (smart mode)
-E, --erase Erase data on device
-F, --flash Flash HEX file to device
-V, --verify Verify device against HEX file
-R, --reboot Reboot device
-I, --interactive <mode>
Interactive mode after operations:
(stdin/stdout redirected to serial port, Esc for exit)
auto - Auto-detect mode from DBG file (default)
none - disabled
out - STDOUT only (TX)
full - STDIN + STDOUT (RX+TX) with VT100 support
-f, --fwversion <num> Set firmware version 0-255 (default: 0, used with keys F,V)
-w, --wait Wait for device (10 seconds)
-r, --retries <num> Number of retries on failure (default: 3)
-q, --quiet Quiet mode (no output)
-x, --tsize Terminal size (default: 159/55)
-v, --version Display version
-h, --help Show this help
Progress indicators:
e/w Page erased/written successfully on first attempt
E/W Page erased/written after retry (error occurred)
i Page identical to existing content (not rewritten)
I Page identical after retry (verification error occurred)
s Page skipped by smart mode (identical to cache)
- Page skipped (no data in hex file for this address)
Example:
j8bf avr:atmega328p 16 main.hex -s -F -V -R -S /dev/ttyUSB0
j8bf detect 16 -S /dev/ttyUSB0
j8bf detect 9.6
Обратите внимание на команду detect благодаря зашитой в бутлоадер информации флешер может однозначно идентифицировать подключенный чип, опрашивая доступные serial-порты.
Для этого достаточно знать только частоту его тактирования.
Также выделю опцию -I, --interactive <mode>. Этот функционал позволяет перенаправить стандартные потоки ввода-вывода (STDIO) микроконтроллера прямо в текущую консоль ПК сразу после прошивки.
Больше не нужно открывать сторонние терминалы, чтобы отправлять команды или смотреть отладочный лог приложения.
На этом разбор базового набора консольных утилит, необходимых для генерации загрузчика и прошивки МК, можно считать завершенным.
Однако, помимо CLI-инструментов, в экосистеме есть официальный плагин для IntelliJ IDEA.
В нём я проработал полноценный графический интерфейс (GUI), который позволяет задействовать весь этот сложный тулчейн буквально в один клик.
Собственно, это моя главная рекомендация: используйте IDEA и плагин — это минимизирует усилия на конфигурацию и существенно повысит удобство и скорость работы с тулкитом в целом.
Прочитать подробнее о плагине и его настройке можно в документации проекта на GitHub.
Под капотом: протокол, команды и алгоритмы
Я рассказал, как весь этот процесс выглядит для конечного пользователя, а теперь заглянем под капот и разберёмся, как всё устроено изнутри на уровне протокола обмена. Взаимодействие между флешером на ПК и бутлоадером в МК происходит строго типизированными кадрами (пакетами).
Структура такого пакета выглядит следующим образом:
Смещение | Поле | Размер | Описание |
|---|---|---|---|
0x00 | MAGIC | 1 байт | Идентификатор протокола, всегда 0x77 |
0x01 | REQUEST/RESPONSE ID | 1 байт | Код команды или ответа |
0x02 | DATA SIZE | 2 байта | Размер полезной нагрузки |
0x04 | DATA | N байт | Полезная нагрузка (тело пакета) |
0x04+N | XOR SUM | 1 байт | Контрольная сумма |
Контрольная сумма (XOR SUM) считается через исключающее ИЛИ (XOR) абсолютно по всем байтам кадра, начиная с первого байта MAGIC и заканчивая последним байтом полезной нагрузки DATA. Такой подход требует минимальных ресурсов процессора и вычисляется «на лету» прямо в процессе циклического приема или передачи данных в программном UART.
Команды управления:
Каждому типу операции соответствует свой уникальный идентификатор в поле CMD / RESP ID.
Полный список команд выглядит так:
0x00 REQ_BLDR_INFO — Запрос паспорта устройства. В ответ бутлоадер возвращает версию загрузчика, аппаратную сигнатуру чипа и его уникальный 8-байтный UID.
0x01 REQ_BLDR_REBOOT — Рестарт микроконтроллера.
0x10 REQ_BLDR_PAGE_ERASE — Очистка страницы Flash-памяти.
0x11 REQ_BLDR_PAGE_VERIFY — Проверка страницы Flash-памяти.
0x12 REQ_BLDR_PAGE_WRITE — Запись страницы Flash-памяти.
0x21 REQ_BLDR_PAGE_EVERIFY — Проверка страницы со сквозной дешифрацией данных.
0x22 REQ_BLDR_PAGE_EWRITE — Запись страницы со сквозной дешифрацией данных. Эти два режима требуют расширения бутлоадера — интеграции в него функции дешифрации прошивки. Скорее всего, в таком режиме его размер превысит 1 КБ Flash, так как помимо самого алгоритма в коде должен храниться ключ шифрования. Пока этот вызов у меня не реализован в железе и заложен на будущее. Саму криптографическую функцию разработчик сможет реализовать самостоятельно для обеспечения уникальности алгоритма.
0x30 REQ_BLDR_FUSE_READ — Чтение текущих значений фьюзов. Напоминаю, что AVR-чипы аппаратно не умеют перезаписывать свои фьюзы изнутри программы, поэтому доступно только чтение.
Урезанный бутлоадер имеет жесткий лимит по размеру и поддерживает исключительно жизненно важный минимум команд: REQ_BLDR_INFO, REQ_BLDR_REBOOT и REQ_BLDR_PAGE_WRITE.
Коды ответов бутлоадера:
На каждую полученную команду загрузчик микроконтроллера возвращает пакет, в поле CMD / RESP ID которого записывается один из следующих статус-кодов:
0x80 RESP_BLDR_OK — Команда успешно выполнена.
0x81 RESP_BLDR_IDENTICAL — Содержимое страниц полностью идентично. Этот статус возвращается при операциях верификации (REQ_BLDR_PAGE_VERIFY) или записи (REQ_BLDR_PAGE_WRITE), позволяя хосту мгновенно понять, что перезапись не требуется.
0x82 RESP_BLDR_NOT_EQUAL — Обнаружено несовпадение данных при верификации страницы (REQ_BLDR_PAGE_VERIFY).
0x88 RESP_BLDR_WRONG_REQUEST — Пакет поврежден, не совпала контрольная сумма XOR SUM или другая причина из-за ошибки в данных запроса.
0x89 RESP_BLDR_WRONG_PAGE — Передан некорректный или выходящий за границы доступной памяти номер страницы.
0x8a RESP_BLDR_WRONG_PAGESIZE — Переданный размер страницы не соответствует аппаратным характеристикам целевого МК.
0x8b RESP_BLDR_DENIED — Операция отклонена.
Тело ответа на запрос REQ_BLDR_INFO (DATA_SIZE:0x11)
Когда флешер запрашивает информацию о чипе с помощью команды REQ_BLDR_INFO, бутлоадер возвращает пакет с полезной нагрузкой строго фиксированного размера — DATA_SIZE всегда равен 0x11 (17 байт). Внутри поля DATA (начиная со смещения 0x04 от начала кадра) данные упакованы в следующую структуру:
Смещение | Поле | Размер | Описание |
|---|---|---|---|
0x04 | BLDR VERSION | 1 байт | Версия бутлоадера (биты 7,6-тип,5-0-версия) |
0x05 | PLATFORM TYPE ID | 1 байт | идентификатор платформы |
0x06 | MCU SIGNATURE | 4 байта | сигнатура МК (4 байта для совместимости) |
0x0a | STDION RX/TX PORT(S) | 2 байта | порты ввода-вывода |
0x0c | MCU UID | 8 байт | уникальный ИД МК |
0x14 | FIRMWARE VERSION | 1 байт | версия основной прошивки |
Благодаря такой плотной упаковке метаданных утилита j8bf на стороне ПК почти моментально получает исчерпывающее представление о подключенном устройстве.
Детализация полей паспорта устройства:
Маска поля BLDR VERSION: Стандарту версии выделены младшие биты, а старшие два бита (биты 7 и 6) определяют тип (профиль) самого загрузчика:
00 — Полнофункциональный стандартный бутлоадер (для чипов семейства ATmega и аналогичных).
01 — Усеченный/сокращенный бутлоадер (для младших чипов семейства ATtiny).
Словарь PLATFORM TYPE ID: Идентификатор архитектуры целевого процессора:
0 — Архитектура не указана (Not specified).
1 — Семейство Microchip (Atmel) AVR.
Остальные ID (для STM8, PIC, Z80 и т.д.) будут задействованы по мере портирования нативного ядра на другие аппаратные платформы.
Структура пакетов работы с памятью и фьюзами
Ниже приведена побайтовая структура полезной нагрузки для команд работы со страницами Flash и чтения конфигурации чипа.
1. Запрос на запись или верификацию страницы (REQ_BLDR_PAGE_WRITE и REQ_BLDR_PAGE_VERIFY)
Общий размер поля данных кадра рассчитывается как DATA_SIZE = 0x02 + PAGE_SIZE.
Смещение | Поле | Размер | Описание |
|---|---|---|---|
0x04 | FLASH ADDR | 2 байта | Адрес целевой страницы во Flash-памяти в байтах |
0x06 | PAGE DATA | N байт | Массив бинарных данных страницы для записи или сравнения |
Примечание: Размер данных страницы (PAGE_SIZE) жестко регламентирован аппаратной архитектурой конкретного типа микроконтроллера.
2. Запрос на очистку страницы (REQ_BLDR_PAGE_ERASE)
Размер полезной нагрузки фиксирован: DATA_SIZE = 0x02.
Смещение | Поле | Размер | Описание |
|---|---|---|---|
0x04 | FLASH ADDR | 2 байта | Адрес очищаемой страницы во Flash-памяти в байтах |
3. Тело ответа на запрос чтения фьюзов (REQ_BLDR_FUSE_READ)
Размер полезной нагрузки фиксирован: DATA_SIZE = 0x06.
Смещение | Поле | Размер | Описание |
|---|---|---|---|
0x04 | FUSE DATA | 6 байт | Сырые байты конфигурации фьюзов, прочитанные из МК |
Интересный нюанс: под фьюзы заложено 6 байт с запасом на будущее. На классической архитектуре AVR (ATmega328p) утилита считывает 4 базовых байта: Low Fuse, High Fuse, Extended Fuse и Lock Bits, оставляя оставшиеся 2 байта незаполненными (0x00).
Алгоритм работы сквозного тулчейна (Флешер - Бутлоадер)
Чтобы вся система работала как единый отладочный и прошивочный комплекс, я реализовал следующий пошаговый алгоритм взаимодействия между флешером j8bf на ПК (Хостом) и микроконтроллером:
Инициация сброса: Хост отправляет в линию связи байт 0x12 (в таблице ASCII это управляющий символ DC2 — Device Control 2). Он выбран специально, так как его появление в стандартных текстовых логах прикладных программ менее всего вероятно.
Перехват на МК: Постоянно работающий STDIO-слой ядра МК обнаруживает этот маркер, отключает глобальные прерывания и выполняет прямой программный переход на начальный адрес бутлоадера во Flash.
Старт загрузчика: Бутлоадер инициализирует GPIO-пин связи. Если в памяти чипа вообще нет основной прошивки, он навсегда остается в режиме программирования. Если прошивка есть — он запускает таймер на 1 секунду и начинает слушать линию RX, ожидая падения уровня в логический ноль.
Пауза хоста: Флешер на ПК выдерживает технологическую паузу в несколько миллисекунд, достаточную для того, чтобы МК гарантированно успел прыгнуть в бутлоадер и завершить его инициализацию.
Стартовый запрос: Хост начинает коммуникацию, отправляя пакет запроса паспорта устройства (REQ_BLDR_INFO).
Фиксация сессии: Как только бутлоадер детектировал низкий уровень от стартующего пакета хоста, код МК мгновенно переходит на обработку старт-бита программного UART. Это критически важное решение, которое позволяет не потерять самый первый отправленный хостом байт. Бутлоадер фиксирует вход в режим прошивки.
Ответ МК: Загрузчик считывает команду хоста и отправляет в линию пакет с информацией о чипе, платформе и версиях ПО.
Анализ линии (Эхо): Хост принимает ответ и проверяет его на «эхо» (слышит ли он свои же отправленные данные в общем фрейме линии). Если эхо присутствует — хост однозначно определяет подключение как однопроводное полудуплексное и активирует режим фильтрации собственного вывода. Если эха нет — значит, подключение классическое двухпроводное.
Валидация железа: Хост сверяет полученную аппаратную сигнатуру и платформу МК с указанными параметрами.
Процесс прошивки: Если проверки пройдены успешно, хост приступает к выполнению заданных пользователем операций (последовательная очистка страниц, запись бинарника, верификация данных).
Квитирование: На каждый успешно записанный или проверенный пакет бутлоадер отвечает статус-кодом RESP_BLDR_OK (или RESP_BLDR_IDENTICAL), либо возвращает код конкретной ошибки.
Финализация: После успешного завершения всех операций хост посылает финальную команду перезагрузки REQ_BLDR_REBOOT и, если это задано настройками проекта, автоматически переключается в интерактивный режим (STDIO-консоль).
Запуск приложения: Бутлоадер принимает команду ребута и безопасно прыгает по адресу 0x0000, запуская свежее приложение.
МК(бутлоадер) по истечении 1 секунды (если низкий уровень не обнаружен) выполняет переход на основноую программу (по адресу 0x0000)
Статус проекта и где его пощупать
Разработанный бутлоадер полностью автономен.
Ему абсолютно всё равно, какой прикладной код записывать во Flash — высокоуровневый бинарник от компилятора J8B или стандартный «выхлоп» из классического avr-gcc (Си). Поэтому, если в вашем собственном проекте нужен надежный, быстрый и компактный загрузчик с поддержкой однопроводного режима, вы можете смело забирать его исходники или готовые бинарники отдельно от остальной экосистемы vm5277.
Сейчас проект находится в стадии активной альфа-разработки.
Ссылки:
• Код бутлоадера на чистом ASM в репозитории vm5277 — bldr.asm
• Документация и руководства — index.md
• Официальный сайт проекта — vm5277.ru
• Открытый репозиторий экосистемы — GitHub
• История изменений и хронология разработки — NEWS.md
P.S. Если заметили ошибку или любую другую неточность — пишите. Буду рад фидбеку и постараюсь оперативно всё пофиксить!
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.