Будучи энтузиастом ретро-компьютерной техники и по роду деятельности имея некоторое отношение к кассовому оборудованию, я питаю некоторую слабость к историческим артефактам из этой области.

Однажды на онлайн-барахолке мне на глаза попался так называемый «дисплей покупателя» — алфавитно-цифровое табло, которое стоит на кассе и показывает текущий товар в чеке и сумму покупки. Это был старый экземпляр, с красивым зелёным VFD-дисплеем, да и стоил он копейки. Пройти мимо я не смог. Ну а потом, как и многие обладатели очумелых ручек до меня, захотел сделать какую-нибудь самоделку на его основе.

Конечно, можно было бы засунуть туда ESP-шку и сделать погодную станцию, часы, или транслировать уведомления о новых сообщениях в ТГ и WhatsApp. Это всё, безусловно, по-своему интересно. Но самым большим моим увлечением являются игры. И поэтому я решил сделать из табло электронную игру. Как говорится, «но зачем?» Ответа у меня нет. Но есть рассказ, что и как я сделал.

Развитие идеи

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

Набросок идеи игры. яйца-продукты катятся из углов
Набросок идеи игры. яйца-продукты катятся из углов

Уже на этом этапе было задумано название будущей игры — «Потребительская корзина». Я предполагал использовать в оформлении игрового процесса тему торговли и обычной кассовой работы. Символы — продукты, посередине — буквально символ корзины. Отсюда и «корзина» в названии. К тому же это было довольно меметическое сочетание, долгое время бывшее на слуху всех и каждого, вызывающее ассоциации с продуктами, магазинами, кассами и мизерной зарплатой.

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

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

Второй концепт, электроника в нашлёпке снизу вместо ножки, кнопки по нижнему краю рамки
Второй концепт, электроника в нашлёпке снизу вместо ножки, кнопки по нижнему краю рамки

Чтобы решить проблему места, возникла идея добавить к корпусу пластиковую нашлёпку для размещения электроники. Но это выглядело бы эстетически странно, уже совсем не аутентично. С таким же успехом можно было создать и новый корпус. Тогда я подумал: а ведь в родной подставке может хватить места и для кнопок, и для электроники, и для аккумулятора. Можно сохранить ножку, немного укоротив её для удобства, а кнопки разместить на подставке. Эргономически вырисовывался этакий странный последователь Virtual Boy, который тоже имел форм-фактор настольной приставки на курьих ножках.

Третий концепт, вся электроника в подставке, кнопки в дырках её крепления
Третий концепт, вся электроника в подставке, кнопки в дырках её крепления

Когда дошло дело до продумывания деталей игрового процесса, я решил обсудить свои задумки с ИИ, чтобы решить некоторые мелкие моменты. Этот диалог вдруг натолкнул меня на совершенно иную идею. Дело в том, что среди прочего электронного барахла у меня завалялся и ручной кассовый сканер, которым пикают товары на кассах и складах. А значит, игру можно сделать вовсе без кнопок и с полным сохранением торгово-кассового антуража! Эта комбинация оборудования сама собой подсказала итоговый игровой процесс: для управления нужно вводить коды сканером. Все кусочки пазла наконец сошлись, и родился эстетически идеальный проект.

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

Табло

Табло, которое мне досталось всего за 331 рубль, оказалось родом из эпохи, когда чёрный цвет ещё не вошёл в моду. Большое, серовато-белое, с металлическим утяжелением в подставке. Судя по надписям на корпусе, это CipherLab P0730367, что бы это ни значило — обозначение больше похоже на инвентарный номер, чем на номер модели.

Верхняя и нижняя половины довольно высокого табло
Верхняя и нижняя половины довольно высокого табло

Основные особенности данного табла: большой VFD-дисплей размером 20 на 2 символа, 5 на 7 точек каждый, плюс какие-то служебные сегменты. Довольно суровый размер. Несъёмный кабель, совмещающий стандартный 9-пиновый разъём COM-порта и питание от 12 вольт через DC-джек, гнездо для которого расположено прямо на разъёме. До кучи к штатному кабелю шёл целый набор переходников для питания: компьютерный молекс, переходящий в «тюльпан» RCA на планке для корпуса системного блока, и только потом уже DC-джек.

Занимательная организация штатного питания для табло
Занимательная организация штатного питания для табло

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

Для теста я запитал табло от своего самодельного портативного источника питания, о котором есть статья на Хабре (регулярно пригождается). На шильдике сказано, что нужен источник питания с током 500 мА, фактическое потребление составило около 350 мА. Для подачи команд я подключил табло к компьютеру через переходник USB на RS-232. Командовал парадом через терминалку RealTerm.

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

VFD дисплей и непростая электроника внутри табло
VFD дисплей и непростая электроника внутри табло

