Пользователь
0,0
рейтинг
16 августа 2015 в 08:31

Разработка → Мобильная печать



В наше время никого уже не удивишь печатью картиночек на листе бумаги. Существует огромный выбор принтеров (в том числе и карманных). Многие из моих знакомых покупают или собирают 3D-принтеры. Я же хочу рассказать, как я снова изобретал велосипед. Итак, снова шаг назад — это история про 2D печать. Рассказ про то, как я делал мобильный принтер для телефона на основе термального принтера (принтер, который печатает на термобумаге — не нужны чернила, только специальная бумага и электричество), модуля bluetooth и ещё нескольких мелочей.
Хочу сразу предупредить, что в электронике и электротехнике я ничего не понимаю, что я принципиально не использовал готовых решений и библиотек. Поэтому это рассказ про рукожопство и велосипеды, про проблемы с которыми я столкнулся. Продолжайте чтение на свой страх и риск.

Супертехнологическое устройство


Я думаю, что мне не нужно напоминать, что мы живём в век мобильных технологий. Я помню, как лет 20 назад я думал, что вот ещё немного и мир будет полон роботов, летающих автомобилей и ещё много-много чего удивительного. С учётом того, что я считал, что ещё немного и у всех будут свои роботы, символично и то, что роботов у нас толком нет (защитников робопылесосов прошу простить мне прошлую фразу), но есть кое-что другое… Я бы скорее всего не поверил, если бы мне тогда сказали, что у почти у каждого человека на земле в кармане будет супертехнологическое устройство, с помощью которого, люди будут, сидя в туалете, запускать симуляцию физических взаимодействий процесса вброса птиц в свиней. А если бы мне сказали, что это устройство будут называть телефоном, я бы и вовсе рассмеялся в лицо рассказчику.

Немного истории


На носу был 95-ый год (может и раньше, но я уже в прошлом абзаце написал про «20 лет назад», а «20» это красивое и круглое число, поэтому я останусь при нём) и у одного из знакомых со двора был Gameboy (DMG-001). Не думаю, что стоит рассказывать, насколько он был крут по меркам того времени и какая очередь выстраивалась, чтобы посмотреть и подержать в руках это чудо техники. В начале 2000-ых данное устройство снова напомнило о себе, т.к. к нему появилась дополнительная периферия, а именно камера и принтер. Реальной необходимости в такой камере или принтере (уж тем более такого качества) не было, но сама идея мобильного устройства, с которого можно было распечатать небольшую картиночку казалось шагом в будущее.



Назад к теме


Итак, прошло 20 лет и я подумал, что можно было бы сделать похожее устройство, только для супертехнологического устройства — телефона.

Идея была довольно проста и основывалась на следующих вещах:
  1. Термальный принтер (A2 Micro Panel Thermal Printer).
  2. Модуль bluetooth (JY-MCU BT_BOARD V1.06).
  3. Контроллер (Stellaris Launchpad LM4F120XL. Чтобы избежать вопросов «а почему?», сразу отвечу, что просто у меня была под рукой именно эта плата).
  4. Экран (LCD 84x48 Nokia 5110) и несколько led диодов для индикации состояния.
  5. Аккумулятор и регулятор (Turnigy UBEC 5A).
  6. Приложение под Android для управления принтером (читать, как «печати»).
  7. Разряд по рукожопству и слабые знания основ электротехники.

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



Чтож, для всех, кто продолжил чтение после прошлой фотографии, доступна ачивка «Пережил ад перфекциониста электротехника»…



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

Поскольку я занимаюсь разработкой программного обеспечения, то с программной частью не было проблем или заморочек. Был реализован простенький протокол обмена данными между принтером и управляющим устройством (поскольку принтер принимает команды по bluetooth, то нет смысла ограничиваться только телефоном):
  1. Используется бинарный протокол для обмана данными.
  2. Данные передаются в виде пакетов, каждый из который начинается с заголовка (что за пакет, сколько данных).
  3. На каждый пакет-запрос есть два ответа (“запрос получен” и “выполнение запроса завершено”). Можно было бы обойтись и одним ответом, но в таком режиме можно было лучше контролировать состояние принтера — нет необходимости реализовывать очередь команд на стороне контроллера, т.к. контроллер сам сообщает, что готов к выполнению нового запроса.

Помимо служебных команд типа «а жив ли принтер?», было реализовано лишь несколько команд, которые напрямую связаны с печатью (печать изображения и подача бумаги — feed).

Поскольку принтер способен печатать только монохромные изображения, то в качестве формата изображения был выбран упрощённый аналог Bitmap с 1bpp (1 бит на пиксель). Принтер способен печатать 384 точек на линию, что означает, что для печати одной полной линии необходимо 48 байт (не считая заголовка).

