Символьный дисплей LCD4004: 40 символов на 4 строки

Символьный дисплей LCD4004, 40 символов на 4 строки

LCD4004 — самый вместительный символьный дисплей семейства HD44780: 40 символов в строке и 4 строки, всего 160 знакомест. Это вдвое больше, чем у привычного LCD2004. Панель длинная — 190 мм, белые символы на синей подсветке.

Работать с ним так же просто, как с 1602, но есть одна особенность, из-за которой новички чаще всего и спотыкаются: внутри у него два контроллера. Об этом и поговорим подробно.

Внутри два контроллера, а не один

Контроллер HD44780 физически не может обслуживать больше 80 знакомест. У LCD4004 их 160, поэтому на плате стоят два одинаковых контроллера: первый рисует строки 1 и 2, второй — строки 3 и 4.

Схема: контроллер E1 обслуживает строки 1 и 2, контроллер E2 — строки 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 — на другом.

Распиновка разъёма 2 на 9 дисплея LCD4004

Найти первый вывод легко: на лицевой стороне его площадка квадратная, а все остальные круглые.

Квадратная площадка отмечает первый вывод дисплея LCD4004
СигналНазначениеКуда подключать
1…8DB7…DB0шина данныхв 4-битном режиме нужны только DB4…DB7
9E1разрешение, строки 1–2любой вывод платы
10RWчтение / записьна GND, если читать из дисплея не нужно
11RSкоманда / данныелюбой вывод платы
12V0контрастсредний вывод потенциометра 10 кОм
13VSSобщийGND
14VDDпитание логики+5 В
15E2разрешение, строки 3–4любой вывод платы
16NCне подключёноставить свободным
17LED-Aанод подсветки+5 В через резистор, отдельный источник
18LED-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 сажаем на землю — дисплей будет работать только на запись.

Схема подключения дисплея LCD4004 к Arduino UNO

Понадобится контактная колодка 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 В больше, подстроить контраст

Что на обратной стороне платы

Обратная сторона платы дисплея LCD4004

Шесть чёрных «таблеток» — это и есть вся электроника дисплея: два контроллера HD44780 и четыре вспомогательных драйвера сегментов. Рядом с выводами 17 и 18 расположены посадочные места резисторов подсветки и площадки A и K, продублированные с обоих концов платы. Прежде чем подавать питание на подсветку, полезно прозвонить тестером, какое сопротивление стоит между выводами 17 и 18 на вашем экземпляре.

Обычный переходник I²C сюда не подойдёт. У модулей на PCF8574 всего восемь выходов, и рассчитаны они на один вывод разрешения. Здесь выводов разрешения два, поэтому дисплей подключается напрямую к выводам платы параллельной шиной.

Смотрите также