Подробную документацию именно на эту модель мне найти не удалось. Беглый гуглёж показал, что, как и прочие устройства подобного рода, поддерживает целую пачку протоколов: VFD-220FC, DSP800, CD5220, ESC/POS, AEDEX, UTC/P, ADM 787, UTC/S. В моём табло изначально был выбран протокол ESC/POS — стандартные ESC-коды Epson для торговых терминалов, вариация на тему ESC-кодов для обычных чековых принтеров. В этом режиме дисплей по сути работает как двухстрочный принтер.

Это заставило забеспокоиться: если устройство работает как принтер, не будет ли оно «печатать» информацию, сдвигая строки? Ведь для игры желательно обновлять содержание дисплея целиком, не допуская никаких ненужных прокруток, иначе получится какой-то Apple I. Впрочем, в стандарте ESC/POS есть команда установки курсора, а значит, должна быть возможность «перезапечатывать» строки.

Стандартные команды ESC/POS для управления чековыми принтерами
Стандартные команды ESC/POS для управления чековыми принтерами

К счастью, всё было проще: я попробовал слать обычный текст через RealTerm, и оказалось, что символы просто печатаются один за другим, начиная с верхнего левого угла, затем печать продолжается на второй строке, а после начинается заново. То есть достаточно слать по 40 байт, и картинка будет обновляться полностью, без лишних сдвигов. Также табло реагирует на команду инициализации $1b $40, очищая экран.

По старой доброй традиции преждевременной оптимизации, у меня сразу возникло желание поднять скорость передачи данных до максимально возможной. Ведь стандартные 9600 бод — это 960 байт в секунду, а значит, для дисплея 20x2 символов это всего 24 обновления в секунду. Маловато будет!

Конфигурационные переключатели
Конфигурационные переключатели

Программных настроек у табло нет, да и скорость COM-порта в устройствах обычно настраивается аппаратно. На плате есть аж двенадцать переключателей, пронумерованных от 1 до 12 соответственно, и как я догадывался, какие-то из них выбирают скорость. К сожалению, так как документацию на это табло найти не удалось, пришлось действовать методом тыка.

Удалось натыкать следующее понимание. Первые три переключателя и один последний, 1-3 и 12, выставляют режим команд. Если 12-ый переключатель выключен, выбирается фиксированная конфигурация: POS 7300, скорость 9600 и таблица символов U.S.A./Europe. Если же он включён, действуют пользовательские настройки.

1

2

3

Режим

Демка

off

off

off

CD5200 Standard Mode

Скролл символов с кодами выше $80, сверху строки с описанием дисплея

on

off

off

UTC Standard Mode

Мигает курсор-полоска

off

on

off

UTC Enhanced Mode

Чёрный экран

on

on

off

AEDEX/EMAX Com. Mode

Чёрный экран

off

off

on

DSP-800 Command Mode

Анимация со скроллом и печатью описания дисплея

on

off

on

ADM787/788 Com. Mode

Чёрный экран

off

on

on

EPSON ESC/POS Mode

Демка с полосками

on

on

on

POS 7300 Command Mode

Чёрный экран

Следующие четыре переключателя, с номерами 4-7, выбирают кодировку и набор символов для символьных кодов выше $80:

4

5

6

7

Character

Code Table

off

off

off

off

USA

PC852

on

off

off

off

UK

Greek

off

on

off

off

USA

PC860

on

on

off

off

Russia

-

off

off

on

off

Slawie

Slawie

on

off

on

off

Denmark2

PC858

off

on

on

off

Norway

PC858

on

on

on

off

Japan

Katakana

off

off

off

on

Spain

PC858

on

off

off

on

Italy

PC858

off

on

off

on

Sweden

PC858

on

on

off

on

Denmark1

PC858

off

off

on

on

U.K.

PC858

on

off

on

on

Germany

PC858

off

on

on

on

France

PC858

on

on

on

on

USA

Europe

Очередные два переключателя, с номерами 8 и 9, задают скорость. Выключатель 10 управляет признаком чётности (off=N, on=E):

8

9

Скорость

off

off

38400

on

off

19200

off

on

9600

on

on

4800

Чем управляет выключатель 11, выяснить не удалось, вроде ничего не меняется.

Таким образом мне удалось выставить скорость 38400 бод, при стандартном формате посылки 8-N-1. Кодировку оставил русской, команды EPSON ESC/POS, так как по факту мне и не нужны никакие особенные возможности. Выбранная скорость позволяет полностью обновлять дисплей до 96 раз в секунду, что было бы очень неплохо. С такими настройками табло без проблем работало с ПК.

Нужно заметить, что после включения табло показывает непропускаемую заставку, в которой указан выбранный интерфейс и параметры. В это время оно ни на какие команды не реагирует, и даже команда $1b $40 для сброса сработает только после окончания заставки.

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

Как выяснилось по разным источникам, в табло есть восемь наборов символов для кодов от 128 и выше, то есть за пределами стандартного ASCII-набора. Их можно переключать ESC-последовательностями:

