Работаем 27 апреля. Отдыхаем с 28 апреля по 1 мая.
Максим Данилин, 31.08.2023

showcase

Как сделать камеру мгновенной печати на Raspberry Pi

Привет, дорогие друзья!

С тех пор как появились Arduino и Raspberry Pi, самодельщики и изобретатели со всего мира успели наклепать целую гору всевозможных поделок. Подобные платы для разработки позволяют быстро создавать действительно функциональные и полезные в быту электронные устройства. Однако зачастую самодельные девайсы на Arduino получаются не столько полезные, сколько уникальные и забавные.

Сегодня мы рассмотрим как раз одно из таких устройств. Попробуем собрать моментальную фотокамеру наподобие Polaroid или Instax, только с чёрно-белой термопечатью снимков. Идея совсем не нова, но мы всё же решили поделиться своим DIY-опытом.

Что хотим сделать?

Суть моментальной камеры в том, что она может сразу же выдать полученный фотоснимок на бумаге или другом физическом носителе.

Как же быстро получить готовую фотографию? На ум сразу приходят некогда популярные камеры Polaroid.

polaroid

В таких камерах для мгновенного получения цветного изображения используются фотоматериалы, автоматически проявляющие снимок благодаря встроенным химическим реагентам. Эта технология называется «одноступенный фотопроцесс».

cartridge

По сути Polaroid — это обычный фотоаппарат, а технология моментальной проявки снимка кроется не в самой камере, а в картридже со специальной бумагой. Сделать даже обычный фотоаппарат в домашних условиях крайне сложно, не говоря уже о химических реактивах, поэтому наш фотоаппарат будет цифровым.

Полученное с цифровой камеры изображение можно легко распечатать, используя обычный домашний принтер. Но классический принтер — громоздкий, а наша мгновенная камера должна быть достаточно портативной, чтобы мы могли взять её на улицу и наделать снимков на открытом воздухе. Для этого нам понадобится портативный фотопринтер.

Разберёмся, как работают портативные фотопринтеры. Они относятся к термопринтерам, где используются не привычные краски или тонер (как в струйных или лазерных принтерах), а метод термопечати.

thermoprint

Термобумага, применяемая для термопечати, имеет особое покрытие, которое меняет цвет при нагреве. Нагревательный элемент в принтере — термоголовка — точечно нагревает выбранные участки на бумаге и тем самым формирует картинку. Термопечать распространена буквально повсюду: именно этой технологией печатаются кассовые чеки, ценники и этикетки по всему миру.

Термопринтеры бывают не только чёрно-белые, но и цветные. Например, цветной мобильный термопринтер Xiaomi Mijia AR ZINK. В нём, как и в случае с моментальными камерами Polaroid, секрет цветной печати кроется в фотоматериале.

zink

Цветная термобумага содержит в себе сразу три особых слоя, каждый из которых отвечает за базовый цвет (голубой, пурпурный, жёлтый). Каждый слой имеет свою температуру нагрева, а после нагрева кристаллизуется. Генерация цвета достигается с помощью термических импульсов разной продолжительности. Комбинация импульсов от термоголовки до бумаги по очереди кристаллизует слои и позволяет получить нужный оттенок.

Подобный принтер можно легко подключить к смартфону и печатать фотографии, полученные с мобильной камеры. Вы, наверное, скажете: вот вам и готовая моментальная фотокамера! Но ведь мы хотим сделать что-нибудь своими руками. Кроме этого, упаковки с цветной термобумагой, как и самопроявляющиеся картриджи — дорогое удовольствие, рассчитанное на небольшое количество снимков.

Из-за сложности технологий и дороговизны мы откажемся от цветной моментальной фотографии, как цифровой, так и плёночной. А вот использовать чёрно-белую фотографию и термопечать нам вполне по силам и по карману.

Устройства со съёмкой и мгновенной печатью чёрно-белых фотографий на термобумаге до недавнего времени даже продавались на рынке. Например, для популярной 8-битной портативной приставки Game Boy от Nintendo существовали модуль камеры Game Boy Camera и термопринтер Game Boy Printer.

gameboy

Мы постараемся сделать нечто похожее из доступных нам электронных модулей и компонентов.

Термопринтер

Первым делом обзаведёмся термопринтером. Для этого эксперимента мы раздобыли на AliExpress пару самых дешёвых китайских термопринтеров под ленту шириной 58 мм.

701a_printer

Данный принтер — встраиваемый, у него есть крепёж для установки в лицевые панели других устройств. Для управления принтером используется интерфейс UART. А модель самого принтера — JP-QR701-TTL, судя по наклейке на задней крышке. Эти принтеры бывают в двух вариантах: с интерфейсом TTL и RS232, нам досталась версия TTL.

701a_printer_back

На лицевой стороне принтера расположены светодиод и кнопка. Тут же находится отсек для хранения катушки термобумаги, при открытии которого мы можем увидеть и саму печатающую термоголовку. На крышке находится прокатывающий ролик, который является частью термоголовки.

701a_printer_front

Модель этого термопринтера оказалась очень популярной среди ардуинщиков. Всё благодаря компании Adafruit, которая в своё время активно продавала эти китайские термопринтеры в разных комплектациях, а также написала ряд библиотек для работы с принтером. Заглянув в их Wiki, можно и сейчас найти множество полезной информации:

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

701a_printer_disassemble

Видим плату управления принтером и шлейф, идущий к печатающей термоголовке, а также небольшую выносную плату для светодиода и кнопки. На управляющей плате видно место под чип RS232, по нему можно определить версию принтера. У нас его нет, значит это однозначно версия TTL. Вытаскиваем всё наружу и избавляемся от корпуса.

701a_printer_guts

