Глава 4: RGB LED Controller (GTK + pigpio)

Приложение для управления RGB-светодиодом с помощью графического интерфейса на GTK+3 и аппаратного ШИМ через pigpio. Работает на Raspberry Pi.

## Описание

Программа предоставляет простой GUI с тремя ползунками (R, G, B), которые управляют яркостью каналов RGB-светодиода. При изменении значений:

  • отправляется ШИМ-сигнал на соответствующий GPIO-пин;

  • обновляется отображаемый цвет в окне;

  • значения яркости отображаются в виде чисел.

Поддерживаются светодиоды с общим катодом (подключение напрямую к GPIO через резисторы).

Используемые пины (Broadcom GPIO):

  • GPIO17 — красный (R)

  • GPIO27 — зелёный (G)

  • GPIO18 — синий (B)

## Требования

Для успешной сборки и запуска проекта вам понадобятся:

  • Raspberry Pi: Любая модель с 40-пиновым разъемом GPIO (например, Raspberry Pi 2, 3, 4, 5).

  • Операционная система: Raspberry Pi OS (ранее Raspbian) или другая совместимая Linux-система.

  • Библиотеки:
    • GTK 3: Библиотека для создания графического интерфейса.

    • pigpio: Библиотека для высокоточного управления GPIO, включая ШИМ (PWM).

  • Аппаратные компоненты:
    • RGB-светодиод: С общим катодом (наиболее распространенный) или общим анодом.

    • Резисторы: Три токоограничивающих резистора (например, 220 Ом или 330 Ом, в зависимости от светодиода и напряжения питания) для каждого из трех цветовых каналов RGB-светодиода.

    • Макетная плата и соединительные провода: Для сборки схемы.

## Подключение компонентов (Схема)

Для RGB-светодиода с общим катодом:

  1. Длинная ножка (общий катод): Подключите к контакту GND (земля) Raspberry Pi.

  2. Красный канал: Подключите ножку красного цвета к GPIO 17 (физический пин 11) через токоограничивающий резистор.

  3. Зеленый канал: Подключите ножку зеленого цвета к GPIO 27 (физический пин 13) через токоограничивающий резистор.

  4. Синий канал: Подключите ножку синего цвета к GPIO 18 (физический пин 12) через токоограничивающий резистор.

Для RGB-светодиода с общим анодом:

  1. Длинная ножка (общий анод): Подключите к контакту 3.3V Raspberry Pi.

  2. Красный канал: Подключите ножку красного цвета к GPIO 17 (физический пин 11) через токоограничивающий резистор.

  3. Зеленый канал: Подключите ножку зеленого цвета к GPIO 27 (физический пин 13) через токоограничивающий резистор.

  4. Синий канал: Подключите ножку синего цвета к GPIO 18 (физический пин 12) через токоограничивающий резистор. * Примечание: Для светодиодов с общим анодом логика ШИМ инвертируется (0% рабочего цикла = максимальная яркость, 100% = выключено). Код написан для общего катода, где более высокое значение соответствует большей яркости. Если цвета будут инвертированы, возможно, потребуется изменить значения value на 255 - value перед вызовом gpioPWM.

## Установка зависимостей

Убедитесь, что у вас установлены необходимые библиотеки.

1. Установка GTK 3:

sudo apt update
sudo apt install libgtk-3-dev

2. Установка pigpio:

Библиотека pigpio часто предустановлена на Raspberry Pi OS. Если нет, вы можете установить её:

sudo apt install pigpio

## Запуск pigpio-демона

Для работы pigpio требуется фоновый демон pigpiod. Его нужно запустить до запуска программы, либо ваша программа может запустить его автоматически.

Рекомендуемый способ запуска (через systemd) для постоянной работы:

Если вы хотите, чтобы pigpiod работал всегда в фоновом режиме (независимо от вашей программы) и был доступен для других приложений, используйте systemctl:

sudo systemctl enable pigpiod # Включает автозапуск при загрузке (однократно)
sudo systemctl start pigpiod  # Запускает демон прямо сейчас

Проверка статуса демона:

sudo systemctl status pigpiod