$1b $74 $00..07

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

Чтобы посмотреть все символы, я подготовил файлы, содержащие 40-байтные последовательности с кодами, возрастающими от $80 до $FF. Их удобно слать через RealTerm. В различных таблицах наблюдаются всякие умляуты, фрагменты псевдографики рамок, даже немного японской катаканы. Кириллица находится в таблице 7, её кодировка совпадает с CP866.

Успешное отображение кириллического сообщения
Успешное отображение кириллического сообщения

Однако, просматривая доступные в шрифтах символы, я заметил, что среди них нет различных горизонтальных полосочек, которые отображает встроенный тест в режиме ESC/POS. Также, конечно, нет официального символа рубля, который официально ввели в обиход в 2013 году, а табло явно родом из более ранней эпохи. А иметь такой символ хотелось, потому что прикольно.

Возникло подозрение, что, вероятно, в этом табло есть что-то, подобное пользовательским определяемым символам в LCD-дисплеях типа 1602 — там можно загружать восемь символов. И действительно, гуглёж выдал статью на Хабре, где объясняется, как загружать собственные символы. С моим табло это тоже работает.

Некоторые из доступных в табло интересных псевдографических символов
Некоторые из доступных в табло интересных псевдографических символов

С помощью ESC-последовательности можно указать, от какого по какой символ нужно загружать графику, сколько байт в высоту (в одном байте столбец из 8 вертикальных точек) и сколько байт в посылке всего. Например, вот так я заменил символ недружественной заморской валюты на символ стабильного рубля:

ESC

&

байт в высоту

от символа

до символа

пять байт

байт 0

байт 1

байт 2

байт 3

байт 4

$1b

$26

$01

$24

$24

$05

$7f

$4a

$4a

$48

$30

Графика символа ориентирована и кодируется следующим образом:

01111111 $7f
01001010 $4a
01001010 $4a
01001000 $48
00110000 $30

Чтобы включить или выключить использование загруженных символов, применяется специальная ESC-последовательность:

$1b $25 $01 (или $00)

Работает это так же, как с таблицами — можно вывести свой изменённый символ, выключить отображение символов, и вывести рядом этот же символ в оригинальном виде. Менять можно любые символы, кроме пробела.

Тестовая загрузка символа в табло через RealTerm
Тестовая загрузка символа в табло через RealTerm

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

Пользовательские символы успешно загружены
Пользовательские символы успешно загружены

Сканер

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

Внешний вид моего сканера
Внешний вид моего сканера

Лет пять назад мне по случаю отдал его за ненадобностью друг. Тогда мне сканер тоже был особо ни к чему, я быстренько проверил его, и положил в ящик, где он терпеливо выжидал своего звёздного часа. И теперь в проекте мне хочется задействовать именно этот сканер — не зря же он столько лежал!

По роду деятельности я уже сталкивался с подобными сканерами, но глубоко не вникал — лишь примерно представлял, как они действуют: могут работать в режиме COM-терминала через реальный RS-232 или виртуальный по USB, либо работать в режиме эмуляции USB-клавиатуры. Теперь же нужно было понять, что за прибор оказался в моих руках, в каких режимах он умеет работать, и как проще всего подключить его к какой-нибудь Ардуине.

Части сканера согласно официальному руководству
Части сканера согласно официальному руководству

Оказалось, что это довольно популярная и для своего времени мощная модель, Symbol SK1-DS6707. Вроде как это одна из лицензионных копий изначальной модели Motorola с таким же индексом, выпущенной в 2006 году, которую потом делали другие производители. На моём сканере даже есть значок Motorola.

Так как модель популярная, быстро нашлась подробнейшая документация на добрых четыре сотни страниц. Сканер умеет очень много чего интересного, в том числе даже работать в режиме 2D-камеры. Но мне нужно малое: просто сканировать 1D или 2D коды по нажатию кнопки и слать их моей Ардуине. Желательно, конечно, по RS-232, чтобы не заморачиваться с технически куда более сложным подключением по USB.

Сканер в процессе разборки на крупные атомы
Сканер в процессе разборки на крупные атомы

К счастью, этот сканер умеет работать как по USB, в режиме виртуального COM-порта и в режиме эмуляции клавиатуры, так и по реальному RS-232. Режимы работы сканера, в том числе выбор способа подключения и всякие полезные опции типа громкости звука, устанавливаются через сканирование специальных кодов из мануала. Перевести его в нужный режим проблемы не составляет.

К сожалению, для работы по RS-232 нужен другой провод: он отсоединяется от самого сканера и содержит в себе стандартный разъём RJ-45, распиновка на который приведена в документации. Причём для пятивольтового RS-232C нужен особый кабель с преобразователем уровней внутри. А у меня в наличии есть только кабель с USB-разъёмом, и это уже проблема.

Неисправная кнопка сканера
Неисправная кнопка сканера