Разобрав принтер, мы видим, что его миниатюрные компоненты идеально подойдут для маленькой мгновенной камеры. В разобранном виде этот термопринтер больше похож на модуль для Arduino, коим он и является в каком-то смысле.

701a_printer_model

На термопечатающей головке присутствует наклейка с незамысловатой маркировкой модели TP-701. Порывшись в интернете, можно узнать, что термоголовка TP-701 является китайской репликой оригинальной термоголовки FTP-628MCL701 от фирмы Fujitsu. В теории эти головки взаимозаменяемы.

Спецификация на оригинальную термоголовку — FTP-628MCL701. В оригинальной документации мы видим, что термоголовки этой модели являются низковольтными, то есть прежде всего предназначены для портативных печатающих систем. Этим и объясняются её небольшие размеры и требования к питанию.

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

Напряжение питания нагревательного элемента от 4,2 до 8,5 В с максимальным постоянным током 0,9 А. Напряжение питания шагового двигателя такое же, от 4 до 8,5 В, а максимальный ток при заклинивании — 1 А. Напряжение сенсорной части от 3 до 5 В. За питание датчиков отвечает сама управляющая плата. Таким образом, термоголовке для работы нужен источник питания примерно 5–9 В с минимальным током в 2 А. Напряжение 5 В привычно в Arduino-проектах, а вот добыть более 2 А тока в портативном устройстве уже может быть проблематично.

Попробуем включить термоголовку. Подадим на управляющую плату напряжение с импульсного блока питания 5 В / 5 А. Просто подключаем положительный провод и землю с управляющей платы в блок питания. В качестве термобумаги воспользуемся рулоном самой обычной кассовой чековой ленты шириной 57 мм.

test_powerup

При подаче питания светодиод на управляющей плате и вынесенный светодиод начнут одинаково мигать. Можем сделать вывод, что второй светодиод дублирующий. Количество миганий светодиода сообщает о текущем состоянии принтера. Согласно документации состояния могут быть следующие:

  • Одно мигание — принтер готов к работе, всё в норме.
  • Два мигания — ошибка, печатающий механизм отсоединён или температура термоголовки слишком низкая.
  • Три мигания — ошибка, нет бумаги.
  • Четыре мигания — ошибка, неисправен резак принтера. (Этой ошибки у нас быть не может, так как мы используем модель термоголовки без резака).
  • Пять миганий — ошибка, перегрев термоголовки.
  • Шесть миганий — ошибка, плохо прижат протягивающий ролик. (Этой ошибки у нас быть не может, так как в нашей модели отсутствует датчик прижатия ролика).
  • Десять миганий — ошибка, термоголовка не опознана управляющей платой.

При нажатии на вынесенную кнопку происходит так называемый «Feed», то есть подача печатающего материала.

test_button_and_led

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

В распечатанных настройках можно найти информацию о версии прошивки контроллера на управляющей плате, о встроенных в контроллер шрифтах, о температурных настройках, но нас пока интересует только скорость последовательного соединения. Наш термопринтер общается на скорости 9600 бит/с. Версия прошивки — 2.64.

test_baudrate

Попробуем теперь подключить принтер к Arduino и распечатать какую-нибудь картинку. Для этого теста мы взяли плату Iskra Nano. Подключаем провода TX и RX интерфейса UART принтера к Iskra Nano. У нас это синий и зелёный провод. Подключим их, например, к пинам 5 и 6. В разъёме принтера есть ещё пятый провод жёлтого цвета, который обозначен DTR. О нём мы поговорим чуть позже, пока что оставим его неподключённым. Питание на плату будем подавать через USB-порт, предварительно объединив землю Iskra Nano с землёй питания термопринтера.

test_arduino

Для работы с принтером Arduino понадобится библиотека. Скачиваем и устанавливаем библиотеку Adafruit-Thermal-Printer-Library в рабочее пространство Arduino IDE.

Выберем картинку. Разрешение нашего принтера небольшое — 384 точки на линию, в термоголовке находятся 384 раздельно управляемых нагревательных элемента. По сути одна нагретая точка это и есть один пиксель изображения. Следовательно, максимальная длина горизонтального изображения равна 384 пикселям, или ширина, если изображение расположено вертикально. При плотности в 8 точек на один миллиметр максимальная длина одной стороны изображения равна 48 мм.

Для теста мы взяли чёрно-белое изображение логотипа Амперки разрешением 384×384 пикселя.

amperka-com-vert-384-bw

Для перевода изображения в массив байтов для C/C++ мы воспользовались ресурсом FileToCArray. Здесь же при желании можно инвертировать изображение.

Получившийся байт-массив поместим в одноимённый заголовочный файл amperka_logo.h. Распечатаем массив байтов, используя функцию printBitmap и пример из библиотеки Adafruit-Thermal-Printer-Library. Для связи с принтером воспользуемся не аппаратным, а программным последовательным интерфейсом SoftwareSerial. Скорость обмена данными устанавливаем исходя из настроек принтера. У нас это 9600 бит/с. По умолчанию на принтере может стоять и другая скорость, узнать её можно, распечатав тестовый листок с настройками. Исходный код программы выглядит следующим образом:

#include "Adafruit_Thermal.h"
#include "SoftwareSerial.h"

#include "amperka_logo.h"

constexpr uint8_t TX_PIN = 6;
constexpr uint8_t RX_PIN = 5;

SoftwareSerial mySerial(RX_PIN, TX_PIN);
Adafruit_Thermal printer(&mySerial);

void setup() {
  mySerial.begin(9600);
  printer.begin();
  printer.printBitmap(384, 384, amperka_logo);
  printer.feed(10);
}

void loop() { }

Загружаем программу в Iskra Nano и смотрим, что получилось.

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

Raspberry Pi и камера

Для получения фотографии мы будем использовать плату Raspberry Pi Zero и цифровую мини-камеру, сделанную специально под форм-фактор Raspberry Pi Zero.

rpi

Почему мы используем именно такие компоненты? На это есть ряд причин:

  • Камера специально для Raspberry Pi. Нам не нужно искать интеллектуальные камеры с собственным контроллером и разбираться, как они работают. Малина выполнит всю работу за нас.
  • Цена камеры. Для Raspberry Pi существует полно камер самых разных видов и функций. Как правило, чем качественнее нужен снимок, тем сама камера и её объектив больше, сложнее и дороже. Максимальный размер нашей фотографии — 384 пикселя, причём в чёрно-белом формате. Поэтому мы можем смело брать самый недорогой и миниатюрный вариант.
  • Размер Raspberry Pi. Форм фактор малины Zero идеально совпадает с шириной термопечатающей головки принтера. Как будто они были созданы друг для друга. Одинаковая ширина может служить базой для будущего корпуса мгновенной камеры.
  • Обработка фотографии. Полученный с камеры снимок — цветной, но нам нужна не просто чёрно-белая фотография, а двухцветная, двухбитная. С такой задачей обработки фотографии отлично справится Raspberry Pi.
  • Питание. Термопринтер можно запитать от 5 В, как и плату Raspberry Pi, значит в мгновенной камере мы можем использовать один источник питания.

В качестве операционной системы для Raspberry Pi используем стандартную Raspberry Pi OS. Берём microSD-карту с Raspberry Pi OS на борту.

По мере работы с проектом мы будем использовать особую версию Raspberry Pi Zero W с модулем Wi-Fi. Зачем? Подключать клавиатуру, мышь и дисплей к Raspberry Pi формата Zero не очень удобно, а так мы сможем работать с Raspberry удалённо по SSH. В конечном устройстве мгновенной камеры не нужен Wi-Fi, поэтому мы сможем просто переставить флеш-карту с нашей готовой программой в Raspberry Pi без выхода в сеть.

Не будем разбирать основы работы с Raspberry Pi, а перейдём сразу к делу. При необходимости можете ознакомиться с основами в нашей Wiki.

Делаем фотографию

Вставляем SD-карту в Raspberry Pi. Подключаем шлейф камеры, запитываем плату от USB, настраиваем Wi-Fi и подключаемся к малине по SSH.

rpi_camera_and_usb

Прежде всего обновляем все пакеты системы:

sudo apt update && sudo apt upgrade

rpi_start

Включим аппаратный интерфейс камеры. Это делается в меню команды raspi-config в разделе Interface OptionsCamera:

sudo raspi-config

rpi_config_camera

При использовании стандартной Raspberry Pi OS больше никаких настроек делать не нужно. Более того, не нужно даже ставить никаких программ — всё уже установлено по умолчанию.

Все наши фотографии будем сохранять в папке photos в домашней директории. Фотографии и видеоролики снимаются программой raspistill. Сделаем тестовую фотографию с камеры. Назовём файл изображения test_photo.jpg:

mkdir ~/photos
raspistill -n -o ~/photos/test_photo.jpg

Параметр команды -n (--nopreview) указывает, что нам не нужно окно предварительного просмотра, ведь мы работаем с малиной удаленно. Параметр -o (--output) указывает путь, куда отправится готовый снимок. У программы очень много полезных и необходимых параметров, можете изучить их все командой:

raspistill -?

Улыбаемся и машем! Щёлк.

test_photo

На стандартных настройках камеры получается фотография с соотношением сторон 4×3, разрешением 2592×1944 пикселя и плотностью 72 точки на дюйм. Вполне хорошее качество.

Обработка фото

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

Функция дизеринга доступна в популярной программе ImageMagick с интерфейсом через командную строку. Устанавливаем программу:

sudo apt install imagemagick

После установки нам доступна утилита convert, способная обрабатывать изображения, конвертировать их в разные форматы и много чего ещё.

Уменьшим изображение до формата нашего термопринтера, сделав его размером 384 пикселя в высоту и 512 в ширину. Это можно сделать параметром -resize. Повернём изображение вертикально. Переведём формат изображения в PNG, а новый файл назовём test_photo_dither.png.

Сделать дизеринг монохромного изображения можно тремя разными способами.

Первый способ — это использовать параметр -monochrome. Градиент цвета будет выглядеть примерно так:

monochrome_gradient

Команда в этом случае будет такая:

convert /home/pi/photos/test_photo.jpg -resize 512x384 -monochrome -rotate 90 /home/pi/photos/test_photo_dither.png

Получившееся изображение выглядит слишком контрастным:

test_photo_dither_monochrome

Можно использовать встроенный паттерн на «заполнение» цвета в 50%, добавив параметр -remap pattern:gray50. Градиент выглядит так:

mono_remap_gradient

Команда:

convert /home/pi/photos/test_photo.jpg -resize 512x384 -remap pattern:gray50 -rotate 90 /home/pi/photos/test_photo_dither.png

Изображение получается уже более детализированное, но слишком блёклое:

test_photo_dither_gray50

Ну и последний способ — это использовать палитру цветов. Она, судя по документации, даёт наилучший градиент:

mono_remap_grad_ctrl

Для этого сначала создаём палитру c 20%-ным заполнением цвета:

convert xc:gray20 xc:white +append /home/pi/photos/ctrl_colors.gif

А затем обрабатываем изображение по палитре:

convert /home/pi/photos/test_photo.jpg -resize 512x384 -remap /home/pi/photos/ctrl_colors.gif -normalize -rotate 90 /home/pi/photos/test_photo_dither.png

На наш взгляд, это лучшая обработка:

test_photo_dither_ctrl_colors

Подробную информацию по дизерингу вы можете найти в документации ImageMagick.

Подключаем принтер

