![](/article_8/000217.gif) С таймерами и прерываниями мы понемногу разобрались, сопряжение с компьютером тоже освоили, теперь не мешало бы научить наш микроконтроллер как-то обозначать свои действия при отладке и нормальной работе. Можно конечно повесить на порт светодиоды и моргать ими в соответствии с алгоритмом, но это ни всегда удобно, особенно если действий много. Напишешь программу, а потом сидишь и втыкаешь как в песне "и кто его знает чего он моргает..." Так что дисплей, каким бы он ни был всегда дисплей, поэтому завязываем с предисловием и приступаем.Разновидностей дисплеев очень много, они отличаются друг от друга: фирмой изготовителем, принципом работы, предназначением, количеством строк, символов в строке и т.д. и т.п.. В основном дисплеи делятся на графические и знакосинтезирующие. Есть еще сегментные ЖК индикаторы и цветные ЖК дисплеи, но принцип у них уже немного другой, поэтому о них позже. Нас в первую очередь интересует знакосинтезирующий дисплей. Как уже понятно из названия он знаковый - показывает символы и знаки и предназначен только для этого. Дисплей у нас будет простенький, две строки в каждой по 16 символов, если не делать читалку для книг или КПК то двух строчек всегда хватает за глаза. Есть несколько фирм, изготавливающих такие дисплеи, в принципе, команды у них похожи, если не сказать, одинаковые, поэтому все написанное ниже будет справедливо для многих других дисплеев. Мы с вами будем пробовать индикатор самой известной фирмы WINSTAR WH1602A. WH1602A это знакосинтезирующий 2-х строчный жк-дисплей, с 16-ю символами в каждой строке. Подключается монитор через 16-ть контактов (уф... минут 5 убил на формулировку). Из которых 2-а контакта отводится под питание, 3-и под управление, 8-мь для параллельной 8-ми битной шины данных, по которой передаются данные и команды, и наконец еще 3-и вывода используются для регулировки контраста и питания подсветки дисплея. В таблице ниже подробно расписано что куда. Номер
| Название
| Описание
| 1 | GND
| Общий вывод источника питания или проще "Земля" | 2 | VCC | Напряжение питания +5V | 3 | VO | Вывод регулировки контраста | 4 | RS | Выбор регистра контроллера (1 - регистр данных, 0 - регистр управления) | 5 | R/W | Выбор режима обмена (0 - запись, 1 - чтение) | 6 | EN | Стробирующий импульс | 7 | D0 | Шина данных
| 8 | D1 | 9 | D2 | 10 | D3 | 11 | D4 | 12 | D5 | 13 | D6 | 14 | D7 | 15 | LED+ | Питание подсветки +5V
| 16 | LED- | Общий вывод питания подсветки
|
Схема подключения проще не бывает:
![](/Article_Display/Disp-conn.png)
Питание дисплея 3,3В или 5В, как правило это указывается в характеристиках. Вывод контрастности подключается как правило через переменный резистор для изменения яркости и четкости изображения от "вообще ничего не видно" (при 0В) и "глаза, мои глаза" (при VCC). Иногда чтобы не заморачиваться вывод контрастности вообще соединяют с питанием. Выводы данных вместе с выводами управления соединяются напрямую с выводами микроконтроллера. Если нет нужды что-то считывать с дисплея (например флаг состояния и выполнения команд) то вывод R/W часто сажают на землю и используется только запись на дисплей. Изучив все мелкие детали и особенности работы с дисплеями приступим к коду. Последовательность передачи информации на дисплей следующая: 1. Устанавливаем бит RS в 1 или сбрасываем в 0 в зависимости от того что у нас будет данные или команда (0 - команда, 1 - данные); 2. Выводим символ на шину D7…D0; 3. Устанавливаем бит строба EN в 1; 4. Делаем задержку в программе на 2 млС; 5. Сбрасываем бит строба EN в 0; 6. Делаем задежку на 40млС. Последовательсть действий всегда одинакова вне зависимости от того, что мы передаем данные или команду. Если у вас пошло что-то не так и дисплей никак не реагирует то скорее всего у вас не совсем правильно реализована задержка. Для себя я в программе написал несколько функций: функция передачи команды или данных на дисплей Code void lcd_printf(unsigned char cBuffer, cInit) { Display_RS=cInit; Display_Data=cBuffer; Display_EN=1; time_delay(2); Display_EN=0; time_delay(40); } и функция временной задержки, реализованной через таймер TMR0 Code void time_delay(unsigned char cDelay) { cDelayCount=cDelay; TMR0ON=1; while(cDelayCount); TMR0ON=0; } Чтобы было удобнее работать и программа стала более читабельной я заменил стандартные определения выводов микроконтроллера своими: Code #define Display_Data PORTD #define Display_EN RB0 #define Display_RW RB1 #define Display_RS RB2 И сразу стало понятно куда что идет. В самом начале, перед использованием дисплея, его необходимо инициализировать как и собственно любой контроллер. Инициализация выполняется следующим образом:
1. Делаем задежку на 40млС. Чтобы дисплей успел включиться (контроллер внутри дисплея жутко тормозной); 2. Передаем команду настройки дисплея - Function set: 0b00111000; 3. Передаем команду включения дисплея - Display ON/OFF: 0b00001111; 4. Передаем команду очистки экрана - Clear Display: 0b00000001; 5. Делаем задежку на 2млС ; 6.
Передаем команду направления перемещения курсора - Entry Mode Set: 0b00000110.
Говоря "передаем команду" я имею ввиду всю ту последовательность действий при передаче данных указанную выше. Теперь дисплей окончательно готов к работе и можно попробовать вывести что нибудь. Code lcd_printf('L', 1); lcd_printf('C', 1); lcd_printf('D', 1); lcd_printf(' ', 1); lcd_printf('t', 1); lcd_printf('e', 1); lcd_printf('s', 1); lcd_printf('t', 1); lcd_printf('!', 1); Теперь все это надо добавить к нашей заготовке программы, подключить все необходимые библиотеки и настроить порт D и порт B на цифровой выход. Собираем простенькую схемку в PROTEUS-е, можно даже переделать старые проекты. Схема: ![](/Article_Display/Disp-conn_2.png)
Текст программы: Я его несколько переделал, вместо одной функции передачи данных я сделал две фиксированные, для передачи текста и для передачи команды. Кроме этого я задействовал USART для последующей реализации передачи данных с компьютера на наш дисплей.
Code #include <pic18.h>
#define Display_Data PORTD #define Display_EN RB0 #define Display_RW RB1 #define Display_RS RB2
// Доп.настройки ядра микроконтроллера __CONFIG(1, HS & FCMEN & IESOEN); __CONFIG(2, PWRTDIS & BORDIS & WDTDIS); __CONFIG(3, CCP2RC1 & PBDIGITAL & LPT1DIS & MCLREN); __CONFIG(4, XINSTEN & STVRDIS & LVPDIS & DEBUGDIS); __CONFIG(5, UNPROTECT); __CONFIG(6, UNPROTECT); __CONFIG(7, UNPROTECT);
bit bBufferFlag; bit bIncomFlag; char cCount_i=0; char cCount_j=0; unsigned char cIndexSymbol=0; unsigned char cTempBuffer=0; unsigned char cBuffer[8]={0,0,0,0,0,0,0,0}; unsigned char cDelayCount=0; unsigned char cDelaySet=0;
void time_delay(unsigned char cDelay); void scanf_buffer(void); void printf_buffer(const char cTempOut); void lcd_command(unsigned char cCommand); void lcd_data(unsigned char cData); void lcd_initialisation(void); void start_initialisation(void);
void interrupt high_main_int(void) { if(TMR0IF) { cDelayCount--; TMR0L=0xEC; TMR0IF=0; } }
void time_delay(unsigned char cDelay) { cDelayCount=cDelay; TMR0ON=1; while(cDelayCount); TMR0ON=0; }
void scanf_buffer(void) { switch(cBuffer[0]) { case 0x01: lcd_command(0b00000001); lcd_data(cBuffer[1]); break; case 0x02: lcd_command(0b00000001); break; default: break; } bBufferFlag=0; }
void printf_buffer(const char cTempOut) { TXREG=0x00; time_delay(5); TXREG=cTempOut; time_delay(5); TXREG=cTempOut; time_delay(5); TXREG=0xFF; }
void lcd_command(unsigned char cCommand) { Display_RS=0; Display_Data=cCommand; Display_EN=1; time_delay(2); Display_EN=0; time_delay(40); }
void lcd_data(unsigned char cData) { Display_RS=1; Display_Data=cData; Display_EN=1; time_delay(2); Display_EN=0; time_delay(40); }
void lcd_initialisation(void) { time_delay(40); lcd_command(0b00111000); lcd_command(0b00001111); lcd_command(0b00000001); time_delay(2); lcd_command(0b00000110); }
void start_initialisation(void) { // Настройка регистров порта A LATA=0b00000000; // Настройка защелки порта A TRISA=0b00000000; // Настройка порта A на цифровой выход PORTA=0b00000000; // Логический уровень порта A // Настройка регистров порта В LATB=0b00000000; // Настройка защелки порта В TRISB=0b00000000; // Настройка порта В на цифровой выход и вход PORTB=0b00000000; // Логический уровень порта В // Настройка регистров порта C LATC=0b00000000; // Настройка защелки порта C TRISC=0b11000000; // Настройка порта C на цифровой выход и USART PORTC=0b00000000; // Логический уровень порта C // Настройка регистров порта D LATD=0b00000000; // Настройка защелки порта D TRISD=0b00000000; // Настройка порта D на цифровой выход PORTD=0b00000000; // Логический уровень порта D // Настройка регистров порта E LATE=0b00000000; // Настройка защелки порта E TRISE=0b00000000; // Настройка порта E на цифровой выход PORTE=0b00000000; // Логический уровень порта E // Настройка таймера TMR0 T0CON=0b00000000; // Регистр управления таймером T08BIT=1; // Режим 8-бит таймер/счетчик T0PS2=1; // Прескалер 256 T0PS1=1; // T0PS0=1; // TMR0L=0xEC; // Временная задержка 1млС при частоте 20МГц // Настройка регистров приемника и передатчика USART TXSTA=0b00000000; // Регистр передатчика USART TXEN=1; // Разрешение передачи USART BRGH=1; // Выбор высокоскоростного режима RCSTA=0b00000000; // Регистр приемника USART SPEN=1; // Разрешение работы последовательного порта CREN=1; // Разрешение приема USART BAUDCON=0b00000000; // Регистр автоопределения скорости приема SPBRG=129; // Скорость работы порта передачи (20МГц, 9600 бод) // Настройка регистров прерываний и приоритетов RCON=0b00000000; // Регистр управления прерываниями 0 IPEN=1; // Разрешение приоритетных прерываний INTCON=0b00000000; // Регистр управления прерываниями 1 GIEH=1; // Разрешение глобальных прерываний с высоким приоритетом GIEL=1; // Разрешение глобальных прерываний с низким приоритетом TMR0IE=1; // Разрешение прерываний от таймера TMR0 INTCON2=0b00000000; // Регистр управления прерываниями 2 TMR0IP=1; // Высокий приоритет прерываний от таймера TMR0 INTCON3=0b00000000; // Регистр управления прерываниями 3 PIR1=0b00000000; // Регистр флагов периферийных прерываний 1 PIR2=0b00000000; // Регистр флагов периферийных прерываний 2 PIE1=0b00000000; // Регистр разрешения периферийных прерываний 1 RCIE=1; // Разрешение прерываний от приемника USART PIE2=0b00000000; // Регистр разрешения периферийных прерываний 2 IPR1=0b00000000; // Регистр приоритета периферийных прерываний 1 IPR2=0b00000000; // Регистр приоритета периферийных прерываний 2 }
// Основная функция программы void main(void) { start_initialisation(); lcd_initialisation(); lcd_command(0b00000001); lcd_data('W'); lcd_data('e'); lcd_data('l'); lcd_data('l'); lcd_data('c'); lcd_data('o'); lcd_data('m'); lcd_data('e'); lcd_data('!'); lcd_command(0b11000000); lcd_data('T'); lcd_data('e'); lcd_data('s'); lcd_data('t'); lcd_data('-'); lcd_data('m'); lcd_data('o'); lcd_data('d'); lcd_data('e'); lcd_data('.'); while(1) { if(bBufferFlag) { scanf_buffer(); } } } После включения симуляции на дисплее должно появиться "LCD test!". Собственно для начала очень даже не плохо, но дисплей то у нас двухстрочный, а мы пока писали на одной строке и если продолжить печатать дальше то курсор уйдет за пределы поля и ничего не будет видно пока позиция курсора не станет равной адресу первого символа второй строки. Вообще позицию курсора можно задавать и писать слева, справа и даже по середине строки, а так же переходить на другую сторку (команда 0b11000000), но обо всем об этом мы поговорим в следующей статье, а заодно в ней мы будем использовать вывод R/W, чтобы читать с дисплея его флаги и состояние.
|