Как подключить матричный принтер MD910 от кассового аппарата Миника

В прошлой своей публикации я подключал ЖКИ дисплей от старого кассового аппарата. Напомню, что я приобрел 3 аппарата за смешные деньги, разобрал их, и в итоге стал обладателем милых сердцу электронных штучек: экраны, принтеры, мелочевки….;)

В комментариях люди интересовались подключением чекового принтера. Выкроив время из своей межвахты наконец то разобрался с принтером. Начал я с матричного. Модель MD910, ниже фото.

Как подключить матричный принтер 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 и клеммник! 😉 МГТФ кончился давным-давно… И его вообще не могу найти в продаже у себя в городе. 🙁

Видео с демонстрацией работы принтера.

Спасибо за внимание!

 
Источник

Читайте также