Теперь пришло время подключить термопринтер к Raspberry Pi и проверить, заработает ли он от малины.

Так как принтер питается от сетевого блока питания 5 В / 5 А, мы можем запитать Raspberry Pi от этого же блока.

test_rpi

Распиновку вашей платы Raspberry Pi всегда можно узнать командой:

gpio readall

UART-интерфейс принтера подключаем к пинам TX и RX на Raspberry. Это пины BCM (Broadcom) 14 и BCM 15 соответственно. DTR-провод принтера пока что никуда не подключаем.

rpi_pinout_uart

Следующим шагом нужно включить аппаратный последовательный интерфейс на Raspberry Pi. Это делается в меню команды raspi-config в разделе Interface OptionsSerial Port:

sudo raspi-config

rpi_config_serial_1

Также в этом меню необходимо отключить логирование консоли через UART.

rpi_config_serial_2

Перезагружаем Raspberry Pi. Для устройства интерфейса UART, к которому мы подключились (пины 14 и 15), в Raspberry Pi OS по умолчанию есть готовый alias /dev/serial0.

ls /dev | grep serial0

Установим скорость общения через /dev/serial0, равную 9600 бит/с.

stty -F /dev/serial0 9600

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

echo -e "Hello Amperka! This is a test! \\n\\n\\n" > /dev/serial0

Работает!

test_shell_print

Печать изображения

Принтер подключён и работает, значит, теперь мы можем распечатать фотографию.

Для распечатки изображений мы воспользуемся Python-библиотекой Python-Thermal-Printer от Adafruit. То есть писать финальную программу для нашей мгновенной камеры мы будем именно на языке Python.

В библиотеке Python-Thermal-Printer распечатка изображений осуществляется методами printBitmap() и printImage(). Сперва printImage() преобразует заданное изображение в необходимый для принтера массив байтов, а затем printBitmap() отправляет принтеру эти байты.

Данная библиотека вполне себе рабочая, но имеет для нас ряд недостатков, которые нам пришлось подправить вручную.

Прежде всего мы убрали ограничение на максимальную ширину изображения. Мы будем печатать фотографию вертикально, а не горизонтально, то есть максимальная длина в нашем случае увеличится с 384 до 512 пикселей.

Вторая важная особенность — работа с буфером принтера. Принтер принимает данные по UART-соединению «порционно» и помещает их во встроенный буфер данных, а из буфера данные отправляются на печать. Принтер не сообщает об уровне заполненности буфера. Скорость передачи данных по UART выше, чем скорость печати принтера. Если отправлять данные потоком, без пауз, то буфер переполнится, что приведёт к ломаному изображению на выходе.

В оригинальной библиотеке Python-Thermal-Printer эта проблема решается вводом времени задержки — timeoutSet() и timeoutWait(). В принтер отправляется небольшая порция данных и включается таймер, за время которого принтер должен успеть распечатать эти данные. По истечению таймера в принтер отправляется следующий набор байтов. Подобрать точное время таймера крайне сложно из-за сильной зависимости скорости и качества печати от времени нагрева и остывания нагревательного элемента термоголовки.

На помощь приходит тот самый жёлтый провод с управляющей платы термопринтера, который называется DTR или Data Ready. Принтер изменяет уровень сигнала на высокий, если буфер переполнен. При низком уровне на этом пине принтер готов принимать новые данные для печати. Мы добавили в библиотеку Python-Thermal-Printer поддержку DTR-пина нашего принтера, а также удалили ненужные для нас в этом проекте методы.

Изменённая нами Python-библиотека ThermalPhotoPrinter.py, исходный код программ и все прочие материалы по этому проекту мы разместили в Github-репозитории instant_thermal_print_camera. Внимание, мы изменили библиотеку конкретно под наш проект. Если у вашего принтера другой интерфейс или нет DTR-пина, вам лучше использовать оригинальную библиотеку.

Подключаем жёлтый DTR-провод термопринтера к любому цифровому пину Raspberry Pi, например к пину BCM 18.

rpi_pinout_dtr

Напишем пробную программу, которая будет распечатывать изображение. Назовем её print_photo.py. В качестве аргумента будем передавать путь до файла.

#!/usr/bin/python

import sys
from ThermalPhotoPrinter import *

printer = ThermalPhotoPrinter("/dev/serial0", 9600, 18)

def printPhoto(image_file):
	printer.feedRows(100)
	printer.printImage(image_file)
	printer.feedRows(100)

if __name__ == "__main__":
	if len(sys.argv) > 1:
		image_file = sys.argv[1]
		print ("Image file is: ", image_file)
		printPhoto(image_file)
	else:
		print ("No image a file!")
		sys.exit(2)

В программе мы создаём новый объект для работы с принтером, указывая имя устройства /dev/serial0, скорость UART-соединения и номер DTR-пина. До и после печати изображения делаем прокрутку ленты на 100 пикселей методом feedRows().

Запустим программу и попробуем распечатать обработанное нами ранее тестовое изображение test_photo_dither.png.

python3 print_photo.py /home/pi/photos/test_photo_dither.png

rpi_print_photo

Взглянем на результат!

test_print_photo

Термостикеры

Можно печатать фотографии и на кассовой чековой ленте, но что потом делать с распечатанным снимком? Как нам получить фотографию на ровной и аккуратной бумажке в духе камер Polaroid, а не на огрызке чека?

У некоторых термопринтеров есть автоматический резак, отделяющий ленту. Другие имеют острые зазубрины на конце термоголовки для аккуратного ручного отрыва бумаги. У нашего же принтера — ни того, ни другого.

Наше решение — использовать термостикеры! Максимальный размер наших снимков — 48×64 мм, поэтому мы можем печатать их на термостикерах размером 58×80 мм. Это самый обычный типоразмер, его можно найти буквально всюду. Одной катушки хватит на 500 снимков.