Проблемы со связью


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



После того, как я смог получать пакеты на стороне контроллера появилась другая проблема: данные приходили частично. До сих пор не могу точно сказать в чём именно была проблема, но тут толи лимит буфера модуля bluetooth, толи его скорость передачи / обработки данных. Проблема была такова: шлём пакет (например, килобайт данных), на стороне контроллера получаем 990-1023 байта с потерями в случайном диапазоне (случайная область данных, случайное количество). Потери данных случались примерно в 50% случаев и составляли всегда небольшой процент от исходных данных (не более 50 байт, даже при пересылки нескольких килобайт). Проблема повторялась на всех скоростях обмена данными (не то, чтобы я проверял все, но проверил как среднее значение, так и границы — 1200 и 115200 бод), но исчезала при добавлении задержки между отправкой данных. Чтобы не встраивать синтетических задержек, я решил, что просто дополню протокол обмена данными:
  1. Была добавлена контрольная сумма пакета.
  2. Данные в рамках пакета передавались фрагментами, после передачи фрагмента данных, ожидался ответ от контроллера, который пересылал порядковый номер полученного фрагмента.
  3. Был добавлен служебный пакет для установки размера фрагмента (обычно используется размер 128 байт, но возможность менять размер фрагмента я оставил).

Итак, первое, что было необходимо, чтобы определить начало пакета — это подпись (определённая последовательность байтов):
inline bool _receiveSignature( )
{
    unsigned char character;
    unsigned int offset = 0;

    while( offset != SIGNATURE_SIZE )
    {
        if( _readByte( &character ) )
        {
            if( character != SIGNATURE[offset] )
            {
                if( character == SIGNATURE[0] )
                    offset = 1;
                else
                    offset = 0;
            }
            else
                offset++;
        }
        else
            return false;
    }
    
    return true;
}


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

Код получения данных фрагментами (область данных, после получения и проверки заголовка):
unsigned char *buffer = m_buffer;
unsigned int size = m_header.size;

_sendCallback( 0 ); // resets fragment id

while( size > 0 )
{
    unsigned int fragmentSize = _min( size, m_fragmentSize );
    if( read( buffer, fragmentSize ) )
    {
        size -= fragmentSize;
        buffer += fragmentSize;
        
        _sendCallback( );
    }
    else
    {
        _sendCallback( 0 ); // sends wrong fragment id
        _setError( READ_ERROR );
        return false;
    }

    // тут был код для вызова callback (экран и прочее)
}

unsigned int checksum = _getChecksum( m_buffer, m_header.size );
if( m_header.checksum != checksum )
{
    send( COMMAND_ERROR );
    _setError( INVALID_CHECKSUM );
    return false;
}


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

Помимо самого принтера


Помимо самого принтера, контроллера и модуля связи (bluetooth), было добавлено несколько элементов для индикации состояния. Были добавлены 3 светодиода:
  1. Индикатор включения.
  2. Индикатор наличия связи с телефоном (горит, если в течении последних 5 секунд была передача данных).
  3. Индикатор печати (горит, когда происходит печать).



Помимо простых индикаторов, был добавлен монохромный экран для вывода прогресса, состояния и прочих мелочей.

Общая схема выглядит так:


Внешний вид


Для корпуса я решил использовать 4мм фанеру, резку фанеры я доверил станку с лазером. От меня требовались только векторный чертёж и деньги за работу. Чертёж вышел вот такой:


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


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


В результате, стекло было спрятано в корпус и не торчало снаружи (не стану тут снова вставлять картинку из начала статьи).

Приложение


На стороне телефона я сделал довольно примитивный интерфейс:
Выбор картинки для печати (с камеры или из файловой системы):


Настройки изображения — яркость и контрастность:


Несколько элементов управления (подключение к принтеру, печать, подача бумаги, поворот изображения):


Результаты


Качество печати довольно приличное (для термального принтера):


Как и многие принтеры, этот не исключение и немного “полосатит”:


