Вступление
Всем привет! Меня зовут Семён, я инженер‑программист и занимаюсь разработкой промышленного оборудования. В нашем цеху на часть оборудования установлены китайские ручные маркираторы с картриджем типа HP45.
Всё началось с вопроса директора: «Можем ли мы сами управлять картриджем и печатать им что угодно, в том числе коды Честного знака? Так, чтобы его можно было без проблем встроить в любой проект и чтобы это было дешевле готовых решений на рынке».
Задача звучала амбициозно. На старте у меня был один уставший картридж HP45, несколько маркираторов на производстве для примера, полное отсутствие документации и огромное желание заставить эту штуку печатать.
Первым делом я изучил тему. Искать пришлось на английском, и информации оказалось очень мало. В итоге я наткнулся на энтузиаста, который уже провёл реверс‑инжиниринг картриджа, — его сайт ytec3d.com. Оттуда же нашлась ссылка на патент HP US5946012. Благодаря этому автору я разобрался, как работает картридж, за что отвечают его контакты и что такое адреса и примитивы. Поначалу было вообще непонятно, как всё устроено, но со временем информация уложилась в голове и превратилась в ценный опыт.
Теория
HP45 — это термоструйный (TIJ, thermal inkjet) картридж. Он состоит из резервуара для чернил, контактной площадки на задней стороне и двух рядов сопел на нижней. Резервуар объёмом 42 мл удерживается под небольшим отрицательным давлением с помощью пружины — так чернила не вытекают из сопел самопроизвольно. Картридж имеет 300 сопел, которые в сумме дают разрешение 600 DPI, работает от 12 В и имеет 52 контакта.
Каждое сопло — это нагревательный элемент сопротивлением около 30 Ом и полевой транзистор. Затвор транзистора подключён к адресной линии, нагреватель — к линии примитива, исток — к GND.

Когда сопло должно выбросить каплю, нагреватель кратковременно включается и мгновенно испаряет небольшое количество чернил в камере. Образовавшийся пузырь пара выталкивает остальные чернила наружу. После этого нагреватель остывает, и камера снова заполняется чернилами.
Сопла объединены в матрицу, благодаря чему 300 сопел управляются всего 36 линиями: 22 адресами и 14 примитивами. Адресу достаточно открыть затвор транзистора, поэтому большой ток ему не нужен — основной ток течёт через примитивы. На схеме земля показана отдельными линиями, но внутри печатающей головки все они соединены. Другие компоненты, например датчик температуры, тоже заземлены через эти выводы.
Что такое адрес и примитив
Адрес открывает затворы транзисторов у группы сопел, а примитив подаёт ток на нагреватели. Сопло срабатывает только тогда, когда одновременно активны его адрес и его примитив.

Алгоритм выстрела выглядит так:
Подаём 12 В на адрес, чтобы открыть затворы.
Ждём около 2 мкс, пока затворы откроются.
Подаём от 9 до 12 В на нужные примитивы (на все или на часть) на 2 мкс.
Снимаем напряжение с примитивов.
Через 3 мкс закрываем адрес.
Переходим к следующему адресу по порядку — от 1 до 22 или от 22 до 1.
Предупреждения:
Адрес нельзя закрывать, пока примитив ещё включён: полевой транзистор будет повреждён или уничтожен. Примитив всегда должен закрываться раньше адреса.
Импульс примитива длиннее 2 мкс может повредить сопло.
Стрельба соплами без чернил тоже может их повредить.
По опыту скажу, что картридж обычно живучее, чем кажется. Перед тем как выйти из строя, он начинает с писком проливать под себя много чернил, а иногда пускает предсмертный белый дымок. Пока это происходит, обычно успеваешь отключить питание и спасти картридж.


Сопла разделены на две группы — чётную и нечётную. Чётные примитивы управляют чётным рядом сопел, нечётные — нечётным. Адресные линии общие для обоих рядов. Расположение контактов показано ниже.

Фактическое расположение контактов на печатающей головке HP45
Как узнать температуру картриджа
На картридже есть два вывода для измерения температуры:
R10X — опорный резистор, сопротивление которого примерно в 10 раз больше сопротивления нагревателя, то есть около 300 Ом. С ним сравнивается сопротивление датчика.
TSR (thermal sense resistor) — терморезистор с номинальным сопротивлением около 300 Ом. При нагреве его сопротивление растёт, при охлаждении — падает.
Сравнивая сопротивление TSR с опорным R10X, можно оценить температуру головки.
Чтобы было нагляднее, мы с командой вскрыли один сломанный картридж и посмотрели, как он устроен внутри.



Первый импульс
Поначалу никакой контактной площадки для картриджа у меня не было, поэтому для первого подключения я просто припаялся к его контактам. Картриджу нужно 12 В, а микроконтроллер работает с логикой 3,3 В, поэтому понадобилось согласование уровней. Я набросал схему ключа на N и P‑канальных MOSFET в симуляторе falstad.com, получил в нём рабочий результат и перешёл к сборке.
К собранной схеме я подключил ESP32, быстро написал тестовый скетч и начал проверять. Не с первой попытки, но на бумаге появилась одна маленькая точка. Это стало стартом для первого прототипа — маленькая победа.
Первый прототип
Схема
Дальше я взялся за схему уже под весь картридж, попутно исправив ошибки первого опыта. Проектировал на SMD‑компонентах, чтобы с обратной стороны платы ничего не торчало и не мешало прижимать картридж к контактной площадке. Получился каскад ключей, и стало ясно, что MOSFET‑ов потребуется больше 50.
У ytec3d уже была своя готовая схема, но тогда я не понимал, как работают используемые им микросхемы. Поэтому пошёл своим путём: хотелось собрать то, что понимаю сам, и, возможно, найти решение проще, надёжнее или дешевле.
Плата
Платы заказали в NextPCB без монтажа. Партия пришла с косяками, и виноват в этом был я: вместо резисторов 0603 поставил в проекте 0201 (паять их вручную очень тяжело), а для MOSFET выбрал не тот футпринт, из‑за чего выводы оказались перепутаны. Эти платы отправились в мусор. Урок усвоен. Я перепроектировал плату, перезаказал, и на этот раз пришло ровно то, что нужно.

