Как-то просматривая объявления на OLX, я наткнулся на распродажу старой электроники по очень низким ценам, в итоге было куплено три кассовых аппарата и один модем (на разбор с целью пополнения запасов). Цена вопроса 1500 тенге — примерно 250 рублей.

image

Разобрав это добро я стал обладателем трех ЖК дисплеев и трех чековых принтеров.

Как подключить дисплей к Ардуино? На плате ЖК дисплея было обнаружено несколько надписей. 5104219-01, 251 12, 251-Т2. Использована микросхема Holtek HT1621B, datasheet был скачан и изучен. К сожалению, тип используемого ЖКИ так и не был опознан.

Прозвонив тестером выводы (6 выводов) я определил их назначение:

1) Data
2) WR
3) CS
4) неизвестно*
5) Gnd
6) Vdd (+5 v)
* — подключены резисторы, конденсаторы, поскольку мне нужно было, я и не разбирался глубоко.

image

Я использовал готовые процедуры для работы с портами HT1621 из Ардуино.

Микросхема HT1621 128 ячеек для ЖКИ, которые организованы следующим образом 32x4 bits, в памяти это 16 байт. В моем ЖКИ были подключены выводы Com0, Com1, Com2 и все сегменты 0-31.

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

image

Теперь стало ясно, как управлять дисплеем. Так, например, чтобы включить сегменты B и C в первой позиции (самая левая) нужно изменить биты D5 и D6 на 1 по адресу 0x05, остальные биты должны быть оставлены без изменений, поскольку они повлияют на другие позиции.

Если обратить внимание на таблицу, можно увидеть, что для отображения какой — либо цифры нужно поменять несколько битов в нескольких байтах. Эту задачу я решил следующим образом. Были подготовлены несколько таблиц:

  1. HT1681_Address(72 байта), HT_1681_Value(72 байта)
  2. b7SegDsp(10 байт)
  3. HT1681_Screen(16 байт)

HT1681_Address(72 байта), HT_1681_Value(72 байта) эти две таблицы связаны между собой следующим образом — вторая таблица представляет собой маску (для OR) для установки соответствующего бита в 1, причем первые девять байтов отвечают за сегменты A,B,C,D,E,F,G + десятичную точку + вернее «подчеркивание» первого знакоместа, следующие девять — за сегменты второго знакоместа, и так далее. Первая таблица — представляет собой адреса, которые соответствуют второй таблице. Для понимания внизу приведена иллюстрация.



Код для Ардуино
// HT1681_Segments 1A 1B 1C 1D 1E 1F 1G	1DP 1Up	..... 8A 8B 8C 8D 8E 8F 8G 8DP 8Up
const byte HT1681_Address[]=
{
  0x04,0x05,0x05,0x04,0x04,0x04,0x04,0x05,0x0E,
  0x06,0x06,0x06,0x06,0x05,0x05,0x06,0x06,0x04,
  0x07,0x08,0x08,0x07,0x07,0x07,0x07,0x08,0x05,
  0x09,0x09,0x09,0x09,0x08,0x08,0x09,0x09,0x07,
  0x0A,0x0B,0x0B,0x0A,0x0A,0x0A,0x0A,0x0B,0x08,
  0x0C,0x0C,0x0C,0x0C,0x0B,0x0B,0x0C,0x0C,0x0A,
  0x0D,0x0E,0x0E,0x0D,0x0D,0x0D,0x0D,0x0E,0x0B,
  0x0F,0x0F,0x0F,0x0F,0x0E,0x0E,0x0F,0x0F,0x0D
};

const byte HT_1681_Value[]=
{
  0x04,0x40,0x20,0x08,0x20,0x40,0x02,0x80,0x08,
  0x40,0x04,0x02,0x80,0x02,0x04,0x20,0x08,0x80,
  0x04,0x40,0x20,0x08,0x20,0x40,0x02,0x80,0x08,
  0x40,0x04,0x02,0x80,0x02,0x04,0x20,0x08,0x80,
  0x04,0x40,0x20,0x08,0x20,0x40,0x02,0x80,0x08,
  0x40,0x04,0x02,0x80,0x02,0x04,0x20,0x08,0x80,
  0x04,0x40,0x20,0x08,0x20,0x40,0x02,0x80,0x08,
  0x40,0x04,0x02,0x80,0x02,0x04,0x20,0x08,0x80
};


b7SegDsp(10 байт) Эта таблица кодирует какие сегменты должны включаться для отображения числа.Первый байт — это кодирование числа 0, последний байт кодирует 9. Старшие семь бит D7-D1 кодируют сегменты A-G, младший D0 — не используется, я его установил в 0, кроме того, это экономит одну операцию битового сдвига влево — я использую маску 0x80 для проверки бита.



