Примечание:

Статья не является оригинальным переводом. Статья созданы на базе статьи (см. источник информации указанный ниже) путем его перевода, использования основного текста оригинала и дополнена автором; данный код протестирован на реальном устройстве ф. Winstar.

LCD-дисплеи (Liquid Crystal Displays) используют для отображения состояния или параметров в различных приборах.

Жидкокристаллический дисплей LCD1602 представляет собой 16-выводное устройство, имеющее 8 выводов для передачи данных (D0-D7) и 3 вывода управления (RS, RW, EN). Остальные 5 выводов предназначены для питания и подсветки ЖК-дисплея. Цифры «1602» указывают на формат выводимой (отображаемой) информации: 16x02 символов (рисунок 1).

Выводы управления помогают нам настроить LCD-дисплей в командном режиме или режиме передачи данных. Они также помогают настроить режим чтения или записи, а также время чтения или записи.

LCD-дисплей 16x2 можно использовать в 4-битном или 8-битном режиме в зависимости от технических требований. Чтобы использовать его, нам необходимо отправить определенные команды на LCD-дисплей в командном режиме, и как только ЖК-дисплей будет настроен в соответствии с нашими требованиями, мы сможем отправить необходимые данные в режиме передачи данных.

Для получения дополнительной информации о LCD-дисплее 16x02 и о том, как его использовать, необходимо обратиться к datasheet.

Рисунок 1 – Распиновка жидкокристаллического дисплея LCD1602 (HD44780)
Рисунок 1 – Распиновка жидкокристаллического дисплея LCD1602 (HD44780)

Схема подключения жидкокристаллического дисплея LCD1602 (HD44780) к микроконтроллеру ATmega8 в 8-битном режиме показана на рисунке 2.

Рисунок 2 – Подключение жидкокристаллического дисплея LCD1602 к микроконтроллеру ATmega8 в 8-битном режиме
Рисунок 2 – Подключение жидкокристаллического дисплея LCD1602 к микроконтроллеру ATmega8 в 8-битном режиме

Подстрочный резистор R1 предназначен для точной подстройки контрастности дисплея. Резистор R2 предназначен для ограничения тока на аноде подсветки дисплея.

Таблица 1 – Аппаратные соединения
Таблица 1 – Аппаратные соединения

Функция инициализации дисплея:

1. Включить питание на LCD-дисплее

2. Необходимо подождать не менее 15 мс, время инициализации включения питания для LCD1602.

3. Отправить команду 0x38 которая переводит LCD-дисплей в 2-строчный, 8-битный режим и 5x8 точек.

4. Отправить одну из команд включения курсора дисплея (0x0E, 0x0C).

5. Отправить команду 0x06 (сдвиг курсора вправо).

Листинг кода:

void LCD_Init (void)	/* LCD Initialize function */
{
	LCD_Command_Dir = 0xFF;	/* Make LCD command port direction as o/p */
	LCD_Data_Dir = 0xFF;	/* Make LCD data port direction as o/p */

	_delay_ms(20);		/* LCD Power ON delay always >15ms */
	LCD_Command (0x38);	/* Initialization of 16X2 LCD in 8bit mode */
	LCD_Command (0x0C);	/* Display ON Cursor OFF */
	LCD_Command (0x06);	/* Auto Increment cursor */
	LCD_Command (0x01);	/* clear display */
	LCD_Command (0x80);	/* cursor at home position */
}

Теперь, когда LCD–дисплей инициализирован, он готов принимать данные для отображения.

 Функция записи команд (инструкций):

1. Отправим значение команды на порт передачи данных LCD1602.

2. Установим вывод RS на «низкий» уровень, RS = 0 (регистр команд).

3. Установим вывод RW на «низкий» уровень, RW = 0 (операция записи)

4. Подадим импульс от «высокого» до «низкого» на выводе Enable (E) с минимальной задержкой 450 нс.

Когда мы подаем разрешающий импульс, LCD-дисплей фиксирует данные, имеющиеся на выводах D0 – D7, и выполняет их как команду, поскольку RS - это регистр команд.

 Листинг кода:

void LCD_Command(unsigned char cmnd)
{
	LCD_Data_Port= cmnd;
	LCD_Command_Port &= ~(1<<RS);	/* RS=0 command reg. */
	LCD_Command_Port &= ~(1<<RW);	/* RW=0 Write operation */
	LCD_Command_Port |= (1<<EN);	/* Enable pulse */
	_delay_us(1);
	LCD_Command_Port &= ~(1<<EN);
	_delay_ms(3);
}