В целом, то, что я хотел бы сказать этой историей — это то, что не стоит бояться делать вещи своими руками, не стоит бояться и велосипедов. В наше время, разработка своих устройств стала намного проще. Контроллеры не так ограничены в ресурсах, неприхотливы в работе и прощают некоторые ошибки (которые бы не допустил человек, понимающий в электротехнике). Делайте вещи своими руками, изобретайте свои велосипеды. Спасибо всем, кто дочитал до конца.
@simbiod
карма
96,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (26)

  • +3
    «Разряд по рукожопству и слабые знания основ электротехники.» — «резку фанеры я доверил станку с лазером»
    Мне бы такой станок и рукожопство. Странно, что Вам станок доверили. А так проект очень интересный, можно как мини кассу для билетов использовать.
    • +7
      Мне станок никто и не доверял. Просто человек со станком за деньги режет по предоставленному чертежу.
      Прошу не оспаривать разряд!
      • 0
        А вот мне любопытно во сколько выходит резка такого корпуса?
        • 0
          Мне обошлась в 10 евро (с учётом материалов и работы). Уверен, что можно и дешевле, но обычно для такого рода проектов, всё упирается в минимальную сумму заказа.
  • +2
    ачивка «Пережил ад перфекциониста электротехника»


    Да разве это ад?

    Не для слабонервных +21 (расчленёнка, тентакли, jpeg, текстовый редактора на arduino)








    • 0
      А вообще с Ардуино всегда довольно кучеряво и страшно выходит))
      Напрототипированные часики на декодере и на дуинке ))

  • +2
    А расскажите о самом термопринтере подробно. Протокол, энергопотребление, каке проблемы с ним были?
    • +1
      Спека протокола доступна тут (одна из версий): www.adafruit.com/datasheets/A2-user%20manual.pdf
      В среднем потребление от 1.5A до 3A.

      Сама спека — отдельный ад. Принтер не всегда реагирует на команды из спецификации. Главноая проблема — инициализация (время разогрева, количество точек, скорость печати и прочие). Как я понял, хоть значения параметров и стандартизированы, но в реальности, каждый раз (для каждого принтара) их нужно выбирать методом калибровки. С этим было больше всего проблем с принтером — выставляешь параметры, печатаешь, меняешь параметры, снова печатаешь. И так до тех пор, пока не получается нормальное качество печати.
      • +1
        Какой у них шикарный китайско-английский в мануале… «Расслабьте кнопку...» =)

        Спасибо, инересный зверек, читаю.

        Тестовый режим пробовали? Как насчет добавить «список покупок» или подобное в функциональность принтера? Это то применение, ради которого я думал когда-то его себе заводить…
        • 0
          Списки и прочее довольно просто реализовать на стороне телефона или компьютера. Взять за основу хоть WebView, а там уже любое форматирование текста. В данном случае, для меня этот принтер интересен процессом, а не результатом. А с точки зрения процесса, разработка редактора списков на Java не особо интересный процесс — похожих задач и на работе хватает. Но да, соглашусь, что для «продукта» такие фичи были бы необходимы, как и печать нотификаций или почты, прогнозов погоды и прочего.
  • +2
    Вспоминается подобный проект на кикстартере, кажется не набрал необходимую сумму. А жаль, очень клевый.
    www.gizmag.com/little-printer-customized-newspapers/20660
    • 0
      Как это не набрал? Вот он: http://littleprinter.com/
      Стоит только дороговато.
  • 0
    На алиэкспрессе таких принтеров завались. Не оспариваем разряд по рукожопству автора, но просто если кому-то нужно такое без рукожопства, то 50-100 баксов и али Вам поможет:)
    • 0
      Многовато. Он около 20-25 стОит.
      В поставке китайский драйвер под винду, либо кривой фильтр CUPS (для остальных)
      Я его с полгода назад отреверсил и написал свой (есть на гитхабе)
      • 0
        Я думаю, имеелся в виду синезубый. Он как раз 50-80 стоит. А USBшный да, меньше 30.
      • 0
        А можно сразу ссылку на гитхаб и на модель принтера на алиэкспрессе или где вы его брали.
        • +1
          Я брал два. На али из daily deal (когда его по десятке продавали), а ещё один на dx.
          Они очень похожи внешне, но, как оказалось, совершенно разные.
          Один — xp-58. У него в комплекте несколько достаточно толковых тулзов, показывающие возможности. И его фильтр cups вполне работоспособен (правда, 32-битный — нужно будет поставить одну 32-битную либу из cups)
          Второй — zj-58. Вот его завести в cups не удалось вообще никак (фильтр просто сразу падает в корку). Его пришлось переписать самому.
          К слову, как оказалось, xp-58 тоже работает с этим самописным фильтром; правда, выдаёт немного мусора. Можно подправить фильтр, чтобы он работал с обеими моделями, но чего-то лень :)

          В общем, на гитхабе роется по запросу zj-58, или у меня в профиле (есть в личке).
          Покупать практически можно «по цвету», приводить ссылку на конкретную модель не вижу смысла.
      • 0
        есть на гитхабе

        имхо, не нужно стесняться давать прямые ссылки на такие репозитории.
  • +2
    Круто! Не удивляюсь уже что одинаковые мысли приходят разным людям совсем. Я делал тоже самое но остановился на лишь на передаче бинарных файлов с телефона и сделал намного проще, а потом забил и выложил что есть на гитхаб 23di.github.io/Printy-Site
  • 0
    Я, пожалуй вставлю свои пять копеек по схожей задаче (печать с планшета на термоленте), ударившей в мне в голову года 2 назад.
    Поискал принтеры (ваш тоже видел), но буквально за дополнительные 10-15 баксов нашелся вариант с блекджеком блютусом и аккумулятором.
    Насколько мне известно, эта штука уже два года трудится где-то в Сибири на складе всякого добра и из проблем имеется только немного выгоревшая печатающая головка (или там термолента не очень).
    Если есть вопросы, пишите, буду рад ответить. Всякие описания протоколов и прочее — по запросу. Протокол довольно простой, ESC-кодом устанавливается режим, затем идут данные (штука сама умеет QR коды и штрихкоды на любой вкус). Кому интересно про протокол есть часть одной из первых версий, сделанных на коленке для Proof of concept на c# (полностью исходники, увы, отданы вместе с устройством).
    Код
                    public bool sendChar(char c){
                            if (!p.IsOpen) return false;
                            try {
                                    this.write(c.ToString());
                            } catch (Exception ex){
                                    Error = ex.Message;
                                    return false;
                            }
                            return true;
                    }
                    public bool sendInt(int i) {return this.sendChar((char) i);}
                    public void setUnderline(bool s){
                            this.sendState('-',(s?1:0));
                    }
                    public void setEmph(bool s){
                            this.sendState('E',(s?1:0));
                    }
                    public void setDS(bool s){
                            this.sendState('D',(s?1:0));
                    }
                    public void setRotate(int s){
                            s=Math.Abs(s);
                            if (s>3) s=0;
                            this.sendState('V',s);
                    }
                    public void setInverse(bool s){
                            this.sendState('\x1D','B',(s?1:0));
                    }
                    public void sendState(char ch,int val){
                            this.sendState('\x1B',ch,val);
                    }
                    public void sendState(char init,char ch,int val){
                            this.sendChar(init);
                            this.sendChar(ch);
                            this.sendInt( val);
                    }
                    public void setSpacing(int dots){
                            dots=Math.Abs(dots);
                            if (dots>255) dots=8;
                            this.sendState('3',dots);
                    }
                    public void setSpacing(){
                            this.setSpacing(8); // default;
                    }
                    public void setJustify(int v){
                            v=Math.Abs(v);
                            if (v>3) v=0;
                            this.sendState('a',v);
                    }
                  public void test(bool full){
                           
                            if (full) {
                            this.setUnderline(true); this.text("UnderLine "); this.setUnderline(false);
                            this.setEmph(true); this.text("Emph");this.setEmph(false);
                            this.setInverse(true); this.text("INVERSE ");this.setInverse(false);
                            this.nline();
                            }
                            //this.setDS(true);this.text("Example DS");this.setDS(false);
                            //this.qrcode("HelloHello",20,5); this.qrcode("Тестовая",20,5);
                           
                            this.text("Привет мир");
                            this.nline();
                            this.sync();
                                   
                    }
    



  • –1
    Википедия не рекомендует возиться с термолентой:
    В состав чековой ленты входит вредное вещество бисфенол А. Его наличие там загрязняет переработанную бумагу[1][2]. При наличии вещества в бумаге даже в следовых концентрациях оно может попадать в кровь через кожу. В статье[3] приводится расчёт, что если подержать чек в течение 5 секунд указательным и средним пальцем, то в среднем 1 мкг бисфенола А (точнее 0,2-6 мкг) проникает через сухую кожу, и примерно в десять раз больше через мокрую или жирную.
    • 0
      Любопытно и то, что при длительном воздействии это вещество может вызывать ожирение (не только ожирение, но и его тоже).
      Теперь у каждого кассира с избыточным весом есть оправдание — во всём виновата термолента.
      • –2
        Человек, бегущий от управления своей жизнью еще и не такое оправдание придумает :)
  • 0
    Я как увидел ваш проект, сразу вспомнил про текстовые РПГ почему-то)
    PS
    Код картинкой это страшный грех.
    <source lang="cpp"></source>


  • +1
    Мне вообще всегда хотелось мелкий принтер, но не термо, а матричный, типа такого ru.aliexpress.com/item/2014-new-hot-USB-port-Free-shipping-44mm-wide-dot-matrix-printer-RD-T16-Micro-Usb/32345130867.html
    • 0
      Проблема в том, что есть дополнительный расходник — кассета, которую нужно менять.
      Ну и количество точек тоже не торт.
      В остальном — интересная игрушка.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.