HT1681_Screen(16 байт) просто видеопамять, все 16 байтов. Вначале рендерится все в память, а затем все копируется в HT1621.

Ниже код, который выполняет рендеринг в HT1681_Screen, который потом просто выводится в память микросхемы для отображения.

// 0<=bPos<=7 - в какой позиции написать число bNum. 
// Если нужна десятичная точка - то lDecimalPoint=1
// Если нужно верхнее "подчеркивание" - то lUpperLine=1
void HT1681_Display(byte bPos, byte bNum, byte lDecimalPoint, byte lUpperLine)
{
  byte i, bCheckByte, bAddr, bValue;
  bCheckByte=b7SegDsp[bNum]; // получаем маску - какие сегменты мы должны включить

  for(i=bPos*9;i<=bPos*9+6;i++) // поскольку сегментов 7 штук 
  {
    if ( (bCheckByte & 0x80) == 0x80) // если старший сегмент =1 то должны 
    {
      bAddr=HT1681_Address[i]; // определить конкретный байт
      bValue=HT_1681_Value[i]; // и определить конкретную маску
      HT1681_Screen[bAddr] = (HT1681_Screen[bAddr] | bValue); // выполним операцию OR для включения соответствующего бита в 1
    };
    bCheckByte=bCheckByte << 0x01; // сдвигаем весь байт влево
  }

  if (lDecimalPoint==1) // отдельно отрабатываем десятичную точку
  {
    bAddr = HT1681_Address[(bPos*9)+7];
    bValue = HT_1681_Value[(bPos*9)+7];
    HT1681_Screen[bAddr] = (HT1681_Screen[bAddr] | bValue);
  }; 

  if (lUpperLine==1) // отдельно отрабатываем верхнее подчеркивание
  {
    bAddr = HT1681_Address[(bPos*9)+8];
    bValue = HT_1681_Value[(bPos*9)+8];
    HT1681_Screen[bAddr] = (HT1681_Screen[bAddr] | bValue);
  };
}

Под спойлером выложен весь код для Arduino
// использованы наработки из http://arduino.ru/forum/programmirovanie/ht1621
// http://arduino.ru/forum/programmirovanie/ht1621#comment-76371

#define sbi(x, y)  (x |= (1 << y))   /*set Register x of y*/
#define cbi(x, y)  (x &= ~(1 <<y ))  /*Clear Register x of y*/       

#define uchar   unsigned char 
#define uint   unsigned int 

//Defined HT1621's command  
#define  ComMode    0x48  //4COM,1/2bias  1000    010 1001  0  
#define  RCosc      0x30  //on-chip RC oscillator(Power-on default)1000 0011 0000 
#define  LCD_on     0x06  //Turn on LCD 
#define  LCD_off    0x04  //Turn off LCD 
#define  Sys_en     0x02  //Turn on system oscillator 1000   0000 0010 
#define  CTRl_cmd   0x80  //Write control cmd 
#define  Data_cmd   0xa0  //Write data cmd   

// //Define port    HT1621 data port
#define CS   2  //Pin 2 as chip selection output
#define WR   3  //Pin 3 as read clock  output
#define DATA 4  //Pin 4 as Serial data output

#define CS1    digitalWrite(CS, HIGH) 
#define CS0    digitalWrite(CS, LOW)
#define WR1    digitalWrite(WR, HIGH) 
#define WR0    digitalWrite(WR, LOW)
#define DATA1  digitalWrite(DATA, HIGH) 
#define DATA0  digitalWrite(DATA, LOW)

#define DelayTime 1000

byte bMask;

// HT1681_Segments 1A 1B 1C 1D 1E 1F 1G	1DP 1Up	..... 8A 8B 8C 8D 8E 8F 8G 8DP 8Up
const byte HT1681_Address[]=
{
  0x04,0x05,0x05,0x04,0x04,0x04,0x04,0x05,0x0E,
  0x06,0x06,0x06,0x06,0x05,0x05,0x06,0x06,0x04,
  0x07,0x08,0x08,0x07,0x07,0x07,0x07,0x08,0x05,
  0x09,0x09,0x09,0x09,0x08,0x08,0x09,0x09,0x07,
  0x0A,0x0B,0x0B,0x0A,0x0A,0x0A,0x0A,0x0B,0x08,
  0x0C,0x0C,0x0C,0x0C,0x0B,0x0B,0x0C,0x0C,0x0A,
  0x0D,0x0E,0x0E,0x0D,0x0D,0x0D,0x0D,0x0E,0x0B,
  0x0F,0x0F,0x0F,0x0F,0x0E,0x0E,0x0F,0x0F,0x0D
};