dev_stickers

Термостикеры — это та же термобумага, только с клеевой подложкой.

Термостикеры бывают двух видов: ЭКО и ТОП. Термостикеры ЭКО более экономичные и не предназначены для долгого хранения информации. Нужно понимать, что термостикеры изначально применяются для маркировки различных товаров и коробок в магазинах и складах. Термостикер должен дожить до того момента, как товар поступит клиенту, дальше же его судьба не важна. Поэтому термостикеры ЭКО сильно подвержены механическому воздействию, температуре окружающей среды и ультрафиолетовому излучению. Наверняка вы хоть раз видели почерневшую или выцветшую наклейку на коробке.

Термостикеры ТОП гораздо более долговечные. Такие термостикеры предназначены для маркировки товаров с долгим сроком или в суровых условиях хранения, например для маркировки замороженных продуктов. Термостикеры ТОП будут сохранять картинку намного дольше, а стереть её пальцами с бумаги будет трудно.

Разумеется, мы решили использовать именно стикеры ТОП. Кроме того, наши стикеры сверху покрыты глянцевым слоем, что добавляет изображению яркости.

test_sticker

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

dev_paper_dimensions

У чековой ленты реальная ширина — 56–57 мм. Реальная ширина самого стикера — 57 мм, а стикерной ленты — 60 мм. В нашей термоголовке ширина паза под печатный материал ровно 58 мм. Видимо, термопринтер изначально не рассчитан на печать с использованием стикеров. Сам стикер проходит, а лента — нет. Поэтому при загрузке в принтер стикерной ленты происходит зажёвывание материала по краям паза.

dev_chewing

Зажёвывание и тот факт, что стикерная лента гораздо толще, чем чековая, приводит к тому, что маленькому шаговому мотору на термоголовке просто не хватает усилия протянуть весь материал.

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

dev_cutted_paper

Эта проблема не даст нам использовать всю катушку стикеров «как есть», но в этом есть и свой плюс. По сравнению с нашими электронными компонентами вся катушка стикеров очень большая и тяжёлая, использовать её как постоянную часть мгновенной камеры было бы неэстетично. К слову, подготовить и обрезать край стикерной ленты не занимает много времени даже для ста снимков.

Бортовое питание

Разберёмся с бортовым питанием. Нам нужен небольшой портативный источник питания 5 В с реальным током 2 А и выше. Найти модуль или плату с готовым решением довольно сложно, поэтому питание для мгновенной камеры мы будем делать из аккумуляторов Li-ion.

Возьмём четыре аккумулятора 18650 Ansmann Li-Ion / 3,6 В / 2600 мА·ч с защитой.

dev_battery

Аккумуляторы соединим попарно, чтобы суммарное напряжение было 7,2 В, а ёмкость — 5200мА·ч. Чтобы закрепить аккумуляторы и соединить их попарно, мы взяли два двойных батарейных отсека.

dev_battery_holders

Для получения стабильных 5 В с аккумуляторной сборки мы используем миниатюрный понижающий преобразователь питания для Arduino с максимальным током 3 А.

dev_dc_dc

Для включения-выключения питания возьмём микротумблер SMTS-102 на 3 А.

dev_switch

Спаяем и подключим всё вместе. Параллельно соединяем два батарейных отсека. Питание 7,2 В с батарейных отсеков через тумблер подаём на понижающий регулятор напряжения (пины VIN и GND). 5 В, полученные с регулятора (с пинов +5V и GND), подводим к шине питания 5 В на плате Raspberry Pi. На ней пара одинаковых пинов 5 В, поэтому от этой же шины берём питание термопринтера.

rpi_pinout_power

Получилась вот такая связка:

dev_portable_power

Температурные настройки

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

Изучая документацию на принтер, находим команду, которая называется «Setting Control Parameter Command». Она-то и отвечает за температурные настройки. Команда состоит из пяти байт, а её синтаксис в формате ASCII выглядит следующим образом:

ESC 7 n1 n2 n3
  • n1 — Max heating dots. Байт отвечает за количество точек, которые принтер нагревает одновременно. Принимает значения от 0 до 255. Множитель значения — (n1 + 1) * 8. Значение по умолчанию — 7. То есть, на стандартных настройках единовременно нагревается (7 + 1) * 8 = 64 точки. Стандартная настройка библиотеки Adafruit — (11 + 1) * 8 = 96 точек.
  • n2 — Heating time. Байт отвечает за время нагрева точки. Принимает значения от 3 до 255. Любое значение умножается на 10 микросекунд. Значение по умолчанию — 80. То есть, на стандартных настройках точка нагревается 80 * 10 = 800 микросекунд. Стандартная настройка библиотеки Adafruit — 120 * 10 = 1200 микросекунд.
  • n3 — Heating interval. Байт отвечает за интервалы времени между нагревами точек. Принимает значения от 0 до 255. Любое значение умножается на 10 микросекунд. Значение по умолчанию — 2. То есть, на стандартных настройках между нагревами групп точек интервал составляет 2 * 10 = 20 микросекунд. Стандартная настройка библиотеки Adafruit — 40 * 10 = 400 микросекунд.

Кроме этого документация даёт нам следующие советы:

  • Чем больше число одновременно нагреваемых точек, тем выше потребляемый ток и быстрее печать.
  • Чем больше время нагрева, тем контрастней точка и больше плотность точек на бумаге, но тем медленнее скорость печати. Если время нагрева слишком короткое, могут образовываться пустые места. Если время нагрева слишком большое, то бумага может залипнуть, «припаяться» к нагревательному элементу.
  • Чем больше интервал нагрева, тем чётче изображение, но ниже скорость печати.

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

