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

в 16:30, , рубрики: launchpad, msp430, ЖКИ, метки: , ,

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. А еще в Минике такой прикольный термопринтер… ;)

Автор: Ten

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js