Глава 7. Графический интерфейс для работы с LCD1602 (I²C)

В этой главе мы разработаем и подключим графический интерфейс (GUI) для вывода текстовой информации на дисплей LCD1602, подключённый по интерфейсу I²C к Raspberry Pi Zero 2W.

Цель

Создать удобный GTK-интерфейс, позволяющий:

  • Вводить произвольный текст в две строки

  • Отправлять его на LCD1602 нажатием кнопки

  • Управлять очисткой экрана

  • Тестировать инициализацию дисплея и отображать статус

Подключение

LCD1602 должен быть подключён к I²C-шине Raspberry Pi:

  • SCL1 → GPIO3 (Physical pin 5)

  • SDA1 → GPIO2 (Physical pin 3)

  • VCC → 5V (Physical pin 2)

  • GND → GND (Physical pin 6)

Убедитесь, что модуль I²C активирован:

sudo raspi-config  # → Interface Options → I2C → Enable
sudo apt install i2c-tools
i2cdetect -y 1     # Убедитесь, что адрес (обычно 0x27) виден

Исходные файлы

  • lcd1602.h, lcd1602.c — простой драйвер для инициализации и записи текста на LCD1602 по I²C

  • lcd_gui.c — GTK-приложение с двумя строками ввода, кнопками «Отобразить» и «Очистить экран», а также меткой статуса.

  • Makefile — сборка проекта

Функциональность GUI

  • Поле ввода для строки 1

  • Поле ввода для строки 2

  • Кнопка Отобразить — отправляет текст на дисплей

  • Кнопка Очистить экран — очищает содержимое дисплея

  • Метка состояния — отображает статус инициализации и отправки текста (успешно/ошибка)

Файл: lcd_gui.c

#include <gtk/gtk.h> // Включаем библиотеку GTK+ 3 для создания графического интерфейса
#include "lcd1602.h" // Включаем наш заголовочный файл драйвера LCD1602

/**
 * @file lcd_gui.c
 * @brief Пример GTK-приложения для управления дисплеем LCD1602 через I2C.
 *
 * Это приложение предоставляет простой графический интерфейс пользователя (GUI)
 * для ввода двух строк текста и отправки их на дисплей LCD1602.
 * Оно использует драйвер lcd1602.h/lcd1602.c для взаимодействия с аппаратным обеспечением.
 *
 * @note Предназначено для использования на Raspberry Pi OS с Raspberry Pi Zero 2 W.
 */

// Глобальные указатели на виджеты GtkEntry для доступа к ним из разных функций.
// Это необходимо, так как мы будем получать текст из этих полей ввода.
GtkWidget *entry_line1;
GtkWidget *entry_line2;
// Глобальный указатель на метку для отображения статуса
GtkWidget *status_label;

/**
 * @brief Обработчик события нажатия кнопки "Отправить на LCD".
 *
 * Эта функция вызывается, когда пользователь нажимает кнопку "Отправить на LCD".
 * Она считывает текст из полей ввода, очищает дисплей и записывает на него новые строки.
 *
 * @param button Указатель на виджет кнопки, которая вызвала событие (не используется напрямую).
 * @param user_data Пользовательские данные, переданные при подключении сигнала (не используются).
 */
void on_send_clicked(GtkButton *button, gpointer user_data) {
    const char *text1 = gtk_entry_get_text(GTK_ENTRY(entry_line1));
    const char *text2 = gtk_entry_get_text(GTK_ENTRY(entry_line2));

    // Очищаем дисплей LCD1602
    lcd1602_clear();
    // Записываем полученные строки на дисплей
    lcd1602_write(text1, text2);
    // Обновляем метку статуса
    gtk_label_set_text(GTK_LABEL(status_label), "Текст успешно отправлен на LCD.");
}

/**
 * @brief Обработчик события нажатия кнопки "Очистить экран".
 *
 * Эта функция вызывается, когда пользователь нажимает кнопку "Очистить экран".
 * Она очищает содержимое дисплея LCD1602.
 *
 * @param button Указатель на виджет кнопки, которая вызвала событие.
 * @param user_data Пользовательские данные.
 */
