Как и у коллег, пишущих статьи с тегом «MSP430», моё знакомство с данным микроконтроллером началось с заметки Комплект разработчика на базе MSP430 от Texas Instruments. Заказанный комплект прибыл через 5 дней. Потом светодиодное «Hello, World» и… был отложен в шкафчик за неимением идей и времени…
… Но однажды на складе были найдены неиспользуемые кассы «Миника 1101Ф». Еще из детства знакомый вопрос «а что внутри?» сделал своё дело 🙂
Внутри был обнаружен ЖК-индикатор ИЖЦ13-8/7-01 на плате с магическими цифрами 5.104.704.
Пролистывая тематические форумы наткнулся на принципиальную схему платы.
Плата двусторонняя, состоит из 2-х микросхем КР1820ВГ1 (да-да, всё правильно, из двух — на схеме их 4-ре, но дан список элементов, которые на плату не установлены). Данная микросхема — полный аналог микросхемы Texas Instruments COP472-3. Привожу datasheet. Еще раз удивился, что советские (или российские?) конструкторы клонировали даже такое!
Каждая микросхема умеет управлять 4-мя цифровыми разрядами индикатора (у данного индикатора этих разрядов 8). Поэтому одна из микросхем отвечает за 4 старшие разряда, а другая — за 4 младшие.
Также инженеры «пропустили» все сигналы через инверторы, собранные в пресловутой К561ЛН2 (зачем — не понял, м.б. так удобнее было для уже разработанной ранее схемы центрального блока кассы).
Подключение
Модуль ЖКИ подключался к основной плате кассового аппарата 6-контактным разъёмом X7. Здесь пришлось перепроверить порядок, в котором расположены контакты на разъёме.
Поглядел, куда идут проводники и нарисовал схему подключения к MSP430.
Порты MSP430G2553 будут задействованы след. образом:
- P1.1 — выбор младшего кристалла
- P1.2 — выбор старшего кристалла
- P1.5 — синхроимпульс
- P1.7 — данные
Алгоритм
Алгоритм работы микросхем КР1820ВГ1 хорошо описан в известной статье 1990-го года Леонида Ивановича Ридико Автомобильные часы-термометр-вольтметр. Также на просторах интернета нашелся полезный скан журнала ЭЛЕКТРОНИКА: Наука, Технология, Бизнес 5/2007.
На следующей картинке изображена работа двух микросхем КР1820ВГ1 в режиме каскадирования. Это именно то, что используется у нас.
Наборы служебных бит здесь хитроумно названы «МЛАДШИЙ1/2» и «СТАРШИЙ».
Опишем алгоритм работы ВГ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 алгоритма).
А на этой — типичная схема разводки разряда ЖКИ (на нашем, правда, «К» над цифрой, а тут — под).
В коде знакогенератора всё через 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/
Добавить комментарий