const byte HT_1681_Value[]=
{
  0x04,0x40,0x20,0x08,0x20,0x40,0x02,0x80,0x08,
  0x40,0x04,0x02,0x80,0x02,0x04,0x20,0x08,0x80,
  0x04,0x40,0x20,0x08,0x20,0x40,0x02,0x80,0x08,
  0x40,0x04,0x02,0x80,0x02,0x04,0x20,0x08,0x80,
  0x04,0x40,0x20,0x08,0x20,0x40,0x02,0x80,0x08,
  0x40,0x04,0x02,0x80,0x02,0x04,0x20,0x08,0x80,
  0x04,0x40,0x20,0x08,0x20,0x40,0x02,0x80,0x08,
  0x40,0x04,0x02,0x80,0x02,0x04,0x20,0x08,0x80
};

// 0-9 7Segment Display Description
// D7 D6 D5 D4 D3 D2 D1 D0
// A  B  C  D  E  F  G  *
//

const byte b7SegDsp[]=
{
  0xFC,0x60,0xDA,0xF2,0x66,0xB6,0xBE,0xE0,0xFE,0xE6
};

// virtual screen 12x6 bits = 72 bits (000X000X)
byte HT1681_Screen[]=
{
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};


// *********************************************************************************************************************************

void SendBit_1621(uchar sdata,uchar cnt) //High bit first
{ 
  uchar i; 
  for(i=0;i<cnt;i++) 
  { 
    WR0;
    delayMicroseconds(1); 
    if(sdata&0x80) DATA1; 
    else DATA0; 
    delayMicroseconds(1);
    WR1;
    delayMicroseconds(1);
    sdata<<=1; 
  } 
  delayMicroseconds(1);
}


void Init_1621(void) 
{
  SendCmd_1621(Sys_en);
  SendCmd_1621(RCosc);    
  SendCmd_1621(ComMode);  
  SendCmd_1621(LCD_on);
} 

void SendCmd_1621(uchar command) 
{ 
  CS0; 
  SendBit_1621(0x80,4);    
  SendBit_1621(command,8);  
  CS1;                    
} 

void Write_1621(uchar addr,uchar sdata) 
{ 
  addr<<=3; 
  CS0; 
  SendBit_1621(0xa0,3);     //Write MODE“101” 
  SendBit_1621(addr,6);     //Write addr high 6 bits
  SendBit_1621(sdata,8);    //Write data  8 bits
  CS1; 
} 

void LCDoff(void) 
{  
  SendCmd_1621(LCD_off);  
} 

void LCDon(void) 
{  
  SendCmd_1621(LCD_on);  
} 

void HT1681_Clear(void)
{
  byte bAddress=0x00;
  for(byte j=0;j<=0x0F;j++)
  {
    bAddress=j;
    HT1681_Screen[bAddress]=0x00;
  };
};

void HT1681_Show(void)
{
  byte i;
  for(i=0;i<=0x0F;i++)
  {
  Write_1621(i, HT1681_Screen[i]);
  };
}


// 0<=bPos<=7 - в какой позиции написать число bNum. 
// Если нужна десятичная точка - то lDecimalPoint=1
// Если нужно верхнее "подчеркивание" - то lUpperLine=1
void HT1681_Display(byte bPos, byte bNum, byte lDecimalPoint, byte lUpperLine)
{
  byte i, bCheckByte, bAddr, bValue;
  
  bCheckByte=b7SegDsp[bNum]; // получаем маску - какие сегменты мы должны включить

  for(i=bPos*9;i<=bPos*9+6;i++) // поскольку сегментов 7 штук 
  {
    if ( (bCheckByte & 0x80) == 0x80) // если старший сегмент =1 то должны 
    {
      bAddr=HT1681_Address[i]; // определить конкретный байт
      bValue=HT_1681_Value[i]; // и определить конкретную маску
      HT1681_Screen[bAddr] = (HT1681_Screen[bAddr] | bValue); // выполним операцию OR для включения соответствующего бита в 1
    };
    bCheckByte=bCheckByte << 0x01; // сдвигаем весь байт влево
  }

  if (lDecimalPoint==1) // отдельно отрабатываем десятичную точку
  {
    bAddr = HT1681_Address[(bPos*9)+7];
    bValue = HT_1681_Value[(bPos*9)+7];
    HT1681_Screen[bAddr] = (HT1681_Screen[bAddr] | bValue);
  }; 

  if (lUpperLine==1) // отдельно отрабатываем верхнее подчеркивание
  {
    bAddr = HT1681_Address[(bPos*9)+8];
    bValue = HT_1681_Value[(bPos*9)+8];
    HT1681_Screen[bAddr] = (HT1681_Screen[bAddr] | bValue);
  };
}