Функция записи данных:

1. Отправим команду на порт передачи данных.

2. Установим вывод RS на «высокий» уровень, RS = 1 (регистр данных)

3. Установим вывод RW на «низкий» уровень, RW = 0 (операция записи)

4. Подадим импульс от «высокого» до «низкого» на выводе Enable (E).

Когда мы подаем разрешающий импульс, LCD-дисплей фиксирует имеющиеся данные (на выводах D0-D7) и отображает их на матрице 5x8, поскольку RS - это регистр данных.

Листинг кода:

void LCD_Char (unsigned char char_data)	/* LCD data write function */
{
	LCD_Data_Port = char_data;
	LCD_Command_Port |= (1<<RS);	/* RS=1 Data reg. */
	LCD_Command_Port &= ~(1<<RW);	/* RW=0 write operation */
	LCD_Command_Port |= (1<<EN);	/* Enable Pulse */
	_delay_us(1);
	LCD_Command_Port &= ~(1<<EN);
	_delay_ms(1);
}

Функция отображения строки:

Эта функция принимает строку (массив символов) и отправляет по одному символу в функцию данных LCD-дисплея до конца строки. Цикл for используется для отправки символа на каждой итерации. Символ NULL указывает на конец строки.

Листинг кода:

void LCD_String (char *str)		
{
	int i;
	for(i=0;str[i]!=0;i++)  /* send each char of string till the NULL */
	{
		LCD_Char (str[i]);  /* call LCD data write */
	}
}

Примечания:

1. Задержка включения LCD-дисплея. После включения LCD1602 мы не можем немедленно отправлять на него команды, поскольку ему требуется время на инициализацию 15 мс. Поэтому при программировании нам нужно позаботиться о том, чтобы обеспечить достаточную задержку включения питания (> 15 мс), а затем отправлять команды на LCD-дисплей.

2. После проверки команд LCD1602 требуется время (в микросекундах) для их выполнения. Но для команды 0x01 (т.е. очистить отображение) выполнение занимает 1,64 мс. Следовательно, после отправки команды 0x01 необходимо обеспечить достаточную задержку (> 1,63 миллисекунды).

Листинг программы:

#define F_CPU 8000000UL			/* Define CPU Frequency e.g. here 8MHz */
#include <avr/io.h>			/* Include AVR std. library file */
#include <util/delay.h>			/* Include inbuilt defined Delay header file */

#define LCD_Data_Dir DDRB		/* Define LCD data port direction */
#define LCD_Command_Dir DDRC		/* Define LCD command port direction register */
#define LCD_Data_Port PORTB		/* Define LCD data port */
#define LCD_Command_Port PORTC		/* Define LCD data port */
#define RS PC0				/* Define Register Select (data/command reg.)pin */
#define RW PC1				/* Define Read/Write signal pin */
#define EN PC2				/* Define Enable signal pin */
 

void LCD_Command(unsigned char cmnd)
{
	LCD_Data_Port= cmnd;
	LCD_Command_Port &= ~(1<<RS);	/* RS=0 command reg. */
	LCD_Command_Port &= ~(1<<RW);	/* RW=0 Write operation */
	LCD_Command_Port |= (1<<EN);	/* Enable pulse */
	_delay_us(1);
	LCD_Command_Port &= ~(1<<EN);
	_delay_ms(3);
}

void LCD_Char (unsigned char char_data)	/* LCD data write function */
{
	LCD_Data_Port= char_data;
	LCD_Command_Port |= (1<<RS);	/* RS=1 Data reg. */
	LCD_Command_Port &= ~(1<<RW);	/* RW=0 write operation */
	LCD_Command_Port |= (1<<EN);	/* Enable Pulse */
	_delay_us(1);
	LCD_Command_Port &= ~(1<<EN);
	_delay_ms(1);
}

void LCD_Init (void)			/* LCD Initialize function */
{
	LCD_Command_Dir = 0xFF;		/* Make LCD command port direction as o/p */
	LCD_Data_Dir = 0xFF;		/* Make LCD data port direction as o/p */
	_delay_ms(20);			/* LCD Power ON delay always >15ms */
	
	LCD_Command (0x38);		/* Initialization of 16X2 LCD in 8bit mode */
	LCD_Command (0x0C);		/* Display ON Cursor OFF */
	LCD_Command (0x06);		/* Auto Increment cursor */
	LCD_Command (0x01);		/* Clear display */
	LCD_Command (0x80);		/* Cursor at home position */
}

