Для активизации четырехбитового режима надо программно сформировать сигналы управления согласно временным диаграммам на рис.1. По структуре они совпадают с диаграммой 8-ми разрядной шины за исключением удвоенного числа импульсов "Е". Линии связи проходят через старшие разряды шины данных DB4-DB7, младшие DB0-DB3 остаются не задействованными.

Рис.1

Достоинство режима - малое число проводников, упрощение топологии печатной платы, экономия линий портов МК. Недостаток - пониженная скорость передачи данных в ЖКИ, так как приходится информацию передавать двумя порциями (нибблами или тетрадами) по 4 бита в каждой. Однако, учитывая обязательные задержки времени в программе и физическую инерционность "жидких кристаллов", снижение скорости почти не чувствуется.

Принцип работы 4-х разрядной шины рассмотрим на примере тестовой программы для нашего LCD. На дисплей будут с секундными паузами выводиться цифры десятичного адреса знакоместа 0-255 и графические образы содержащихся в них символов.

Рис. 2

Как известно, каждый LCD имеет встроенный знакогенератор, представляющий собой область ПЗУ объемом более 8 Кб, которая прошивается на заводе-изготовителе. Традиционно первая половина ПЗУ с адресами 00-7Fh содержит начертания цифр, знаков препинания, а также заглавных и строчных букв латинского алфавита. Все как в IBM PC. Вторая половина "отдана на откуп" национальным алфавитам. В связи с этим HD44780 имеет модификации исполнения с тремя основными вариантами зашивки знакогенератора:

латиница и европейские языки (European standard font или Euro)
латиница и японские иероглифы (Japanese standard font или Japan)
латиница и кириллица (Custom font или Russian, рис.2)

Не все из ячеек знакогенератора заполнены. При обращении к "пустым" ячейкам на экране будет выведена произвольная информация, чаще всего состоящая из засвеченных точек. Первые 8 символов с адресами 0х00-0х07 отмечены "звездочкой". При желании они могут быть самостоятельно запрограммированы пользователем.

Какой знакогенератор имеется в конкретном LCD, должно быть указано в его условном обозначении или в технических параметрах, хотя на практике приходится верить честному слову продавца. Другой подход воочию увидеть на экране LCD все возможные начертания символов. Напишем, откомпилируем программу и запрограммируем контроллер, после чего в верхней строке экрана LCD будут с секундными паузами будут выводиться цифры десятичного адреса знакоместа 0-255 и графические образы содержащихся в них символов. Если графика и очередность появления символов соответствует рис.2, значит, LCD в порядке.

Далее собираем схему согласно рис.3, ее отличие только в том что шина данных подключена по 4-х проводной линии, т.е. DB4-DB7 подключены, а DB0-DB3 остаются не задействованными. Вывод R/W дисплея подключен на минус, т.к. дисплей у нас является приемником данных.

Подключение по 4-х проводной шине

Код программы проверки знакогенератора LCD приведен ниже.

// Программа проверки знакогенератора LCD

#include <avr/io.h>
#include <util/delay.h>

#define RS PC0 
#define EN PC2

// Функция записи команды в ЖКИ
void lcd_com(unsigned char p)
{
PORTC &= ~(1 << RS); // RS = 0 (запись команд)
PORTC |= (1 << EN);  // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p & 0xF0); // Выделяем старший нибл
_delay_us(100);
PORTC &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
PORTC |= (1 << EN); // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p << 4); // Выделяем младший нибл
_delay_us(100);
PORTC &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
}

// Функция записи данных в ЖКИ
void lcd_dat(unsigned char p)
{
PORTC |= (1 << RS)|(1 << EN); // RS = 1 (запись данных), EN - 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p & 0xF0); // Выделяем старший нибл
_delay_us(100);
PORTC &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
PORTC |= (1 << EN); // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p << 4); // Выделяем младший нибл
_delay_us(100);
PORTC &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
}

