Символьный дисплей LCD4004: 40 символов на 4 строки
LCD4004 — самый вместительный символьный дисплей семейства HD44780: 40 символов в строке и 4 строки, всего 160 знакомест. Это вдвое больше, чем у привычного LCD2004. Панель длинная — 190 мм, белые символы на синей подсветке.
Работать с ним так же просто, как с 1602, но есть одна особенность, из-за которой новички чаще всего и спотыкаются: внутри у него два контроллера. Об этом и поговорим подробно.
Внутри два контроллера, а не один
Контроллер HD44780 физически не может обслуживать больше 80 знакомест. У LCD4004 их 160, поэтому на плате стоят два одинаковых контроллера: первый рисует строки 1 и 2, второй — строки 3 и 4.
Шина данных, вывод RS (команда или данные) и вывод RW (чтение или запись) у контроллеров общие. Разные только выводы разрешения: E1 и E2. Дёрнули E1 — байт ушёл в верхнюю половину, дёрнули E2 — в нижнюю.
Отсюда простое правило: дисплей ведёт себя как два независимых экрана 40×2, поставленных друг на друга. У каждого своя память знакомест, свой курсор и своя память пользовательских символов.
Строки 1 и 3 обе начинаются с адреса 0x00, строки 2 и 4 — с адреса 0x40. Разница только в том, каким выводом E вы воспользовались.
Технические характеристики
| Параметр | Значение |
|---|---|
| Формат | 40 символов × 4 строки, знакоместо 5×8 точек |
| Контроллер | HD44780 × 2 (совместимые KS0066, SPLC780D, ST7066) |
| Напряжение питания логики | 4,7…5,5 В, типовое 5 В |
| Ток логики | около 3 мА без подсветки |
| Напряжение на ЖК (VDD − V0) | 4,3…4,7 В при 25 °C |
| Мультиплексирование | 1/16, смещение 1/5 |
| Подсветка | светодиодная синяя, около 4,2 В, до 480 мА |
| Интерфейс | параллельный, 4 или 8 бит, два вывода разрешения |
| Знакогенератор | таблица A00: латиница, цифры, японская кана. Кириллицы нет |
| Пользовательских символов | по 8 на каждый контроллер |
| Габариты платы | 190 × 54 × 13,1 мм |
| Крепёжные отверстия | 183 × 47 мм, 4 × ⌀3,5 мм |
| Видимая область | 149 × 30 мм |
| Активное поле | 140,45 × 23,16 мм |
| Размер символа | 2,78 × 4,89 мм, шаг 3,53 мм по строке |
| Размер точки | 0,50 × 0,55 мм |
| Разъём | двухрядный 2×9, шаг 2,54 мм |
| Рабочая температура | −20…+70 °C |
| Вес | около 130 г |
Распиновка
На плате выведен двухрядный разъём 2×9 с шагом 2,54 мм под пайку. Нумерация как у шлейфовых разъёмов: выводы 1 и 2 находятся рядом на одном краю, 17 и 18 — на другом.
Найти первый вывод легко: на лицевой стороне его площадка квадратная, а все остальные круглые.
| № | Сигнал | Назначение | Куда подключать |
|---|---|---|---|
| 1…8 | DB7…DB0 | шина данных | в 4-битном режиме нужны только DB4…DB7 |
| 9 | E1 | разрешение, строки 1–2 | любой вывод платы |
| 10 | RW | чтение / запись | на GND, если читать из дисплея не нужно |
| 11 | RS | команда / данные | любой вывод платы |
| 12 | V0 | контраст | средний вывод потенциометра 10 кОм |
| 13 | VSS | общий | GND |
| 14 | VDD | питание логики | +5 В |
| 15 | E2 | разрешение, строки 3–4 | любой вывод платы |
| 16 | NC | не подключён | оставить свободным |
| 17 | LED-A | анод подсветки | +5 В через резистор, отдельный источник |
| 18 | LED-K | катод подсветки | GND |
Шину данных разные производители подписывают по-разному. Управляющие выводы 9…18 совпадают во всех источниках, а выводы 1…8 — нет:
- вариант A — даташит LONGTECH LCM4004A и Powertip PC4004A: вывод 1 = DB7, вывод 8 = DB0;
- вариант B — часть чертежей из комплекта и Powertip PC4004B: вывод 1 = DB0, вывод 8 = DB7.
Если на экране мусор или пусто — просто перенесите четыре провода данных с выводов 1–4 на выводы 5–8 (или обратно). Ниже в статье есть скетч, который определяет верный вариант сам.
Схема подключения к Arduino
Обычно дисплей подключают в 4-битном режиме: он экономит четыре вывода платы, а на скорость почти не влияет. Занято 7 выводов: четыре линии данных, RS и два разрешения E1, E2. Вывод RW сажаем на землю — дисплей будет работать только на запись.
Понадобится контактная колодка PLS-40, чтобы припаять гребёнку к плате дисплея, и потенциометр 10 кОм для регулировки контраста.
Подсветку нельзя питать от вывода 5V платы Arduino. Панель большая: по даташиту прямое напряжение подсветки около 4,2 В, а ток — до 480 мА. Плата, питаемая от USB, столько не отдаст, сработает защита порта или перегреется стабилизатор.
Питайте подсветку от отдельного источника 5 В и обязательно через токоограничивающий резистор: разница между 5 В и 4,2 В маленькая, поэтому ток очень резко растёт от напряжения. Начните со 100 Ом и уменьшайте сопротивление до нужной яркости. Регулировать яркость лучше ШИМ через транзистор.
Первое включение
Не торопитесь подключать сигнальные провода. Сначала подайте только питание и контраст:
- вывод 13 (VSS) → GND;
- вывод 14 (VDD) → +5 В;
- вывод 12 (V0) → средний вывод потенциометра 10 кОм, крайние выводы потенциометра — на +5 В и GND.
Медленно покрутите потенциометр. Исправный дисплей покажет ряд тёмных квадратов в первой и третьей строке — это нормальное состояние неинициализированного HD44780. Увидели квадраты — стекло, питание и контраст в порядке, можно подключать шину данных.
Квадраты появляются именно в 1-й и 3-й строке, потому что каждый контроллер после подачи питания включает свою «первую» строку. Если квадраты есть только в 1-й строке — второй контроллер не получил питания, проверьте пайку.
Пример 1. Здравствуй, мир на четырёх строках
Отдельная библиотека не нужна: берём штатную LiquidCrystal и создаём два объекта, которые отличаются только выводом E. Для каждого объекта дисплей выглядит как экран 40×2.
#include <LiquidCrystal.h>
// RS -> D8, E1 -> D9, E2 -> D10, DB4...DB7 -> D4...D7, RW -> GND
LiquidCrystal lcdTop ( 8, 9, 4, 5, 6, 7 ); // строки 1 и 2
LiquidCrystal lcdBottom( 8, 10, 4, 5, 6, 7 ); // строки 3 и 4
void setup() {
// каждый контроллер инициализируем отдельно
lcdTop.begin(40, 2);
lcdBottom.begin(40, 2);
lcdTop.setCursor(0, 0);
lcdTop.print("Stroka 1: privet ot iarduino.ru");
lcdTop.setCursor(0, 1);
lcdTop.print("Stroka 2: eyo risuet kontroller E1");
lcdBottom.setCursor(0, 0);
lcdBottom.print("Stroka 3: a eto uzhe kontroller E2");
lcdBottom.setCursor(0, 1);
lcdBottom.print("Stroka 4: vsego 160 znakomest");
}
void loop() {
}
Обратите внимание: строка 3 — это setCursor(0, 0) у нижнего объекта, а не setCursor(0, 2). Каждый контроллер знает только про свои две строки.
Пример 2. Обёртка: один экран 40×4
Постоянно помнить, какая строка в какой половине, неудобно. Заведём небольшой класс, который принимает номер строки от 0 до 3 и сам выбирает нужный контроллер. Наследование от Print даёт привычные print() и println() для любых типов данных.
#include <LiquidCrystal.h>
LiquidCrystal lcdTop ( 8, 9, 4, 5, 6, 7 );
LiquidCrystal lcdBottom( 8, 10, 4, 5, 6, 7 );
class LCD4004 : public Print {
public:
LCD4004(LiquidCrystal &top, LiquidCrystal &bottom)
: _top(top), _bottom(bottom), _row(0) {}
void begin() {
_top.begin(40, 2);
_bottom.begin(40, 2);
clear();
}
void clear() { // очищать нужно ОБЕ половины
_top.clear();
_bottom.clear();
_row = 0;
}
void setCursor(uint8_t col, uint8_t row) {
if (row > 3) row = 3;
if (col > 39) col = 39;
_row = row;
half(row).setCursor(col, row & 1);
}
void createChar(uint8_t index, uint8_t pattern[]) {
_top.createChar(index, pattern); // память символов у контроллеров
_bottom.createChar(index, pattern); // раздельная, грузим в оба
_top.setCursor(0, 0);
_bottom.setCursor(0, 0);
}
size_t write(uint8_t value) { // требование класса Print
return half(_row).write(value);
}
using Print::write;
private:
LiquidCrystal &half(uint8_t row) { return (row < 2) ? _top : _bottom; }
LiquidCrystal &_top;
LiquidCrystal &_bottom;
uint8_t _row;
};
LCD4004 lcd(lcdTop, lcdBottom);
void setup() {
lcd.begin();
lcd.setCursor(0, 0); lcd.print("Teper' stroki numeruyutsya 0...3");
lcd.setCursor(0, 1); lcd.print("i pechatat' mozhno chisla: ");
lcd.print(2 * 2);
lcd.setCursor(0, 2); lcd.print("i drobnye: ");
lcd.print(3.14159, 3);
lcd.setCursor(0, 3); lcd.print("iarduino.ru");
}
void loop() {
}
Текст, не поместившийся в 40 колонок, переносится на соседнюю строку той же половины, а не на следующую строку экрана. Это особенность HD44780, а не ошибка обёртки.
Пример 3. Свои символы и полоса загрузки
В памяти каждого контроллера помещается 8 собственных символов размером 5×8 точек. Классическое применение — полоса загрузки во всю ширину экрана: 40 знакомест, а значит шкала получается заметной.
#include <LiquidCrystal.h>
LiquidCrystal lcdTop ( 8, 9, 4, 5, 6, 7 );
LiquidCrystal lcdBottom( 8, 10, 4, 5, 6, 7 );
// свои символы: сердце и три ступени заполнения
byte charHeart[8] = {0b00000, 0b01010, 0b11111, 0b11111, 0b11111, 0b01110, 0b00100, 0b00000};
byte charBar1[8] = {0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000};
byte charBar3[8] = {0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100};
byte charBar5[8] = {0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
void setup() {
lcdTop.begin(40, 2);
lcdBottom.begin(40, 2);
// картинки грузим в ОБА контроллера, иначе внизу будет мусор
LiquidCrystal *halves[2] = { &lcdTop, &lcdBottom };
for (uint8_t i = 0; i < 2; i++) {
halves[i]->createChar(0, charHeart);
halves[i]->createChar(1, charBar1);
halves[i]->createChar(2, charBar3);
halves[i]->createChar(3, charBar5);
halves[i]->setCursor(0, 0);
}
lcdTop.setCursor(0, 0);
lcdTop.print("Zagruzka ");
lcdTop.write((uint8_t)0);
}
void loop() {
// полоса на всю ширину: 40 знакомест по 5 точек = 200 шагов
for (uint16_t step = 0; step <= 200; step++) {
uint8_t full = step / 5; // сколько знакомест залито целиком
uint8_t rest = step % 5; // остаток внутри текущего знакоместа
lcdTop.setCursor(0, 1);
for (uint8_t i = 0; i < 40; i++) {
if (i < full) lcdTop.write((uint8_t)3);
else if (i == full && rest) lcdTop.write(rest >= 3 ? (uint8_t)2 : (uint8_t)1);
else lcdTop.write(' ');
}
lcdBottom.setCursor(0, 0);
lcdBottom.print("Progress: ");
lcdBottom.print(step / 2);
lcdBottom.print(" % ");
delay(40);
}
}
Пример 4. Скетч определяет распиновку сам
Если непонятно, в какую сторону идёт шина данных, не нужно гадать. Подключите все восемь выводов данных подряд (вывод 1 → D2, вывод 2 → D3 и так далее до вывода 8 → D9), а RW — не на землю, а на вывод платы. Тогда из дисплея можно не только писать, но и читать.
Идея простая: ставим адрес знакоместа 0, пишем пять символов и читаем обратно счётчик адреса. Если шина подключена правильно, счётчик равен 5. При перевёрнутой шине команда 0x80 превращается в 0x01 (очистка экрана), и обратное чтение даёт другое число.
// вывод дисплея 1...8 -> вывод Arduino
const uint8_t BUS[8] = {2, 3, 4, 5, 6, 7, 8, 9};
const uint8_t PIN_RS = 10, PIN_RW = 11, PIN_E1 = 12, PIN_E2 = 13;
uint8_t busOrder = 0; // 0 = вывод 1 это DB7, 1 = вывод 1 это DB0
inline uint8_t bitForIndex(uint8_t i) { return (busOrder == 0) ? (7 - i) : i; }
void busAsOutput() { for (uint8_t i = 0; i < 8; i++) pinMode(BUS[i], OUTPUT); }
void busAsInput() { for (uint8_t i = 0; i < 8; i++) pinMode(BUS[i], INPUT); }
void busWrite(uint8_t v) {
for (uint8_t i = 0; i < 8; i++) digitalWrite(BUS[i], (v >> bitForIndex(i)) & 1);
}
uint8_t busRead() {
uint8_t v = 0;
for (uint8_t i = 0; i < 8; i++) if (digitalRead(BUS[i])) v |= (uint8_t)1 << bitForIndex(i);
return v;
}
void pulse(uint8_t e) {
digitalWrite(e, HIGH); delayMicroseconds(2);
digitalWrite(e, LOW); delayMicroseconds(2);
}
void lcdWrite(uint8_t e, uint8_t value, bool rs) {
digitalWrite(PIN_RW, LOW);
digitalWrite(PIN_RS, rs);
busAsOutput();
busWrite(value);
pulse(e);
delayMicroseconds(60);
}
uint8_t lcdRead(uint8_t e, bool rs) {
busAsInput();
digitalWrite(PIN_RS, rs);
digitalWrite(PIN_RW, HIGH);
delayMicroseconds(2);
digitalWrite(e, HIGH);
delayMicroseconds(2);
uint8_t v = busRead();
digitalWrite(e, LOW);
digitalWrite(PIN_RW, LOW);
busAsOutput();
delayMicroseconds(60);
return v;
}
void lcdInit(uint8_t e) {
busAsOutput();
digitalWrite(PIN_RS, LOW);
digitalWrite(PIN_RW, LOW);
delay(60);
lcdWrite(e, 0x30, false); delay(5);
lcdWrite(e, 0x30, false); delayMicroseconds(200);
lcdWrite(e, 0x30, false); delayMicroseconds(200);
lcdWrite(e, 0x38, false); delayMicroseconds(200); // 8 бит, 2 строки, 5x8
lcdWrite(e, 0x08, false); delayMicroseconds(200); // дисплей выключен
lcdWrite(e, 0x01, false); delay(3); // очистка
lcdWrite(e, 0x06, false); delayMicroseconds(200); // курсор вправо
lcdWrite(e, 0x0C, false); delayMicroseconds(200); // дисплей включён
}
bool probe(uint8_t e) {
lcdInit(e);
lcdWrite(e, 0x80, false); // адрес знакоместа 0
delayMicroseconds(200);
const char *sample = "TEST!";
for (uint8_t i = 0; i < 5; i++) lcdWrite(e, sample[i], true);
delayMicroseconds(200);
return (lcdRead(e, false) & 0x7F) == 5;
}
void setup() {
Serial.begin(9600);
pinMode(PIN_RS, OUTPUT); pinMode(PIN_RW, OUTPUT);
pinMode(PIN_E1, OUTPUT); pinMode(PIN_E2, OUTPUT);
digitalWrite(PIN_E1, LOW); digitalWrite(PIN_E2, LOW); digitalWrite(PIN_RW, LOW);
busAsOutput();
for (uint8_t variant = 0; variant < 2; variant++) {
busOrder = variant;
if (probe(PIN_E1) && probe(PIN_E2)) {
Serial.println(variant == 0
? F("Vyvod 1 = DB7. Dlya 4 bit berite vyvody 4, 3, 2, 1")
: F("Vyvod 1 = DB0. Dlya 4 bit berite vyvody 5, 6, 7, 8"));
return;
}
}
Serial.println(F("Ni odin variant ne otozvalsya: proverte pitanie, kontrast i RS/RW"));
}
void loop() {
}
Если что-то не работает
| Что видно | Скорее всего | Что делать |
|---|---|---|
| Экран равномерно светится, ничего нет | контраст выкручен в край | крутить потенциометр на выводе 12 до появления квадратов |
| Тёмные квадраты есть, текста нет | питание в порядке, не проходит инициализация | проверить RS, E1, E2 и направление шины данных |
| Вместо букв мусор | шина данных подключена задом наперёд | перенести провода с выводов 1–4 на 5–8 или запустить пример 4 |
| Работают только строки 1 и 2 | не подключён или не инициализирован E2 | вывод 15 на свой пин платы, begin() вызвать для обеих половин |
| Верхняя половина повторяет нижнюю | E1 и E2 соединены вместе | развести на разные выводы платы |
| В строках 3 и 4 вместо своих значков мусор | createChar() вызван только для одной половины | грузить картинки в оба контроллера |
| Плата перезагружается, USB отваливается | подсветка питается от Arduino | отдельный источник 5 В и резистор в цепи вывода 17 |
| Изображение бледнеет на холоде | напряжение ЖК зависит от температуры | это нормально: при −20 °C нужно примерно на 0,5 В больше, подстроить контраст |
Что на обратной стороне платы
Шесть чёрных «таблеток» — это и есть вся электроника дисплея: два контроллера HD44780 и четыре вспомогательных драйвера сегментов. Рядом с выводами 17 и 18 расположены посадочные места резисторов подсветки и площадки A и K, продублированные с обоих концов платы. Прежде чем подавать питание на подсветку, полезно прозвонить тестером, какое сопротивление стоит между выводами 17 и 18 на вашем экземпляре.
Обычный переходник I²C сюда не подойдёт. У модулей на PCF8574 всего восемь выходов, и рассчитаны они на один вывод разрешения. Здесь выводов разрешения два, поэтому дисплей подключается напрямую к выводам платы параллельной шиной.
Смотрите также
- Символьный дисплей LCD4004 — товар в нашем магазине;
- Работа с символьными ЖК дисплеями 1602, 2004 — общая статья про семейство HD44780;
- Символьный дисплей LCD2004 — 20 символов на 4 строки, если 40 избыточно;
- Библиотека LiquidCrystal — стандартная библиотека Arduino IDE.