Таким образом, обозначилось два сценария развития проекта: в одном я нахожу готовый (вряд ли) или делаю самодельный провод для подключения RS-232, а в другом просто подключаюсь по USB. Первое технически проще, но мой самодельный провод будет довольно колхозным, да и конструкция станет менее повторяемой, так как сканер с классическим COM-портом сейчас найти непросто. Второе немного сложнее, потому что нужна будет Ардуина, умеющая работать с USB. Но в современном мире любительской электроники это вроде уже не такая уж и проблема. Поэтому я решил идти вторым путём.

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

Удивительные технологии древности
Удивительные технологии древности

Внутри обнаружились удивительно высокие технологии, учитывая год выпуска и по нынешним временам кажущийся простой функционал прибора. Один чип даже блестит, как будто голый кристалл без корпуса, но с маркировкой. Впрочем, высокая сложность вполне ожидаема, учитывая, что это по сути целая CCD-камера со встроенным анализатором маркировок.

Исправленная неисправность кнопки
Исправленная неисправность кнопки

Причина западания курка оказалась простой: со временем продавилась резиновая прокладка над тактовой кнопкой, вероятно, предназначенная для защиты прибора от попадания влаги. Я помёл по сусекам, и нашёл в них резинку от Game Boy Advance SP, которая просто идеально встала поверх штатной — ни резать, ни клеить не пришлось. После этого курок заработал чётко и приятно.

Ардуина

Устройство вывода и ввода были освоены и готовы к применению. Теперь проекту требовались какие-никакие мозги.

Осуществить задуманное можно было разными способами: просто в виде программы для обычного ПК, или на одноплатнике типа Raspberry Pi, или даже удариться в экзотику и взять что-нибудь из старой ненужной кассовой техники на Linux’е (и такая даже имелась под рукой). Но, с одной стороны, я хотел сделать игру самодостаточной, со всей необходимой электроникой внутри корпуса, а с другой — более-менее легко повторяемой, без излишних заморочек с реализацией. Поэтому внутри будет не неонка, а обычная Ардуина. Хотя, может, и не совсем обычная.

Так как я решил работать со сканером в режиме USB, а также мне нужно было работать с табло через адаптер RS232-TTL, возник вопрос — какой контроллер выбрать. Есть два стула:

  • Обычный Arduino Uno (Nano) плюс USB Host Shield.

  • ESP32-S3 с двумя разъёмами USB, поддерживающий USB Host.

Каждый вариант имеет свои особенности, но ни один из них не имеет решающего перевеса. Основная разница в том, что классический Arduino имеет меньше вычислительных ресурсов и требует дополнительную плату для работы в режиме USB Host. Зато он пятивольтовый. Но вчера. ESP32-S3 значительно мощнее, сразу умеет работать как USB Host, что позволяет избежать использования лишней платы. Но он трёхвольтовый. Но сегодня.

Плата Arduino Uno с установленным модулем USB Host Shield
Плата Arduino Uno с установленным модулем USB Host Shield

Изучив вопрос, я выяснил, что разное напряжение питания не играет существенной роли: адаптер RS232 на микросхеме MAX3232 поддерживает работу с 3-вольтовыми устройствами. Достаточно запитать его от 3.3 вольт, для соблюдения правильных уровней между контроллером и чипом адаптера, а снаружи размах напряжений всё равно будет достаточным, так как микросхема имеет встроенный повышающий преобразователь. Ну а делать дополнительные источники питания для работы табло (12 вольт) и сканера (5 вольт) придётся при любом раскладе.

Дальше возник вопрос, как именно работать со сканером через USB с точки зрения протокола и кода, и какие библиотеки для этого есть для каждого контроллера.

Сканер умеет работать во многих режимах, в том числе в режиме USB CDC (виртуальный COM-порт), позволяющий выставлять умопомрачительные скорости, вплоть до 230400 бод. Но… при проверке под Windows после переключения сканера в таковой режим он не появляется в системе как COM-порт. Значит, ему нужен особый драйвер от производителя. И если для ПК это не проблема, для микроконтроллера это тупик.

Плата ESP32-S3 с двумя разъёмами USB Type C
Плата ESP32-S3 с двумя разъёмами USB Type C

Тогда остаётся только один вариант: режим виртуальной клавиатуры. В нём сканер представляется как HID-устройство, и при сканировании кода как бы нажимает клавиши отсканированной последовательности. Однако, делает он это неторопливо, даже если выбрать режим без дополнительной задержки набора (есть три скорости) — видно, как код печатается по одной букве. И хотя для моей задумки хорошо бы иметь более быструю реакцию, за отсутствием альтернативы придётся как-нибудь настроить игровой процесс с учётом этой задержки.

Осталось выбрать конкретную библиотеку для работы с USB HID. Для классического Arduino с дополнительной платой уже давно есть развитая и удобная в использовании библиотека USB Host Shield Library 2.0. Код примера работы с клавиатурой в ней содержит всего сотню строк.

