Примечание:

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

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

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

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

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

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

В 4-битном режиме данные/команды отправляются в 4-битном (полубайтном) формате.

Для этого сначала необходимо отправить «старшие» (верхние)  4 бита, а затем отправить «младшие» (нижние) 4 бита данных/команд.

Только 4 выводы данных (D4 - D7) LCD-дисплея 16x02 подключены к микроконтроллеру, а другие управляющие выводы RS (выбор регистра), RW (чтение/запись), E (сигнал разрешения) подключены к другим GPIO выводам микроконтроллера (рисунок 2).

Таким образом, благодаря такому подключению мы можем сэкономить четыре GPIO вывода микроконтроллера, которые можно использовать для других целей.

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

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

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

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

2. Отправить команду 0x02, которая инициализирует LCD-дисплей 16x2 в 4-битном режиме.

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

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

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

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

void LCD_Init (void)  /* LCD Initialize function */
{
	LCD_Dir = 0xFF;		/* Make LCD port direction as o/p */
	_delay_ms(20);		/* LCD Power ON delay always >15ms */
	
	LCD_Command(0x02);	/* Send for 4 bit initialization of LCD  */
	LCD_Command(0x28);	/* 2 line, 5*7 matrix in 4-bit mode */
	LCD_Command(0x0c);	/* Display on cursor off */
	LCD_Command(0x06);	/* Increment cursor (shift cursor to right) */
	LCD_Command(0x01);	/* Clear display screen */
	_delay_ms(2);
}

Теперь мы успешно инициализировали LCD-дисплей, и он готов принимать данные в 4-битном (полубайтном) режиме для отображения.

Чтобы отправить команду/данные на LCD -дисплей 16x02, мы должны отправить «старший» (верхний) полубайт, а затем «младший» (нижний) полубайт. Поскольку выводы D4-D7 LCD-дисплея 16x02 подключены как выводы данных, мы должны сдвинуть младший полубайт вправо на 4 перед передачей.

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

1. Сначала отправим более «высокий» полубайт команды.

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

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

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

5. Отправим «младший» полубайт команды.

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

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

void LCD_Command( unsigned char cmnd )
{
	LCD_Port = (LCD_Port & 0x0F) | (cmnd & 0xF0);/* Sending upper nibble */
	LCD_Port &= ~ (1<<RS);		/* RS=0, command reg. */
	LCD_Port |= (1<<EN);		/* Enable pulse */
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_us(200);
	LCD_Port = (LCD_Port & 0x0F) | (cmnd << 4);/* Sending lower nibble */
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}

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

1. Сначала отправим более «высокий» полубайт данных.

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

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

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

5. Отправим «младший» полубайт данных.

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

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

void LCD_Char( unsigned char data )
{
	LCD_Port = (LCD_Port & 0x0F) | (data & 0xF0);/* Sending upper nibble */
	LCD_Port |= (1<<RS);  /* RS=1, data reg. */
	LCD_Port|= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_us(200);
	LCD_Port = (LCD_Port & 0x0F) | (data << 4);  /* Sending lower nibble */
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}

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

#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 Delay header file */

#define LCD_Dir  DDRB			/* Define LCD data port direction */
#define LCD_Port PORTB			/* Define LCD data port */
#define RS PB0				/* Define Register Select pin */
#define EN PB1 				/* Define Enable signal pin */
 

void LCD_Command( unsigned char cmnd )
{
	LCD_Port = (LCD_Port & 0x0F) | (cmnd & 0xF0); /* sending upper nibble */
	LCD_Port &= ~ (1<<RS);		/* RS=0, command reg. */
	LCD_Port |= (1<<EN);		/* Enable pulse */
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);

	_delay_us(200);

	LCD_Port = (LCD_Port & 0x0F) | (cmnd << 4);  /* sending lower nibble */
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}


void LCD_Char( unsigned char data )
{
	LCD_Port = (LCD_Port & 0x0F) | (data & 0xF0); /* sending upper nibble */
	LCD_Port |= (1<<RS);		/* RS=1, data reg. */
	LCD_Port|= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);

	_delay_us(200);

	LCD_Port = (LCD_Port & 0x0F) | (data << 4); /* sending lower nibble */
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}

