В комментариях люди интересовались подключением чекового принтера. Выкроив время из своей межвахты наконец то разобрался с принтером. Начал я с матричного. Модель MD910, ниже фото.
Ну поскольку Ардуинка, паяльник, и прочая-прочая давным давно ждут своего выхода на сцену решил я таки его неприкаенного подключить и что-нибудь напечатать. Что самое главное в нашей жизни? Это инструкция! Так вот озадачился я поиском datasheet-а на этот принтер. Нагуглил, правда самой первой редакции. Там нашлась схема распиновки выводов, тайминги, немного про устройство принтера. Не было параметров подключения светодиода оптопары и еще нескольких данных. На ум пришла идея спросить эту информацию в мастерских по ремонту кассовых аппаратов. Здесь, я Вам скажу меня ждало разочарование — эти парни лениво протянули, что у них никаких datasheet-ов, сервисных manual-ов и даже схем кассовых аппаратов у них нет, и вообще они тут делом заняты…;)
А если спросить эту информацию непосредственно у самих производителей (Citizen Business Machines)? Я так и сделал — написал им имейл — так мол и так, я радиолюбитель, сейчас хочу прикрутить этот принтер и печатать на нем листовки, будьте добры и любезны предоставьте datasheet. И Citizen Systems Europe мне через пару дней прислало заправшиваемую информацию!
Собрал платы подключения датчиков — а их там два: Dot Pulse и Reset Pulse. Спаял драйвера для управления двигателем и печатающей головкой.
Цифры обозначают к каким выводам принтера подключены эти точки. Поскольку на входы Ардуино подаются инвертированные сигналы (например 1 в случае, если выключатель разомкнут), то при написании программы необходимо учитывать этот момент.
Что касается драйверов для мотора и печатающей головки. В загашниках лежало несколько микросхем SMA4033 и STA471A, которые были выпаяны из неисправного матричного принтера Эпсон (типа FX800). Вот перипетии судеб микросхем — старый матричный принтер был разобран на запчасти, чтобы через несколько лет реинкарнироваться в облике нового принтера! 😉
Документация была найдена при первом же запросе Гугла (кстати, я их выложил на GitHub). Эти микросхемы представляют собой 4 транзистора Дарлингтона в едином корпусе, разница между ними (кроме напряжений питания) в наличии защитных диодов в SMA4033. Мне они очень понравились — отличные параметры, можно приклеить на радиатор и просто припаять проводки к выводам, корпус относительно массивный, так что легко выдерживает выпайку при помощи строительного фена! 😉
Схема подключения мотора. Используется только два канала из четырех микросхема SMA4033.
Схема подключения печатающей головки к микросхемам STA471A (коллекторы). Необходимо помнить, что печатающая голова состоит из 2 блоков по 4 иголки. Поэтому нам нужно 8 силовых выходов.
Общее для обоих микросхем. Выходные пины Ардуино через резисторы сопротивление (680 ом — 1к) подключены на базы транзисторов Дарлингтона.
#define b1stHead_D 8 #define b1stHead_B 9 #define b1stHead_A 10 #define b1stHead_C 11 #define b2ndHead_H 4 #define b2ndHead_F 5 #define b2ndHead_E 6 #define b2ndHead_G 7 #define Motor 13 #define Feed 12 #define DotPulse 3 #define ResetPulse 2
Как работает принтер?
Печатающая головка состоит из двух одинаковых частей. Каждая часть включает в себя четыре вертикально стоящие иголки. Однако четные и нечетные иголки чуть-чуть сдвинуты относительно друг друга, полагаю, чтобы они не сильно мешали друг-другу — ведь расстояние между ними совсем крошечное! Однако это немного усложняет алгоритм печати: вначале нужно напечатать нечетные точки, потом, дождавшись, когда головка сдвинется на 0.5 точки напечатать четные точки.
Что касается двух половинок. Левая часть печатающей головки печатает первую половину строки, правая — вторую. Чтобы напечатать строку из 8 пикселей высотой нужно сделать два прохода. Вот смотрите, за четыре первых такта печатается один первый столбец буквы A. Первый такт — печатаем точки A и C, потом 1 такт — головка сдвигается на половину точки, потом печатаются точки B и D, потом опять сдвигается на полточки. Потом опять за четыре такта печатается следующий столбец буквы A.
При этом в принтере всего 144 столбца, 72 для каждой части печатающей головки. Если будем использовать шрифт 8×8, то мы сможем напечатать 18 символов в каждой строке.
Мы выяснили каким образом печатает принтер. Теперь это дело нужно оформить в виде программного кода.
Что нам нужно, чтобы напечатать текст?
1) входная информация — строка из 18-ти символов;
2) шрифт
3) программа
Большинство растровых шрифтов устроено следующим образом: каждый байт (или несколько байтов) описывает одну строчку символа, следующий — следующую строчку и т.д. Это удобно для отображения на экране — поскольку используется построковое построение изображения. В случае печати, иголочки стоят вертикально. Поэтому нам был бы удобен шрифт, который описан по стоблцам. Однако, такого я не нашел. Пришлось исходить из того, что есть.
Кроме того, я решил печать организовать следующим образом: первый шаг — это рендеринг строки текста в буфер, второй шаг — это непосредственно печать подготовленной информации из буфера рендеринга. Это также дает некоторую уверенность в том, что тайминги печати будут соблюдены. Написание кода рендеринга было самой сложной частью программы.
Давайте посмотрим, как работает рендеринг
Здесь красным цветом выделены биты и байты экранного шрифта, зеленым цветом — биты и байты шрифта для принтера. Основная задача рендеринга — сконвертировать экранный шрифт в принтерный в буфер. Обработка выглядит примерно так: все D7-ые биты (всех 8-и байтов шрифта) необходимо превратить в биты D7-D0 (первый столбец), все D6-ые биты необходимо превратить в биты D7-D0 (второй столбец). Таким образом экранный шрифт для латинской буквы A (0x30, 0x78, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0x00) превратится в последовательность (0x3E, 0x7E, 0xC8, 0xC8, 0x7E, 0x3E, 0x00, 0x00) для буфера рендеринга. Надеюсь, что приведенный ниже кусок кода поможет понять алгоритм рендеринга.
С еще одной большой проблемой я боролся целый вечер: при попытке отработки кода рендеринга — в терминал выбрасывался мусор, если закомментировать этот участок, все работало без проблем. Мне показалось, что переполняется/переписывается оперативная память и поэтому возникает мусор в выдаче. Прочитав про то, что Ардуино хранит все переменные в ОЗУ я понял, что всему вина — это 2 килобайта данных шрифта. Пришлось хранить его непосредственно в теле программы (флэш) и обращаться через специальные функции. Все заработало. Здесь и здесь более подробно об этом.
// FontData=pgm_read_byte(&(CP1251Font[Address])); // https://www.arduino.cc/reference/en/language/variables/utilities/progmem/ // к сожалению оперативная память всего 1К поэтому фонт размещен в программной памяти // http://www.nongnu.org/avr-libc/user-manual/pgmspace.html // длина печатаемой строки 18 символов (144 / 8) +1 байт на символ конца строки = 19 байт void MD910_RenderPrintStr(char String2Print[19]) { byte TmpVal[8], MaskBit[8]; byte i,j,k, Code, Column, Value; word FontStart; //*************************************************************** Column=0; // индекс на MD910_Buffer[] for(j=0;j<=17;j++) // отрабатываем каждый символ { Code=byte(String2Print[j]); FontStart=Code*8; // индекс, указывающий на начало данных шрифта в CP1251Font[] for (k=0; k<=7; k++) { TmpVal[k]=pgm_read_byte(&(CP1251Font[FontStart+k])); // готовлю временный буфер (0) }; for(i=0;i<=7;i++) { for (k=0; k<=7; k++) { MaskBit[k]=(TmpVal[k] & 128) >> k; // выделяю старший бит и сохраняю его в матрице масок (1) }; Value=0; for (k=0; k<=7; k++) { Value=(Value | MaskBit[k]); // матрица масок превращается в одну маску (2) }; MD910_Buffer[Column]=Value; // столбец сформирован из общей маски (3) for (k=0; k<=7; k++) { TmpVal[k]=TmpVal[k] << 1; // сдвигаю данные временного буфера (4) }; Column++; // переходим к обработке следующей колонке }; }; //*************************************************************** };
void MD910_PrintBuffer() { byte PinA, PinB, PinC, PinD, PinE, PinF, PinG, PinH; Serial.println("Printing...."); //*********************************************************************************************************** DP_Count=0; // печатаем верхнюю часть строки *************************** if (RP_Status()==false) { for(byte j=0;j<=71;j++) { PinA=MD910_Buffer[j] & 0x80; PinB=MD910_Buffer[j] & 0x40; PinC=MD910_Buffer[j] & 0x20; PinD=MD910_Buffer[j] & 0x10; PinE=MD910_Buffer[j+72] & 0x80; PinF=MD910_Buffer[j+72] & 0x40; PinG=MD910_Buffer[j+72] & 0x20; PinH=MD910_Buffer[j+72] & 0x10; // 1 такт // можно включать точки A,C и E,G (нечетные) if (PinA>0) digitalWrite(b1stHead_A, HIGH); // if (PinC>0) digitalWrite(b1stHead_C, HIGH); // if (PinE>0) digitalWrite(b2ndHead_E, HIGH); // if (PinG>0) digitalWrite(b2ndHead_G, HIGH); // while (DP_Status()==true) {}; while (DP_Status()==false) {}; DP_Count++; digitalWrite(b1stHead_A, LOW); digitalWrite(b1stHead_C, LOW); digitalWrite(b2ndHead_E, LOW); digitalWrite(b2ndHead_G, LOW); while (DP_Status()==true) {}; while (DP_Status()==false) {}; DP_Count++; // 2 такт // можно включать точки B,D и F,H (четные) if (PinB>0) digitalWrite(b1stHead_B, HIGH); // if (PinD>0) digitalWrite(b1stHead_D, HIGH); // if (PinF>0) digitalWrite(b2ndHead_F, HIGH); // if (PinH>0) digitalWrite(b2ndHead_H, HIGH); // while (DP_Status()==true) { }; while (DP_Status()==false) { }; DP_Count++; digitalWrite(b1stHead_B, LOW); digitalWrite(b1stHead_D, LOW); digitalWrite(b2ndHead_F, LOW); digitalWrite(b2ndHead_H, LOW); while (DP_Status()==true) {}; while (DP_Status()==false) {}; DP_Count++; }; } while (RP_Status()==false) {}; // ждем когда головка переместится в начало while (RP_Status()==true) {}; // теперь ждем когда головка переместится в начало области печати // печатаем нижнюю часть строки *************************** if (RP_Status()==false) { for(byte j=0;j<=71;j++) { PinA=MD910_Buffer[j] & 0x08; PinB=MD910_Buffer[j] & 0x04; PinC=MD910_Buffer[j] & 0x02; PinD=MD910_Buffer[j] & 0x01; PinE=MD910_Buffer[j+72] & 0x08; PinF=MD910_Buffer[j+72] & 0x04; PinG=MD910_Buffer[j+72] & 0x02; PinH=MD910_Buffer[j+72] & 0x01; // 1 такт // можно включать точки A,C и E,G (нечетные) if (PinA>0) digitalWrite(b1stHead_A, HIGH); // if (PinC>0) digitalWrite(b1stHead_C, HIGH); // if (PinE>0) digitalWrite(b2ndHead_E, HIGH); // if (PinG>0) digitalWrite(b2ndHead_G, HIGH); // while (DP_Status()==true) {}; while (DP_Status()==false) {}; DP_Count++; digitalWrite(b1stHead_A, LOW); digitalWrite(b1stHead_C, LOW); digitalWrite(b2ndHead_E, LOW); digitalWrite(b2ndHead_G, LOW); while (DP_Status()==true) {}; while (DP_Status()==false) {}; DP_Count++; // 2 такт // можно включать точки B,D и F,H (четные) if (PinB>0) digitalWrite(b1stHead_B, HIGH); // if (PinD>0) digitalWrite(b1stHead_D, HIGH); // if (PinF>0) digitalWrite(b2ndHead_F, HIGH); // if (PinH>0) digitalWrite(b2ndHead_H, HIGH); // while (DP_Status()==true) { }; while (DP_Status()==false) { }; DP_Count++; digitalWrite(b1stHead_B, LOW); digitalWrite(b1stHead_D, LOW); digitalWrite(b2ndHead_F, LOW); digitalWrite(b2ndHead_H, LOW); while (DP_Status()==true) {}; while (DP_Status()==false) {}; DP_Count++; }; } while (RP_Status()==false) {}; while (RP_Status()==true) {}; //*********************************************************************************************************** Serial.println("Done!"); }
Еще интересный сюрприз преподнес мне Power Bank от Xiaom, который я планировал использовать как источник питания. Просто он не включался от нагрузки в виде Ардуино, при насильном включении (нажав на кнопку) он включался, питал нагрузку пару секунд, потом отрубался. Причина — думаю одна: Ардуинка не так много потребляет, моторчики и печатающая головка (основной потребитель) тянет импульсами, но не постоянно (нагрузка скачет от десятков миллиампер до пары ампер)…
Пришлось городить блок питания из 12-ти вольтового 5-ти амперного блока питания для светодиодной ленты + 2 DC-DC конвертера на народных LM2596. В выходную цепь +5 вольт я включил по диоду Шоттки + резистор 2,5 ома для ограничения токов.
Поскольку принтер матричный, для печати используется картридж с лентой, да-да, как в старину. Я пытался попробовать восстановить родной картридж, замочил его в воде на недельку. Попробовал разобрать, чтобы пропитать поролон краской-мастикой. Поролон просто рассыпался… Ближайший картридж от меня находится в Самаре. 🙁
Решил тогда попробовать найти бумагу, чувствительную к ударам (когда ее покупал, продавец пару раз меня предупредил, что эта лента не подходит никуда 😉 Пришлось ее заверять, что я все понял, и претензий потом от меня не будет...;) ). Нашел только ленту шириной 80 мм, пришлось резать на кусочки и уменьшать ширину до 57 мм, добрым старым ламповым способом при помощи шариковой ручки и линейки… Зато печатает! 😉
Как выглядит финальный результат. На 20 мм фанерке закреплен принтер и платы управления. При монтаже использован ШВВП 2*0.5мм, коннекторы WAGO и клеммник! 😉 МГТФ кончился давным-давно… И его вообще не могу найти в продаже у себя в городе. 🙁
Видео с демонстрацией работы принтера.
Спасибо за внимание!
Источник