Как подключить термопринтер к микроконтроллеру

Как подключить термопринтер к микроконтроллеру

Приветствую всех!

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

Тем не менее, подробного описания работы с такими железками я нигде не встречал.

Как подключить термопринтер к микроконтроллеру

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

❯ О чём я?

Как подключить термопринтер к микроконтроллеру

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

В данной же статье разберём именно «голые» механизмы термопринтеров (thermal printer core), не имеющие какого-то готового интерфейса. Причин для их применения достаточно — возможно, у вас есть корпус, разработанный под конкретный механизм, или же, может, хочется собрать какой-то девайс, но старый кассовый аппарат на вторичке стоит в разы дешевле такого блока, ну, или же просто к вам в руки попала такая железка, которую хочется запустить.

К слову, когда-то давно товарищем BaurzhanD был написан пост про то, как подключить принтер от кассы к Arduino. Но тот принтер был матричным (к слову, было бы неплохо попробовать найти такой экземпляр поиграться), а сегодня речь пойдёт про куда более распространённые термопринтеры.

❯ Обзор оборудования

Где же взять экземпляр на опыты?

Как подключить термопринтер к микроконтроллеру

Разумеется, его можно пойти и купить. Искать следует нечто вроде thermal printer core или embedded thermal printer. Также его можно изъять из отслужившей своё кассовой техники. В (теперь уже) далёком две тысячи семнадцатом году появился закон 54-ФЗ, сделавший обязательным использование онлайн-касс, отчего сотни аппаратов старого образца, не подлежащих модернизации, отправились на свалку и просторы вторички. До сих пор такие экземпляры можно найти даром или по до смешного малой цене. Также можно найти просто неисправную кассу: хоть печатающая головка и не вечна, импортные экземпляры весьма надёжны и редко выходят из строя. Ну а если попадётся обычный термопринтер для COM-порта или встраиваемый экземпляр от какого-нибудь торгового автомата, то тут всё вообще просто — большинство из них имеют хорошо задокументированный протокол ESC/POS, так что разбирать их вообще не надо.

Как подключить термопринтер к микроконтроллеру

Вот для примера кассовые аппараты «Феликс-РК» и «Феликс-02К». К слову, их можно запустить в качестве термопринтера и без препарирования, как это сделать, расскажу чуть позже.

Как подключить термопринтер к микроконтроллеру

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

Как подключить термопринтер к микроконтроллеру
Как подключить термопринтер к микроконтроллеру

А вот и принтер. Он от помершего терминала VeriFone VX510. Именно с ним мы и будем проводить эксперименты.

Как подключить термопринтер к микроконтроллеру

Ещё один, от Hypercom Optimum T4220. По интерфейсу подключения он ничем не отличается от предыдущего (только слегка другая распиновка). Единственная разница в механизме — у VeriFone VX510 прижимной резиновый валик снимался нажатием на рычаг, тут же его удерживают просто две пружинки и для извлечения нужно за него потянуть. Практически во всех таких термопринтерах конструкция корпуса такова, что приводной вал закреплён на крышке отсека бумаги, соответственно, для заправки необходимо просто прижать бумагу крышкой, а дальше она встанет как надо сама, не надо будет проматывать её как в матричных принтерах.

Как подключить термопринтер к микроконтроллеру

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

❯ Про кассы

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

Как подключить термопринтер к микроконтроллеру

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

Как подключить термопринтер к микроконтроллеру

А вот печать на кассовом аппарате «Феликс-02К».

Как подключить термопринтер к микроконтроллеру

С терминалами сложнее — для них нужен SDK, при помощи которого надо написать программу для печати. В своё время именно это довелось сделать товарищу vladkorotnev (именно его прошивкуя тут запустил). Но если софта у вас нет (увы, для большинства терминалов всё именно так), то напечатать что-то будет тяжело.

❯ Как устроен термопринтер

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

Как подключить термопринтер к микроконтроллеру

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

Как подключить термопринтер к микроконтроллеру

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

Как подключить термопринтер к микроконтроллеру

Обратная сторона. Керамическая основа приклеена к пластиковой корпусной детали. На шлейфике датчик наличия бумаги.

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

Как подключить термопринтер к микроконтроллеру

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

❯ Интерфейс

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

Как подключить термопринтер к микроконтроллеру

Термопечатающая головка — по сути сдвиговый регистр (один-единственный или группа из нескольких, в зависимости от модели). Она имеет всё те же линии LATCH, DATA, CLK, что и у типичного представителя таких устройств вроде 74HC595N. Каждой из шести групп точек соответствует свой регистр. Соответственно, алгоритм печати строки получается примерно следующий:

  • Загоняем в принтер группу точек.
  • Записываем их при помощи LATCH.
  • Аналогичным образом записываем остальные группы.
  • Последовательно прожигаем каждую из шести групп.
  • Проматываем бумагу.
