Пример подключения дисплея LCD1602 по I2C интерфейсу и клавиатуры 4×4 через матрицу контактов к контроллеру ESP32C3 на плате LuatOS.
Внешний вид макета:
LCD1602 подключен по интерфейсу I2C через PCF8574 I2C.
Клавиатура: контакты 1, 2, 3, 4 – столбцы; контакты 5, 6, 7, 8 – строки. Функционал клавиш: цифровые – печать значения; A – cтереть экран; B – переход с одной строки на другую; C – заполнение всех знакомест экрана; D – демонстрация бегущей строки (нажатие любой клавиши включает свой функционал); * – сдвиг курсора влево; # – сдвиг курсора вправо.
Кнопки RST и BOOT введены для удобства (они больше, чем на самом ESP32C3, к тому же у меня модуль установлен маркировкой контактов вверх).
Назначение контактов ESP32C3 LuatOS
Таблица соединений
БП
Кнопки
Клавиатура
LCD1602
ESP32C3
+5V
+5V
GND
GND
RST
EN
BOOT
IO09
GND
GND
1
IO02
2
IO03
3
IO10
4
IO06
5
IO12
6
IO18
7
IO19
8
IO013
GND
GND
VCC
+5V
SDA
IO04
SCL
IO05
В Arduino IDE выбрал ESP32C3 Dev Module. Для правильной прошивки в “Инструменты” нужно установить опцию Flash Mode в DIO.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// НАСТРОЙКИ ПИНОВ ДЛЯ ESP32-C3
#define I2C_SDA 4
#define I2C_SCL 5
// Клавиатура 4x4
const byte ROW_PINS[4] = {12, 18, 19, 13}; // GPIO для строк
const byte COL_PINS[4] = {2, 3, 10, 6}; // GPIO для столбцов
// Символы на клавиатуре в соответствии с матрицей
char keys[4][4] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
LiquidCrystal_I2C lcd(0x27, 16, 2); // Адрес I2C = 0x27
int cursorX = 0; // текущая позиция курсора (0..15)
int cursorY = 0; // текущая строка (0 или 1)
// Флаги для бегущей строки
bool marqueeActive = false;
unsigned long lastMarqueeTime = 0;
int marqueeOffset = 0;
String marqueeLine1 = "Hotel California "; // 16 символов + пробелы
String marqueeLine2 = " Eagles 1976 "; // 16 символов + пробелы
// ИНИЦИАЛИЗАЦИЯ КЛАВИАТУРЫ
void initKeypad() {
for (int i = 0; i < 4; i++) {
pinMode(ROW_PINS[i], OUTPUT);
digitalWrite(ROW_PINS[i], HIGH); // по умолчанию высокий уровень
}
for (int i = 0; i < 4; i++) {
pinMode(COL_PINS[i], INPUT_PULLUP);
}
}
// Опрос клавиатуры (без задержек, неблокирующий)
char getKey() {
for (int r = 0; r < 4; r++) {
// активируем текущую строку (низкий уровень)
digitalWrite(ROW_PINS[r], LOW);
for (int c = 0; c < 4; c++) {
if (digitalRead(COL_PINS[c]) == LOW) {
// небольшая задержка на антидребезг
delay(50);
while (digitalRead(COL_PINS[c]) == LOW) { } // ждём отпускания
digitalWrite(ROW_PINS[r], HIGH); // деактивируем строку
return keys[r][c];
}
}
digitalWrite(ROW_PINS[r], HIGH); // деактивируем строку
}
return 0; // ничего не нажато
}
// ФУНКЦИИ УПРАВЛЕНИЯ КУРСОРОМ И ЭКРАНОМ
void showCursor() {
lcd.setCursor(cursorX, cursorY);
lcd.cursor(); // включаем видимость курсора
lcd.blink(); // и мигание
}
void moveCursorLeft() {
if (cursorX > 0) {
cursorX--;
} else if (cursorY == 1) {
// если в начале второй строки, переходим в конец первой
cursorX = 15;
cursorY = 0;
} else if (cursorY == 0 && cursorX == 0) {
// уже в начале первой строки — не двигаемся
return;
}
lcd.setCursor(cursorX, cursorY);
showCursor();
}
void moveCursorRight() {
if (cursorX < 15) {
cursorX++;
} else if (cursorY == 0) {
cursorX = 0;
cursorY = 1;
} else if (cursorY == 1 && cursorX == 15) {
// в конце второй строки — не двигаемся
return;
}
lcd.setCursor(cursorX, cursorY);
showCursor();
}
void toggleLine() {
// Перемещение курсора между строками в первую позицию
if (cursorY == 0) {
cursorY = 1;
cursorX = 0;
} else {
cursorY = 0;
cursorX = 0;
}
lcd.setCursor(cursorX, cursorY);
showCursor();
}
void clearScreen() {
lcd.clear();
// после очистки курсор автоматически становится на (0,0)
cursorX = 0;
cursorY = 0;
showCursor();
}
void fillAlphabet() {
// Заполнение 32 символами: AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPP
char alphabet[33] = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPP";
lcd.clear();
lcd.setCursor(0, 0);
for (int i = 0; i < 16; i++) {
lcd.print(alphabet[i]);
}
lcd.setCursor(0, 1);
for (int i = 16; i < 32; i++) {
lcd.print(alphabet[i]);
}
// курсор остаётся на первой позиции первой строки
cursorX = 0;
cursorY = 0;
lcd.setCursor(cursorX, cursorY);
showCursor();
}
void startMarquee() {
marqueeActive = true;
marqueeOffset = 0;
lastMarqueeTime = millis();
// Гарантируем достаточную длину для бесконечной прокрутки
while (marqueeLine1.length() < 48) {
marqueeLine1 += marqueeLine1;
}
while (marqueeLine2.length() < 48) {
marqueeLine2 += marqueeLine2;
}
}
void updateMarquee() {
if (!marqueeActive) return;
unsigned long now = millis();
if (now - lastMarqueeTime >= 1000) {
lastMarqueeTime = now;
// Выводим первую строку со сдвигом
lcd.setCursor(0, 0);
lcd.print(marqueeLine1.substring(marqueeOffset, marqueeOffset + 16));
// Выводим вторую строку со сдвигом
lcd.setCursor(0, 1);
lcd.print(marqueeLine2.substring(marqueeOffset, marqueeOffset + 16));
// Увеличиваем смещение, зацикливая его
marqueeOffset++;
if (marqueeOffset >= marqueeLine1.length() - 16) {
marqueeOffset = 0;
}
// Восстанавливаем курсор
lcd.setCursor(cursorX, cursorY);
showCursor();
}
}
void stopMarqueeIfNeeded(char key) {
if (marqueeActive && key != 0) {
marqueeActive = false;
// Текст замирает в текущем состоянии
// Просто показываем курсор
lcd.setCursor(cursorX, cursorY);
showCursor();
}
}
void setup() {
// Инициализация I2C для LCD
Wire.begin(I2C_SDA, I2C_SCL);
lcd.init();
lcd.backlight();
// Начальный вывод
lcd.setCursor(0, 0);
lcd.print("Hotel California");
lcd.setCursor(0, 1);
lcd.print(" Eagles 1976");
// Курсор в виде подчеркивания в первой позиции первой строки
cursorX = 0;
cursorY = 0;
lcd.setCursor(cursorX, cursorY);
lcd.cursor(); // включаем подчеркивание
lcd.blink(); // мигание курсора
// Инициализация клавиатуры
initKeypad();
}
void loop() {
char key = getKey();
if (key != 0) {
// При любом нажатии останавливаем бегущую строку
stopMarqueeIfNeeded(key);
switch (key) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
lcd.print(key);
// Сдвигаем курсор вправо
if (cursorX < 15) {
cursorX++;
}
else if (cursorY == 0) {
// Переход на вторую строку
cursorX = 0;
cursorY = 1;
}
else if (cursorY == 1 && cursorX == 15) {
// Достигнут конец экрана - перемещаем в начало первой строки
cursorX = 0;
cursorY = 0;
}
lcd.setCursor(cursorX, cursorY);
showCursor();
break;
case 'A':
clearScreen();
break;
case 'B':
toggleLine();
break;
case 'C':
fillAlphabet();
break;
case 'D':
startMarquee();
break;
case '*':
moveCursorLeft();
break;
case '#':
moveCursorRight();
break;
}
}
// Обновление бегущей строки (если активна)
if (marqueeActive) {
updateMarquee();
}
delay(20); // небольшое торможение для стабильности
}
Немного истории.
Термин Marquee (марки́) — это прямое название эффекта «бегущей строки» в программировании и дизайне. Вот что их связывает: 1. Происхождение названия: Слово Marquee изначально означало козырек или навес над входом в театр или казино. В XX веке на таких навесах стали размещать ламповые панели с движущимся текстом (анонсами фильмов или шоу). Отсюда название перекочевало в цифровую среду. 2. HTML-тег <marquee>: В эпоху раннего интернета (Internet Explorer 3.0 и выше) существовал специальный тег <marquee>текст</marquee>. Любой текст внутри него автоматически начинал двигаться по экрану. Сейчас тег считается устаревшим, но само слово стало стандартом для обозначения этого эффекта.