В этой заметке речь больше про принцип — программатор можно сделать так, чтобы на стороне компьютера не требовался дополнительный софт. Будем прямо лить HEX-файл в последовательный порт. Идея не новая, но не лишне напомнить (в том числе об одном-двух подводных камнях рассказать).
Собственно программатор — голый Arduino с прошивкой, разбирающей HEX-файл построчно и реализующий программирование целевого чипа по SPI. Прошивка и инструкции сложены в репозитории на гитхабе: At89s-prog.
Конкретные чипы (At89s...) здесь скорее для примера. Мы не будем воспевать достоинства 8051-й архитектуры (конечно, они архаичны но у них есть плюсы, благодаря которым они до сих пор сохраняют популярность). У меня в какой‑то момент оказалась их горстка а программатора под рукой не нашлось. Они (те что с индексом S) программируются через SPI, а не стандартным «многоногим» интерфейсом что делает удобным их применение в любительских поделках.
Предисловие
Когда вы занимаетесь микроконтроллерами в промышленных условиях, обычно используете более‑менее однотипные чипы и нужный инвентарь — программаторы, отладчики и т. п. — есть под рукой. Типичный случай — утилита для прошивки устанавливается на компьютер — подключается специальный девайс (программатор) и уже он подключается к схеме содержащей программируемый контроллер. На картинке ниже этот случай изображён сверху. Снизу представлена ситуация описанная в этой статье — мы исключаем утилиту на стороне компьютера, а в качестве программатора используем Arduino.

В любительской практике бывает и так, что в одной поделке использовал AVR, в другой ST32, в третьей MSP430, в четвёртой ещё что‑нибудь. Зоопарк чипов в хобби‑проектах имеет свойство копиться со временем. Закупаться соответствующим количеством нужных программаторов не всегда имеет смысл. Дело даже не в деньгах а в том что (как многие «электронщики» знают — со временем становится затруднительно вспомнить где что лежит).
Сам я поэтому часто использую чипы у которых есть встроенный UART‑загрузчик (например ST32 и MSP430 упомянутые). Но в частности Atmel издавна упорно шёл другим путём и многие их контроллеры программируются по SPI. Большинство из них поддержаны в AvrDude и программаторе AVRISP — эта прошивка доступна в примерах Arduino IDE.
Однако с попавшими мне в руки 8051-ми клонами (изначально At89s4051 — удобными 20-ногими узкими чипами — а после и At89s52 — они в стандартных 40-ногих корпусах) — эти инструменты не работают. При этом чипы удобные и стоят (на «али») очень недорого.
Открыв datasheet и почитав раздел про SPI команды я подумал что могу быстренько состряпать самодельный программатор.
При этом удобным показалось чтобы к нему ещё не требовалась дополнительная утилита на стороне компьютера. Отправил файл в последовательный порт — и ура!
Вещь получилась рабочая — одна из первых поделок которые я с её помощью разрабатывал — этот радиоприёмник на Si4735:

Атмеловские 8051-е клоны
Мы рассматриваем такие модификации серии At89...
(этот префикс у них означает 8051-е чипы):
At89s52 и At89s51 (различаются как 8052 и 8051 — 8 против 4 кб флеш‑памяти, лишний таймер) — в «полных» 40-ногих корпусах, или например в архаичных но милых PLCC
At89s4051 и At89s2051 — аналоги 8051 но в уменьшенных 20-ногих корпусах, выведены только P1 и P3.
Обратите внимание на букву S после At89 — она означает модели с программированием по SPI (параллельное тоже доступно впрочем но нас не интересует).
Вообще сейчас есть и более интересные 8051-совместимые контроллеры — наверняка многие замечали китайские STC...
— в частности STC8G1K08
— тут и прошивка по UART и встроенный RC‑генератор тактовой частоты, и АЦП. И корпуса похожие. Но документация может оставить чувство лёгкого раздражения с непривычки. В любом случае они за пределами нашей сегодняшней темы.
Применение
Возьмите прошивку (arduino-проект) из репозитория указанного выше.
Прошейте её в какой-нибудь Arduino-модуль, который найдётся под рукой. Там нет какой-то специфики и скорее всего подойдёт любой.
Для примера тут же представлены файл blink.asm
и скомпилированный из него файл blink.hex
— хотя вы можете написать и скомпилировать собственный код. Например код для цифрового радио на Si4735, упомянутый выше — там уже прошивка объёмистая.
Подключите ваш программатор (прошитый) ардуино к программируемому чипу:
подключите питание (GND, VCC и если есть отдельный — то VPP)
RST к 9й ноге, SCK, MISO, MOSI к 12, 11, 10 соответственно
также нужен кварц на XTAL или внешний генератор тактовой частоты
Например вот подключение «голого» At89s52. Для демонстрации добавлены 2 светодиода которыми прошивка blink.hex
будет перемигивать:

То же самое с 20-ногим узким At89s4051:

Теперь нужно лишь настроить последовательный порт (с которым всё ещё соединён Arduino) на параметры 1200 бод, 1 стоп‑бит, без чётности.
Низкая скорость выбрана чтобы процесс прошивки «успевал» за подачей данных. С некоторыми оговорками её можно увеличить (см. ниже) но м.б. не очень актуально.
Удобно использовать какую‑нибудь терминальную программулину, которая позволит и файл отправить — и следить что программатор отвечает. Однако можно использовать и стандартные инструменты ОС — в частности примеры приведены в виде shell‑файлов под линукс:
stty -F /dev/ttyUSB0 1200 -cstopb -parenb cs8 -icrnl -echo
Настроили последовательный порт. Кроме вышеупомянутых настроек тут ещё параметры для подавления эха и формата переводов строк.
cat /dev/ttyUSB0 &
в бэкграунде читаем (и дампаем) что пишет программатор обратно по ходу работы
cat blink.hex >/dev/ttyUSB0
отправляем файл с прошивкой в последовательный канал.
В случае успеха программатор пишет некую информацию по ходу обработки получаемого файла, выглядит это так:

PRG EN — это ответ на команду перехода в режим программирования (не все чипы содержательно на неё отвечают). SIG — сигнатура чипа. Если она совпадает с даташитом, по крайней мере понятно что интерфейс SPI работает.
Дальше строчки с W@
расшифровываются как «write at <addr> <count>» — вот мы записываем 16 байт по адресу 0x0 (первая строка hex-файла) потом ещё 14 по адресу 0×10 (вторая строка).
В третьей строке программатор обнаруживает признак завершения и пишет `Prg mode off`. Напоследок он дёргает ещё раз ресет — где‑то была такая рекомендация, хотя по-моему и без этого чип нормально запустится в рабочий режим.
Из скрипта burn.sh
после выполнения выходим по Ctrl‑C — иначе он висит продолжая ожидать ещё чего-нибудь от программатора :)
Стирание чипа требуется (обычно) перед каждой перезаписью. Поскольку время стирания может быть разным, пока оно сделано отдельной командой. Нужно просто отправить в порт восклицательный знак. Для удобства это реализовано в файле erase.sh
в виде команды:
echo "!" >/dev/ttyUSB0

Иногда полезно считать программу обратно из чипа - чтобы верифицировать нет ли ошибки записи, например. Для этого пока реализованаочень простая «фича» — используются такие же команды как для записи строк HEX‑файла, но с вопросительным знаком вместо двоеточия в начале. Поэтому из строки на самом деле берется только адрес и длина считываемых данных, например (в readmem.sh
):
echo "?10000000" >/dev/ttyUSB0
Считаем 0x10 байт с нулевого адреса:

Замечания по коду прошивки программатора
Программатор сам возвращается в «ждущий» режим. То есть вы можете отправить ему команду на стирание, потом закачать новый hex‑файл, потом считать память для проверки — между этими действиями не требуется каких‑то дополнительных манипуляций.
Запись в чип происходит после считывания очередной строки hex‑файла — и ведётся она в побайтовом режиме. Постраничный я сначала использовал, но преимуществ у него не нашёл а размер страниц у чипов разный, да ещё и файлы собранные сложными компиляторами (вроде Keil или SDCC) будут содержать строки в сложном порядке и код собирающий данные для страницы выглядит достаточно мутно.
В строке берутся первые четыре байта (9 символов) чтобы определить режим (чтение‑запись), адрес, количество байт — и тип операции (01 — останов). После чего следующие байты непосредственно записываются.
При этом скорость приёма байт по UART должна быть согласована со скоростью операций записи (сейчас сделано с запасом) — да ещё и со скоростью вывода информации обратно пользователю. В частности из‑за этого некоторые отладочные строчки были сокращены — замените W@
на «writing data at» и что‑нибудь в этом духе — и на достаточно объёмных прошивках вы можете получить пропущенные байты при записи:)
Вероятно эту ситуацию можно улучшить с использованием механизма Xon / Xoff, но у меня с первой попытки не получилось — поэтому пока отложил как некритичный импрувмент.
Заключение
Как сказано выше, цель статьи больше в том чтобы обратить внимание на возможность убрать одно из «звеньев» в инструментах для прошивки. С чипами имеющими встроенный загрузчик можно бы убрать и «программатор». Только к сожалению дефолтных загрузчиков использующих hex‑файлы я пока не встречал. А без этого выходит что всё равно сначала нужен программатор чтобы кастомный загрузчик прошить.