// Функция инициализации ЖКИ
void lcd_init(void)
{
DDRC |= (1 << PC2)|(1 << PC0); // PC1, PC0 - выходы
PORTC = 0x00;
DDRD = 0xFF; // порт D - выход
PORTD = 0x00;

_delay_ms(50); // Ожидание готовности ЖК-модуля

// Конфигурирование четырехразрядного режима
PORTD |= (1 << PD5);
PORTD &= ~(1 << PD4);

// Активизация четырехразрядного режима
PORTC |= (1 << EN);
PORTC &= ~(1 << EN);
_delay_ms(5); 

lcd_com(0x28); // Шина 4 бит, LCD - 2 строки
lcd_com(0x08); // Полное выключение дисплея
lcd_com(0x01); // Очистка дисплея
_delay_us(100);
lcd_com(0x06); // Сдвиг курсора вправо
_delay_ms(10);
lcd_com(0x0C); // Включение дисплея, курсор не видим
}

// Основная программа
int main (void)
{
unsigned char znak = 0; // определяем переменную

lcd_init(); // Инициализация дисплея

while (1)
{
lcd_com(0x80); // Вывод в верхнюю левую позицию 1 строки
lcd_dat(znak/100 + '0'); // Выделяем сотни
lcd_dat((znak/10)%10 + '0'); // Выделяем десятки 
lcd_dat(znak%10 + '0'); // Выделяем единицы
lcd_dat('='); // Выводим знак равенства
lcd_dat(znak); // Выводим содержимое знакогенератора
_delay_ms(100); // Тут можно поменять задержку вывода символов
znak++; // Следующий символ знакогенератора
}
}

Комментарии  

0 #41 Magnetic 14.01.2017 18:11
Добрый день.
При работе наблюдается задержка смены выводимого символа, большая чем задана в коде. Например, при
Код: _delay_ms(100); смена символа происходит за время около 1 секунды.
При Код: _delay_ms(1000); - около 5 секунд. Если предположить, что выложенное в конце статьи видео демонстрирует работу кода, приведенного в статье, то задержка также не соответствует 100мс.
Что может быть причиной???
Частота работы процессора в коде Код: #define F_CPU 16000000UL задана правильно.
Сообщить модератору
+1 #42 Ruslan Valeev 14.01.2017 22:58
Добрый вечер кто-нибудь может мне помочь записал этот код в атмел Студио 6.0. при компиляции компилятор выдает кучу ошибок первая
#warning "F_CPU not defined for ″" [- Wcpp] что это за ошибка и как ее исправить
Сообщить модератору
0 #43 Magnetic 15.01.2017 02:03
Прописать строчку в самом начале Код: #define F_CPU 16000000UL где укажите частоту на которой работает Ваш микроконтроллер в герцах.
В приведенном примере это 16000000 (16МГц).
Сообщить модератору
0 #44 Ruslan Valeev 15.01.2017 13:27
Это #43 Magnetic 15.01.2017 02:03
Прописать строчку в самом начале
Код:
#define F_CPU 16000000UL
где укажите частоту на которой работает Ваш микроконтроллер в герцах.
В приведенном примере это 16000000 (16МГц).
Не исправляет ошибку
Сообщить модератору
0 #45 Magnetic 15.01.2017 21:52
#44 Ruslan Valeev
Ошибка типа:
Warning #warning "F_CPU not defined for " [-Wcpp]
обычно возникает когда не указана частота МК необходимая для библиотеки delay.h, с помощью которой формируются временные задержки функциями _delay_us() или _delay_ms(). Исправляется указанием частоты МК. Начало кода в этом случае должно выглядеть так: Код:
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

