[Перевод] Взлом цветного картриджа HP: превращаем его в ручной принтер
Ещё с юности, когда у нас был старый DeskJet, меня интересовали картриджи струйных принтеров. Эти картриджи казались очень интересными и как только в них заканчивались чернила, я сразу забирал их себе. В то время я не мог сделать с ними ничего, кроме как разобрать и пачкать руки… Хоть я и знал, что там внутри есть какая-то сложная электроника, но при касании контактов батарейкой не происходило ничего интересного, а моих знаний по электронике на большее не хватало.
Чуть позже, когда я стал студентом, мне удалось раздобыть старый струйный принтер. В то время сам я пользовался лазерным принтером, поэтому он мне был не очень интересен, зато было любопытно исследовать картриджи и попытаться выполнить их реверс-инжиниринг. Я в самом деле написал статью об управлении этими картриджами, и хотя они работали достаточно хорошо, были и недостатки: мне так и не удалось выяснить точный порядок сопел, картридж был только монохромным (печатал маджентой), к тому же довольно старым, а потому разрешение оказалось довольно низким.
Недавно моя девушка занялась рисованием, поэтому это стало хорошим оправданием для возврата к струйным картриджам в надежде, что и мне удастся нарисовать что-то на холсте. На этот раз мне повезло: удалось найти способ привязки всех сопел к правильным сигналам. Кроме того, сегодня картриджи принтеров управляют бОльшим количеством сопел используя меньшее количество сигналов, что упрощает управление картриджем и увеличивает поверхность, которую можно покрыть за один проход.
Мне наконец-то удалось получить управление трёхцветным картриджем и печатать в полном цвете!
Если вы хотите пройти со мной путь от кучи принтеров до полного контроля над картриджем принтера, то я делал доклад об этом на Hackaday Supercon 2018. Видеозапись выступления добавлена ниже. Если вам интересны подробности реверс-инжиниринга, то посмотрите его. В статье я расскажу о технических деталях созданной мной электроники, а также конкретные подробности управления картриджем, чтобы вы сами смогли нарисовать Nyancat при помощи ESP32 или другого микроконтроллера.
Если вы не смотрели видео, то вот его содержание вкратце: я разобрал цветной картридж для принтера HP1112 (в Китае это картридж HP 803, но артикул зависит от региона), сделал снимки кристалла и пытался разобраться, как он работает. Когда мне не удалось выяснить многого, я начал считывать сигналы, передаваемые между принтером и картриджем, разобрался, какие сигналы нужно отправлять, чтобы картридж подчинялся моим приказам, а затем распечатал Nyancat и другие забавные штуки.
Часть исследования, посвящённая таймингам сигналов, в основном была процессом проб и ошибок. Я могу только догадываться, какая связь существует между сигналами, поэтому было довольно сложно разобраться в порядке между фронтами и в том, какие сигналы можно отложить, а какие нужно передавать вовремя. Для получения этой информации я изучил кремний картриджа. Оказалось, что мне и в самом деле удалось получить её, засунув картридж под микроскоп, но совсем не так, как я ожидал.
До выступления на Supercon я в изучал цветные картриджи, потому что они казались мне самыми интересными. После возвращения с Supercon мне захотелось выполнить обратную разработку и чёрного картриджа: его печатающая головка крупнее, чем у цветного картриджа, поэтому за раз я мог бы печатать больше. Вероятно, добавить поддержку и этого картриджа будет не так сложно: расположение контактов кажется таким же, и я знал, что протокол, скорее всего, будет похожим, потому что уже пробовал подключать чёрный картридж к своему «железу». Даже несмотря на то, что ПО передавало цветные изображения, ему всё равно удавалось что-то печатать.
Вот что я сделал с цветным картриджем: засунул его под микроскоп, убрал силиконовое покрытие с контактов, подготовился к соединению нескольких снимков в одно крупное изображение. Однако чёрный картридж отличался от цветного тем, что на его металлической пластине с соплами было больше надписей: под силиконовым покрытием скрывались названия сигналов для всех контактов!
(Кстати, если вы хотите посмотреть полные снимки с микроскопа во всём 40-мегапиксельном величии, то вот shield and кремний цветного картриджа! Полюбуйтесь на сложность сопел и снимка кристалла чёрного картриджа!)
Хотя может показаться, что это не так уж и много, в море немаркированных печатных плат, чипов без справочных материалов и артикулов, которые никуда не ведут, названия нескольких сигналов — это настоящая находка. По наитию я вбил отдельные названия сигналов с названием «Hewlett Packard» в Google Patents и обнаружил конкретный патент (и другой, более старый, на который ссылается первый) с чётким описанием технологий и сигналов, используемых в картриджах. Это бы сэкономило мне так много времени, когда я боролся с таймингами картриджа… ну да ладно. Могу искренне сказать, что эту подсказку найти было очень сложно: сигналы были не только покрыты силиконовой плёнкой, но и оказались крошечными: буквы имеют размер всего 30 микрометров, а это меньше, чем толщина человеческого волоса.
Патент описывает внутреннюю работу картриджа и его стоит прочитать (если вам удастся разобраться в использованном там юридическом жаргоне) просто чтобы понимать, насколько странную логику иногда применяет HP для управления всеми соплами. Сам по себе патент полезен, но его недостаточно для управления картриджем; по крайней мере, основная часть предпринятых мной трудов по реверс-инжинирингу всё равно была бы необходима, даже если бы у меня был этот патент.
Здесь и ниже я буду использовать названия сигналов и контактов, применяемые в патенте. Учтите, что в коде всё равно могут встречаться мои собственные названия сигналов; таблицу переводов я включу вместе с документацией.
Итак, вот как выглядят исследуемые картриджи. При поверхностном взгляде это довольно простые устройства: внутри они почти полностью состоят из пропитанной чернилами губки. В случае с картриджем, поставляемым в комплекте с принтером, чернил очень мало: губками занята всего половина места в картридже, а сами губки тоже наполовину пусты:
На боку есть 16 контактов, идущих снизу, где находится печатающая головка. Как можно было увидеть на снимках с микроскопа, в печатающей головке чёрного картриджа примерно 336 сопел, а в цветном картридже — 612 сопла. На печатающей головке сопла расположены вертикальными рядами, и каждым соплом можно управлять электроникой, чтобы оно выстреливало крошечной капелькой чернил вниз, в сторону вставленной в принтер бумаги. Перемещая головку по вертикали, принтер может печатать «полосу» или любое другое изображение; эта полоса в случае чёрного картриджа имеет длину примерно 15 мм, а для цветного картриджа — 8 мм.
Очевидно, что соплами можно управлять с помощью контактов. Согласно крошечным надписям на печатающей головке, контакты содержат следующие сигналы:
Так как контактов всего 16, должна существовать какая-то схема мультиплексирования для управления всеми соплами. В патенте объясняется, как это работает: управление соплами разделено на 14 отдельных групп. Эти группы срабатывают последовательно: сначала получает свои данные и срабатывает группа 1, затем группа 2, и так далее. Каждая группа управляет максимум 24 соплами, а данные для них передаются по трём шинам передачи данных. В случае цветного картриджа данные в трёх шинах соответствуют цветам: D1 — это данные жёлтого цвета, D2 — данные мадженты, а D3 управляет соплами, печатающими цианом.
В патенте работа подробно описывается на примере одной шины данных. На этом рисунке из патента показаны используемые сигналы:
В шине данных содержится восемь байтов, 0–7. Чётные байты управляются задним фронтом DCLK, нечётные байты — задними фронтами S1-S4. Сопла, данные которых управляются первыми четырьмя байтами, можно включить подачей питания по шине питания F3; сопла, связанные с последними четырьмя битами, включаются шиной F5.
Я понятия не имею, почему HP решила использовать для управления данными в соплах такой сложной схемой. Можно сказать, что здесь бы вполне нормально сработало нечто очевидное, вроде регистра сдвига. Я понимаю, что HP использует свои патенты как оружие против компаний, занимающихся перезаправкой картриджей; возможно, кто-то уже запатентовал более простое решение, и им пришлось придумать это более сложное решение, чтобы быть уникальными.
На этом графике, сделанном мной на логическом анализаторе, несложно найти сигналы, описанные в патенте:
Кроме управления соплами, картриджу также требуется сигнал (csync) для перехода к следующей группе сопел или для сброса и возврата к первой группе. Его можно увидеть на изображении с логического анализатора: на нём показаны предпоследняя и последняя группы из 14, а сигнал csync имеет в последней группе узнаваемую форму; он выполняет «сброс» картриджа, чтобы следующей получала данные первая группа. Этот сигнал также можно использовать для обхода групп сопел в обратном порядке; это полезно, когда печатающая головка ходит и слева направо, и справа налево. Хотя во втором патенте описывается, как это работает, я решил просто закодировать переход к следующей группе и выполнять сброс сигналов, показанных на моих изображениях линией csync.
Заметьте, что всё это происходит с довольно большой скоростью; задержка между двумя передними фронтами сигнала DCLK примерно равна 0,4 мкс, а расстояние между группами примерно равно 4 мкс.
Теперь мы знаем, что каждый бит в этих трёх шинах данных из 14 байт содержит команду срабатывания для одного сопла. Если бит равен 0, то соответствующее ему сопло срабатывает; если равен 1, то сопло не срабатывает. Чего мы не знаем, так это соответствия между битами и соплами. Если вы смотрели презентацию, то знаете, как мне удалось в этом разобраться: я распечатал на работающем принтере известный мне паттерн, перехватывая сигналы с помощью логического анализатора, а затем разобрался, каким должен быть порядок сигналов, чтобы декодировать сигналы обратно исходное изображение
.
К сожалению, соответствие битов соплам кажется довольно постоянным, но не полностью логичным. Похоже, что в основном это вызвано необходимостью физического отдаления на достаточное расстояние одновременно срабатывающих сопел (чтобы избежать перегрева или возникновения локального вакуума в ёмкости с чернилами). Кроме того, я также выяснил, что простота маршрутизации сигналов в картридже может сделать сопоставление битов и сопел довольно запутанным. В своей прошивке я просто реализовал это сопоставление как набор таблиц поиска.
Теперь, когда мы знаем, как работают сигналы, мы можем управлять картриджем принтера с помощью простого микроконтроллера, верно? Ну, не сразу. В картридже принтера не используется простая логика на 5 В или 3,3 В. Шины данных управляются шинами на 16 В или 9 В. Шины питания тоже управляются 16 В и на самом деле в зависимости от количества срабатывающих сопел их можно подтянуть до тока источника питания. Нам нужно выполнить преобразование уровней.
В качестве преобразователя уровней я выбрал MC14504. Это старый однонаправленный шестнадцатеричный чип преобразования уровней, который может повышать напряжение до 18 В. Хоть этот чип и работает, оглядываясь назад, могу сказать, что это был не лучший выбор: он может подавать на выход всего несколько мА и имеет довольно большую задержку распространения. Думаю, он обеспечивает задержку некоторых выходных сигналов в зависимости от картриджа и подаваемой на выводы чипа нагрузки. У меня есть по крайней мере один картридж, которому требуется для работы сигналов небольшая регулировка таймингов, и я думаю, что причина в этом. К сожалению, готовые преобразователи уровней на 16 В сегодня не так доступны, поэтому я не могу заменить его чем-то получше. Впрочем, этого классического чипа с небольшой регулировкой оказывается вполне достаточно.
С шинами питания всё немного сложнее. Кроме того, что эти контакты забирают большую долю тока, также они непосредственно подключены к резисторам включенных сопел: если по каким-то причинам питание будет подаваться слишком долго, то эти крошечные резисторы перегорят и сопло полностью выйдет из строя. К тому же, этого «слишком долго» достичь довольно просто: достаточно включить сопла всего на несколько микросекунд, а если подать питание всего на миллисекунду, то они просто испарятся, полностью сломав сопло. Чтобы этого не происходило из-за программного бага или плохого соединения, я добавил аппаратной логики, обеспечивающей ограничение импульса небольшой величиной, кратной 10 мкс.
В первом прототипе я оставил несколько преобразователей уровней и не знал, как проявит себя ПО, поэтому решил проблему настоящим однотактным мультивибратором. В этой схеме два использованы мультивибратора в 74HC123, генерирующие импульсы, ширина которых задаётся сочетанием R/C, подключённого к контакту RCExt. Получившийся импульс генерируется только при возрастающем входном сигнале, поэтому постоянно высокий сигнал не приведёт ни к чему, кроме точно заданного, но паразитного импульса на выходе. После этого канал MC14504 используется как преобразователь уровней для поднятия напряжения до +16 В, а P-канальный МОП-транзистор обеспечивает необходимый ток.
На второй печатной плате я понял, что если изменю логику контактов питания так, чтобы они не использовали два канала схемы сдвига уровня, то достаточно будет всего двух чипов MC14504. Теперь у меня есть достаточно хороший программный контроль над шириной импульса, но мне всё равно хочется иметь защиту от постоянно высокого сигнала входного контакта. Вот схема, к которой я пришёл. Она работает так: в нормальном состоянии при низком сигнале PWRB_IN конденсатор C28 пуст, потому что любое напряжение в нём медленно стекает по R20 и R21: затвор транзистора Q4 имеет высокий уровень, а PWRB_OUT отсоединён от шины питания 16 В. Как только в PWRB_IN появляется высокий сигнал, Q6 заземляет один из концов C28; так как напряжение по нему равно 0 В, изначально это также подтягивает вниз другую его сторону, которая соединена с затвором Q4. Подтягивание затвора Q4 вниз делает его проводимым, и это позволяет току течь от +16 В к PWRB_OUT. В обычном состоянии PWRB_IN достаточно быстро снова переходит в состояние с низким уровнем, закрывая затвор Q4 и прерывая ток. Однако пока PWRB_IN имеет состояние low, C28 медленно заряжается: одна его сторона заземлена Q6, а другая подключена к 16 В через R21 и R31. Когда конденсатор достаточно зарядится, Q4 «увидит» высокий уровень на своём затворе и перекроет ток в PWRB_OUT, даже если PWRB_IN по-прежнему находится в состоянии с высоким сигналом. Этот механизм гарантирует, что PWRB_OUT будет подавать питание только в ограниченный промежуток времени.
В схеме также имеется небольшой резистор, соединённый последовательно с шиной питания 16 В (R31), а также небольшой конденсатор, подключенный параллельно к выходному сигналу (C15). Они нужны для «снятия напряжения» сигнала питания: без них резкое включение и отключение Q4 будет индуцировать кучу электромагнитных помех, искажающих сигналы, передаваемые в картридж.
Кроме этой логики больше особо ничего не требуется. Очевидно, что необходимы преобразователи уровней на +9 В и +16 В. Источник питания +9 В должен быть довольно скромным: я не замечал, чтобы эти шины в целом использовали больше нескольких мА. Так как он питает резисторы сопел, источник 16 В должен чуть более сильным: я сделал так, чтобы мой мог обеспечивать непрерывно не менее 400 мА, а также добавил довольно много развязывающей ёмкости.
Наконец, самая важная нагрузка по обработке изображений и генерации сигналов ложится на плечи микроконтроллера. Для этой цели я выбрал ESP32, в основном потому, что взял несколько штук с работы, но ещё и потому, что в нём есть довольно мощный контроллер I2S, в котором используется очень удобный параллельный режим: по сути, мы можем просто задать тактовую частоту, указать контроллеру I2S область памяти, и он будет параллельно выводить эти байты. Благодаря этому он идеально подходит для генерации необходимых сигналов управления; то, что у него есть два мощных ядра на 240 МГц, также помогает в обработке изображений.
Разумеется, несколько преобразователей и МОП-транзисторов сами по себе не могут стать работающим контроллером картриджа принтера. Поэтому я создал отдельное устройство, задуманное как платформа для экспериментов с картриджем и его возможностями. Оно имеет модуль ESP32, логику, необходимую для управления картриджем, а также несколько источников питания для работы от литий-ионной ячейки. Также оно оснащено несколькими датчиками, призванными компенсировать неидеальные движения рук человека, а также кнопками и дисплеем, обеспечивающим обратную связь о печатаемых изображениях. Давайте разберём компоненты, возможно, для кого-то это станет источником вдохновения для хакинга картриджей:
Давайте начнём с источника питания. Питание поступает от литий-ионной ячейки и преобразуется в 3,3 В, 16 В и 9 В. Напряжение 3,3 В необходимо для датчиков и ESP32; оно генерируется с помощью простого LDO-регулятора HT7833. Напряжения 9 В и 16 В генерируются двумя повышающими преобразователями, созданными на основе чипа повышающего преобразователя XR2203. Учтите, что источник питания 16 В должен трудиться намного усерднее, чем источник питания 9 В; картридж потребляет от 9 В всего несколько миллиампер. Два повышающих преобразователя созданы на одном чипе просто потому, что мне достаточно было купить для обоих один тип компонентов.
Так как всё устройство питается от литий-ионной ячейки, нам нужно как-то заряжать и её. У меня осталось немного места, поэтому я добавил зарядное устройство литий-ионных аккумуляторов на основе TP4056, чтобы можно было подзаряжать аккумулятор от любого USB-источника питания.
Интеллектуальность устройства обеспечивается модулем ESP-Wrover32. Я использовал вариант с 8 МиБ флеш-памяти и 8 МиБ ОЗУ SPI; вполне достаточно для выполнения сложной обработки изображений. Ещё у модуля есть 5-контактный разъём, позволяющий программировать и отлаживать прошивку, а также две кнопки, которые можно использовать для выбора опций и запуска отрисовки при работающей прошивке.
Выбранные опции отображаются на небольшом цветном ЖК-экране размером 160×80. Экран имеет соединение SPI и может напрямую управляться одним из периферийных SPI-разъёмов, имеющихся в ESP32.
Это интерфейс картриджа. Как сказано выше, он не особо сложен. Уровень всех сигналов преобразуется парой MC14504, один для сигналов 9 В и один для сигналов 16 В. Также на схеме видна схема сдвига уровня/защиты, управляющая двойными шинами питания.
Вот три использованных мной типа датчиков. Все они подключены с помощью одной шины I2C, то есть в ESP32 они занимают всего два GPIO. Это блок инерциальных датчиков MPU9250 (акселерометр, гироскоп и цифровой компас) для измерения движения, три лазерных датчика расстояния VL53L0X (показан только один), направленные вверх, влево и вправо. Идея заключается в том, что комбинируя эту информацию, теоретически можно определить абсолютное положение картриджа. Это полезно, например, при рисовании больших изображений свободным движением руки. Последний — это цветовой датчик TCS3472. Цветовой датчик расположен рядом с белым светодиодом; его можно использовать для «копирования» цвета с объекта или для компенсации цвета носителя, на котором мы печатаем.
Так как мне нужны были дополнительные GPIO, я подключил к шине расширитель GPIO. Он контролирует шины сброса для трёх датчиков расстояния, шину сброса для ЖК-экрана, включение повышающего преобразователя и два МОП-транзистора (не показаны), которые управляют белым светодиодом, используемым для освещения цели для цветового датчика, и подсветку ЖК-экрана. Датчикам расстояния нужна отдельная шина сброса, потому что они включатся по тому же I2C-адресу. Однако у них есть команда, изменяющая I2C-адрес после включения. Включая и перемещая их один за другим по разным I2C-адресам, я могу контролировать все три по одной I2C-шине.
Вот печатная плата, которую я спроектировал на основе схемы. Она имеет странную форму, потому что должна быть разделена на четыре отдельные платы и «окружить» картридж принтера. Они соединены электрически и физически; преимущество этого в том, что производители печатных плат не считают такую схему четырьмя отдельными платами и платить нужно только за одну.
Ещё одно преимущество в том, что я могу собирать плату как один элемент, а затем протестировать её, когда все компоненты находятся на одной плоскости. Это позволяет мне не заниматься тщательной балансировкой собранного устройства при отладке. Небольшое примечание: датчики VL53L0X используют инфракрасный лазерный луч; похоже, он достаточно силён, чтобы пробить фильтр защиты от ИК-излучения в моей «зеркалке» и проявляется в кадре как небольшие фиолетовые пятна света.
И вот каким будет конечный результат после сборки. Заметьте, что при разделении плат соединения между ними оказались разорванными. У плат есть небольшие площадки для пайки, к которым можно припаять небольшой кусок провода и загнуть его. Очевидно, что для производственного уровня нужно будет использовать технологии наподобие FPC PCB или flex-rigid PCB, но для дешёвого прототипа это вполне сойдёт.
Если вы хотите использовать этот прототип для справки или поэкспериментировать с ним, то можете скачать файлы проекта KiCad (там же лежат схема в pdf и gerber) и собрать его самостоятельно, или использовать его подсистемы.
Так как это прототип, ПО довольно… неоднородно. Я дам ссылку на репозиторий, в котором его разрабатывал, но учтите, что это слепок почти всего цикла разработки, поэтому в нём содержится всё по порядку, от записей сигналов логическим анализатором до распечатанных Nyancat и Моны Лизы. К сожалению, поэтому код представляет собой почти недокументированный хаос с наполовину завершёнными путями и остатками старого кода. Если вы всё ещё хотите изучить его, то можете склонировать в git этот этот URL.
Однако если вас больше интересует ПО, способное с лёгкостью управлять картриджем принтера с помощью ESP32 (и содержащее полезные процедуры для управления им через другой микроконтроллер), то продолжайте чтение.
Чтобы упростить для других умельцев использование принтерных картриджей в их собственных проектах, я также создал минимальную работающую версию драйвера. В нём отсутствует поддержка всей периферии и хаки из кода прототипа, но архитектура подчищена и поэтому может стать прочным фундаментом для дальнейшего развития. В драйвере есть простой пример программы, печатающей при нажатии кнопки «HELLO!» цветным или чёрным картриджем.
Я не создал для него специализированного оборудования, но по сути вы можете заново использовать «железо» из предыдущего раздела: просто возьмите источник питания, ESP32
и преобразователи уровней и примените их в собственной схеме. Также можно полностью использовать описанный в предыдущем разделе прототип: достаточно просто обеспечить постоянный высокий сигнал BOOST_EN, чтобы повышающие преобразователи 9 В/16 В были всегда включены. (Таким образом я отлаживал код.)
Сам код можно найти на Github, он структурирован стандартно для проекта ESP-IDF. Основной код драйвера находится в components/printcart; код, считывающий кнопку и решающий, когда включать сопло, а также код инициализации содержится в main/main.c. В примере данные сопел считываются из встроенного rgb-изображения.
Система имеет следующую архитектуру: printcart_i2s.c содержит простой драйвер для параллельного режима периферийных разъёмов I2S контроллера ESP32. Он выделяет два буфера и передаёт из буферов 16-битные слова с частотой 3,3 МГц контактам GPIO (максимум 16 контактам). (Здесь эти контакты GPIO соединены с преобразователями уровней, управляющими картриджем.) При каждом опустошении буфера драйвер выполняет обработчик события для заполнения буфера.
Обработчик события находится в printcart_buffer_filler.c. Он получает данные сопел из очереди данных сопел и передаёт их в функцию в printcart_genwaveform.c, которая по шаблону преобразует эти данные сопел в сигналы. Шаблон зависит от типа картриджа (цветной или чёрный), и его можно изменять, загружая в браузер tools/waveform_editor.html.
С другой стороны очереди данных сопел находится процедура цикла в main.c. Она ожидает нажатия кнопки, и при нажатии генерирует данные сопел парсингом простого файла изображения, преобразованного в сырые данные rgb и встроенного в прошитый двоичный файл, сканируя данные слева направо. Благодаря этому можно нажать кнопку, проводя картриджем над бумагой, и распечатать содержимое изображения в виде полосы чернил.
Конечный результат выглядит примерно так:
Тут сильно заметно, что чёрный картридж печатает примерно в два раза больше по высоте, чем цветной (0,7 см и 1,5 см), поэтому если вам не нужен цвет и требуется хорошая видимость, то лучше выбирать чёрный картридж. Также стоит заметить, что в main.c есть define, переключающийся между двумя картриджами; код может работать с обоими. Не совсем понятно, почему на чёрном изображении есть смазанные линии: возможно, в моём сигнале есть ошибка, а может быть, картридж немного устал от тестирования. Как бы то ни было, распечатанные данные красивы и хорошо узнаваемы.
Реверс-инжиниринг этих картриджей принтера был долгим приключением, но в конце концов моя работа оказалась плодотворной; хоть и осталось несколько загадок (например: что делает контакт ID?), я считаю, что хорошо разобрался с загадками сигналов, используемых в картридже. Надеюсь, что опубликовав код и схемы этого проекта, я добавлю в инструментарий умельцев, хакеров и творцов использование принтерных картриджей. Не могу дождаться, когда увижу интересные примеры использования, которые придумает сообщество. Если вам удастся сделать что-то интересное с моей работой, то обязательно пришлите мне сообщение.
Что же касается моего намерения творить искусство… эммм… вот это можно так назвать?