Прошивка
Плату я тестировал с осциллографом и выяснил, что тайминги неправильные — с такими можно было повредить картридж. Почему, если в коде стоит 2 мкс?
Проект под ESP32 я писал в PlatformIO на фреймворке Arduino, и главным пожирателем времени оказались как раз функции Arduino. Один вызов digitalWrite() занимал больше 1 мкс. Функция ESP‑IDF gpio_set_level() работала быстрее, но тоже заметно тормозила. Самым быстрым вариантом стала прямая запись в регистры GPIO ESP32 (GPIO.out_w1ts / GPIO.out_w1tc) через макросы — переключение вывода сократилось с микросекунд до наносекунд.
#define SET_HIGH(Pin) ((Pin) >= 32 ? (GPIO.out1_w1ts.val = (1 << ((Pin) - 32))) : (GPIO.out_w1ts = (1 << (Pin)))) #define SET_LOW(Pin) ((Pin) >= 32 ? (GPIO.out1_w1tc.val = (1 << ((Pin) - 32))) : (GPIO.out_w1tc = (1 << (Pin))))
Вторым сюрпризом стала delayMicroseconds(): она давала задержку чуть больше заданных 2 мкс. Поэтому вместо неё я, как и ytec3d в своём коде, использовал пустые инструкции процессора (NOP):
for (uint8_t d = 0; d < 13; d++) { // NOP __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); }
У меня такой цикл даёт примерно 2 мкс, но число итераций зависит от частоты процессора и подбирается по осциллографу. После оптимизации я получил на осциллографе чистые 2 мкс.
Отдельный момент — прерывания. Если во время импульса примитива сработает прерывание или проснётся Wi‑Fi‑стек, импульс растянется, а для нагревателя это опасно. Поэтому критичный участок генерации импульсов нужно выполнять с запрещёнными прерываниями и желательно на отдельном ядре.
Затем я написал функцию выстрела всеми соплами одного ряда (чётного или нечётного). Каждый примитив управлялся своим GPIO, и тут проявилась проблема схемы. Я включал GPIO по очереди, ждал 2 мкс и по очереди же их выключал. Первые включившиеся примитивы ждали, пока переключатся остальные, задержки складывались, и вместо 2 мкс получалось около 4 мкс. Чтобы уложиться в адекватные тайминги, я просто убрал явную задержку: переключение всей линии примитивов само по себе занимало около 2 мкс, и для экспериментов этого хватало.
Ещё я написал функцию выстрела одним соплом, чтобы потом прорисовывать каждую точку по отдельности. Ей тоже можно выстрелить всеми соплами, но это займёт больше времени.
Первая линия
Запаяв всё, я протестировал плату и, к своему удивлению, смог стрельнуть большей частью сопел. Правда, подключение было очень неудобным. Что‑то в линию напечатать получилось, но на готовый результат это ещё не походило.

Библиотека DataMatrix для печати кодов Честного знака
Готовых библиотек DataMatrix для ESP32 я не нашёл. Зато нашлась ZXing‑C++, не адаптированная под микроконтроллеры. Я успешно встроил её в проект: подтянул нужные зависимости и вырезал всё лишнее, чтобы библиотека не занимала много места.
Для печати кодов, картинок и вообще чего угодно нужен буфер, куда складываются все точки. Чтобы он занимал поменьше памяти, я использовал двумерный массив uint8_t, где каждый байт хранит 8 точек.
Первый напечатанный DataMatrix успешно отсканировался, но приложение Честного знака его ещё не принимало: для маркировки нужен GS1 DataMatrix со служебными символами. На следующих этапах я добавил поддержку GS1 в интегрированную библиотеку, и после этого коды стали нормально считываться приложением Честного знака.


Промежуточный прототип
Схема
Мне не давало покоя количество MOSFET‑ов и занятых выводов контроллера. Только на управление картриджем уходило 36 линий (22 адреса и 14 примитивов), и это съедало почти все свободные GPIO ESP32. А в конечном устройстве нужны выводы ещё и под датчики и интерфейсы связи.
Прежде чем проектировать следующую версию, я основательно разобрался в работе MOSFET и подобрал микросхемы, которые позволяли сделать схему компактнее:
74HC595 — сдвиговые регистры для расширения портов;
TLC59213 — 8-битный драйвер с параллельным входом для коммутации 12 В на примитивы;
4017 — десятичные счётчики для адресов, соединённые каскадом через логику «И» 4081 со сбросом;
питание — на линейном стабилизаторе, у каждой микросхемы керамический конденсатор 0,1 мкФ, плюс электролит 100 мкФ для чистоты питания.
В этот раз мы решили изготовить платы сами: это дешевле, и не нужно ждать доставку.
Самодельные платы
Подготовка
Для самодельной двухслойной платы понадобятся:
двусторонний фольгированный текстолит, нарезанный по размеру;
проект платы;
негативный плёночный фоторезист;
ламинатор для накатки фоторезиста;
фотополимерный 3D‑принтер;
мини‑дрель с цангой под свёрла меньше 1 мм.
Плату я проектировал в Fusion 360. Открываем её в 3D‑виде, добавляем рамки для совмещения, отключаем всё лишнее и экспортируем верхний и нижний слои в два STL‑файла. Затем с помощью ламинатора накатываем фоторезист на текстолит с обеих сторон.
Засветка
Для засветки мы использовали фотополимерный принтер Elegoo Mars 2 Pro: снимаем с него ванночку и всё лишнее, нужен только экран. Он засвечивает фоторезист ультрафиолетом и позволяет получать очень тонкие дорожки. Задание для принтера я подготовил в слайсере CHITUBOX.
Важно: плату нужно максимально точно отцентрировать на экране, иначе слои не совпадут, и придётся смывать фоторезист и наносить его заново.
После засветки проверяем, что отверстия на обеих сторонах совпадают, проявляем плату в слабом растворе кальцинированной соды и травим в хлорном железе по стандартной технологии. Работали в перчатках, очках и халатах.

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

Если самодельная плата пойдёт в реальное устройство, лудить её нужно обязательно целиком, иначе медь окислится.
Прошивка
В прошивке появилась передача данных по SPI в регистры 74HC595. Регистров в цепочке два, поэтому за одну передачу уходит 16 бит, и все нужные выходы выставляются разом. Проблема с задержками ушла: нужные примитивы теперь включаются одновременно.
С драйвером TLC59213 всё довольно просто. Один вывод (RST) мгновенно сбрасывает все выходы в низкий уровень, а на вывод CLK я подаю импульс по таймингам из даташита, чтобы защёлкнуть новое состояние выходов.
Адреса теперь переключает каскад счётчиков — по порядку от 1 до 22. В конце обязательно нужен сброс, иначе счётчик зациклится на последней микросхеме в цепочке.
Кроме кодов Честного знака нужно печатать дату и номер партии. Для этого я подключил готовый растровый шрифт. Дату напечатать получилось, но работать так неудобно: под каждый новый шрифт нужно искать или делать его растровую версию.

Хорошо печаталась только верхняя дата. Причина была в прошивке: я неправильно составил таблицу соответствия сопел адресам и примитивам. При неверной адресации что‑то печатается, но результат плохой. Сверив свою таблицу с таблицей ytec3d, я нашёл ошибки и переписал её. Теперь каждую дату уже можно было хотя бы прочитать:

Для печати на русском нужны шрифты с кириллицей. Тут мой взгляд упал на популярную библиотеку для дисплеев u8g2: в ней уже есть буфер, работа со шрифтами и много документации.
Запустить её без дисплея можно тремя способами:
Написать драйвер своего «виртуального» дисплея.
Взять из имеющихся в библиотеке дисплей с наиболее подходящим разрешением.
Вообще не использовать сущность дисплея и задать размеры буфера самостоятельно.
Сначала я пошёл вторым путём, потому что ещё не знал про третий: взял подходящий по размеру дисплей 160×512 и смог рисовать дату и вообще всё, что умеет библиотека. Позже нейросеть подсказала третий вариант — он оказался самым правильным, и я переделал на него.
На тестах я успешно печатал тексты, картинки и коды Честного знака. Самодельную тестовую плату никуда не встроишь, поэтому дальше я начал трассировать итоговую плату для заказа в Китае.
Итоговый прототип
Схему итоговой платы и исходники прошивки я не публикую: это коммерческая разработка компании. Но общий подход и архитектура описаны ниже, а принципы работы с картриджем — в разделах выше.
Схема
В итоговой версии я разделил электронику на две платы:
маленькую плату‑коннектор, к которой подключается картридж и на которой стоит только FFC‑разъём для шлейфа;
основную плату со всей остальной электроникой.
Так печатающая голова получается компактной и не громоздкой.
После трассировки я несколько раз перепроверил схему: каждая ошибка на этом этапе стоит денег. Платы заказали со сборкой в Китае через AliExpress, общались с производителем по почте на английском. Вывод на будущее: если заказываете сборку у китайцев, сразу проектируйте в EasyEDA — там подключена библиотека компонентов LCSC, и вам не придётся согласовывать кучу правок, когда что‑то не сходится по размерам или чего‑то нет в наличии.
Плата
Через месяц платы пришли. Перед запуском я шаг за шагом всё прозвонил и убедился, что нет замыканий. Потом подал питание и проверил, что напряжения приходят куда нужно. На последнем этапе подключил микроконтроллер и проверил работу всех микросхем.
Архитектура и прошивка
На этом этапе я в основном занимался протоколами связи, командами печати и доводкой основного алгоритма. Прежде чем писать код, я набросал на бумаге архитектуру системы и в итоге отказался от u8g2: генерация DataMatrix осталась на ESP32, а всю растеризацию я отдал хосту.
Общая схема получилась такой:

Плюсы такого разделения:
у хоста гораздо больше возможностей для растеризации любой сложности;
контроллер маркиратора занимается только управлением картриджем: ему всё равно, что печатать, главное — правильно загрузить данные в буфер. Прошивка становится проще и компактнее.
Основной протокол связи работает по UART. Дополнительно я сделал HTTP API по Wi‑Fi, но заранее закладывал переход на Ethernet: на производстве Wi‑Fi ненадёжен из‑за помех от высокочастотного оборудования.
Через команды можно менять параметры маркиратора, запускать печать и загружать данные в буфер. Целостность загружаемого буфера проверяется по CRC-16/CCITT‑FALSE. Маркиратор сообщает хосту об ошибках, а все вводимые параметры проверяются на допустимый диапазон.
Все команды я задокументировал в инструкции для разработчиков хоста.
Чтобы уместить Wi‑Fi‑стек, я использовал схему разметки флеш‑памяти min_spiffs.csv. С ней не получится обновлять прошивку по воздуху, но для наших задач это и не нужно.
В прошивке реализовано измерение температуры по разнице сопротивлений опорного резистора R10X и терморезистора TSR. Можно задать максимальную температуру: если картридж перегреется сверх неё, печать блокируется. Параметр необязательный, без него маркиратор тоже работает.
Настоящие 600 DPI
Отдельно я реализовал печать в 600 DPI. По ходу выяснилось, что на ручных китайских маркираторах режим 600 DPI неполноценный. Дело в том, что каждый ряд сопел HP45 сам по себе даёт только 300 DPI, а честные 600 DPI получаются, только когда оба ряда работают синхронно и их точки ложатся между собой.
Реализовать это можно двумя способами:
Простой. Печатать одним рядом в 300 DPI и делать несколько выстрелов в одну точку. Отпечаток выходит жирнее и визуально похож на 600 DPI, но реального прироста разрешения нет.
Сложный. Организовать синхронную работу обоих рядов сопел.
Я выбрал второй вариант. Для него понадобился параметр сдвига точек, который отвечает за совмещение двух рядов. Расстояние между рядами +-4.1 мм исходя из данных патента, но параметр я оставил настраиваемым, чтобы его можно было откалибровать на конкретном оборудовании.
В режиме 600 DPI критически важно направление печати. Сдвиг задерживает выстрелы одного ряда относительно другого с расчётом на определённое направление движения. Если картридж движется в обратную сторону, ряды не сойдутся ни при каком значении сдвига.
Хост
На производстве у меня два основных сценария интеграции:
машина для оклейки — печать даты и партии (этикетировщик);
конвейерная линия — печать кодов Честного знака.
Для первого сценария хост у меня уже был: веб‑панель на Python Flask, которая управляет ЧПУ и системой компьютерного зрения. Сервером служит Orange Pi, объединяющий станок, камеру, а теперь и маркиратор. В панель я добавил блок настроек маркиратора:
ввод даты и партии;
размер текста;
положение на холсте и предпросмотр того, что уйдёт в буфер;
сверка буфера — проверка, что сейчас лежит в памяти устройства.
Текст на холсте рисуется библиотекой Pillow. Для маркиратора я добавил отдельное подключение по UART и реализовал команды по своей документации.

Для второго сценария я решил сделать полноценное универсальное приложение для ПК. Выбирал между Qt и Tauri (Rust) и остановился на Qt: это самый популярный фреймворк для подобных приложений, и мне хотелось получить опыт работы с ним.
Главная задача этого хоста — потоковая отправка кодов Честного знака на печать. Приложение читает CSV‑файлы с кодами, выгруженные из Честного знака. Если код не удалось прочитать или маркиратор не смог его сгенерировать, код отбраковывается, хост уведомляет об этом и переходит к следующему. В будущем я подключу к системе сканер кодов с отбраковщиком, чтобы снимать с линии продукцию с нечитаемыми кодами. У нашей компании уже есть такой опыт — мы собирали на аутсорс линию со сканером и отбраковкой, так что проблем здесь не ожидаю.


Итоги


Сейчас маркиратор уже работает на машине для оклейки и почти установлен на линию, где будет печатать дату, партию и коды Честного знака одновременно. Устройство будет проходить испытания в реальных производственных условиях.
Собственный маркиратор обходится компании дешевле китайских аналогов. Кроме того, у него есть потенциал как полностью русифицированного продукта с полной поддержкой под российский рынок. А главное — теперь мы можем делать с картриджем что угодно, и в схемотехнике ещё много возможностей для улучшений, которые сделают устройство дешевле и технологичнее в производстве.
Этот проект дал мне очень много опыта и знаний, которые точно пригодятся. Начинать было тяжело, но сейчас, оглядываясь назад, я вижу проделанный путь и горжусь им. Спасибо за внимание!
Комментарии (3)

Overphase
10.11.2026 08:42Для полного понимания не хватает, как физически расположены сопла, и как отображается матрица адресов и примитивов на их расположение. И само перемещение головки делается после того, как будут перебраны все 22 адреса?
Ghost_nsk
Посмотрите на другие микросхемы LED драйверов, например TLC5947 (24 канала + PWM), TLC59711 (12 каналов - 3 корпуса и как раз ваши 36 линий). Им не нужна будет обвязка в виде сдвиговых регистров и счетчиков, они управляются по SPI и каскадируются. Из минусов - корпуса с мелким шагом и охлаждением на брюхе, tssop. Но при сборке на заказ возможно даже лучше, меньше сверлить.
И это только то что вспомнил, оно с 12bit PWM, для вас излишне, и данных много гонять. Если полистать каталоги, то вроде было что то и проще.
ms5g Автор
Спасибо за наводку! К сожалению, для HP45 эти микросхемы не подойдут, TLC5947 и TLC59711 драйверы-стоки. У HP45 нагреватель уже коммутируется на землю транзистором внутри картриджа, поэтому здесь нужен драйвер-исток, которым как раз является TLC59213.