Как подключить термопринтер к микроконтроллеру

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

❯ Что нужно для запуска

Конечно, у меня уже были эти кассовые аппараты. И я даже пользовался одним из них («Феликс-РК», с головкой под более широкую ленту) — печатал шпоры, заметки, списки и тому подобные документы. Но, конечно, хотелось запустить и «голый» экземпляр.

Итак, для того, чтобы это сделать, нам понадобится примерно следующее:

  • Собственно, сам принтер. О нём детально расскажем чуть позже.
  • МК с логическими уровнями в пять вольт. Разбираться с их преобразованием не хотелось, взял обычную Arduino Nano.
  • Драйвер шагового двигателя. Как нетрудно догадаться, нужен он для протяжки бумаги.
  • DC-DC преобразователь. Он будет создавать напряжение порядка семи вольт (точные его значения можно узнать из документации к принтеру), которое будет подаваться на нагреватели. Суровые отечественные термоголовки работают от двенадцати вольт.
Как подключить термопринтер к микроконтроллеру

Самое сложное тут — найти документацию на сам принтер. Мой экземпляр имел маркировку LTPA245S и был произведён Seiko/Epson. В поисках распиновки я даже начал реверсить схему терминала и даже определил некоторые выводы (на скриншоте то, к чему я успел прийти), но в итоге даташит таки был найден по запросу "ltpa245 technical reference".

Как подключить термопринтер к микроконтроллеру

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

Как подключить термопринтер к микроконтроллеру

А вот и долгожданная распиновка. Vp и Vdd подключаем к питанию, LATCH, DAT, CLK — к цифровым пинам контроллера. Если у вас есть, чем обеспечить стабильное питание, DST1...DST6 можно соединить вместе и подключить к одному контакту МК. К слову, в терминале это было реализовано именно так. Разъёма для этого шлейфа под рукой не оказалось, так что я просто припаял к контактам тонкий провод МГТФ.

❯ Испытания

Теперь очередь программы. На просторах нашлась вот такая статья, где (на удивление) весьма доходчиво было описано подключение термоголовки. Скачиваем скетч, перепиливаем работу с драйвером на STEP/DIR, заливаем.

Как подключить термопринтер к микроконтроллеру
Как подключить термопринтер к микроконтроллеру

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

Как подключить термопринтер к микроконтроллеру

Для дальнейших экспериментов разместил всё на обломке макетки. Драйвер шагового двигателя заменён на EasyDriver.

❯ Совершенствованию нет предела

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

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