void LCD_String (char *str)		/* Send string to LCD function */
{
	int i;
	for(i=0;str[i]!=0;i++)		/* Send each char of string till the NULL */
	{
		LCD_Char (str[i]);
	}
}

void LCD_String_xy (char row, char pos, char *str)/* Send string to LCD with xy position */
{
	if (row == 0 && pos<16)
	LCD_Command((pos & 0x0F)|0x80);	/* Command of first row and required position<16 */
	else if (row == 1 && pos<16)
	LCD_Command((pos & 0x0F)|0xC0);	/* Command of first row and required position<16 */
	LCD_String(str);		/* Call LCD string function */
}

void LCD_Clear()
{
	LCD_Command (0x01);		/* clear display */
	LCD_Command (0x80);		/* cursor at home position */
}
 
int main()
{

	LCD_Init();			/* Initialize LCD */

	LCD_String("Hello World");	/* write string on 1st line of LCD*/
	LCD_Command(0xC0);		/* Go to 2nd line*/
	LCD_String("8 bit");	/* Write string on 2nd line*/

	return 0;

После загрузки прошивки на дисплее LCD1602 мы увидим следующий результат, показанный на рисунке 3.

Хорошим тоном программирования является создание отдельной библиотеки для нашего ЖК-дисплея.

Рисунок 3 – Результат работы прошивки
Рисунок 3 – Результат работы прошивки

Расшифровка наиболее употребляемых команд, посылаемых от микроконтроллера в дисплей LCD1602 (HD44780) приведена в таблицах 2,3.

Таблица 2 – Расшифровка наиболее употребляемых команд, посылаемых от микроконтроллера в дисплей LCD1602 (HD44780)
Таблица 2 – Расшифровка наиболее употребляемых команд, посылаемых от микроконтроллера в дисплей LCD1602 (HD44780)

Время выполнения команд указано приблизительно. Оно определяется частотой внутреннего RC-генератора LCD-дисплея, которая, в свою очередь, зависит от технологического разброса и температуры нагрева корпуса.

Таблица 3 – Команды для перехода на определенное знакоместо верхней или нижней строки экрана для дисплея LCD1602 (HD44780)
Таблица 3 – Команды для перехода на определенное знакоместо верхней или нижней строки экрана для дисплея LCD1602 (HD44780)

Источник информации:

https://www.electronicwings.com/avr-atmega/lcd16x2-interfacing-with-atmega16-32

Комментарии (20)


  1. nochkin
    04.04.2023 20:59
    +1

    Создавать две статьи из-за дополнительных 4 бит выглядит немного странным. Проще было бы объединить, так как общего очень много.

    Но ещё было бы интересно увидеть разницу между этими режимами на таком мелком экране.


    1. FranzDev Автор
      04.04.2023 20:59
      -3

      Ну рунете инфы по этой теме мало, поэтому пускай будет


      1. nochkin
        04.04.2023 20:59
        +1

        Если будет две одинаковые статьи, то от этого информации больше не станет.


      1. progchip666
        04.04.2023 20:59
        +3

        Эта тема даже на хабре давно присутствует. Какой то смысл возможно имела бы статья об особенностях инициализации LCD индикаторов от MELT или примеры использования пользовательских символов.. Но там же самому что то писать надо, а тут целый "перевод", достойный "инженера по автоматизации"


        1. Rusrst
          04.04.2023 20:59

          Так что то, что то особой сложностью не отличается.


      1. DrGluck07
        04.04.2023 20:59
        +2

        Потому что работу с HD447XX все освоили примерно в середине нулевых. Вот тогда бы мне эти посты пригодились. Да и то, хорошо, что пришлось разбираться самому и теперь мне вообще пофигу что по какому интерфейсу подключать: открыл мануал, сделал. Иногда не без проблем, но на то мы и опытные практики, чтоб их решать.


  1. Rusrst
    04.04.2023 20:59
    +1

    Я вот точно помню, что ждать 180 мс не нужно, есть флаг готовности и по нему можно проверять готов ли модуль к следующей команде, может стоит им воспользоваться?


    1. FranzDev Автор
      04.04.2023 20:59
      -3

      Каждый разработчик решает это по-своему на основании данных из datasheet и экспериментальных данных. Я лишь показал только вариант


    1. COKPOWEHEU
      04.04.2023 20:59
      +1

      Это надо лишнюю ножку использовать. А учитывая тормознутость самого дисплея и малые объемы данных, эти 180 мс не влияют ни на что. Собственно, именно поэтому 8-битный режим сейчас не используется. Тащить 6 ног или 11 — есть разница! А для особенно жадных есть еще и платы-переходники, например, под I2C, там вообще 2 ноги, причем их можно продолжать использовать для других I2C устройств.


      8-битный режим был отчасти актуален если вешать дисплей прямо на системную шину, где и так есть все нужные сигналы. А сейчас, когда все это приходится эмулировать — не более чем атавизм.


      1. VT100
        04.04.2023 20:59

        Тащить 6 или 11 — да.
        А вот "продолжать использовать 2 ноги I2C" — притянуто за уши. Линия дисплея "E" — является, по сути, chip select'ом. А все остальные — можно разделять с другой аппаратурой.


        1. MaFrance351
          04.04.2023 20:59

          Да. Можно хоть кнопки навесить на эти линии. Пока EN поднят, дисплею глубоко всё равно, что происходит на его шине данных.
          И не только ради системной шины можно, но и ради того, чтобы выделить под дисплей целый порт МК и одной строкой кода выставлять на выводах команду или данные.


        1. COKPOWEHEU
          04.04.2023 20:59

          А вот "продолжать использовать 2 ноги I2C"

          Это вообще отдельная ситуация, которая требует использования дополнительной платы.


          Линия дисплея "E" — является, по сути, chip select'ом. А все остальные — можно разделять с другой аппаратурой.

          При желании можно. Вот только библиотеку работы с дисплеем придется допилить.


          Пока EN поднят, дисплею глубоко всё равно, что происходит на его шине данных.

          Пока EN опущен — тоже. Важен именно фронт переключения.


          И не только ради системной шины можно, но и ради того, чтобы выделить под дисплей целый порт МК и одной строкой кода выставлять на выводах команду или данные.

          Так на современных МК все равно не получается. Так и так управляющие линии приходится дергать отдельно от линий данных.


          1. VT100
            04.04.2023 20:59

            Пока EN поднят, дисплею глубоко всё равно, что происходит на его шине данных.
            Пока EN опущен — тоже. Важен именно фронт переключения.

            Не совсем. Если и R/!W и E в "1", то дисплей выводит информацию на ШД. Возможен конфликт.


  1. An_private
    04.04.2023 20:59

    А можно глупый вопрос - кто-то сейчас эти индикаторы в параллельном режиме еще использует? Ну то есть я понимаю, что это легаси 20+ летней давности времён 8080 - тогда такой вариант подключения был удобен. Но после появления массового адаптера на i2c использовать минимум 6 ножек контроллера на LCD? Серьезно?

    Или может я что-то не понимаю и все тру-разработчики именно так и делают?


    1. DrGluck07
      04.04.2023 20:59

      не туда написал

      Раз уж всё равно есть этот коммент, то отвечу. Ещё кое-где используется, потому что "работает - не трогай". Вот недавно пришлось править код общения с таким дисплеем, потому что были некоторые схемотехнические проблемы, которые частично удалось заткнуть программно.


    1. osmanpasha
      04.04.2023 20:59

      Мы использовали в паре проектов. Если у МК есть лишние ноги, или у МК основная функция - работать с экраном и клавиатурой, зачем ещё один чип ставить?


    1. dlinyj
      04.04.2023 20:59

      Цена вопроса в массовом производстве. Экономия одной микросхемы сказывается на цене.


      1. MaFrance351
        04.04.2023 20:59

        Более того, даже иногда оптимизация ради возможности поставить чуть более тупой МК при массовом производстве сказывается на цене.


    1. progchip666
      04.04.2023 20:59

      Недавно как раз добавлял как раз I2C драйвер по настоятельной просьбе заказчика на МЕЛТовский LCD Тут есть однако одно оправдание - сам этот драйвер всё равно подключается по 4 битному I2C интерфейсу и ознакомиться с ним бывает полезно. Приходится использовать мелтовские, когда нужны русские шрифты. Кстати, про само такое подключение на Хабре уже есть отличная статья. Вот только у Мелтовских есть одно мелкое отличие в инициализации, в результате которого они не работают со стандартными драйверами.


  1. DrGluck07
    04.04.2023 20:59
    +1

    Вопрос ровно тот же, что в 4-битном режиме. Зачем такая длинная задержка после каждого символа?