Приветствую всех!
Не так давно я уже рассказывал про то, какими были принтеры начала эпохи термопечати. И в той статье я упомянул, что подобные устройства использовались совместно с платёжными терминалами VeriFone.
Увы, найти именно такой принтер у меня не вышло, так что сегодня поговорим о его не менее популярном в данной области матричном собрате, который тоже оказался крайне интересным.
Итак, в сегодняшней статье поговорим о том, как устроен и работает такой принтер. Разберёмся, как подключить его к обычному компьютеру. Традиционно будет много интересного.
❯ О чём я?
Давным-давно, где-то в середине восьмидесятых, не было простых и дешёвых термопринтеров. Поэтому чеки печатались на матричных экземплярах. Один из таких принтеров (в составе терминала Tranz 460) я уже мимоходом демонстрировал в одной из своих статей. Экземпляр вполне себе печатал (а также радовал ни с чем не сравнимыми звуками), но мне хотелось именно отдельно стоящий, причём желательно в таком же исполнении (принтеры подкладной печати являются по сути уменьшенными аналогами «больших» аналогов, но об этом несколько позже).
И вот ко мне в руки попал именно такой аппарат, так что можно и попробовать его подключить и запустить.
❯ Обзор оборудования
А вот и сам экземпляр, VeriFone Printer 250. Называется он так, правда, только в собственной инструкции и в торговых каталогах, во всех остальных местах его именуют просто P250.
Выпускался с начала девяностых и является предшественником более старого P200, использовался совместно с большинством старых терминалов VeriFone. Устройства типа Tranz (на фото), Omni (на КДПВ) были рассчитаны на работу с ним «из коробки», даже была соответствующая библиотека в составе SDK. Даже в более поздних Omni-3350, Omni-3750, оснащённых термопринтерами, поддержку внешних матричных до конца не выкинули.
Сам принтер очень похож на современный и практически ничем не выдаёт обывателю матричную натуру. Сверху у него две крышки — одна для моточка с лентой, под другой находится картридж. Два индикатора, кнопка протяжки бумаги, тумблер питания.
Снизу ничего интересного, за исключением P/N. Выпускалось множество модификаций такого принтера, которые хоть и были одинаковы на вид, совершенно отличались по функциям.
Сзади два разъёма miniDIN — питание и RS-232.
Принтер со снятыми крышками.
А вот окошко датчика наличия бумаги. Сделан он весьма своеобразно — контролирует он тут не конец ленты, а просто то, что рулон близок к окончанию. Если бумага кончится, ничто не будет мешать механизму отбить весь текст по пустому месту. Также этот датчик не работает, если крышка снята — из-за попадающего света принтер начинает думать, что бумага есть всегда.
Блок питания. Очень суровый, трансформаторный. На нём же указана распиновка разъёма.
❯ Внутренности
Разумеется, принтер я разобрал. Ничего сложного в этом нет, просто берём широкую плоскую отвёртку и вставляем её в две прорези спереди, а затем осторожно снимаем крышку. К ней идут провода от тумблера и очень короткий шлейфик на плату индикации, который тоже надо аккуратно отсоединить. Вот и всё.
Снимем механизм. Принтер построен на микроконтроллере Z8. В отличие от того, что установлен в некогда упомянутом PrintPak, здешний чип с УФ-стиранием, что даже в теории даёт возможность смены шрифтов. Механизм посажен на четыре самореза через резиновые прокладки, аналогичные тем, что используются для гашения вибраций в компьютерных оптических приводах.
❯ Расходники
Разберёмся с бумагой и картриджем.
В качестве ленты используется Epson ERC-23BR. Можно, конечно, вставить и другую (ERC-23P, ERC-23B), но тогда пропадёт возможность цветной печати.
VeriFone продавала и брендированные картриджи. Не думаю, что она выпускала их сама, скорее всего, это какой-то OEM. Лента красно-чёрная, что даёт возможность печатать двумя цветами.
Моточек бумаги. В эпоху термопринтеров его ширина выглядит нестандартной — 76 мм. Восьмидесятимиллиметровый рулон в принтер не лезет.
Сама бумага двухслойная, причём нижний слой чувствителен к ударам (на фото другой рулон, где я провёл ногтем по верхнему слою, что отпечаталось на нижнем). Это позволяло печатать сразу две копии чека за раз, при этом одна оставалась у продавца, другая выдавалась клиенту.
❯ Механизм
Этот принтер по принципу действия сильно отличается от привычных нам матричников.
Вот, например, принтер подкладной печати, Star SP298.
Несмотря на то, что это чековый принтер, внутри него точно такая же конструкция с двумя шаговыми двигателями, механизмом протяжки бумаги, подвижной головкой с девятью иглами… Аналогичный механизм стоял в первых отечественных электронных кассовых аппаратах.
Здесь же устройство совершенно иное: иглы в головке расположены строго горизонтально. Управляет всем простой коллекторный двигатель, который крутит как валик протяжки бумаги, так и червячный вал, перемещающий головку. Редуктор рассчитан на то, что за один проход головки бумага сдвигается на расстояние одного пикселя.
Сама конструкция называется «shuttle impact printer» (линейно-матричный принтер), про неё даже была коротенькая статья тут. Принтеры большого формата тоже выпускались в подобном исполнении, обычно это большие и суровые аппараты для огромных объёмов печати. А вот немного про обслуживание таких монстров. По сравнению с традиционной конструкцией, у них намного выше скорость печати (конкретно у моего экземпляра она приближается к старым термопринтерам). Тем не менее, высокие даже по меркам матричных принтеров цена и шум сильно ограничивают их применение.
Цветная печать реализована просто — картридж просто наклоняется электромагнитом, отчего головка бьёт по другой половине ленты. На фото показаны оба положения.
К слову, в некоторых десктопных принтерах (типа Epson LX-300+) была возможность печати сразу четырьмя цветами (после добавления специального комплекта для модернизации под названием Color Kit). Принцип был аналогичным — шаговый мотор, синхронизированный с остальным механизмом, наклонял картридж, подставляя под головку нужную часть ленты. Тем не менее, полноценной цветной печатью назвать это было нельзя, при проходе несколькими цветами по одному месту лента пачкалась ещё не до конца высохшей краской и следующие листы становились грязными. Впрочем, для печати цветного текста или, например, цветных графиков, аппарат подходил идеально.
❯ Первый запуск
Итак, с устройством разобрались. Можно пробовать включать.
Заправляем бумагу и включаем снова с зажатой кнопкой протяжки. Принтер начнёт печатать ASCII-таблицу.
Работает.
❯ Подключение к компьютеру
Вообще, эти принтеры предназначались для работы с терминалами. Но, понятное дело, ничто не мешает нам подсоединить его к ПК.
Распиновка оказалась вот такая. К принтеру подошёл некогда спаянный мною кабель для прошивки терминала Tranz 460.
Кстати, вот как бывает: в мануале к Tranz’у распиновка скромно опущена (тогда мне пришлось прозванивать всё самому), зато здесь она дана.
В недрах принтера рядом с механизмом находятся DIP-переключатели. Помним об этом и не забываем выставить нужные параметры. Я оставил значения по умолчанию: 9600 бод, 7 бит, чёт.
Принтер можно подрубить к любому компьютеру с MS-DOS/Win9x, для чего надо установить на этот COM-порт драйвер принтера «Generic/Text Only» и в настройках включить рулонную подачу. Точно так же можно печатать из любого текстового редактора под DOS, где есть поддержка непрерывной печати и настройки ограничения числа символов в строке. Например, можно использовать всем известный «Лексикон».
Но, разумеется, если бы всё ограничивалось подключением принтера к старому компьютеру, то никакой статьи не было бы. На самом деле этот девайс умеет куда больше. Ну а как это использовать — будем разбираться.
❯ Протокол принтера
Экземпляр, как и многие другие принтеры, управляется ESC-последовательностями. Тем не менее, это не ESC/P и не ESC/POS, обычно применяющиеся в таких принтерах. Сам протокол достаточно подробно описан в неком «VeriFone Printer 250 Reference and programmer’s manual».
Для проверки пробуем отправить какую-то строку, после чего символ LF (0x0A). Принтер немедленно напечатает её.
При печати получаются огромные вертикальные поля, что тоже следует учитывать. Для того, чтобы автоматически промотать бумагу до конца, необходимо отправить символ FF (0x0C).
Избавиться от этого не выйдет даже при большом желании, такова конструкция. На фото видно, насколько большой путь проходит бумага от головки до отрывной зубчатой планки.
Разумеется, просто писать команды в терминал не так интересно, поэтому даже написал прогу для этого.
#include
#include
#include
using namespace std;
HANDLE hSerial;
uint8_t openPort(int portNumber) {
char sPortName[10];
sprintf (sPortName, "COM%i", portNumber);
hSerial = ::CreateFile(sPortName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
DCB dcbSerialParams = { 0 };
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
if(!GetCommState(hSerial, &dcbSerialParams)){
cout << "getting state error\n";
exit(0);
}
dcbSerialParams.BaudRate = CBR_9600;
dcbSerialParams.ByteSize = 7;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = EVENPARITY;
if(!SetCommState(hSerial, &dcbSerialParams)){
cout << "error setting serial port state\n";
exit(0);
}
cout << "Opened port: " << sPortName << endl;
return 1;
}
uint8_t ReadCOM(uint8_t & sReceivedByte) {
DWORD iSize;
ReadFile(hSerial, &sReceivedByte, 1, &iSize, 0); // получаем 1 байт
return iSize;
}
DWORD WriteCOM(uint8_t data) {
DWORD dwBytesWritten; // тут будет количество собственно переданных байт
DWORD dwSize = 1;
WriteFile(hSerial, &data, dwSize, &dwBytesWritten, NULL);
return dwBytesWritten;
}
void print(string data) {
int len = data.length();
if(len > 40) len = 40;
for(int i = 0; i < len; i++) WriteCOM(char(data[i]));
WriteCOM(0x0A);
}
int main()
{
SetConsoleCP(866);
SetConsoleOutputCP(866);
openPort(4);
string s;
cin >> s;
print(s);
return 0;
}
Теперь, убедившись, что принтер печатает, можно пробовать и другие команды.
❯ Размер и цвет текста
Принтер позволяет печатать с двойной шириной или двойной высотой. И если ширина переключается одним-единственным байтом, то для изменения высоты требуется отдельная ESC-последовательность.
Функции для задания этих параметров получились вот такие:
void setHeight(int height) {
WriteCOM(0x1B);
WriteCOM(0x66);
if(height == 0) {
WriteCOM(0x30);
WriteCOM(0x30);
}
else if(height == 1) {
WriteCOM(0x31);
WriteCOM(0x31);
}
WriteCOM(0x3B);
}
void setWidth(int width) {
if(width == 0) {
WriteCOM(0x1F);
}
else if(width == 1) {
WriteCOM(0x1E);
}
}
В обоих случаях 0 — обычный размер, 2 — увеличенный.
Вот для примера печать с обычной и удвоенной шириной.
❯ Русский шрифт
У принтера есть отдельная команда выставления кодировки. Но русской там нет.
С русским шрифтом всё куда интереснее, для него используется отдельная кодовая таблица. Причём она не соответствует ни 866, ни 1251, ни какой-либо ещё. Это просто все кириллические символы, которых нет в латинице (типа Э, Ю, Я, Ы, И, П, Ц…), записанные по порядку.
Соответственно, для печати строки на русском необходимо загнать так все символы, похожие на латинские, и с предварительным выбором кодировки те, которых в ASCII нет.
Осложняет ситуацию то, что буквы раскиданы как попало, то есть банальным смещением кода символа в таблице не обойтись. К слову говоря, в этом списке указаны адреса символов в ROM принтера, при печати же они лежат 20h:40h. И да, это всё, на что способен этот принтер, печать строчных букв не поддерживается.
Ничего, кстати, не смущает? Да, букв «З» и «Ч» нет, зато вместо них зачем-то есть такие истинно кириллические символы как «J», «U», “/”, а также дублирующиеся из латиницы А, Е, Т и другие. Ну да ладно.
Итак, вот такая функция получилась в итоге:
void printCyrillic(string data) {
int len = data.length();
if(len > 40) len = 40;
for(int i = 0; i < len; i++) {
if(uint8_t(data[i]) >= 0xA0) {
WriteCOM(' ');
}
else if(uint8_t(data[i]) >= 0x80 && uint8_t(data[i]) <= 0x9F) {
loadCyrillic(uint8_t(data[i]));
}
else {
WriteCOM(uint8_t(data[i]));
}
}
WriteCOM(0x0A);
}
Принцип очень простой: если символ из ASCII, грузим его так, если это кириллица, то вызываем отдельную функцию, иначе просто отправляем вместо этого символа пробел (да, печатать стиль SCP Foundation символ █ принтер тоже не умеет).
Я долго думал, как сделать саму загрузку кириллицы в буфер принтера, но так и не придумал чего-то лучше, нежели вручную прописать коды всех символов:
void loadCyrillic(uint8_t symbol) {
switch(symbol) {
case 0x80:
WriteCOM('A'); //А
break;
case 0x81:
WriteCOM(0x03);
WriteCOM(0x21); //Б
WriteCOM(0x0F);
break;
case 0x82:
WriteCOM('B'); //В
break;
case 0x83:
WriteCOM(0x03);
WriteCOM(0x37); //Г
WriteCOM(0x0F);
break;
case 0x84:
WriteCOM(0x03);
WriteCOM(0x2F); //Д
WriteCOM(0x0F);
break;
case 0x85:
WriteCOM('E'); //Е
break;
case 0x86:
WriteCOM(0x03);
WriteCOM(0x2F); //Ж
WriteCOM(0x0F);
break;
case 0x87:
WriteCOM('3'); //З
break;
case 0x88:
WriteCOM(0x03);
WriteCOM(0x25); //И
WriteCOM(0x0F);
break;
case 0x89:
WriteCOM(0x03);
WriteCOM(0x33); //Й
WriteCOM(0x0F);
break;
case 0x8A:
WriteCOM('K'); //К
break;
case 0x8B:
WriteCOM(0x03);
WriteCOM(0x2E); //Л
WriteCOM(0x0F);
break;
case 0x8C:
WriteCOM('M'); //М
break;
case 0x8D:
WriteCOM('H'); //Н
break;
case 0x8E:
WriteCOM('O'); //О
break;
case 0x8F:
WriteCOM(0x03);
WriteCOM(0x2A); //П
WriteCOM(0x0F);
break;
case 0x90:
WriteCOM('Р'); //Р
break;
case 0x91:
WriteCOM('C'); //С
break;
case 0x92:
WriteCOM('T'); //Т
break;
case 0x93:
WriteCOM(0x03);
WriteCOM(0x28); //У
WriteCOM(0x0F);
break;
case 0x94:
WriteCOM(0x03);
WriteCOM(0x24); //Ф
WriteCOM(0x0F);
break;
case 0x95:
WriteCOM(0x03);
WriteCOM(0x3D); //Х
WriteCOM(0x0F);
break;
case 0x96:
WriteCOM(0x03);
WriteCOM(0x39); //Ц
WriteCOM(0x0F);
break;
case 0x97:
WriteCOM(0x03);
WriteCOM(0xB9); //Ч
WriteCOM(0x0F);
break;
case 0x98:
WriteCOM(0x03);
WriteCOM(0x2C); //Ш
WriteCOM(0x0F);
break;
case 0x99:
WriteCOM(0x03);
WriteCOM(0x32); //Щ
WriteCOM(0x0F);
break;
case 0x9A:
WriteCOM(0x03);
WriteCOM(0x3F); //Ъ
WriteCOM(0x0F);
break;
case 0x9B:
WriteCOM(0x03);
WriteCOM(0x35); //Ы
WriteCOM(0x0F);
break;
case 0x9C:
WriteCOM(0x03);
WriteCOM(0x30); //Ь
WriteCOM(0x0F);
break;
case 0x9D:
WriteCOM(0x03);
WriteCOM(0x20); //Э
WriteCOM(0x0F);
break;
case 0x9E:
WriteCOM(0x03);
WriteCOM(0x22); //Ю
WriteCOM(0x0F);
break;
case 0x9F:
WriteCOM(0x03);
WriteCOM(0x3C); //Я
WriteCOM(0x0F);
break;
}
}
Проверяем, и вот результат:
❯ Тестовые команды
Помимо непосредственно управления печатью есть несколько служебных последовательностей, в частности, запрос статуса или информации о принтере. Увы, актуальны они лишь для некоторых ревизий, мой же принтер на них не отвечает ничего.
❯ Вот как-то так
Итак, принтер оказался определённо крайне интересным устройством, причём не только в плане конструкции, но и в плане управления (так как аппарат был создан задолго до массового распространения ESC/POS). Увы, постоянно его использовать не выйдет ввиду теперь уже малого распространения расходников для него. Тот же новый картридж пока что не вышло достать вообще нигде.
В более новых моделях терминалов VeriFone перешла на всем известную термопечать с неподвижной головкой, после чего внешние матричные и термопринтеры у неё окончательно ушли в историю.
Такие дела.
Ссылки
- Using the Verifone 250 Receipt Printer With MPI 2002
- Printer 250 Reference and programmer's manual
- Аналогичная программа, но не на Си, а на Питоне
- Ещё немного про линейно-матричную печать
- Мелкий ремонт линейно-матричного принтера OKI MX1100