Вы должны увидеть Active: active (running).

Остановить вручную (при необходимости):

sudo systemctl stop pigpiod

## Исходный код

Сохраните следующий код в файл с именем rgb_pwm_gui.c:

#include <gtk/gtk.h> // Подключаем библиотеку GTK для создания графического интерфейса
#include <pigpio.h> // Подключаем библиотеку pigpio для работы с GPIO и ШИМ
#include <stdio.h>   // Подключаем стандартную библиотеку ввода/вывода для snprintf

// Определяем константы для номеров GPIO-пинов, связанных с каждым цветом.
// Эти пины будут использоваться для ШИМ.
#define RED_PIN    17
#define GREEN_PIN  27
#define BLUE_PIN   18

// Глобальные указатели на виджеты GTK.
// color_area: Виджет, который будет отображать текущий смешанный цвет.
// label_r, label_g, label_b: Метки для отображения числовых значений (0-255) каждого цвета.
GtkWidget *color_area;
GtkWidget *label_r, *label_g, *label_b;

// Глобальные указатели на виджеты ползунков для более легкого доступа в on_scale_changed
GtkWidget *scale_r_global;
GtkWidget *scale_g_global;
GtkWidget *scale_b_global;

// --- Функции ---

// Функция для обновления цвета виджета в GUI
void update_color_display(int r, int g, int b) {
    char css[128];
    // Формируем строку CSS, которая задает фоновый цвет виджета
    snprintf(css, sizeof(css), "#color_display { background-color: rgb(%d,%d,%d); }", r, g, b);

    GtkCssProvider *provider = gtk_css_provider_new();
    gtk_css_provider_load_from_data(provider, css, -1, NULL);

    GtkStyleContext *context = gtk_widget_get_style_context(color_area);
    gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
    g_object_unref(provider);
}

// Функция обратного вызова, вызываемая при изменении значения любого ползунка.
// user_data теперь не используется для получения значений ползунков,
// так как они глобальны.
void on_scale_changed(GtkRange *range, gpointer user_data) {
    // Получаем текущие значения со всех трех ползунков напрямую, так как они глобальны
    int r = (int)gtk_range_get_value(GTK_RANGE(scale_r_global));
    int g = (int)gtk_range_get_value(GTK_RANGE(scale_g_global));
    int b = (int)gtk_range_get_value(GTK_RANGE(scale_b_global));

    // Обновляем текстовые метки рядом с ползунками
    char text[16];
    snprintf(text, sizeof(text), "R: %d", r);
    gtk_label_set_text(GTK_LABEL(label_r), text);

    snprintf(text, sizeof(text), "G: %d", g);
    gtk_label_set_text(GTK_LABEL(label_g), text);

    snprintf(text, sizeof(text), "B: %d", b);
    gtk_label_set_text(GTK_LABEL(label_b), text);

    // Отправляем ШИМ-сигналы на GPIO-пины с помощью pigpio
    gpioPWM(RED_PIN, r);
    gpioPWM(GREEN_PIN, g);
    gpioPWM(BLUE_PIN, b);

    // Обновляем цвет отображаемого виджета в GUI
    update_color_display(r, g, b);
}

// --- Основная функция программы ---