void on_clear_clicked(GtkButton *button, gpointer user_data) {
    lcd1602_clear();
    gtk_label_set_text(GTK_LABEL(status_label), "Экран LCD очищен.");
}

/**
 * @brief Главная функция приложения.
 *
 * Инициализирует GTK, инициализирует LCD, создает и настраивает GUI,
 * запускает главный цикл GTK и очищает ресурсы при завершении.
 *
 * @param argc Количество аргументов командной строки.
 * @param argv Массив аргументов командной строки.
 * @return 0 при успешном завершении, 1 при ошибке инициализации LCD.
 */
int main(int argc, char *argv[]) {
    // Инициализация GTK. Должна быть вызвана первой в любом GTK-приложении.
    gtk_init(&argc, &argv[]);

    // --- Создание главного окна приложения ---
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); // Создаем новое окно верхнего уровня
    gtk_window_set_title(GTK_WINDOW(window), "LCD1602 Controller"); // Устанавливаем заголовок окна
    gtk_window_set_default_size(GTK_WINDOW(window), 300, 250); // Устанавливаем размер окна по умолчанию
    // Подключаем сигнал "destroy" (закрытие окна) к функции gtk_main_quit,
    // которая завершает главный цикл GTK и, соответственно, приложение.
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // --- Контейнеры для виджетов ---
    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); // Главный вертикальный контейнер
    gtk_container_add(GTK_CONTAINER(window), vbox);

    GtkWidget *button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); // Контейнер для кнопок
    gtk_box_pack_start(GTK_BOX(vbox), button_box, FALSE, FALSE, 0);

    // --- Создание и добавление поля ввода для первой строки ---
    entry_line1 = gtk_entry_new(); // Создаем новое поле ввода текста
    // Устанавливаем текст-заполнитель, который отображается, когда поле пустое
    gtk_entry_set_placeholder_text(GTK_ENTRY(entry_line1), "Текст первой строки (до 16 символов)");
    // Добавляем поле ввода в контейнер vbox.
    // FALSE, FALSE, 0 означает, что виджет не будет расширяться и заполнять доступное пространство.
    gtk_box_pack_start(GTK_BOX(vbox), entry_line1, FALSE, FALSE, 0);

    // --- Создание и добавление поля ввода для второй строки ---
    entry_line2 = gtk_entry_new(); // Создаем новое поле ввода текста
    gtk_entry_set_placeholder_text(GTK_ENTRY(entry_line2), "Текст второй строки (до 16 символов)");
    gtk_box_pack_start(GTK_BOX(vbox), entry_line2, FALSE, FALSE, 0);

    // --- Кнопка "Отобразить" ---
    GtkWidget *send_button = gtk_button_new_with_label("Отобразить"); // Создаем кнопку с текстом
    // Подключаем сигнал "clicked" (нажатие кнопки) к нашей функции on_send_clicked
    g_signal_connect(send_button, "clicked", G_CALLBACK(on_send_clicked), NULL);
    gtk_box_pack_start(GTK_BOX(button_box), send_button, TRUE, TRUE, 0); // Добавляем в контейнер кнопок

    // --- Кнопка "Очистить экран" ---
    GtkWidget *clear_button = gtk_button_new_with_label("Очистить экран");
    g_signal_connect(clear_button, "clicked", G_CALLBACK(on_clear_clicked), NULL);
    gtk_box_pack_start(GTK_BOX(button_box), clear_button, TRUE, TRUE, 0); // Добавляем в контейнер кнопок

    // --- Метка статуса ---
    status_label = gtk_label_new("Ожидание инициализации LCD...");
    gtk_box_pack_start(GTK_BOX(vbox), status_label, FALSE, FALSE, 0);

    // Инициализация LCD1602.
    // Используем "/dev/i2c-1" для I2C-шины на Raspberry Pi и адрес 0x27 для PCF8574.
    if (lcd1602_init("/dev/i2c-1", 0x27) != 0) {
        // Если инициализация LCD не удалась, выводим сообщение об ошибке
        g_printerr("Ошибка инициализации LCD. Убедитесь, что I2C включен и адрес 0x27 корректен.\n");
        gtk_label_set_text(GTK_LABEL(status_label), "Ошибка инициализации LCD!");
        // Не возвращаем 1 сразу, чтобы GUI все равно показался, но с ошибкой.
        // return 1;
    } else {
        gtk_label_set_text(GTK_LABEL(status_label), "LCD успешно инициализирован.");
    }


    // Показываем все виджеты в окне (окно и все его дочерние элементы)
    gtk_widget_show_all(window);

    // Запускаем главный цикл GTK.
    // Приложение будет ожидать событий (нажатия кнопок, ввод текста и т.д.)
    // до тех пор, пока не будет вызвана gtk_main_quit (например, при закрытии окна).
    gtk_main();

    // После завершения главного цикла GTK, закрываем I2C-соединение с LCD.
    lcd1602_close();
    return 0; // Успешное завершение программы
}