Что за контроллер используете?
Сообщить модератору
0 #46 Ruslan Valeev 15.01.2017 21:58
Magnetic
Я прописывал частоту указывал и 4кгц и 10 кгц и 16 кгц хотя в приведенной схеме нет внешнего генератора, но при компелировании результат один.
Контролер атмега 8 как по схеме в корпусе dip 28
Сообщить модератору
+1 #47 Magnetic 16.01.2017 00:10
Если нет внешнего кварца, то МК работает от внутреннего генератора с частотой от 1 до 8 МГц (задается выбором соответствующих CKSEL Fuses при прошивке).
Вот так долно быть записано в коде:
Код:#define F_CPU 8000000UL
И частота контроллера в МГц а не кГц.
Но даже если частоту указать неправильно, то это повлияет лишь на корректность времени задержки. Ошибки быть не должно.
Сообщить модератору
0 #48 Magnetic 16.01.2017 00:20
Скопировал код в AtmelStudio7.0, добавил определение частоты мк. Ошибки нет, все компилируется нормально.
При создании нового проекта, естественно, необходимо выбрать контроллер под который пишется код.
Сообщить модератору
0 #49 zadan 11.01.2018 09:08
Пример в среде Arduino тоже работает. Но с аналогичным экраном M392 Pamasonic-FAX символ соответствует коду+1.
Сообщить модератору
0 #50 uuu000 24.04.2019 13:34
Не могу понять строки 51,52.
Зачем устанавливаем 1 в порт PORTD5,а порт PORTD4 сбрасываем в 0?
Спасибо.
Сообщить модератору
0 #51 AntonChip 24.04.2019 22:17
Цитирую uuu000:
Не могу понять строки 51,52.
Зачем устанавливаем 1 в порт PORTD5,а порт PORTD4 сбрасываем в 0?
Спасибо.

Команда необходима для инициализации 4 битной шины данных
Сообщить модератору
0 #52 Алексей Базилевич 24.09.2019 02:50
Доброго времени суток. Не совсем понял функции записи команд и данных, точнее их логику. Зачем сначала EN в 1, а потом обратно в 0? И не понял по задержкам, откуда именно такие, в таблице команд и их задержек подобного не нашел. Помогите разобраться.
Сообщить модератору
0 #53 AntonChip 29.09.2019 15:09
Цитирую Алексей Базилевич:
Доброго времени суток. Не совсем понял функции записи команд и данных, точнее их логику. Зачем сначала EN в 1, а потом обратно в 0? И не понял по задержкам, откуда именно такие, в таблице команд и их задержек подобного не нашел. Помогите разобраться.

Вывод Е - сигнал синхроимпульса, он по сути должен переключаться
Задержки примерные, согласно описанию на контроллер
Сообщить модератору
0 #54 uuu000 22.01.2021 19:34
Можно подробно объяснить строки 14,19.
Спасибо.
Сообщить модератору
0 #55 AntonChip 22.01.2021 21:19
Цитирую uuu000:
Можно подробно объяснить строки 14,19.
Спасибо.

Приветствую, читайте комментарий №17
Сообщить модератору
0 #56 uuu000 24.01.2021 17:32
Код:...
Прошу прощения за занудство
Вроде разобрался но не понимаю почему не нужна эта операция(см ***).

// Функция записи команды в ЖКИ
void lcd_com(unsigned char p)
{
PORTC &= ~(1 << RS); // RS = 0 (запись команд)
PORTC|= (1 << EN); // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F;
/*обнуление старшего ниббла
PORTD =0b00001111;
если ,например р=0b01101010;
получаем запись в LCD
0b01101010 & (0xF0 =0b1111000)=0b01100000;-это только старший ниббл
PORTD |=0b01100000;-запись старшего ниббла команды в LCD
*/
PORTD |= (p & 0xF0); // Выделяем старший нибл и записываем его в LCD
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD)

PORTD &= 0x0F;
/*обнуляем старшй ниббл PORTD =0b00001111;
теперь ,как в предыдущем комментарии пологаем р=0b01101010;
производим операцию сдвига влево для р=0b10100000;
имеем 0b00001111 & 0b10100000=0b10100000; ВОТ ЭТУ ОПЕРАЦИЮ Я НЕ ВИЖУ В КОДЕ ***
младший ниббл передан для записи команды в LCD
*/
PORTD |= (p << 4); // Выделяем младший нибл
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
}...
Сообщить модератору
0 #57 fnfr 05.12.2023 16:46
вместо PORTD &= 0x0F; для лучшего восприятия кода можно написать PORTD =0;
Сообщить модератору