Почему квадраты на дисплее 1602

В группах ВК по тематике Arduino часто встречаю вопросы о дисплеях 1602 (и подобных), построенных на контроллере HD44780. Один из них — почему на дисплее отображаются квадраты вместо того, что запрограммировано в скетче. И чтобы не повторять каждый раз всё это в комментариях, я решил написать эту статейку.

Квадраты на дисплее 1602

HD44780 (а также совместимый с ним KS0066) — контроллер монохромных жидкокристаллических знакосинтезирующих дисплеев с параллельным 4- или 8-битным интерфейсом. Разработан фирмой Hitachi. Управляющий интерфейс и протокол являются де-факто стандартом для такого типа дисплеев. Эти контроллеры были фактически монополистами на рынке в 90-е годы.

На базе этого контроллера выпускалось огромное количество моделей с различным конструктивом и разрешением, начиная с 8×1 (восемь символов в одной строке), и заканчивая 40×4 (содержащих два независимых управляющих чипа). Часто встречаются 16×2 и 20×4, а также некоторые другие.

Википедия

LCD-дисплей WH1602B-YYK-CTK

LCD-дисплей WH1602B-YYK-CTK (обратная сторона)

Квадраты это не неисправность дисплея, а вполне адекватная его реакция на отсутствие инициализации. Своеобразный self-test. Отсутствовать инициализация может по нескольким причинам. Но для начала надо вспомнить, какие типы подключения бывают у дисплеев этого формфактора.

Вариант 1: подключение к микроконтроллеру по интерфейсу HD44780

В этом случае между дисплеем и МК идёт шина из, как минимум, шести проводов (не считая питания):
Подключение LCD
Код для Arduino в этом случае выглядит примерно так:

#include <liquidcrystal.h>

//Создаем объект для работы с дисплеем.
//При создании указываем номера портов
//в порядке RS, E, DB4, DB5, DB6, DB7
LiquidCrystal lcd(12, 10, 5, 4, 3, 2);

void setup()
{
  //Выставлем число столбцов и строк
  lcd.begin(16, 2);
  //Выводим текст
  lcd.print("hello, world!");
}

void loop()
{
  lcd.setCursor(0, 1);
  lcd.print(millis()/1000);
}

Причины отображения квадратов:

  1. Плохой контакт или неправильное подключение проводов (не те пины или перепутан порядок);
  2. Неправильно указаны номера пинов в коде
    LiquidCrystal lcd(12, 10, 5, 4, 3, 2);

Исправление ошибки сводится к проверке качества пайки контактов, а также соответствия пинов в коде.

Вариант 2: подключение к микроконтроллеру по интерфейсу I2C

Здесь используется всего два пина для подключения. И эти пины жёстко фиксированы:

  • Arduino Uno, Arduino Ethernet — A4 (SDA), A5 (SCL)
  • Arduino Mega2560 — 20 (SDA), 21 (SCL)
  • Arduino Leonardo — 2 (SDA), 3 (SCL)
  • Arduino Due — 20 (SDA), 21 (SCL)

На самом же дисплее уже впаян конвертор с I2C на HD44780, который по сути представляет из себя обычный I2C-расширитель на базе чипа PCF8574. Особенность шины в том, что по двум проводам можно параллельно подключить множество различных устройств. Каждое устройство имеет уникальный (в пределах одной шины) адрес, который либо жёстко установлен производителем, либо может менять в определённых пределах с помощью перемычек или джамперов:

Адрес для PCF8574 можно посмотреть по таблице:

ВходАдрес на шине I2C
A2A1A0DECHEX
000320x20
001330x21
010340x22
011350x23
100360x24
101370x25
110380x26
111390x27

Есть ещё PCF8574A. Для них адресация немного другая:
ВходАдрес на шине I2C
A2A1A0DECHEX
000560x38
001570x39
010580x3A
011590x3B
100600x3C
101610x3D
110620x3E
111630x3F

Код при подключении по I2C обычно выглядит примерно так:
#include <wire.h> 
#include <liquidcrystal_i2c.h>

LiquidCrystal_I2C lcd(0x20,16,2);  // установка адреса и размера дисплея

void setup()
{
  lcd.init();                      // инициализация дисплея
 
  lcd.backlight();
  lcd.print("Hello, world!");
}

void loop()
{
}

Но есть ещё одна версия библиотеки, в которой указывается не только адрес дисплея, но и порядок подключения контактов дисплея к контактам чипа PCF8574. Тогда код выглядит так:
#include <wire.h>
#include <liquidcrystal_i2c.h>

// Установка адреса и пинов I2C чипа, используемых для LCD подключения:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

void setup()
{
  lcd.begin(16,2);   // инициализация дисплея и указание его размера

  lcd.backlight();
  lcd.print("Hello, world!");
}

void loop()
{
}

Обратите внимание на отличия в объявлении объекта дисплея и инициализации. И всё было бы хорошо, но разные дисплеи иногда имеют разный порядок подключения контактов между собственно самим дисплеем и чипом PCF8574. Я лично встречал два варианта. Первый указан в коде выше. Второй способ объявления выглядит так:
LiquidCrystal_I2C lcd(0x27, 4, 5, 6, 0, 1, 2, 3, 7, NEGATIVE);

Соответственно возможны следующие причины появления квадратов:

  1. Плохой контакт или неправильное подключение проводов (перепутаны местами SDA и SCL);
  2. Неправильно указано соответствие пинов дисплея и I2C чипа в коде
    LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
  3. Неправильно указан адрес дисплея.

Порядок поиска неисправности в данном случае такой:

  1. Проверить правильность подключения SDA и SCL;
  2. Запустить сканер шины I2C: I2C scanner (1.02 KB);
  3. Если сканер выдал информацию об адресе устройства, то сверяем его с тем, который указан в скетче. Если сканер «молчит», то возвращаемся к первому пункту или делаем вывод о неисправности I2C модуля на дисплее;
  4. Пробуем менять в коде комбинации пинов (соответствия дисплей — I2C). Два варианта я привёл выше. Есть ли ещё другие — не знаю.

Иногда встречаются советы покрутить контрастность. На самом деле это не поможет, так как если вы видите отчётливо верхние квадраты, но не видите нижние (как на фото в статье) — значит контрастность УЖЕ настроена нормально.