Файл: Makefile

# @file Makefile
# @brief Makefile для сборки GTK-приложения и драйвера LCD1602.
#
# Этот Makefile автоматизирует процесс компиляции исходных файлов C
# (lcd_gui.c и lcd1602.c) и компоновки их в исполняемый файл 'lcd_gui'.
# Он использует pkg-config для автоматического определения флагов компиляции и
# библиотек GTK+ 3.
#
# @note Предназначен для использования на системах с установленным GTK+ 3 и GCC.

# Компилятор C
CC = gcc

# Флаги компилятора:
# `pkg-config --cflags gtk+-3.0` - автоматически добавляет необходимые флаги для заголовочных файлов GTK+ 3.
# -Wall - включает все предупреждения компилятора, что помогает писать более чистый и безопасный код.
CFLAGS = `pkg-config --cflags gtk+-3.0` -Wall

# Библиотеки для компоновки:
# `pkg-config --libs gtk+-3.0` - автоматически добавляет необходимые библиотеки GTK+ 3 для компоновки.
LIBS = `pkg-config --libs gtk+-3.0`

# Цель по умолчанию: 'all'. При вызове 'make' без аргументов, будет выполнена эта цель.
all: lcd_gui

# Цель 'lcd_gui': Компонует объектные файлы в конечный исполняемый файл.
# Зависимости: lcd_gui.o и lcd1602.o (объектные файлы).
lcd_gui: lcd_gui.o lcd1602.o
    $(CC) -o lcd_gui lcd_gui.o lcd1602.o $(LIBS)

# Цель 'lcd_gui.o': Компилирует lcd_gui.c в объектный файл lcd_gui.o.
# Зависимости: lcd_gui.c и lcd1602.h (если lcd1602.h изменится, lcd_gui.c будет перекомпилирован).
lcd_gui.o: lcd_gui.c lcd1602.h
    $(CC) $(CFLAGS) -c lcd_gui.c -o lcd_gui.o

# Цель 'lcd1602.o': Компилирует lcd1602.c в объектный файл lcd1602.o.
# Зависимости: lcd1602.c и lcd1602.h.
lcd1602.o: lcd1602.c lcd1602.h
    $(CC) -c lcd1602.c

# Цель 'clean': Удаляет все сгенерированные объектные файлы и исполняемый файл.
# Полезно для "чистой" пересборки проекта.
clean:
    rm -f *.o lcd_gui

Пример использования

  1. Соберите проект:

make
  1. Запустите:

./lcd_gui
  1. Введите текст в строки и нажмите Отобразить. Нажмите Очистить экран, чтобы очистить дисплей.

Результат: - Первая и вторая строки LCD отобразят ваш текст - Статус внизу покажет «Успешно отправлено» или сообщение об ошибке

Заключение

Данный проект демонстрирует интеграцию GTK и I²C для управления LCD-дисплеем через дружественный графический интерфейс. Подобный подход легко расширяется — например, для отображения температуры, сетевого статуса или другой информации.

В следующих главах мы рассмотрим расширение GUI и интеграцию с другими компонентами системы.