При печати на обычной кассовой ленте отлично подходят стандартные Adafruit настройки — 11 120 40. Однако термостикеры, которые мы используем, толще из-за клеевого и глянцевого слоя. Подобрать оптимальные настройки для термостикеров оказалось очень и очень сложно. Мы распечатали около двухсот фотографий на разных температурных настройках! Даже при одинаковых настройках два разных изображения получаются разного качества. Похоже, что на качество печати сильно влияет количество чёрного цвета на изображении и связанный с этим процесс нагрева/охлаждения отельных точек термоголовки.

Очевидно, что термоголовка TP-701 не предназначена для прогрева толстых термостикеров, поэтому мы и получаем такой непредсказуемый результат — наша камера становится ЛОМО-камерой.

Мы решили сделать такие настройки: одновременный нагрев 144 точек, 1250 микросекунд на прогрев точки и интервал нагрева 400 микросекунд. Температурная команда принимает вид:

ESC 7 17 125 40

Внесём правки в наш скрипт print_photo.py, отправив новую команду принтеру сразу после его инициализации.

printer = ThermalPhotoPrinter("/dev/serial0", 9600, 18)
printer.writeBytes(27, 55, 17, 125, 40)

И попробуем распечатать стикер на новых температурных настройках с полностью автономным питанием.

Индикация и управление

Отлично, самое время добавить нашей камере элементов управления и индикации. Тут всё просто.

Во-первых, мы взяли обычный красный светодиод и резистор на 220 Ом. Пусть этот светодиод сообщает нам, что питание с DC-DC-преобразователя подано на плату Raspberry Pi.

dev_red_led

Далее нам нужна кнопка спуска затвора камеры и светодиод для индикации состояния камеры. На этот случай мы можем взять маленькую выносную плату с кнопкой и светодиодом от термопринтера.

Что делает эта плата сейчас? Сейчас зелёный светодиод на плате дублирует состояние термопринтера, а при нажатии на кнопку происходит подача материала. И то, и другое мы можем сделать программно, используя библиотеку ThermalPhotoPrinter.py. А значит, эта плата принтеру не нужна. Но мы можем её использовать в своих целях, так как у неё небольшой удобный размер и посадочные места под крепления. Ничего не выкидываем, всё идет в дело.

dev_green_led

Подключим плату со светодиодом и кнопкой к Raspberry Pi. То что на плате совсем нет резисторов — не страшно, мы можем программно использовать встроенные подтяжки в контроллере. Подключаем к Raspberry Pi и наш самодельный красный светодиод к любым пинам GND и 3,3 В.

dev_button_and_leds

Для кнопки мы использовали пин BCM 24, а для зелёного светодиода — BCM 23.

rpi_pinout_leds

Программа

Напишем итоговую программу для нашей мгновенной камеры. Назовём скрипт с программой take_and_print_photo.py. Все скрипты и исходные файлы проекта мы разместили в репозитории instant_thermal_print_camera.

Взглянем на исходный код:

#!/usr/bin/python

import subprocess
import RPi.GPIO as GPIO
from ThermalPhotoPrinter import *
from gpiozero import Button, LED

led_pin = 23
button_pin = 24

button = Button(button_pin, False)
led = LED(led_pin, False)

printer = ThermalPhotoPrinter("/dev/serial0", 9600, 18)
printer.writeBytes(27, 55, 17, 125, 40)


def on_pressed():
    printer.feedRows(10)


def on_held():
    print("Taking original photo")
    led.blink(0.05, 0.05)
    p = subprocess.Popen(
        ["raspistill -n -ex auto -br 55 -o /home/pi/photos/original_photo.jpg"], shell=True)
    p.wait()

    print("Converting image")
    led.blink(0.2, 0.2)
    p = subprocess.Popen(
        ["convert /home/pi/photos/original_photo.jpg -resize 512x384 -remap /home/pi/photos/ctrl_colors.gif -normalize -rotate 90 /home/pi/photos/photo_dither.png"], shell=True)
    p.wait()

    print("Remove original photo")
    p = subprocess.Popen(
        ["rm  /home/pi/photos/original_photo.jpg"], shell=True)
    p.wait()

    print("Printing image")
    led.blink(0.5, 0.5)
    printer.feedRows(50)
    printer.printImage("/home/pi/photos/photo_dither.png")
    printer.feedRows(50)

    led.on()


if __name__ == "__main__":
    led.on()

    button.when_pressed = on_pressed
    button.when_held = on_held

    while (1):
        try:
            pass
        except KeyboardInterrupt:
            button.close()
            led.close()
            break

Разберёмся, как работает программа.

Для обработки состояний кнопки и работы со светодиодом мы используем классы Button и LED из библиотеки gpiozero. Эта специальная библиотека для работы с периферийными устройствами, подключенными к пинам GPIO, является надстройкой над стандартной Python-библиотекой RPi.GPIO.

При использовании стандартной Raspberry Pi OS библиотеку gpiozero устанавливать не нужно, она встроена по умолчанию. Если у вас возникли проблемы, обратитесь к официальной документации на gpiozero. Библиотека описана очень хорошо.

Создаём объект для работы с кнопкой и зелёным светодиодом.

led_pin = 23
button_pin = 24

button = Button(button_pin, False)
led = LED(led_pin, False)

Инициализируем термопринтер и отправляем в него наши температурные настройки.

printer = ThermalPhotoPrinter("/dev/serial0", 9600, 18)
printer.writeBytes(27, 55, 17, 125, 40)

Мы решили реализовать следующее поведение программы. При запуске программа переходит в бесконечный цикл и крутится в нём, пока не будет завершена. Также при запуске зажжётся наш светодиод — led.on(), сообщая пользователю, что камера готова к работе.

