Подключение 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.// Команды для управления портами LCD007./* 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.voidregistr(unsignedchardata, unsignedcharWriteOrErase)036.{037.volatilestaticunsignedchartempdata = 0;038.039.if(WriteOrErase == 1)040.tempdata = (tempdata|data);041.else042.tempdata &= ~(data);043.PORTB &= ~(1 << PB1);// ST_CP 0044.045.PORTB &= ~(1 << PB2);// SH_CP 0046.if(tempdata & 0x80)PORTB |= (1 << PB0);047.elsePORTB &= ~(1 << PB0);048.PORTB |= (1 << PB2);// SH_CP 1049.PORTB &= ~(1 << PB2);// SH_CP 0050.if(tempdata & 0x40)PORTB |= (1 << PB0);051.elsePORTB &= ~(1 << PB0);052.PORTB |= (1 << PB2);// SH_CP 1053.PORTB &= ~(1 << PB2);// SH_CP 0054.if(tempdata & 0x20)PORTB |= (1 << PB0);055.elsePORTB &= ~(1 << PB0);056.PORTB |= (1 << PB2);// SH_CP 1057.PORTB &= ~(1 << PB2);// SH_CP 0058.if(tempdata & 0x10)PORTB |= (1 << PB0);059.elsePORTB &= ~(1 << PB0);060.PORTB |= (1 << PB2);// SH_CP 1061.PORTB &= ~(1 << PB2);// SH_CP 0062.if(tempdata & 0x08)PORTB |= (1 << PB0);063.elsePORTB &= ~(1 << PB0);064.PORTB |= (1 << PB2);// SH_CP 1065.PORTB &= ~(1 << PB2);// SH_CP 0066.if(tempdata & 0x04)PORTB |= (1 << PB0);067.elsePORTB &= ~(1 << PB0);068.PORTB |= (1 << PB2);// SH_CP 1069.PORTB &= ~(1 << PB2);// SH_CP 0070.if(tempdata & 0x02)PORTB |= (1 << PB0);071.elsePORTB &= ~(1 << PB0);072.PORTB |= (1 << PB2);// SH_CP 1073.PORTB &= ~(1 << PB2);// SH_CP 0074.if(tempdata & 0x01)PORTB |= (1 << PB0);075.elsePORTB &= ~(1 << PB0);076.PORTB |= (1 << PB2);// SH_CP 1077.PORTB |= (1 << PB1);// ST_CP 1078.}079.080.// Функция передачи данных или команды в LCD081.voidwrite_to_lcd(charp, unsignedcharrs)082.{083.if(rs == 1) RS1;084.elseRS0;085.086.E1;087.088.if(p&0x10) D41;elseD40;089.if(p&0x20) D51;elseD50;090.if(p&0x40) D61;elseD60;091.if(p&0x80) D71;elseD70;092.E0;093._delay_ms(2);094.095.E1;096.097.if(p&0x01) D41;elseD40;098.if(p&0x02) D51;elseD50;099.if(p&0x04) D61;elseD60;100.if(p&0x08) D71;elseD70;101.E0;102.103._delay_ms(2);104.}105.106.// Функция инициализации LCD107.voidlcd_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.voidlcd_puts(char*str)117.{118.unsignedchari = 0;119.120.while(str[i])121.write_to_lcd(str[i++], 1);122.}123.124.// Функция вывода переменной125.voidlcd_num_to_str(unsignedintvalue, unsignedcharnDigit)126.{127.switch(nDigit)128.{129.case4: write_to_lcd((value/1000)+'0', 1);130.case3: write_to_lcd(((value/100)%10)+'0', 1);131.case2: write_to_lcd(((value/10)%10)+'0', 1);132.case1: write_to_lcd((value%10)+'0', 1);133.}134.}135.136.// Функция чтения АЦП137.intreadADC(unsignedcharch)138.{139.ADMUX = ch;// Выбираем канал АЦП140.141.ADCSRA |= (1 << ADSC);// Запускаем преобразование142.while((ADCSRA & (1 << ADSC)));// Ждем окончания преобразования143.144.return(ADC*11/2);// Возвращаем значение АЦП145.}146.147.intmain(void)148.{149.DDRB = 0b00000111;// Настраиваем входы/выходы150.151.ADCSRA |= (1 << ADEN)// Разрешение АЦП152.|(1 << ADPS2)|(1 << ADPS1);// Предделитель на 64153.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);// Выводим строки на LCD162.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 на LCD170.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 на LCD174.lcd_gotoxy(8, 1);175.lcd_num_to_str(readADC(3)%100/10, 1);176._delay_ms(50);177.}178.}Проект AVRStudio4