#include "FontTable.h" #define TH_LATCH 2 #define TH_STROBE 4 #define TH_CLOCK 11 #define TH_DATA 10 #define FEED_BUTTON 12 #define STEP A0 #define DIR A1 #define MOT_EN A2 #define STEPS_REQUIRED 35 #define MOTOR_DELAY 500 #define HEATING_TIME 4000 #define COOLING_TIME 1000 #define LINE_INTERVAL 6 #define FORM_INTERVAL 15 #define LINE_FEED 0 #define FORM_FEED 1 #define PIXEL_FEED 2 #define WIDTH_SCALE 4 String currentPrinting = ""; int countDots = 1; int count2 = 1; int currentStb = 1; void setup() { pinMode(STEP, OUTPUT); pinMode(DIR, OUTPUT); pinMode(MOT_EN, OUTPUT); digitalWrite(MOT_EN, HIGH); digitalWrite(DIR, HIGH); pinMode(TH_LATCH, OUTPUT); digitalWrite(TH_LATCH, HIGH); pinMode(TH_CLOCK, OUTPUT); digitalWrite(TH_CLOCK, HIGH); pinMode(TH_DATA, OUTPUT); digitalWrite(TH_DATA, HIGH); pinMode(TH_STROBE, OUTPUT); heatOff(); pinMode(FEED_BUTTON, INPUT_PULLUP); Serial.begin(9600); while (!Serial);; delay(500); Serial.println("--------input string (ENTER=Finish)------------"); Serial.println(" ширина шрифта =" + String(WIDTH_SCALE)); } void loop() { while (digitalRead(FEED_BUTTON) == LOW) // прогон ленты по кнопке { paperFeed(FORM_FEED); } String inputString = Serial.readString(); if (inputString.length() > 0) { Serial.println("печатаем строку : '" + inputString + "'\0 "); printString(inputString); paperFeed(LINE_FEED); } } void printString(String target) { currentPrinting = target; for (int jj = 0; jj < 8; jj++) { print384DotsRow(jj); burn1Line384Dots(); paperFeed(PIXEL_FEED); } currentPrinting = ""; } void print384DotsRow(uint8_t row) { currentStb = 1; int nextSymbol = 0; int len = currentPrinting.length(); unsigned char code = currentPrinting[nextSymbol]; unsigned char horizontalPosition = 0; unsigned char vertical8dots; countDots = 1; int maxSymbols = min(round(64 / WIDTH_SCALE), len); int symbolWidth = (int)(6 * WIDTH_SCALE); while (countDots <= 384) { if ( fmod (count2, 6) == 0) // 6 точек - 1 символ (точек : 5 + 1 на разделитель) { nextSymbol++; if (nextSymbol <= maxSymbols) { code = currentPrinting[nextSymbol]; } else { code = char(' '); // оставшуюся пустую часть забиваем пробелами } // добавляем разделитель, т.е. пустую точку for (int dd = 1; dd <= WIDTH_SCALE; dd++) { if (loadToPrinter_1Dot(0) == 1) return; } count2++; horizontalPosition = 0; } else { vertical8dots = pgm_read_byte(&FontTable[code][horizontalPosition]); int vv = (vertical8dots >> row) & 0x01; // for (int dd = 1; dd <= WIDTH_SCALE; dd++) { if (loadToPrinter_1Dot(vv) == 1) return; } count2++; horizontalPosition++; } } return; } void sendToPrinter_LAT() { digitalWrite(TH_LATCH, LOW); delayMicroseconds(1); digitalWrite(TH_LATCH, HIGH); delayMicroseconds(1); } void burn1Line384Dots() { digitalWrite(TH_STROBE, HIGH); delayMicroseconds(HEATING_TIME); digitalWrite(TH_STROBE, LOW); delayMicroseconds(COOLING_TIME); heatOff(); } int loadToPrinter_1Dot(uint8_t val) { countDots++; digitalWrite(TH_CLOCK, LOW); delayMicroseconds(1); if (val == 1) { digitalWrite(TH_DATA, HIGH); } else { digitalWrite(TH_DATA, LOW); } delayMicroseconds(1); digitalWrite(TH_CLOCK, HIGH); delayMicroseconds(1); // если заполнились 64 точки , то надо просто защелкнуть LAT if ( fmod (countDots, 64) == 0) // 64 новый STB { sendToPrinter_LAT(); if (currentStb == 7) return 1; currentStb++; } return 0; } void heatOff() { digitalWrite(TH_LATCH, HIGH); digitalWrite(TH_STROBE, LOW); } void motorStep() { digitalWrite(MOT_EN, LOW); for (int k = 0; k < STEPS_REQUIRED; k++) { digitalWrite(STEP, LOW); delayMicroseconds(MOTOR_DELAY); digitalWrite(STEP, HIGH); } digitalWrite(MOT_EN, HIGH); } void paperFeed(uint8_t mode) { int i; switch (mode) { case LINE_FEED: for (i = 0; i < LINE_INTERVAL; i++) { motorStep(); } break; case FORM_FEED: for (i = 0; i < FORM_INTERVAL; i++) { motorStep(); } break; case PIXEL_FEED: motorStep(); break; } }

Шрифт имеет размер 8*5 и по виду очень напоминает тот, что используется в дисплеях на HD44780.

Ну что же, разберёмся, как это работает.

Самой важной является, естественно, загрузка данных в ТПГ. Здесь это реализовано так:

int loadToPrinter_1Dot(uint8_t val) { countDots++; digitalWrite(TH_CLOCK, LOW); delayMicroseconds(1); if (val == 1) { digitalWrite(TH_DATA, HIGH); } else { digitalWrite(TH_DATA, LOW); } delayMicroseconds(1); digitalWrite(TH_CLOCK, HIGH); delayMicroseconds(1); // если заполнились 64 точки , то надо просто защелкнуть LAT if ( fmod (countDots, 64) == 0) // 64 новый STB { sendToPrinter_LAT(); if (currentStb == 7) return 1; currentStb++; } return 0; }

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

Здесь мы увеличиваем счётчик числа загруженных точек, после чего отправляем значение на входе в ТПГ: опускаем CLOCK, выставляем нужное значение на выходе DATA, а затем вновь поднимаем CLOCK. Линейка нагревательных резисторов поделена на шесть промежутков по шестьдесят четыре точки, поэтому при заполнении очередной группы необходимо дёрнуть LATCH, чтобы принтер сохранил эти данные. Если места уже не осталось (то есть последняя группа забита), функция возвращает единицу, иначе — ноль.

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

void burn1Line384Dots() { digitalWrite(TH_STROBE, HIGH); delayMicroseconds(HEATING_TIME); digitalWrite(TH_STROBE, LOW); delayMicroseconds(COOLING_TIME); heatOff(); } void heatOff() { digitalWrite(TH_LATCH, HIGH); digitalWrite(TH_STROBE, LOW); }

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

Далее проматываем бумагу:

void motorStep() { digitalWrite(MOT_EN, LOW); for (int k = 0; k < STEPS_REQUIRED; k++) { digitalWrite(STEP, LOW); delayMicroseconds(MOTOR_DELAY); digitalWrite(STEP, HIGH); } digitalWrite(MOT_EN, HIGH); } void paperFeed(uint8_t mode) { int i; switch (mode) { case LINE_FEED: for (i = 0; i < LINE_INTERVAL; i++) { motorStep(); } break; case FORM_FEED: for (i = 0; i < FORM_INTERVAL; i++) { motorStep(); } break; case PIXEL_FEED: motorStep(); break; } }

Тут всё крайне просто — типичное управление STEP/DIR-драйвером. Значение STEPS_REQUIRED — число шагов, необходимых для того, чтобы промотать бумагу на расстояние одного пикселя. Для разных механизмов эти значения могут отличаться.

❯ Шрифт

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

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

void printString(String target) { currentPrinting = target; for (int jj = 0; jj < 8; jj++) { print384DotsRow(jj); burn1Line384Dots(); paperFeed(PIXEL_FEED); } currentPrinting = ""; } void print384DotsRow(uint8_t row) { currentStb = 1; int nextSymbol = 0; int len = currentPrinting.length(); unsigned char code = currentPrinting[nextSymbol]; unsigned char horizontalPosition = 0; unsigned char vertical8dots; countDots = 1; int maxSymbols = min(round(64 / WIDTH_SCALE), len); int symbolWidth = (int)(6 * WIDTH_SCALE); while (countDots <= 384) { if ( fmod (count2, 6) == 0) // 6 точек - 1 символ (точек : 5 + 1 на разделитель) { nextSymbol++; if (nextSymbol <= maxSymbols) { code = currentPrinting[nextSymbol]; } else { code = char(' '); // оставшуюся пустую часть забиваем пробелами } // добавляем разделитель, т.е. пустую точку for (int dd = 1; dd <= WIDTH_SCALE; dd++) { if (loadToPrinter_1Dot(0) == 1) return; } count2++; horizontalPosition = 0; } else { vertical8dots = pgm_read_byte(&FontTable[code][horizontalPosition]); int vv = (vertical8dots >> row) & 0x01; // for (int dd = 1; dd <= WIDTH_SCALE; dd++) { if (loadToPrinter_1Dot(vv) == 1) return; } count2++; horizontalPosition++; } } return; }

Итак, для печати мы копируем строку в буфер, а затем восемь раз (для каждой из восьми горизонтальных линий, из которых состоит строка символов) вызываем функцию для загрузки данных в ТПГ. Далее очередная линия прожигается, а бумага — проматывается.

Теперь надо преобразовать эти самые символы в набор горизонтальных линий для печати. Для начала необходимо получить длину строки и рассчитать число символов на ленте. Если оно превышает максимальное число, которое может влезть, то лишние просто отбрасываются. Если же их меньше, то пустое пространство забивается пробелами. Далее из Flash считывается байт, представляющий собой одну вертикальную линию размером в восемь точек, соответствующий символу с нужным кодом. При помощи битовых операций получается нужное значение точки в данной строке, которое закидывается в ТПГ. Тут используется параметр WIDTH_SCALE, предназначенный для того, чтобы регулировать ширину шрифта. Всё просто: чему равно это значение, столько раз и будет загружен каждый бит, а также, соответственно, во столько же раз меньше символов уместится на одной строке ленты. После этого необходимо отправить пустую точку. Это разделитель, нужный для того, чтобы символы не стояли впритык.

Операция эта повторяется восемь раз, после каждого раза печатается очередная строка.

❯ Печать картинок

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

Делается это примерно так:

void loadLine(uint8_t * newLine) { countDots = 1; for (int i = 0; i < 48; i++) { for (int j = 0; j < 8; j++) { loadToPrinter_1Dot((newLine[i] >> j) & 1); } } }

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

void printImageFromPROGMEM(uint8_t * image, int height) { uint8_t temp[48]; for(int i = 0; i < height; i++) { for(int j = 0; j < 48; j++) temp[j] = pgm_read_byte(&image[48*i + j]); loadLine(temp); burn1Line384Dots(); paperFeed(PIXEL_FEED); } paperFeed(FORM_FEED); }

Конвертируем изображение при помощи того же LCDAssistant, загружаем в память МК и получаем примерно следующее:

Как подключить термопринтер к микроконтроллеру

Работает.

❯ Вот как-то так

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

Автор: MaFrance351

Больше интересных статей в нашем блоге на Хабре.

3030
Начать дискуссию