На ESP32-S3 с подобными библиотеками ситуация менее однозначная. Есть порт примера из ESP-IDF в Arduino IDE, но код работы с клавиатурой там содержит аж шесть сотен строк — этот подход очень гибкий и универсальный. Зато есть гораздо более простой EspUsbHost, работающий только с клавиатурой и мышью. Пример его использования, напротив, очень компактен.

Обе платы рядом для сравнения габаритов
Обе платы рядом для сравнения габаритов

Так как явного фаворита так и не определилось, я решил выбрать по размеру: плата ESP32-S3 просто меньше, чем Arduino Uno с шилдом. К тому же, было интересно поработать с USB OTG на ESP32.

Электроника

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

Детали в ожидании сборки. До финала доберутся не все
Детали в ожидании сборки. До финала доберутся не все

По следам первоначальных задумок с портативностью я решил сделать устройство автономным. Энергии ему нужно не так уж мало. Поэтому я взял два аккумулятора 18650 на 2200 мА·ч и популярный модуль зарядки и балансировки на микросхеме IP2326. Это обеспечивает выходное напряжение источника питания в диапазоне примерно 7.2…8.4 вольт.

Сборка аккумуляторов
Сборка аккумуляторов

Так как потребители в схеме рассчитаны на иные напряжения, понадобилась парочка DC-DC преобразователей. Плата ESP32-S3 сама обладает встроенным линейным стабилизатором и может принимать питающее напряжение 5-7 вольт для получения своих штатных 3.3 вольт питания. Так как помимо контроллера мне нужно питать ещё и пятивольтовый сканер, потребляющий до 350 мА, я применил модуль понижающего преобразователя на неизвестном науке китайском чипе…

Джамперы на плате ESP32-S3
Джамперы на плате ESP32-S3

Из коробки плата ESP32-S3 не выдаёт питание на разъём USB и вообще не работает в режиме USB-OTG. Но она может делать и то и другое, нужно лишь замкнуть два соответствующих джампера. Один из них находится на нижней стороне платы, поэтому важно не забыть сделать соединения до установки платы в устройство.

Для питания табло нужно 9-12 вольт и ток до 500 мА. Для этого я применил модуль повышающего преобразователя на микросхеме LM2596, имеющий подстроечный резистор для выставления нужного напряжения. Табло не особо чувствительно к точности значения питающего напряжения, но я выставил ровно 12 вольт.

Схема принципиальная упрощённая
Схема принципиальная упрощённая

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

Конструкция

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

Собранная плата в первоначальном варианте
Собранная плата в первоначальном варианте

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

Низ табло после окончательной сборки
Низ табло после окончательной сборки

Таким образом удалось смонтировать плату, чтобы она не мешала закрываться железному днищу-утяжелителю. В самом днище было просверлено отверстие для выхода звука. Помимо штатных саморезов для крепления днища, я использовал силиконовые ножки для разделочных досок, закреплённые в крепёжные отверстия на винты для крепления ПК-шных вентиляторов. Это помогло приподнять табло над поверхностью стола и сделать звук громче.

Служебная кнопка, ключ-включ и светодиод
Служебная кнопка, ключ-включ и светодиод

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

Разъёмы USB для подключения сканера и зарядки
Разъёмы USB для подключения сканера и зарядки

На задней стенке подставки уже были предусмотрены две выемки под штатные провода. Их я расширил модельным скальпелем и закрепил там два разъёма. Полноразмерный USB-разъём для подключения сканера был закреплён на наш любимый термоклей, так как я слегка промахнулся с размером отверстия под ушки-защёлки. Провода от разъёма соединил с обрезком ненужного Type C кабеля и подключил в штатный разъём платы ESP32-S3, чтобы обойтись без пайки. Маленький разъём Type C для зарядки аккумулятора дублирует разъём на плате зарядки и балансировки, ныне скрытой в глубинах подставки.

Плата со сборкой аккумуляторов в термоусадке
Плата со сборкой аккумуляторов в термоусадке

После добавления всего необходимого добра места в подставке для аккумуляторов уже не осталось. Но была ещё пустотелая ножка! С помощью клеевой термоусадки диаметром 22 миллиметра и фена для усадки термоусадки я собрал два аккумулятора 18650 в колбаску и закрепил на подвесе из той же самой термоусадки внутри ножки. Греть аккумуляторы феном было то ещё удовольствие. Но процесс прошёл нормально.

Кабель-переходник для подключения сканера без пайки
Кабель-переходник для подключения сканера без пайки

Чтобы подключить табло к моей плате с электроникой, я изначально планировал просто смотать лишний кабель и использовать стандартный разъём DB9. Но места для этого уже не было. Поэтому я просто разрезал кабель пополам, а в месте разреза на каждом конце сделал разъём из IDC-гребёнки. На плате с электроникой я сделал такую ответную часть. Это решение позволило при желании подключать обратно отрезанный штатный кабель и использовать табло на ПК, что очень пригодилось для наладки.

