Подключение LCD HD44780 к AVR через регистр сдвига 74HC595.
Делаем двухканальный вольтметр на Attiny13


Как известно ЖК дисплей на базе контроллера HD44780 требует для управления минимум 6 линий ввода/вывода микроконтроллера, поэтому подключить его к микроконтроллеру с малым числом портов, например Attiny13, в стандартном 8/4-битном режиме невозможно.

В этой статье мы рассмотрим технику управления ЖК дисплеем с использованием всего лишь трех линий ввода/вывода микроконтроллера. Команды управления и данные будут пересылаться последовательно в сдвиговый регистр 74HC595(8-разрядный сдвиговый регистр с защелкой на выходе), а параллельные выходные данные с регистра поступают на LCD.

Символьные ЖК дисплеи на базе контроллера HD44780 требуют 14 выводов для управления: 8 линий данных (D0…D7), 3 линии управления (RS, E, R/W), 3 линии питания (Vdd, Vss, Vo). Кроме того многие модели оснащены подсветкой.

К параллельным выходным линиям регистра сдвига подключен ЖК индикатор: выводы данных D4-D7 и выводы E и RS (4-битный режим работы). Такое решение потребует от микроконтроллера лишь трех линий ввода/вывода:

- SH_CP для передачи тактового сигнала
- DS для передачи данных
- ST_CP для защелкивания данных регистра.

Так как используется 4-битный режим работы, любые восемь бит (команда или данные) передаются в два этапа: сначала старший полубайт, затем передается младший полубайт. Стоит отметить также, что вывод управления индикатора R/W (чтение/запись) подключается к общему проводнику, вследствие чего чтение данных или состояния ЖК модуля при таком подключении невозможно.

Практическим примером такого решения является двухканальный вольтметр (0 - 25V) на микроконтроллере Attiny13  с выводом данных на ЖК дисплей. При подключении дисплея через регистр у контроллера остается как раз две свободных линии, воспользуемся этим и подключим к ним два канала АЦП. Микроконтроллер работает от внутреннего тактового генератора частотой 9,6MHz. Этот вольтметр не является образцовым, но обладает достаточно хорошей точностью измерения напряжения. Схема вольтметра показана на рисунке ниже:

В управляющей программе используются две функции для общения микроконтроллера и дисплея:

- функия registr(unsigned char data, unsigned char WriteOrErase) для передачи данных регистру 74HC595, где data - данные,  в зависимости от состояния переменной WriteOrErase(0 или 1) можно устанавливать отдельный бит передаваемых данных(не трогая другие) в лог. 0 или лог. 1 .
- функция write_to_lcd(char p, unsigned char rs) для передачи данных или команд в ЖК дисплей, где p - данные, в зависимости от состояния переменной rs(0 или 1) передаем команду или передаем данные.

Чтение АЦП производится с помощью функции readADC(unsigned char ch), переменная ch определяет номер используемого канала АЦП. В бесконечном цикле уже преобразованное значение АЦП раскладываем на целое значение и значение после запятой. Полный текст программы показан ниже:

001.// Подключение LCD HD44780 к AVR через регистр сдвига
002. 
003.#include <avr/io.h>
004.#include <util/delay.h>
005. 
006.// Команды для управления портами LCD
007./* RS */
008.#define RS1 registr(0x01, 1)
009.#define RS0 registr(0x01, 0)
010. 
011./* E */
012.#define E1 registr(0x02, 1)
013.#define E0 registr(0x02, 0)
014. 
015./* D4 */
016.#define D41 registr(0x04, 1)
017.#define D40 registr(0x04, 0)
018. 
019./* D5 */
020.#define D51 registr(0x08, 1)
021.#define D50 registr(0x08, 0)
022. 
023./* D6 */
024.#define D61 registr(0x10, 1)
025.#define D60 registr(0x10, 0)
026. 
027./* D7 */
028.#define D71 registr(0x20, 1)
029.#define D70 registr(0x20, 0)
030. 
031.// Функция установки курсора в указанную точку
032.#define lcd_gotoxy(x, y) write_to_lcd(0x80|((x)+((y)*0x40)), 0)
033. 
034.// Функция передачи данных в регистр
035.void registr(unsigned char data, unsigned char WriteOrErase)
036.{
037.volatile static unsigned char tempdata = 0;
038.     
039.if(WriteOrErase == 1)
040.tempdata = (tempdata|data);
041.else
042.tempdata &= ~(data);
043.PORTB &= ~(1 << PB1); // ST_CP 0
044. 
045.PORTB &= ~(1 << PB2); // SH_CP 0
046.if(tempdata & 0x80)PORTB |= (1 << PB0);
047.else PORTB &= ~(1 << PB0);
048.PORTB |= (1 << PB2); // SH_CP 1
049.PORTB &= ~(1 << PB2); // SH_CP 0
050.if(tempdata & 0x40)PORTB |= (1 << PB0);
051.else PORTB &= ~(1 << PB0);
052.PORTB |= (1 << PB2); // SH_CP 1
053.PORTB &= ~(1 << PB2); // SH_CP 0
054.if(tempdata & 0x20)PORTB |= (1 << PB0);
055.else PORTB &= ~(1 << PB0);
056.PORTB |= (1 << PB2); // SH_CP 1
057.PORTB &= ~(1 << PB2); // SH_CP 0
058.if(tempdata & 0x10)PORTB |= (1 << PB0);
059.else PORTB &= ~(1 << PB0);
060.PORTB |= (1 << PB2); // SH_CP 1
061.PORTB &= ~(1 << PB2); // SH_CP 0
062.if(tempdata & 0x08)PORTB |= (1 << PB0);
063.else PORTB &= ~(1 << PB0);
064.PORTB |= (1 << PB2); // SH_CP 1
065.PORTB &= ~(1 << PB2); // SH_CP 0
066.if(tempdata & 0x04)PORTB |= (1 << PB0);
067.else PORTB &= ~(1 << PB0);
068.PORTB |= (1 << PB2); // SH_CP 1
069.PORTB &= ~(1 << PB2); // SH_CP 0
070.if(tempdata & 0x02)PORTB |= (1 << PB0);
071.else PORTB &= ~(1 << PB0);
072.PORTB |= (1 << PB2); // SH_CP 1
073.PORTB &= ~(1 << PB2); // SH_CP 0
074.if(tempdata & 0x01)PORTB |= (1 << PB0);
075.else PORTB &= ~(1 << PB0);
076.PORTB |= (1 << PB2); // SH_CP 1
077.PORTB |= (1 << PB1); // ST_CP 1
078.}
079. 
080.// Функция передачи данных или команды в LCD
081.void write_to_lcd(char p, unsigned char rs)
082.{
083.if(rs == 1) RS1;
084.else RS0;
085.     
086.E1;
087. 
088.if(p&0x10) D41; else D40;
089.if(p&0x20) D51; else D50;
090.if(p&0x40) D61; else D60;
091.if(p&0x80) D71; else D70;
092.E0; 
093._delay_ms(2);
094.     
095.E1;
096.     
097.if(p&0x01) D41; else D40;
098.if(p&0x02) D51; else D50;
099.if(p&0x04) D61; else D60;
100.if(p&0x08) D71; else D70;
101.E0;
102. 
103._delay_ms(2);
104.}
105. 
106.// Функция инициализации LCD
107.void lcd_init(void)
108.{
109.write_to_lcd(0x02, 0); // Курсор в верхней левой позиции
110.write_to_lcd(0x28, 0); // Шина 4 бит, LCD - 2 строки
111.write_to_lcd(0x0C, 0); // Разрешаем вывод изображения, курсор не виден
112.write_to_lcd(0x01, 0); // Очищаем дисплей
113.}
114. 
115.// Функция вывода строки
116.void lcd_puts(char *str)
117.{
118.unsigned char i = 0;
119. 
120.while(str[i])
121.write_to_lcd(str[i++], 1);
122.}
123. 
124.// Функция вывода переменной
125.void lcd_num_to_str(unsigned int value, unsigned char nDigit)
126.{
127.switch(nDigit)
128.{
129.case 4: write_to_lcd((value/1000)+'0', 1);
130.case 3: write_to_lcd(((value/100)%10)+'0', 1);
131.case 2: write_to_lcd(((value/10)%10)+'0', 1);
132.case 1: write_to_lcd((value%10)+'0', 1);
133.}
134.}
135. 
136.// Функция чтения АЦП
137.int readADC(unsigned char ch)
138.{
139.ADMUX = ch; // Выбираем канал АЦП
140.      
141.ADCSRA |= (1 << ADSC); // Запускаем преобразование
142.while((ADCSRA & (1 << ADSC))); // Ждем окончания преобразования
143. 
144.return(ADC*11/2); // Возвращаем значение АЦП
145.}
146. 
147.int main(void)
148.{
149.DDRB = 0b00000111; // Настраиваем входы/выходы
150. 
151.ADCSRA |= (1 << ADEN) // Разрешение АЦП
152.       |(1 << ADPS2)|(1 << ADPS1); // Предделитель на 64
153. 
154.ACSR |= (1 << ACD); // Выключаем аналаговый компаратор
155.DIDR0 |= (1 << ADC3D)|(1 << ADC2D); // Отключаем неиспользуемые цифровые входы
156. 
157.lcd_init(); // Инициализация дисплея
158. 
159.write_to_lcd(0x01, 0); // Очищаем дисплей
160. 
161.lcd_gotoxy(0, 0); // Выводим строки на LCD
162.lcd_puts("U1 =   .  V");
163.lcd_gotoxy(0, 1);
164.lcd_puts("U2 =   .  V");
165. 
166.while(1)
167.{
168.lcd_gotoxy(5, 0);
169.lcd_num_to_str(readADC(2)/100, 2); // Выводим данные АЦП1 на LCD
170.lcd_gotoxy(8, 0);
171.lcd_num_to_str(readADC(2)%100/10, 1);
172.lcd_gotoxy(5, 1);
173.lcd_num_to_str(readADC(3)/100, 2); // Выводим данные АЦП2 на LCD
174.lcd_gotoxy(8, 1);
175.lcd_num_to_str(readADC(3)%100/10, 1);
176._delay_ms(50);
177.}
178.}

Архив для статьи "Подключение LCD HD44780 к AVR через регистр сдвига 74HC595. Делаем двухканальный вольтметр на Attiny13"

Проект AVRStudio4