void LCD_Init (void)			/* LCD Initialize function */
{
	LCD_Dir = 0xFF;			/* Make LCD port direction as o/p */
	_delay_ms(20);			/* LCD Power ON delay always >15ms */
	
	LCD_Command(0x02);		/* send for 4 bit initialization of LCD  */
	LCD_Command(0x28);              /* 2 line, 5*7 matrix in 4-bit mode */
	LCD_Command(0x0c);              /* Display on cursor off*/
	LCD_Command(0x06);              /* Increment cursor (shift cursor to right)*/
	LCD_Command(0x01);              /* Clear display screen*/
	_delay_ms(2);
}


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 */
	_delay_ms(2);
	LCD_Command (0x80);		/* Cursor at home position */
}
 
int main()
{

	LCD_Init();			/* Initialization of LCD*/

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

Для удобства использования оформим описанный выше код в виде библиотеки. Для этого создадим два файла LCD1602.h и LCD1602.c.

 Листинг файла LCD1602.h

#ifndef LCD1602_H_
#define LCD1602_H_

#define LCD_Dir  DDRB
#define LCD_Port PORTB
#define RS PB0
#define EN PB1

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

void LCD_Command( unsigned char cmnd );
void LCD_Char( unsigned char data );
void LCD_Init (void);
void LCD_String (char *str);
void LCD_String_xy (char row, char pos, char *str);	
void LCD_Clear();

#endif /* LCD1602_H_ */

Листинг файла LCD1602.c

#include "LCD1602.h"

void LCD_Command( unsigned char cmnd )
{
	LCD_Port = (LCD_Port & 0x0F) | (cmnd & 0xF0);
	LCD_Port &= ~ (1<<RS);
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);

	_delay_us(200);

	LCD_Port = (LCD_Port & 0x0F) | (cmnd << 4);
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}


void LCD_Char( unsigned char data )
{
	LCD_Port = (LCD_Port & 0x0F) | (data & 0xF0);
	LCD_Port |= (1<<RS);
	LCD_Port|= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);

	_delay_us(200);

	LCD_Port = (LCD_Port & 0x0F) | (data << 4);
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}

void LCD_Init (void)
{
	LCD_Dir = 0xFF;
	_delay_ms(20);
	
	LCD_Command(0x02);
	LCD_Command(0x28);
	LCD_Command(0x0c);
	LCD_Command(0x06);
	LCD_Command(0x01);
	_delay_ms(2);
}


void LCD_String (char *str)
{
	int i;
	for(i=0;str[i]!=0;i++)
	{
		LCD_Char (str[i]);
	}
}

void LCD_String_xy (char row, char pos, char *str)
{
	if (row == 0 && pos<16)
	LCD_Command((pos & 0x0F)|0x80);
	else if (row == 1 && pos<16)
	LCD_Command((pos & 0x0F)|0xC0);
	LCD_String(str);
}

void LCD_Clear()
{
	LCD_Command (0x01);
	_delay_ms(2);
	LCD_Command (0x80);
}

Для подключения библиотеки необходимо файлы LCD1602.h и LCD1602.c поместить в папку с проектом, в программе Atmel Studio правой клавишей мыши кликнуть на имя проекта, выбрать Add - Existing Item, найти и выбрать все два файла, нажать OK.

Далее пишем код:

#include <avr/io.h>
#include "LCD1602.h"

int main()
{
	LCD_Init();
	LCD_String("Hello World");
	LCD_Command(0xC0);
	LCD_String("4 bit");

	while(1)
    {				
	
    }
}

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

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

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

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

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

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

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

https://www.electronicwings.com/avr-atmega/interfacing-lcd-16x2-in-4-bit-mode-with-atmega-16-32-

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


  1. Rusrst
    04.04.2023 21:01

    Это мэлтовский что ли индикатор? Если да, то у него система команд таки слегка отличается. Я уже за давностью лет не помню в чем там соль.


    1. FranzDev Автор
      04.04.2023 21:01

      Winstar на МЭЛТ не тестировалось


  1. DrGluck07
    04.04.2023 21:01
    +3

    Передача каждой команды занимает минимум 200 микросекунд, really? Проверьте свои тайминги, это не должно работать так медленно и печально. Насколько я помню, там вообще нет таймингов больше 1 микросекунды и все тайминги даны в наносекундах. Это мы так будем одну строку рисовать несколько миллисекунд, а весь остальной контроллер будет стоять и ждать (в смысле всё, что работает в фоновом цикле).


    1. DrGluck07
      04.04.2023 21:01
      +2

      Дополнение, потому что слона-то я и не заметил. Задержка 2-3 миллисекунды между символами?