Собранная конструкция перед началом наладки
Собранная конструкция перед началом наладки

Наладка

Казалось бы, что можно налаживать в настолько простом устройстве? Но электронные самоделки — вещь крайне капризная. Очень часто они не работают с первого раза, даже если состоят из пары проводков. А в этой конструкции проводков было не менее трёх. И на их пересчёт я потратил почти двое суток.

Первые проблемы возникли уже с загрузкой скетча в собранное устройство. Скетч сразу начал загружаться очень нестабильно. Arduino IDE постоянно выдавала сообщение типа:

A fatal error occurred: Invalid head of packet (0xC1): Possible serial noise or corruption.

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

Попытки загрузить скетч через второй разъём USB давали непонятный результат: пару раз загрузилось само, потом перестал появляться COM-порт. Помог ручной перевод в загрузочный режим по нажатию Reset при зажатом Boot перед каждой загрузкой. Однако, иногда компьютер не видел плату. Но тогда, если переключиться в первый разъём, несколько раз успешно срабатывала обычная загрузка. Никакой логики в этом непонятном поведении платы я не обнаружил, пользовался то одним, то другим способом, какой работал в каждый момент.

Так или иначе, скетч стал загружаться. Но вывод в Serial1 не выводил ничего на табло. Проверил подключение, номера пинов, пропаял соединения. С помощью осциллографа проследил путь сигнала. С пина 17 (TX) на TX адаптера приходит. Если не подключать табло, на выходном TXD адаптера хороший прямоугольный сигнал. Но когда табло подключено, вместо него на выходе слабенькие пилообразные импульсы. Значит, выход что-то просаживает. Наиболее вероятный вариант — перепутаны провода RXD и TXD. Так оно и оказалось.

Но на этом проблемы лишь начинались. Вместо передаваемого текста «Hello world!» табло принимало случайные символы. Один раз я пошевелил провода, и оно вроде заработало почти правильно, но потом снова пошли глюки, и как я не шевелил контакты и провода, ничего уже не менялось.

Ничего не подозревающие штатные конденсаторы на плате MAX3232
Ничего не подозревающие штатные конденсаторы на плате MAX3232

По советам ИИ я пустился во все тяжкие. Даже поменял микроскопические SMD-конденсаторы на плате MAX3232 на выводные увеличенной ёмкости, так как ёмкости штатных могло не хватать. Но это не помогло, хотя напряжения после замены на выводах 2 и 6 были в норме, 6 вольт. Я попытался перевести MAX3232 на питание 5 вольт, так как мне достаточно только отправлять данные, а RX можно к ESP32 просто не подключать и не решать проблему разных логических уровней. Но так уже совсем ничего не работало, передавались одинаковые байты. Питание от отдельного 78L33 почему-то тоже не помогло, с такими же симптомами.

Как обычно, электроника — наука о контактах. Пропаял кажущуюся надёжно припаянной гребёнку, к которой подключается провод RS-232 от табло, и на нём снова начало печататься какое-то подобие искомого «Hello world!». Но всё равно, постоянно вылезали левые символы, которых не должно быть. Я попробовал все скорости передачи, но стабильности добиться не удалось.

Суровая модификация платы MAX3232
Суровая модификация платы MAX3232

По очередному совету ИИ добавил резистор 330 ом в разрыв между TX контроллера и TX адаптера. Это вроде бы улучшило ситуацию, но не до конца. Потом ещё раз перепаял конденсаторы на MAX3232, на этот раз напрямую на ножки микросхемы, согласно схеме из даташита. Лучше не стало. Потом всё стало работать совсем плохо, и внятного текста я больше не наблюдал. В какой-то момент обнаружилось, что провод от пита TX контроллера припаян на пин RX адаптера. Как тогда передавалось хоть что-то, я ума не приложу.

Дополнительные проблемы обнаружились и в коде. Тестируя передачу, я изначально добавил стандартную ESC-последовательность для установки курсора в левый верхний угол, не проверив её работоспособность на табло в RealTerm:

$1b $24 $00 $00

По факту она не работала, добавляя лишние мусорные символы в вывод и вызывая сдвиг картинки, что сбивало меня с толку — я-то ожидал получить стабильно выводимый текст. ESC-команда Home также не заработала, выводя букву H:

$1b $5b $48

В итоге я нагуглил давно забытый, самый базовый вариант, и он сработал:

$0b

На промежуточном этапе всё вроде бы начало работать: я выводил команду сброса позиции и потом 40 байт текста с одним постоянно меняющимся символом в конце — чтобы видеть, что программа не зависла. Но по какой-то причине это изменение символа иногда залипало на заметное время, как будто подвисал код вывода, иногда текст помаргивал, а в районе 30-го байта то и дело мелькали случайные символы, всегда в одном и том же месте.

