Подключение 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.
}
Проект AVRStudio4