// ***************************************************************************************************************************
void setup()
{
  pinMode(CS, OUTPUT); //Pin 2 
  pinMode(WR, OUTPUT); //Pin 3 
  pinMode(DATA, OUTPUT); //Pin 4
  Init_1621();
  HT1681_Clear();
  HT1681_Show();
  Serial.begin(9600);
}

// ***************************************************************************************************************************

void loop()
{
byte i,j;

  HT1681_Clear();
  HT1681_Show();

  LCDoff(); delay(100);
  for (i=0; i<=7; i++)
  {
    for (j=0; j<=9; j++)
    {
      HT1681_Clear(); 
      LCDoff();
      HT1681_Display(i,j,0,0);
      HT1681_Show();
      LCDon();
      delay(150);
    };
  };
}


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


  1. ITMatika
    28.01.2018 15:40

    Респект. Лет 15 назад делал нечто подобное на pic16 и большом дисплее от калькулятора. Самое сложное было — достать ht1621. Конторы в моём городе, которые могли их привезти, работали только с юрлицами и не хотели брать мелкие заказы.


  1. IronHead
    28.01.2018 15:56
    +5

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


    1. golf2109
      28.01.2018 18:22

      да да написть драйвер для такого принтера, это вам не ардуину мучить


    1. BaurzhanD Автор
      28.01.2018 20:22
      +1

      Если успею до отъезда на вахту, то попробую написать пост. Да, кстати, там два типа принтера — один из них на термобумаге, а вот второй — матричный, там и картридж с синей лентой, марка принтера MD910SS, ну картридж MD910, MD911, IDP3110, IDP3111.


      1. esaulenka
        29.01.2018 23:31

        Могу только подсказать, что термопринтер — суть сдвиговый регистр (подключается к SPI'ю) + выключатель нагрева (подключается к таймеру, чтобы точно задавать время) + шаговый двигатель (подключается ко второму двухканальному таймеру, нужен специализированный драйвер шаговых двигателей). Можно упростить задачу, взяв «умный» драйвер ш.д. (со входом «один импульс — поворот на один шаг»).
        Далее — берём растеризованную картинку, загоняем первую строчку в сдвиговый регистр, включаем на N миллисекунд нагрев, потом прокручиваем бумагу на 1 линию (1/8 мм, как правило). Если не торопиться — вполне ардуина справится (главное — голову не сжечь, забыв её выключить).
        Вот если пытаться выжать все паспортные 200-300 мм/сек (это, правда, скорость «взрослых» термопринтеров, тут будет максимум 50-70), надо делать всё одновременно, тут 32-битник (без ардуино-прослойки, конечно же) очень пригодится.


        1. BaurzhanD Автор
          29.01.2018 23:37

          Я уже потихонечку начал копаться, там просто делается строб сигнал на двух инверторах и RC цепочке, я планирую заменить их на К561ЛЕ5 (4 ИЛИ-НЕ). Не я первый, кто хочет печатать на принтере из кассового аппарата — я читал про опыты одного умельца. По поводу драйвера — шагового двигателя лежит в запасниках готовый модуль L298N — все готово, подключить только +12 вольт и ардуинку… Большая проблема — одна — это отсутствие распайки шлейфа, чувствую придется опять все разбирать и тестером звонить выводы… Нашел несколько схем кассовых аппартов в интернете — не совсем подходят…


          1. esaulenka
            30.01.2018 12:22

            Строб прожига там, вероятно, устроен следующим образом: логика с RC-цепочкой отрубает строб, если превышено макс. время, а при нормальной работе время строба контролирует процессор. У нас 74HC123 вообще всё питание головки рубил.

            L298, боюсь, там всё выжгет нафиг. Посмотрите, как устроен драйвер — маловероятно, что они выдают все 12 вольт на обмотку; по-хорошему, надо выдавать фиксированный ток в обмотку. Посмотрите, как устроен ST'шный L6219 (ещё можно поискать у ST, TI, Allegro). Можно, конечно, самостоятельно ШИМить, но это неудобно и небезопасно.

            При отсутствии документации (что странно, схемы на кассы найти можно было. Во всяком случае, в России в центрах тех.обслуживания они есть (и активно утекают на всякие narod.ru)) очень-очень-очень советую купить у китайцев Saleae Logic (хоть самый простой, за 5 баксов) и снять весь обмен.


            1. BaurzhanD Автор
              30.01.2018 17:11

              USB Logic Analyze 24M — такая штука есть. Просто я разобрал кассовый аппарат, супруга не дала был этот пыльный хлам занести в дом. Пришлось разбирать во дворе. Еще и аппарат был нерабочим.
              L298 — в качестве драйвера шагового двигателя.


              1. esaulenka
                30.01.2018 17:41

                Я понимаю, что L298 — драйвер ШД. Но он не умеет ограничивать ток (можно навернуть это снаружи, повесив токоизмерительные резисторы, компараторы и капельку логики), а включать ШД от термопринтера без ограничения тока в 12 вольт чревато его убийством (померяйте сопротивление обмоток).


  1. altruist_pasha
    28.01.2018 20:17

    Не могли бы вы подробнее описывать процесс определения выводов экрана?


    1. BaurzhanD Автор
      28.01.2018 20:18

      Все очень просто! Даташит на HT1621 + тестер. Один щуп тестера в ножку микросхемы, другой в поочередно в выводы платы.


    1. serafims
      29.01.2018 12:06

      А я писал программу для поочередного вывода в регистры контроллера единичек, и смотрел, как в итоге разводка дисплея идет. В принципе они последовательны по цифрам, но один знак иногда смещен, типа последний на дисплее — первый логически…


  1. 1801BM1
    28.01.2018 20:17

    4 вывод — это сброс, скорее всего. По включению питания и опционально внешний. Еще момент — стекла и их подключение к 1621 бывают весьма разнообразными, но это легко решаемо.


    1. BaurzhanD Автор
      28.01.2018 20:19

      Я тоже так думаю, скорее всего это сброс. Там RC цепочка подключена. При включении питания — скорее всего надежно сбрасывает микросхему.


  1. asyan4ik
    28.01.2018 20:20

    Добрый день. Я с ардуино совсем недавно знаком, поэтому вопрос: А вы случайно не сталкивались со светодиодными панелями p10 smdimage
    Хотелось бы вывести текст на кириллице.


    1. IronHead
      28.01.2018 20:29

      Почитайте это www.silvesterdao.com/2014/10/at89c52-p10-led-module-controller.html там весь принцип вывода информации описан, быстро разберетесь как оно работает.


      1. asyan4ik
        28.01.2018 21:09

        Я это читал. Там про одноцветную панельку ( у меня есть зеленого цвета, использовал DMD либы ).
        А вот эта с rgb светодиодами. Кое чего получилось завести, но не все корректно отображается.
        github.com/asyan4ik/p10-rgb-16x32


        1. IronHead
          28.01.2018 21:17

          Если отображается не корректно — правьте шрифт. Он лежит в файле glcdfont.c в массиве font[]. Либо приводите свои символы к таблице utf8.


  1. madf
    29.01.2018 11:54

    Зачем это всё?


    1. iig
      29.01.2018 14:41

      Да, чем-то напомнило разобрать будильник, вынуть шестеренку и крутить вместо волчка.


      1. BaurzhanD Автор
        29.01.2018 23:44

        Когда годами занимаешься рутинкой на вахте, это помогает не закиснуть шестеренкам в своей голове! Это для меня развлечение!


  1. serafims
    29.01.2018 12:05

    Забавно, совсем недавно мне попался такой же дисплей, и я написал класс для работы с ним по мотивам какой-то другой статьи, но не успел это никуда выложить в свободный доступ!
    Кстати, из такого кассового аппарата вполне можно сделать законченное изделие, какой-нибудь логгер данных с выводом на печать, ибо у них все есть практически — порты, дисплей, клавиатура, а если поискать, можно найти примерную схему этих ККМ.
    Кстати, советую с осциллографом потыкаться в чековый принтер на работающей ККМ, чтобы узнать рабочие задержки сигнала «прожиг» строки, там принцип какой — в сдвиговый регистр (или несколько последовательностей регистров параллельно) пихаем строку пикселей, которую хотим прожечь на бумаге, потом делаем строб, потом подаем что-то типа 12 В на резисторы с определенной длительностью (чтобы не сжечь эти резисторы в принтере!) для нагрева их и прожига соответственно строки на бумаге…


    1. BaurzhanD Автор
      29.01.2018 23:42

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


  1. UA3MQJ
    29.01.2018 14:03

    Расскажите, где брать прозрачные кнопки, как на кассовых аппаратах?