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

ссылка на оригинал статьи http://habrahabr.ru/post/159083/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *