Глава 4: RGB LED Controller (GTK + pigpio) ========================================== Приложение для управления RGB-светодиодом с помощью графического интерфейса на `GTK+3` и аппаратного ШИМ через `pigpio`. Работает на Raspberry Pi. .. contents:: :local: --- ## Описание Программа предоставляет простой 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:** .. code-block:: bash sudo apt update sudo apt install libgtk-3-dev **2. Установка pigpio:** Библиотека pigpio часто предустановлена на Raspberry Pi OS. Если нет, вы можете установить её: .. code-block:: bash sudo apt install pigpio --- ## Запуск pigpio-демона Для работы `pigpio` требуется фоновый демон `pigpiod`. Его нужно запустить **до запуска программы**, либо ваша программа может запустить его автоматически. **Рекомендуемый способ запуска (через systemd) для постоянной работы:** Если вы хотите, чтобы `pigpiod` работал всегда в фоновом режиме (независимо от вашей программы) и был доступен для других приложений, используйте `systemctl`: .. code-block:: bash sudo systemctl enable pigpiod # Включает автозапуск при загрузке (однократно) sudo systemctl start pigpiod # Запускает демон прямо сейчас **Проверка статуса демона:** .. code-block:: bash sudo systemctl status pigpiod Вы должны увидеть `Active: active (running)`. **Остановить вручную (при необходимости):** .. code-block:: bash sudo systemctl stop pigpiod --- ## Исходный код Сохраните следующий код в файл с именем `rgb_pwm_gui.c`: .. code-block:: c #include // Подключаем библиотеку GTK для создания графического интерфейса #include // Подключаем библиотеку pigpio для работы с GPIO и ШИМ #include // Подключаем стандартную библиотеку ввода/вывода для 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; }