MSP430 + ЖКИ от «Миника 1101Ф»

    image
    Как и у коллег, пишущих статьи с тегом «MSP430», моё знакомство с данным микроконтроллером началось с заметки Комплект разработчика на базе MSP430 от Texas Instruments. Заказанный комплект прибыл через 5 дней. Потом светодиодное «Hello, World» и… был отложен в шкафчик за неимением идей и времени…
    … Но однажды на складе были найдены неиспользуемые кассы «Миника 1101Ф». Еще из детства знакомый вопрос «а что внутри?» сделал своё дело :)

    Внутри был обнаружен ЖК-индикатор ИЖЦ13-8/7-01 на плате с магическими цифрами 5.104.704.
    Вот так выглядит эта плата
    imageimage

    Пролистывая тематические форумы наткнулся на принципиальную схему платы.
    Плата двусторонняя, состоит из 2-х микросхем КР1820ВГ1 (да-да, всё правильно, из двух — на схеме их 4-ре, но дан список элементов, которые на плату не установлены). Данная микросхема — полный аналог микросхемы Texas Instruments COP472-3. Привожу datasheet. Еще раз удивился, что советские (или российские?) конструкторы клонировали даже такое!
    Каждая микросхема умеет управлять 4-мя цифровыми разрядами индикатора (у данного индикатора этих разрядов 8). Поэтому одна из микросхем отвечает за 4 старшие разряда, а другая — за 4 младшие.
    Также инженеры «пропустили» все сигналы через инверторы, собранные в пресловутой К561ЛН2 (зачем — не понял, м.б. так удобнее было для уже разработанной ранее схемы центрального блока кассы).

    Подключение


    Модуль ЖКИ подключался к основной плате кассового аппарата 6-контактным разъёмом X7. Здесь пришлось перепроверить порядок, в котором расположены контакты на разъёме.
    Поглядел, куда идут проводники и нарисовал схему подключения к MSP430. image
    Порты MSP430G2553 будут задействованы след. образом:
    • P1.1 — выбор младшего кристалла
    • P1.2 — выбор старшего кристалла
    • P1.5 — синхроимпульс
    • P1.7 — данные

    Алгоритм


    Алгоритм работы микросхем КР1820ВГ1 хорошо описан в известной статье 1990-го года Леонида Ивановича Ридико Автомобильные часы-термометр-вольтметр. Также на просторах интернета нашелся полезный скан журнала ЭЛЕКТРОНИКА: Наука, Технология, Бизнес 5/2007.

    На следующей картинке изображена работа двух микросхем КР1820ВГ1 в режиме каскадирования. Это именно то, что используется у нас.
    image

    Наборы служебных бит здесь хитроумно названы «МЛАДШИЙ1/2» и «СТАРШИЙ».image
    Опишем алгоритм работы ВГ1 в каскаде. Можно было бы написать «алгоритм работы платы», но это было бы не верно — не забываем, что у нас впаяны инверторы на все входы платы.
    Работа заключается в 1) инициализации и 2) собственно, рабочем режиме.
    Инициализация происходит след. образом:
    1) на обоих чипах ставим ^CS1,2=0;
    2) выдаем на линию «D» 32 бита данных;
    3) выдаем 4 бита специальных сегментов (в данном индикаторе это чёрточки над цифрами);
    4) выдаем 4 управляющих бита 0111 (см. в табл. «МЛАДШИЙ1»);
    5) на обоих чипах ставим ^CS1,2=1;
    6) выбираем старшую микросхему ^СS2=0, данные теперь будет получать она;
    7) выдаем на линию «D» 32 бита данных; выдаем 4 бита специальных сегментов; выдаем 4 управляющих бита 1000 (см. в табл. «СТАРШИЙ»);
    8) ^CS=0.
    Код инициализации выглядит так
    /*
     * Инициализация LCD
     */
    void initLCD(){
        P1OUT |= CS1+CS2; // установим на обоих чипах вывод ~CS в "0" 
                          // (на ЖК-плате стоят инверторы, поэтому фактически засылаем туда "1")
                          // теперь данные будет принимать младшая микросхема
        spi_IO(0);    spi_IO(0);    spi_IO(0);    spi_IO(0); //  4 байта данных
        spi_IO(0x07); // 4 бита сегментов + 4 управляющих 
        P1OUT &= ~(CS1 + CS2); // установим ~CS=1 на обоих чипах (не забываем про инверсию)
    
        P1OUT |= CS2; // установим ~CS=0 старшей схеме - теперь данные будет получать она
        spi_IO(0);    spi_IO(0);    spi_IO(0);    spi_IO(0); // пошлём 4 байта
        spi_IO(0x08); // 4 бита сегментов + 4 управляющих
        P1OUT &= ~(CS2 + CS1); // переключим ~CS=1 (инвертор!) 
    }
    

    Всякие полезные функции
    /* "1" в линию данных */
    void bit1()
    {
        P1OUT &= ~D;
        P1OUT &= ~CLK;
        P1OUT |= CLK;
    }
    
    /* "0" в линию данных */
    void bit0()
    {
        P1OUT |= D;
        P1OUT &= ~CLK;
        P1OUT |= CLK;
    }
    /* Отправляем байт в линию данных */
    void spi_IO( unsigned int data ) {
        unsigned int i;
        for( i = 0; i < 8; i++ ) {
           // выдаем побитно в линию данных
           if( data & 0x80 )
              bit1();
            else
              bit0();
           data <<= 1;
        }
    }
    

    Микросхема переходит в рабочий режим.
    Теперь чтобы записать данные в старшие/младшие 4 цифровые разряда (т.е. в старшую/младшую микросхему) можно просто выбирать нужный чип и писать сразу в него.
    Рабочий режим:
    1) выбираем необходимый чип (младший ^CS1=0 или старший ^CS2=0);
    2) посылаем 32 бита данных;
    3) посылаем 4 бита спец-сегментов;
    4) посылаем 4 управляющих бита (0110 для младшего «МЛАДШИЙ2» или 1000 для старшего);
    5) «развыбираем» чип (тот, что в п.1. выбрали =1).
    Прошу не судить строго за след. кусок кода. Можно оптимизировать и оптимизировать, но цель статьи не в этом :)
    Реализация вывода строки символов на дисплей
    /*
     * Выводим строку символов
     * data - строка
     * n - количество символов
     * dot - позиция точки (точек)
     */
    void print_LCD( char data[], char n, char dot )
    {
            unsigned char copy[8];
            unsigned char i;
            if(n<1) return ;
            if(n>8) n=8;
    
            for( i = 0; i < 8; i++ ) if(i+1>n) copy[i] = ' ' ;  else copy[i] = data[i]; // перевернем
    
            // заполняем левые 4 символа
           P1OUT |= CS1;
            for(i=0;i<4;i++){
                   if( dot & 0x80 )
                         spi_IO(char2byte(copy[i])+0x80);
                   else
                         spi_IO(char2byte(copy[i]));
                  dot <<= 1;
           }
            // 4 бита , определяющие спецсегменты (надчеркивания )
           bit0(); bit0(); bit0(); bit0();
            // служебные биты
           bit0(); bit1(); bit1(); bit0();
           P1OUT &= ~CS1;
    
            // заполняем правые 4 символа
           P1OUT |= CS2;
            for(i=4; i<8; i++){
                   if( dot & 0x80 )
                         spi_IO(char2byte(copy[i])+0x80);
                   else
                         spi_IO(char2byte(copy[i]));
                  dot <<= 1;
           }
            // 4 бита , определяющие спецсегменты (надчеркивания )
           bit0(); bit0(); bit0(); bit0();
            // служебные биты
           bit1(); bit0(); bit0(); bit0();
           P1OUT &= ~CS2;
    }

    Теперь мы умеем подсвечивать отдельные сегменты. Но это не совсем интересно, поэтому нам нужен знакогенератор!

    Знакогенератор


    На этой картинке мы видим порядок следования битов в кодовой посылке (см. п. 2 алгоритма).
    image
    А на этой — типичная схема разводки разряда ЖКИ (на нашем, правда, «К» над цифрой, а тут — под).
    image
    В коде знакогенератора всё через case-ы, прошу прощения :)
    Код знакогенератора
    /*
     * Нелепое превращение символа в байт сегмента
     */
    int char2byte( char ch ){
            switch(ch){
                   case '1' : return 0x06;
                   case '2' : return 0x5B;
                   case '3' : return 0x4F;
                   case '4' : return 0x66;
                   case '5' : return 0x6D;
                   case '6' : return 0x7D;
                   case '7' : return 0x07;
                   case '8' : return 0x7F;
                   case '9' : return 0x6F;
                   case 'O' :
                   case 'o' :
                   case '0' : return 0x3F;
                   case 'a' :
                   case 'A' : return 0x77;
                   case 'b' :
                   case 'B' : return 0x7C;
                   case 'c' :
                   case 'C' : return 0x39;
                   case 'd' :
                   case 'D' : return 0x5E;
                   case 'e' :
                   case 'E' : return 0x79;
                   case 'f' :
                   case 'F' : return 0x71;
                   case '-' : return 0x40;
                   case ' ' : return 0x00;
                   case 'p' :
                   case 'P' : return 0x73;
                   case 'g' :
                   case 'G' : return 0x31;
                   case 'l' :
                   case 'L' : return 0x38;
                   case 'h' :
                   case 'H' : return 0x76;
                   case 'y' :
                   case 'Y' : return 0x38;
                   case 'r' :
                   case 'R' : return 0x50;
                   case 'u' : return 0x1C;
                   case 'U' : return 0x3E;
                   case '|' : return 0x30;
                   case '~' : return 0x01;  // верхняя черта
                   case '_' : return 0x08;	// нижняя
                   case '=' : return 0x09;	// верхняя и нижняя
                   case 'j' : return 0x21;  // левый верхний угол (тут у меня фантазии не хватило - 
                                            // на клавиатуре jkmn квадратом расположены)
                   case 'k' : return 0x03;	// правый верхний угол
                   case 'm' : return 0x0C;	// правый нижний угол
                   case 'n' : return 0x18;	// левый нижний угол
    
                   default: return 0x00;
           }
    }

    Далее были написаны несколько демонстрационных функций, которые можно посмотреть по ссылке в конце статьи.

    Послесловие


    Плата ЖКИ питается от 5В. На MSP430 Launchpad разведены только 3.3В. От них плата работает тоже, но очень тускло. Вероятно, подбором резистора R3* можно добиться необходимой яркости, но и впаивание штырька на входе USB и снятие с него 5В тоже работает :) В конце концов, у нас не законченное изделие, а лабораторный макет.

    Еще замечу, что на данной плате старшие 4 сегмента цифр расположены справа, а младшие — слева.

    Полный код микропрограммы можно скачать здесь или
    посмотреть
    #include <msp430g2553.h>
    
    #define CS1 BIT1 // Выбор младшего чипа
    #define CS2 BIT2 // Выбор старшего чипа
    #define CLK BIT5 // Синхронизация
    #define D   BIT7 // Выход данных
    
    char flag_next=0; // флаг перехода на следующее демо ( устанавливается обработчиком прерывания)
    
    /* "1" в линию данных */
    void bit1()
    {
        P1OUT &= ~D;
        P1OUT &= ~CLK;
        P1OUT |= CLK;
    }
    
    /* "0" в линию данных */
    void bit0()
    {
        P1OUT |= D;
        P1OUT &= ~CLK;
        P1OUT |= CLK;
    }
    
    /*
     * Задержка (в милисекундах)
     */
    void delay( unsigned int ms)
    {
    	while (ms--) __delay_cycles(16000);
    }
    
    /* Отправляем байт в линию данных */
    void spi_IO( unsigned int data ) {
        unsigned int i;
        for( i = 0; i < 8; i++ ) {
           // выдаем побитно в линию данных
           if( data & 0x80 )
              bit1();
            else
              bit0();
           data <<= 1;
        }
    }
    
    /*
     * Нелепое превращение символа в байт сегмента
     */
    int char2byte( char ch ){
            switch(ch){
                   case '1' : return 0x06;
                   case '2' : return 0x5B;
                   case '3' : return 0x4F;
                   case '4' : return 0x66;
                   case '5' : return 0x6D;
                   case '6' : return 0x7D;
                   case '7' : return 0x07;
                   case '8' : return 0x7F;
                   case '9' : return 0x6F;
                   case 'O' :
                   case 'o' :
                   case '0' : return 0x3F;
                   case 'a' :
                   case 'A' : return 0x77;
                   case 'b' :
                   case 'B' : return 0x7C;
                   case 'c' :
                   case 'C' : return 0x39;
                   case 'd' :
                   case 'D' : return 0x5E;
                   case 'e' :
                   case 'E' : return 0x79;
                   case 'f' :
                   case 'F' : return 0x71;
                   case '-' : return 0x40;
                   case ' ' : return 0x00;
                   case 'p' :
                   case 'P' : return 0x73;
                   case 'g' :
                   case 'G' : return 0x31;
                   case 'l' :
                   case 'L' : return 0x38;
                   case 'h' :
                   case 'H' : return 0x76;
                   case 'y' :
                   case 'Y' : return 0x38;
                   case 'r' :
                   case 'R' : return 0x50;
                   case 'u' : return 0x1C;
                   case 'U' : return 0x3E;
                   case '|' : return 0x30;
                   case '~' : return 0x01;  // верхняя черта
                   case '_' : return 0x08;	// нижняя
                   case '=' : return 0x09;	// верхняя и нижняя
                   case 'j' : return 0x21;  // левый верхний угол (тут у меня фантазии не хватило -
                                            // на клавиатуре jkmn квадратом расположены)
                   case 'k' : return 0x03;	// правый верхний угол
                   case 'm' : return 0x0C;	// правый нижний угол
                   case 'n' : return 0x18;	// левый нижний угол
    
                   default: return 0x00;
           }
    }
    
    /*
     * Выводим строку символов
     * data - строка
     * n - количество символов
     * dot - позиция точки (точек)
     */
    void print_LCD( char data[], char n, char dot )
    {
            unsigned char copy[8];
            unsigned char i;
            if(n<1) return ;
            if(n>8) n=8;
    
            for( i = 0; i < 8; i++ ) if(i+1>n) copy[i] = ' ' ;  else copy[i] = data[i]; // перевернем
    
            // заполняем левые 4 символа
           P1OUT |= CS1;
            for(i=0;i<4;i++){
                   if( dot & 0x80 )
                         spi_IO(char2byte(copy[i])+0x80);
                   else
                         spi_IO(char2byte(copy[i]));
                  dot <<= 1;
           }
            // 4 бита , определяющие спецсегменты (надчеркивания )
           bit0(); bit0(); bit0(); bit0();
            // служебные биты
           bit0(); bit1(); bit1(); bit0();
           P1OUT &= ~CS1;
    
            // заполняем правые 4 символа
           P1OUT |= CS2;
            for(i=4; i<8; i++){
                   if( dot & 0x80 )
                         spi_IO(char2byte(copy[i])+0x80);
                   else
                         spi_IO(char2byte(copy[i]));
                  dot <<= 1;
           }
            // 4 бита , определяющие спецсегменты (надчеркивания )
           bit0(); bit0(); bit0(); bit0();
            // служебные биты
           bit1(); bit0(); bit0(); bit0();
           P1OUT &= ~CS2;
    }
    
    /*
     * Инициализация LCD
     */
    void initLCD(){
        P1OUT |= CS1+CS2; // установим на обоих чипах вывод ~CS в "0"
                          // (на ЖК-плате стоят инверторы, поэтому фактически засылаем туда "1")
                          // теперь данные будет принимать младшая микросхема
        spi_IO(0);    spi_IO(0);    spi_IO(0);    spi_IO(0); //  4 байта данных
        spi_IO(0x07); // 4 бита сегментов + 4 управляющих
        P1OUT &= ~(CS1 + CS2); // установим ~CS=1 на обоих чипах (не забываем про инверсию)
    
        P1OUT |= CS2; // установим ~CS=0 старшей схеме - теперь данные будет получать она
        spi_IO(0);    spi_IO(0);    spi_IO(0);    spi_IO(0); // пошлём 4 байта
        spi_IO(0x08); // 4 бита сегментов + 4 управляющих
        P1OUT &= ~(CS2 + CS1); // переключим ~CS=1 (инвертор!)
    }
    
    /*
     * Очистка экрана
     */
    void clear_LCD()
    {
    	print_LCD( "        " , 8, 0); // а другого варианта всё равно нет - только запись в разряды
    }
    
    
    /*
     * Демонстрация
     */
    void demo()
    {
    	int demo_n = 1;
        while(1){
           switch(demo_n){
                   case 0:{
                	   int ms=50;
                	   while(1){
                		   print_LCD("~~      " ,8,0);       delay(ms);
                		   print_LCD(" ~~     " ,8,0);       delay(ms);
                		   print_LCD("  ~~    " ,8,0);       delay(ms);
                		   print_LCD("   ~~   " ,8,0);       delay(ms);
                		   print_LCD("    ~~  " ,8,0);       delay(ms);
                		   print_LCD("     ~~ " ,8,0);       delay(ms);
                		   print_LCD("      ~~" ,8,0);       delay(ms);
                		   print_LCD("       k" ,8,0);       delay(ms);
                		   print_LCD("       1" ,8,0);       delay(ms);
                		   print_LCD("       m" ,8,0);       delay(ms);
                		   print_LCD("      __" ,8,0);       delay(ms);
                		   print_LCD("     __ " ,8,0);       delay(ms);
                		   print_LCD("    __  " ,8,0);       delay(ms);
                		   print_LCD("   __   " ,8,0);       delay(ms);
                		   print_LCD("  __    " ,8,0);       delay(ms);
                		   print_LCD(" __     " ,8,0);       delay(ms);
                		   print_LCD("__      " ,8,0);       delay(ms);
                		   print_LCD("n       " ,8,0);       delay(ms);
                		   print_LCD("|       " ,8,0);       delay(ms);
                		   print_LCD("j       " ,8,0);       delay(ms);
                		   if(flag_next==1) { demo_n=1; flag_next=0; break; }
                	   }
                   }
                   case 1:{
                         clear_LCD();
                          while(1){
                               print_LCD( "       H" , 8, 0);
                               delay(200);
                               print_LCD( "      HE" , 8, 0);
                               delay(200);
                               print_LCD( "     HEL" , 8, 0);
                               delay(200);
                               print_LCD( "    HELL", 8, 0);
                               delay(200);
                               print_LCD( "   HELLO", 8, 0);
                               delay(200);
                               print_LCD( "  HELLO ", 8, 0);
                               delay(200);
                               print_LCD( " HELLO  ", 8, 0);
                               delay(1000);
                               print_LCD( "HELLO   ", 8, 0);
                               delay(200);
                               print_LCD( "ELLO    ", 8, 0);
                               delay(200);
                               print_LCD( "LLO     " , 8, 0);
                               delay(200);
                               print_LCD( "LO      " , 8, 0);
                               delay(200);
                               print_LCD( "O       " , 8, 0);
                               delay(200);
                               print_LCD( "       H" , 8, 0);
                               delay(200);
                               print_LCD( "      HA" , 8, 0);
                               delay(200);
                               print_LCD( "     HAB" , 8, 0);
                               delay(200);
                               print_LCD( "    HABR", 8, 1);
                               delay(200);
                               print_LCD( "   HABRR", 8, 2);
                               delay(200);
                               print_LCD( "  HABRRu", 8, 4);
                               delay(200);
                               print_LCD( " HABRRu ", 8, 8);
                               delay(1000);
                               print_LCD( "HABRRu  ", 8, 16);
                               delay(200);
                               print_LCD( "ABRRu   ", 8, 32);
                               delay(200);
                               print_LCD( "BRRu    ", 8, 64);
                               delay(200);
                               print_LCD( "RRu     " , 8, 128);
                               delay(200);
                               print_LCD( "Ru      " , 8, 0);
                               delay(200);
                               print_LCD( "u       " , 8, 0);
                               delay(200);
                                if(flag_next==1) { demo_n=2; flag_next=0; break; }
                         } //while
                  }
                   case 3:{
                         clear_LCD();
                          int ms=50;
                          while(1){
                        	  print_LCD( "~~~~~~~~",8,0);       delay(ms);
                        	  print_LCD( "kkkkkkkk ",8,0);       delay(ms);
                        	  print_LCD( "11111111",8,0);       delay(ms);
                        	  print_LCD( "mmmmmmmm ",8,0);       delay(ms);
                        	  print_LCD( "nnnnnnnn ",8,0);       delay(ms);
                        	  print_LCD( "||||||||",8,0);       delay(ms);
                        	  print_LCD( "jjjjjjjj ",8,0);       delay(ms);
                        	  if(flag_next==1) { demo_n=0; flag_next=0; break; }
                         }
                  }
           }
        }
    }
    /*
     * main.c
     */
    void main( void) {
        // убьём собаку
        WDTCTL = WDTPW + WDTHOLD;
        // установим частоту - используется для delay()
        BCSCTL1 = CALBC1_16MHZ;
        DCOCTL = CALDCO_16MHZ;
    
        // These are the pins we need to drive.
        P1DIR |= CLK + D + CS1 + CS2;
    
        P1SEL &= ~BIT3;                              // порт 1.3 будет нашей кнопкой
        P1DIR &= ~BIT3;                              // Port 1 P1.3 (push button) as input, 0 is input
        P1REN |= BIT3;                               // резистивная подтяжка
        P1IE |= BIT3;                                // разрешим прерывания от кнопки
        P1IFG &= ~BIT3;                              // очистим флаг прерывании
        delay(500);									 // пауза, исключим всякие переходные процессы
        initLCD(); // инициализация LCD
        __bis_SR_register(GIE);                      // включим обработку прерываний
    
        demo(); // запустим демонстрацию
    }
    
    #pragma vector=PORT1_VECTOR
    __interrupt void Port_1( void) {
    
           P1IFG &= ~BIT3; // очистим флаг сработки
           flag_next=1;    // установим флаг сработки для демо
    }
    
    Слабонервным в код советую не смотреть :)

    Ну и небольшое видео работы


    Очень интересны советы, комментарии и замечания!
    Надеюсь, кому-нибудь эта информация будет полезна.
    Спасибо за внимание и до новых встреч.

    P.S. А еще в Минике такой прикольный термопринтер… ;)
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 13
    • +3
      Похоже эта статья на хабре в свое время повлияла на появление в последнее время множества статей о MSP430. На очереди моя статься ))
      • 0
        На самом деле, таких статей было как минимум 3 штуки, до того, как этой платформой начали интересоваться так сильно.
      • 0
        Что то все так заюзали этот кит msp-шный, что даже не знают про серию со встроенным LCD драйвером, с которым все эти процессы становяться на порядок проще
        • 0
          Ну почему же не знаем? Знаем. Что есть в наличии — на том и делаем ;)
          • +1
            У нас, кстати, на встроенном драйвере, LCD вот так кошмарил
            image
            Хотя с частотой все окей при этом было :) Так и отправили на печать платы, ссылаясь на наводки
            • 0
              Да, читал вашу статью… сочувствую
              • 0
                Извините, мою статью? А можно ссыль?
                • +1
                  Может быть не вашу, но индикатор был точно этот же… и мигал аналогично. Сейчас поищу
                  • +1
                    forum.cxem.net/index.php?showtopic=115391
                    не статью, пост в форуме
                    • 0
                      Оу. Ну да. Этой я :) Что то там не особо помогли стандартными примерами.
          • 0
            >>P.S. А еще в Минике такой прикольный термопринтер… ;)
            У меня тоже пара термопринтеров лежит. Все думаю как бы их завести.
            • 0
              Громадное спасибо! У меня такой блок уже несколько лет лежит, желание подсоединить есть, но имеющаяся документация в интернетах скудна.Ну а теперь разогреваю паяльник…
              • 0
                Пожалуйста :) Будут вопросы — обращайтесь

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