Как и у коллег, пишущих статьи с тегом «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. А еще в Минике такой прикольный термопринтер… ;)
Автор: Ten