Перед запуском цикла мы передаём указатели на две callback-функции для тактовой кнопки, которые будут вызываться при определённых событиях. При коротком нажатии на кнопку button.when_pressed мы вызовем функцию on_pressed(), а при длительном нажатии button.when_held функцию on_held().

Пусть при коротком нажатии на кнопку происходит подача материала, то есть «Feed». Это пригодится нам при протяжке темроленты до нужной длины. Установим прокрутку ленты в 10 пикселей за одно нажатие.

def on_pressed():
    printer.feedRows(10)

При длительном нажатии на кнопку on_held() будем делать снимок, а после печатать его. Сперва делаем сам снимок, используя программу raspistill из терминала в параллельном процессе. Добавим в команду автоматическую экспозицию и чуть увеличим яркость — -ex auto -br 55. Пусть, пока делается снимок и сохраняется фото, светодиод быстро мигает, например с частотой 10 Гц. Быстрое мигание светодиода будет говорить пользователю о том, что сейчас происходит съёмка и нужно улыбнуться и не трясти камеру руками.

print("Taking original photo")
led.blink(0.05, 0.05)
p = subprocess.Popen(["raspistill -n -ex auto -br 55 -o /home/pi/photos/original_photo.jpg"], shell=True)
p.wait()

Как только снимок будет сделан и сохранён в файл, мы запустим дизеринг изображения. Пусть светодиод при этом мигает реже, сообщая нам о том, что снимок начал обрабатываться и камеру уже можно не держать на месте. Дизеринг производим тем же способом, что и в предыдущей программе.

print("Converting image")
led.blink(0.2, 0.2)
p = subprocess.Popen(["convert /home/pi/photos/original_photo.jpg -resize 512x384 -remap /home/pi/photos/ctrl_colors.gif -normalize -rotate 90 /home/pi/photos/photo_dither.png"], shell=True)
p.wait()

print("Remove original photo")
p = subprocess.Popen(["rm  /home/pi/photos/original_photo.jpg"], shell=True)
p.wait()

Наконец, отправляем обработанное изображение на печать. В процессе печати светодиод мигает раз в секунду, а по окончании печати возвращается во включённое состояние, сообщая пользователю, что камера вновь готова к работе.

print("Printing image")
led.blink(0.5, 0.5)
printer.feedRows(50)
printer.printImage("/home/pi/photos/photo_dither.png")
printer.feedRows(50)
led.on()

Перед печатью изображения мы делаем подачу ленты на 50 пикселей и ещё на 50 пикселей после печати. Это связано с расположением изображения на стикере. Стикер длиной в 80 мм, а напечатанное изображение — 64 мм. Как же сделать так, чтобы фотография распечаталась строго по центру стикера 58×80 мм, а не с краю?

Идеальным решением было бы добавить сенсор для распознавания линии начала нового стикера на ленте, а затем прокручивать стикер до того места, где должна начаться печать. В термоголовку уже встроен фотопрерыватель. Но этот датчик — бинарный, то есть сигнал на нём строго 1 или 0 (либо бумага есть либо её нет). В нашем случае цвет ленты, на которой находятся стикеры, очень близок к белому цвету самого стикера. Поэтому для определения начала стикера на ленте бинарный датчик не подойдёт, необходим аналоговый инфракрасный датчик цвета. Нужен сенсор, способный по градации белого цвета определить начало стикера на ленте. У платы Raspberry Pi нет встроенного АЦП для работы с аналоговыми датчиками, поэтому мы решили не возиться с внешними модулями, а подгонять стикер «на глаз». Так что, если вы захотите повторить данный проект, это отличная возможность улучшить его и добавить подобную автоматизацию.

Сделаем автоматический запуск скрипта при загрузке операционной системы в сервисе с помощью Systemd.

Сперва сделаем так, чтобы наша Raspberry Pi автоматически логинилась под пользователем pi при загрузке системы. В настройках raspi-config в меню «System Options → Boot / Auto Login» устанавливаем значение B2 Console Autologin. Параметр B4 Desktop Autologin нам не нужен, ведь мы не пользуемся монитором или дисплеем.

rpi_autologin

Создадим новый unit-файл для нашего сервиса и отредактируем его. Назовём сервис instant_camera.service.

sudo touch /etc/systemd/system/instant_camera.service
sudo nano /etc/systemd/system/instant_camera.service

В unit-файле создадим следующее описание сервиса с указанием пути до нашего Pyhton-скрипта:

[Unit]
Description=Instant camera script
After=multi-user.target

[Service]
Type=idle
User=pi
ExecStart=/usr/bin/python3 /home/pi/instant_thermo_camera/python/take_and_print_photo.py

[Install]
WantedBy=multi-user.target

Про параметры unit-файлов для сервисов можно почитать в мануале.

Перезапускаем демон systemd и перезагружаем Raspberry Pi:

sudo systemctl daemon-reload
sudo systemctl enable instant_camera.service
sudo reboot now

После перезагрузки сервис запустится автоматически. Зелёный светодиод нашей камеры включится, если скрипт был запущен. При необходимости мы можем проверить, как работает наш новый сервис, командой:

sudo systemctl status instant_camera.service

Python-скрипт должен запуститься без проблем. Также мы можем при необходимости остановить сервис, запустить или перезапустить командами:

sudo systemctl stop instant_camera.service
sudo systemctl start instant_camera.service
sudo systemctl restart instant_camera.service

Посмотреть лог работы сервиса можно командой journalctl:

journalctl -u instant_camera.service

Корпус камеры и сборка

Пришло время сделать корпус для нашей камеры. Мы решили напечатать корпус из PLA-пластика на 3D-принтере.

Перед печатью мы смоделировали нашу камеру в CAD-системе Solidworks, чтобы максимально компактно разместить все имеющиеся электронные компоненты.

dev_design

Разумеется, наш дизайн не является обязательным, а служит лишь примером. Корпус камеры сильно зависит от тех компонентов и крепежа, которые вы используете в проекте. Если вы хотите использовать другие компоненты, скорее всего, вам придётся моделировать корпус самостоятельно. Если вы хотите в точности повторить данный проект, можете использовать 3D-исходники нашего корпуса. Все материалы этого проекта мы разместили в репозитории instant_thermal_print_camera.

Наш корпус состоит из трёх деталей: основного корпуса, крышки электроники и крышки батарейного отсека.

dev_printed_parts

Можем приступать к сборке.

Крепить все компоненты в корпусе мы будем винтами с прямым шлицем и внутренним шестигранником М2×5, М2×8, М2,5×5. Чтобы минимизировать использование гаек, мы будем устанавливать вплавляемые резьбовые вставки М2×4×4 и М2,5×4×3. Данный вид крепежа отлично работает с деталями, напечатанными на 3D-принтере.

dev_fasteners

Убираем все разъёмы с платы принтера и Raspberry Pi, в нашем случае они будут только занимать лишнее место. Провода будем припаивать прямо к плате.

dev_no_pins

Резьбовые вставки вставляются паяльником.

dev_inserts

Аккуратно вплавляем все вставки с обоих сторон корпуса.

dev_all_inserts

Вставляем термоголовку в корпус.

dev_install_head_1

С внутренней стороны фиксируем термоголовку винтом М2×8 и гайкой.

dev_install_head_2

Устанавливаем батарейные отсеки и прикручиваем их винтами М2×5.

dev_install_battery_holder

Устанавливаем мини-тумблер.

dev_install_switch

Вставляем красный светодиод в корпус.

dev_install_red_led

Далее устанавливаем выносную плату со светодиодом и тактовой кнопкой. Плату крепим оригинальными маленькими саморезами.

dev_install_led_pcb_1

Колпачок для кнопки берём тот, что шёл в комплекте с принтером.

dev_install_led_pcb_2

Прикручиваем Raspberry Pi двумя винтами М2×5.

dev_install_pi

Таким же образом крепим плату управления принтером.

dev_install_printer_pcb

Пришло время соединить все компоненты. Обрезаем провода до нужной длины и аккуратно припаиваем их к нужным пинам Raspberry Pi.

dev_soldering

У понижающего преобразователя DC-DC много открытых контактных мест. Мы положим его в пустое пространство в корпусе камеры. Чтобы случайно не закоротить контакты, поместим преобразователь в термоусадку.

dev_install_dc_dc

Утрамбовывам все провода в корпусе и подключаем шлейф от термоголовки к плате управления.

dev_install_link

Берём напечатанную на 3D-принтере крышку и вставляем в неё камеру. Крепёж не нужен — посадка камеры с натягом.

dev_install_camera

Закрываем корпус крышкой с камерой. Крышку фиксируем четырьмя винтами М2×5.

dev_install_camera_cover

Устанавливаем аккумуляторы 18650 в батарейные отсеки.

dev_install_batteries

Закрываем корпус второй крышкой. Крышка батарейных отсеков сделана с защелками. Таким образом при замене или зарядке аккумуляторов не нужно использовать инструмент, крышку легко открыть руками.

dev_install_battery_cover

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

Сборка готова! Включаем тумблер, ждём загрузки операционной системы с сервисом нашего скрипта.

Короткое нажатие на кнопку прогоняет термоленту, а долгое нажатие запускает процесс съёмки. Попробуем сделать фотографию и напечатать её, используя уже новый корпус. По зелёному светодиоду рядом с кнопкой мы можем следить за текущим статусом камеры согласно нашей программе.

Снимаем и печатаем

Время наделать снимков!

Заключение и советы

Проект моментальной камеры получился крайне интересным. Корпус камеры оказался местами не слишком эргономичным, зато очень компактным. Четырёх аккумуляторов 18650 хватает примерно на шесть часов непрерывной автономной работы камеры.

Если захотите доработать этот проект или сделать какое-нибудь похожее устройство самостоятельно — вот вам несколько полезных советов по улучшению и нюансов, которые мы выявили в процессе работы:

  • С термоголовкой TP-701 качество печати на чековой бумаге намного выше, чем на термостикерах.
  • Фотографии, которые получаются на миниатюную камеру для Raspberry Pi Zero, очень сильно зависят от освещения. В свою очередь, светлота изображения сильно влияет на дизеринг и итоговую печать. Сделанный в тёмном помещении снимок может дать на бумаге практически чёрное изображение без деталей, а сделанный при ярком свете — почти полностью белое. Идеальное решение — использовать конкретные настройки яркости, экспозции, выдержки при каждом снимке.
  • Можно значительно расширить пользовательский интерфейс камеры. Например, можно убрать светодиоды и добавить к Raspberry Pi полноценный цветной сенсорный дисплей, способный выводить превью фотографии перед печатью. Или же добавить больше кнопок и крутилок для изменения пользовательских настроек фотографии, таких как экспозиция, ISO, задержка, яркость, контраст.
  • Термопринтер желательно запитывать от 9 В, а не от 5 В. Более высокое напряжение даст лучший контраст и усилит шаговый двигатель. Возможно, помимо понижающего преобразователя напряжения в проекте нужен и повышающий. Либо исходное напряжение литиевых аккумуляторов нужно поднять до трёх «банок» 11,1 В.
  • При использовании стикеров можно сделать автоматическую систему обнаружения начала и подгонки стикера. Это можно сделать с помощью аналоговых датчиков цвета.
  • Отличным усовершенствованием была бы зарядка камеры от USB. Это позволило бы избавиться от крышки и необходимости доставать батарейки. Для этого понадобятся специализированные платы заряда и балансировки аккумуляторов Li-Po, Li-Ion.