Я ещё раз проконтролировал форму сигналов, и где-то на этом этапе измученная нарзаном и множественными перепайками конденсаторов плата MAX3232 просто перестала работать: импульсы на вход шли, а на выходе TXD уже ничего не было.

Классическая однотранзисторная схема сопряжения TTL-RS232
Классическая однотранзисторная схема сопряжения TTL-RS232

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

Тогда я стал подозревать код и начал эксперименты. Выключил Wi-Fi модуль, увеличил буфера RX/TX, добавил Flush после вывода пакета байт, добавил запрет прерываний на время отсылки, измерил время выполнения write. Выяснилось, что время полностью одинаковое, а на TX импульсы следуют без подвисаний, наблюдаемых на дисплее. До кучи я попробовал перенести тестовый код на второе ядро ESP32-S3, проверил «странные» скорости передачи, типа 37400 или 39400 на случай рассинхронизации тактовых частот, а также снова снизил скорость до 9600 бод. Ничего из этого не дало никакого эффекта, табло работало не хуже и не лучше.

Из этих наблюдений можно было сделать вывод: дело не в железе, не в адаптере, и не в коде. Дело в самом табло, хоть в связке с ПК оно и работает нормально. Учитывая довольно древний год его рождения, я начал подозревать штуку попроще наводок на линиях и неправильных форм сигнала: возможно, оно просто не способно работать без задержек. Возможно, интерфейс реализует какой-то флаг занятости, который автоматически отрабатывается моим адаптером USB-RS232 на ПК, но не реализован в MAX3232 и в моём коде. Лёгкий намёк на эту версию давало наличие ещё двух задействованных проводов для линий RI, RTS/DSR (соединены вместе) в оригинальном кабеле.

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

Я начал со скромных значений, постепенно их увеличивая: 20, 100, 200, 500, 800 микросекунд. Глюки оставались. Лишь при задержке между байтами аж 1000 в микросекунд передача данных стала полностью стабильной. Пытаясь добиться улучшений, я попробовал передавать данные пакетами по несколько байт, а между ними ждать. Это не помогло, даже пакеты по 4 байта с ожиданием в 1000 микросекунд давали прежние глюки.

void display_update(void)
{
  const int d = 700;
  Serial1.write(0x0b);  //home
  Serial1.flush();
  delayMicroseconds(d);
  for (int i = 0; i < sizeof(display_buf); ++i)
  {
	Serial1.write(display_buf[i]);
	Serial1.flush();
	delayMicroseconds(d);
  }
}

Потратив на эксперименты немало времени, я так и не смог добиться дальнейших улучшений. Но довести проект до результата было нужно хоть как-то, пришлось пойти на компромисс. Я вернул скорость 38400 бод, и при подобранной задержке в 700 микросекунд с flush после каждого байта общее время обновления дисплея составило неизменные 39786 микросекунд. Таким образом, максимальная частота обновления экрана получилась около 25 кадров в секунду.

Хитроумный кронштейн крепления динамика
Хитроумный кронштейн крепления динамика

Немало неожиданных проблем доставил звук. Изначально я планировал использовать обычный пьезодинамик, питаемый в противофазе от двух пинов контроллера — в прошлых проектах это давало хороший результат. Но так вышло, что единственная большая пьеза, которая нашлась в закромах, оказалась активной, и при работе постоянно противно пищала. Я пробовал ставить нормальные пассивные, помельче, но они звучали очень тихо. Поиграв пару часов с различными вариантами из имеющихся в наличии деталей, я остановился на плоском динамике мощностью 0.5 ватт и платке усилителя на PAM8403. Динамик на плате крепить было уже негде, и я сделал для него 3D-печатный кронштейн.

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

Вид электроники после окончания пусконаладочных мероприятий
Вид электроники после окончания пусконаладочных мероприятий

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

Код

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

Для отладки игры я мог напрямую использовать сканер, ведь он работает как клавиатура. Но каждый раз браться за сканер было не совсем удобно. К тому же он громко пикал, и это утомляло. Я попробовал найти различные тайперы — утилиты, которые по нажатию кнопки виртуально «печатают» заданные последовательности символов. Но ни одна из них почему-то не заработала с SDL-приложением. Пришлось написать код, имитирующий набор строк с кодами прямо внутри приложения по нажатию F-клавиш. В этой рутинной задаче помог ИИ, выдав почти готовое решение.

Внешний вид и устройство штрих-кода формата EAN-8
Внешний вид и устройство штрих-кода формата EAN-8

Так как сканер передаёт код очень неторопливо, а игра подразумевает некоторую оперативность действий, я выбрал самый простой и короткий формат кода: RCN-8. Это вариант EAN-8 — одномерного восьмизначного штрих-кода. Обычно в нём хранится GTIN-8, уникальный код конкретного реального продукта, выдаваемого специально обученной организацией GS1. RCN-8 же может использоваться в локальном окружении. То есть коды такого формата не имеют шансов случайно попасться на каком-нибудь залётном йогурте. От прочих кодов их отличает наличие цифры 0 или 2 в начале. Далее следуют любые 6 цифр и цифра контрольной суммы.

