Примечание:
Статья не является оригинальным переводом. Статья созданы на базе статьи (см. источник информации указанный ниже) путем его перевода, использования основного текста оригинала и дополнена автором; данный код протестирован на реальном устройстве ф. 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)](https://habrastorage.org/getpro/habr/upload_files/962/856/dd8/962856dd8b27397bb75150a6894bba48.png)
В 4-битном режиме данные/команды отправляются в 4-битном (полубайтном) формате.
Для этого сначала необходимо отправить «старшие» (верхние) 4 бита, а затем отправить «младшие» (нижние) 4 бита данных/команд.
Только 4 выводы данных (D4 - D7) LCD-дисплея 16x02 подключены к микроконтроллеру, а другие управляющие выводы RS (выбор регистра), RW (чтение/запись), E (сигнал разрешения) подключены к другим GPIO выводам микроконтроллера (рисунок 2).
Таким образом, благодаря такому подключению мы можем сэкономить четыре GPIO вывода микроконтроллера, которые можно использовать для других целей.
![Рисунок 2 – Подключение жидкокристаллического дисплея LCD1602 к микроконтроллеру ATmega8 в 4-битном режиме Рисунок 2 – Подключение жидкокристаллического дисплея LCD1602 к микроконтроллеру ATmega8 в 4-битном режиме](https://habrastorage.org/getpro/habr/upload_files/d62/604/f68/d62604f68aa2f955df79c554d4f6f076.jpg)
Подстрочный резистор 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 – Результат работы прошивки](https://habrastorage.org/getpro/habr/upload_files/0cf/274/d54/0cf274d54644b2fbdf09d15c383a74f1.png)
Расшифровка наиболее употребляемых команд, посылаемых от микроконтроллера в дисплей LCD1602 (HD44780) приведена в таблицах 1, 2.
![Таблица 1 – Расшифровка наиболее употребляемых команд, посылаемых от микроконтроллера в дисплей LCD1602 (HD44780) Таблица 1 – Расшифровка наиболее употребляемых команд, посылаемых от микроконтроллера в дисплей LCD1602 (HD44780)](https://habrastorage.org/getpro/habr/upload_files/f6f/45b/f70/f6f45bf70d684bde8add8102f2f8e53a.gif)
Время выполнения команд указано приблизительно. Оно определяется частотой внутреннего RC-генератора LCD-дисплея, которая, в свою очередь, зависит от технологического разброса и температуры нагрева корпуса.
![Таблица 2 – Команды для перехода на определенное знакоместо верхней или нижней строки экрана для дисплея LCD1602 (HD44780) Таблица 2 – Команды для перехода на определенное знакоместо верхней или нижней строки экрана для дисплея LCD1602 (HD44780)](https://habrastorage.org/getpro/habr/upload_files/b7f/564/a33/b7f564a3338d927bc74b1c3e2da156eb.gif)
Источник информации:
https://www.electronicwings.com/avr-atmega/interfacing-lcd-16x2-in-4-bit-mode-with-atmega-16-32-
Комментарии (4)
DrGluck07
04.04.2023 21:01+3Передача каждой команды занимает минимум 200 микросекунд, really? Проверьте свои тайминги, это не должно работать так медленно и печально. Насколько я помню, там вообще нет таймингов больше 1 микросекунды и все тайминги даны в наносекундах. Это мы так будем одну строку рисовать несколько миллисекунд, а весь остальной контроллер будет стоять и ждать (в смысле всё, что работает в фоновом цикле).
DrGluck07
04.04.2023 21:01+2Дополнение, потому что слона-то я и не заметил. Задержка 2-3 миллисекунды между символами?
Rusrst
Это мэлтовский что ли индикатор? Если да, то у него система команд таки слегка отличается. Я уже за давностью лет не помню в чем там соль.
FranzDev Автор
Winstar на МЭЛТ не тестировалось