int main(int argc, char *argv[]) {
    // Инициализация GTK. Должна быть вызвана первой.
    gtk_init(&argc, &argv);

    // --- Инициализация библиотеки pigpio ---
    // Это должно быть сделано перед любым использованием pigpio функций.
    // Если gpioInitialise() возвращает < 0, это означает ошибку (демон не запущен или недоступен).
    if (gpioInitialise() < 0) {
        g_printerr("Ошибка: Демон pigpiod не запущен или недоступен.\n");
        g_printerr("Пожалуйста, убедитесь, что pigpiod запущен (например, командой 'sudo pigpiod' или 'sudo systemctl start pigpiod').\n");

        // Создаем простое сообщение об ошибке для пользователя
        GtkWidget *dialog;
        dialog = gtk_message_dialog_new(NULL,
                                        GTK_DIALOG_MODAL,
                                        GTK_MESSAGE_ERROR,
                                        GTK_BUTTONS_CLOSE,
                                        "Ошибка инициализации pigpio");
        gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
                                                 "Не удалось подключиться к демону pigpiod.\n"
                                                 "Убедитесь, что демон запущен и работает.\n"
                                                 "Попробуйте запустить 'sudo pigpiod' в терминале.");
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);

        return 1; // Завершаем программу с ошибкой
    }

    // Устанавливаем диапазон ШИМ для каждого пина в 255.
    // Это означает, что значения от 0 до 255 будут соответствовать 0% до 100% рабочего цикла.
    gpioSetPWMrange(RED_PIN, 255);
    gpioSetPWMrange(GREEN_PIN, 255);
    gpioSetPWMrange(BLUE_PIN, 255);

    // --- Создание главного окна GTK ---
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "RGB LED Control (pigpio)"); // Заголовок окна
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 450); // Увеличиваем высоту окна, чтобы вместить ползунки
    // Подключаем сигнал "destroy" (закрытие окна) к функции gtk_main_quit для завершения приложения.
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // --- Создание основного вертикального контейнера ---
    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); // Вертикальный контейнер с отступом 10px
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 20); // Устанавливаем отступ от края окна для vbox
    gtk_container_add(GTK_CONTAINER(window), vbox); // Добавляем vbox в главное окно

    // --- Виджет для отображения цвета ---
    color_area = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Создаем контейнер, который будет служить областью цвета
    gtk_widget_set_size_request(color_area, 250, 150); // Увеличиваем размер области цвета
    gtk_widget_set_name(color_area, "color_display"); // Присваиваем CSS-идентификатор для стилизации
    gtk_box_pack_start(GTK_BOX(vbox), color_area, FALSE, FALSE, 0); // Добавляем в vbox
    gtk_widget_set_halign(color_area, GTK_ALIGN_CENTER); // Центрируем по горизонтали

    // --- Сетка для ползунков и меток ---
    GtkWidget *grid = gtk_grid_new(); // Создаем виджет сетки
    gtk_grid_set_row_spacing(GTK_GRID(grid), 15); // Увеличиваем отступы между строками
    gtk_grid_set_column_spacing(GTK_GRID(grid), 15); // Увеличиваем отступы между столбцами
    gtk_box_pack_start(GTK_BOX(vbox), grid, TRUE, TRUE, 20); // Добавляем сетку в vbox, увеличиваем отступ

    // --- Создание ползунков (GtkScale) и присвоение их глобальным переменным ---
    scale_r_global = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 255, 1);
    scale_g_global = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 255, 1);
    scale_b_global = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 255, 1);

    // Установка минимального размера для ползунков.
    // Ширина 250px для горизонтальных ползунков. Высота обычно регулируется автоматически.
    gtk_widget_set_size_request(scale_r_global, 250, -1);
    gtk_widget_set_size_request(scale_g_global, 250, -1);
    gtk_widget_set_size_request(scale_b_global, 250, -1);


    // --- Создание меток для отображения значений R, G, B ---
    label_r = gtk_label_new("R: 0");
    label_g = gtk_label_new("G: 0");
    label_b = gtk_label_new("B: 0");

    // --- Размещение виджетов в сетке ---
    gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Red"), 0, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), scale_r_global,      1, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), label_r,             2, 0, 1, 1);

    gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Green"), 0, 1, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), scale_g_global,      1, 1, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), label_g,             2, 1, 1, 1);

    gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Blue"), 0, 2, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), scale_b_global,      1, 2, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), label_b,             2, 2, 1, 1);

    // --- Подключение сигналов к ползункам ---
    // Теперь user_data не нужен, можно передать NULL
    g_signal_connect(scale_r_global, "value-changed", G_CALLBACK(on_scale_changed), NULL);
    g_signal_connect(scale_g_global, "value-changed", G_CALLBACK(on_scale_changed), NULL);
    g_signal_connect(scale_b_global, "value-changed", G_CALLBACK(on_scale_changed), NULL);

    gtk_widget_show_all(window);
    gtk_main();

    // --- Очистка ресурсов pigpio после завершения работы GUI ---
    gpioTerminate();
    return 0;
}