Единственный подходящий референс, найденный в интернете. Фото «Агенство городских новостей Москва»
Единственный подходящий референс, найденный в интернете. Фото «Агенство городских новостей Москва»

Чтобы задизайнить основной экран, мне понадобились референсы, как этот экран выглядит на реальных кассах. Поразительно, но оказалось очень сложно найти фото табло в момент набора чека в Интернете. В основном попадались только всякие тестовые надписи и добро пожаловать. Я потратил буквально пару часов, прежде чем нашёл первую подходящую картинку-референс. Проще было бы просто сходить в магазин и снять самому. Так я в итоге и сделал.

Так как код игры я писал в виде прототипа под SDL, он выполнен не в стиле Arduino, с отдельными циклами для игровых ситуаций и задержками внутри них, а в стиле ПК, с отдельной функцией для обновления игрового состояния 60 раз в секунду. Это дало некоторую специфику при последующей адаптации кода для железа. Зато после (довольно длительной) отладки Arduino-специфичной обвязки сама игра сразу же заработала. К тому же, с помощью ПК-прототипа можно хоть как-то посмотреть, что представляет из себя игра, не заморачиваясь сборкой железного стенда. Поэтому прототип я тоже опубликовал.

Отображение кириллицы с кодировкой CP866 в VS и Arduino IDE
Отображение кириллицы с кодировкой CP866 в VS и Arduino IDE

Так как ни Visual Studio, ни Arduino IDE не умеют работать с кодировкой CP866, а конверсии кодировок на лету я хотел избежать, я вынес все кириллические сообщения в один отдельный файл, который редактировал в Notepad++.

Чтобы решить проблему медленного обновления и сканирования кодов, а также дать игровому циклу возможность работать на своих 60 обновлениях в секунду, я вынес код работы с внешними устройствами в отдельную задачу, работающую на втором ядре ESP32. Код обвязки в основном потоке отслеживает, были ли сделаны игрой изменения в буфере дисплея в текущем кадре. Если да, сообщает куда следует, то есть коду обновления, и тот выполняет пересылку при первой возможности параллельно с работой кода игры. Если обновление дисплея не требуется, вместо него постоянно выполняется обслуживание USB-хоста, чтобы ускорить получение сканируемых кодов.

SDL-прототип игры, работающий под Windows
SDL-прототип игры, работающий под Windows

Обновление дисплея делается построчно: если изменилась только одна строка из двух, обновляется только она. Возможность установки позиции курсора я так и не нашёл (ни один режим и команда не работают), но работает перевод строки по $0a. Это позволило хоть немного увеличить скорость обновления дисплея для анимаций типа прилёта букв на начальном экране.

Помимо программного кода, для игры понадобились и штрих-коды. Первые тестовые наборы оказалось очень легко подготовить в Inkscape: там уже содержится встроенный генератор штрих-кодов в меню Render > Barcode. Но когда позиций стало больше десяти, понадобилось более эффективное решение, а готового не нашлось. Тогда я снова обратился к ИИ, который после некоторых уточнений выдал мне вполне рабочий скрипт на Питоне, конвертирующий коды прямо из массива в моём исходном коде непосредственно в SVG-файлы с желаемым форматированием.

Игровой процесс

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

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

Финальный результат: весь необходимый реквизит для игры
Финальный результат: весь необходимый реквизит для игры

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

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

Папка со штрих-кодами для игры
Папка со штрих-кодами для игры

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

В этом и заключается идея и основное веселье игры: перебор бумажек с кодами, запоминание, где какой находится. Сама организация кодов на бумаге определяет игровой процесс: это могут быть разложенные по всему столу листы, или стопка, или, как сделал я, папка с файлами — так удобнее листать, но зато тяжелее сканировать (папка тоже ждала своего часа в закромах 15 лет, и вот пригодилась). Физика реального мира становится частью примитивного игрового процесса, делая его богаче.

Всего в игре 110 разных товарных позиций, и от 20 до 60 из них встречаются на уровнях. Наименования и цены в основном были взяты со случайного сайта, отслеживающего стоимость товаров продуктовой корзины, но некоторые были заменены или убраны, отдельные позиции с ценой за килограмм пересчитаны на более реалистичное количество, а дробная часть повсеместно заменена на традиционные ныне 99 копеек. К сожалению, у меня не было времени придумать сотню креативных наименований и установить более достоверные цены, но, думаю, решающего значения это не имеет.

Заключение

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

Ну а к теме самодельных электронных развлечений я ещё вернусь. Идей навалом!

P.S. Исходники пока выложены в открытом посте на Бусти.

© 2026 ООО «МТ ФИНАНС»