Arduino

RGB-светодиод и простые светодиоды: ШИМ-управление цветом и яркостью

Введение

В комплекте есть как простые одноцветные светодиоды (красный, зелёный, жёлтый, по 5 штук каждого), так и RGB-модуль, объединяющий три светодиода (красный, зелёный, синий) в одном корпусе, что позволяет получать произвольные цвета через смешивание. В статье разберём управление яркостью отдельных светодиодов и цветом RGB-модуля через ШИМ, а также передачу выбранного пользователем цвета из Qt-интерфейса.

Концепция

Яркость светодиода, подключённого к ШИМ-совместимому пину, регулируется через analogWrite(pin, value), где значение от 0 до 255 определяет скважность импульсов, воспринимаемую глазом как пропорциональную яркость свечения. RGB-модуль управляется тем же способом, но независимо для каждого из трёх цветовых каналов — комбинация трёх значений яркости (R, G, B) определяет итоговый воспринимаемый цвет, что напрямую соответствует модели RGB, используемой и в Qt при работе с QColor.

Пример кода

// firmware/src/main.cpp — управление RGB-модулем и отдельными светодиодами
#define RED_PIN 9
#define GREEN_PIN 10
#define BLUE_PIN 11
#define STATUS_LED_PIN 6

void setup()
{
    Serial.begin(9600);
    pinMode(RED_PIN, OUTPUT);
    pinMode(GREEN_PIN, OUTPUT);
    pinMode(BLUE_PIN, OUTPUT);
    pinMode(STATUS_LED_PIN, OUTPUT);
    delay(100);
    Serial.println("READY");
}

void loop()
{
    static String inputBuffer = "";
    while (Serial.available() > 0) {
        char c = Serial.read();
        if (c == 'n') {
            processCommand(inputBuffer);
            inputBuffer = "";
        } else if (inputBuffer.length() < 64) {
            inputBuffer += c;
        }
    }
}

void processCommand(String command)
{
    command.trim();
    if (command.startsWith("RGB:")) {
        // Формат команды: RGB:255,128,0
        int r, g, b;
        sscanf(command.c_str() + 4, "%d,%d,%d", &r, &g, &b);
        analogWrite(RED_PIN, constrain(r, 0, 255));
        analogWrite(GREEN_PIN, constrain(g, 0, 255));
        analogWrite(BLUE_PIN, constrain(b, 0, 255));
        Serial.println("ACK:RGB");
    } else if (command.startsWith("LED:BRIGHTNESS:")) {
        int brightness = constrain(command.substring(15).toInt(), 0, 255);
        analogWrite(STATUS_LED_PIN, brightness);
        Serial.println("ACK:LED:BRIGHTNESS");
    }
}
// rgbcontrolwidget.h — выбор цвета через стандартный QColorDialog
#pragma once
#include <QWidget>
#include <QPushButton>
#include <QColorDialog>
#include "arduinoconnection.h"

class RgbControlWidget : public QWidget
{
    Q_OBJECT
public:
    explicit RgbControlWidget(ArduinoConnection *connection, QWidget *parent = nullptr)
        : QWidget(parent), m_connection(connection)
    {
        auto *pickColorButton = new QPushButton("Выбрать цвет", this);
        auto *layout = new QVBoxLayout(this);
        layout->addWidget(pickColorButton);

        connect(pickColorButton, &QPushButton::clicked, this, [this]() {
            const QColor color = QColorDialog::getColor(Qt::white, this, "Цвет RGB-светодиода");
            if (color.isValid()) {
                m_connection->sendCommand(QString("RGB:%1,%2,%3")
                    .arg(color.red()).arg(color.green()).arg(color.blue()));
            }
        });
    }

private:
    ArduinoConnection *m_connection;
};

Пояснения к коду

Команда RGB:255,128,0 разбирается через sscanf() — компактный способ извлечь три целых числа из строки за один вызов без последовательного разделения по запятым вручную; результат сразу применяется через три отдельных вызова analogWrite(), каждый со своим ограничением через constrain() для защиты от некорректных значений. RgbControlWidget показывает удобство использования стандартного QColorDialog — пользователь выбирает цвет через привычный, нативный диалог выбора цвета операционной системы, и Qt-приложению остаётся только извлечь компоненты red()/green()/blue() выбранного QColor и переслать их в виде той же текстовой команды формата протокола.

Подводные камни

  • Не все три ШИМ-пина RGB-модуля физически совместимы с ШИМ на конкретной модели Arduino — на Arduino Uno только определённые пины (3, 5, 6, 9, 10, 11) поддерживают analogWrite() с полноценной ШИМ-функциональностью; при использовании не-ШИМ пина analogWrite() либо не скомпилируется с ожидаемым поведением, либо даст только цифровое включение/выключение без промежуточных уровней яркости.
  • Использование sscanf() без проверки количества успешно распознанных значений (возвращаемое значение sscanf — число успешно прочитанных полей) — если команда пришла в искажённом виде (например, не все три числа присутствуют), sscanf оставит часть переменных r, g, b неинициализированными, что приведёт к использованию случайного «мусорного» значения памяти вместо ожидаемого числа — для надёжности стоит проверять возвращаемое значение sscanf() перед использованием результатов.
  • Несовпадение цветовой модели восприятия человеком и линейной шкалы ШИМ — равные шаги изменения значения ШИМ (например, от 10 до 20 против от 200 до 210) воспринимаются глазом не как равные изменения яркости, поскольку восприятие яркости человеком нелинейно; для более визуально равномерного управления яркостью через слайдер в Qt-интерфейсе иногда применяют гамма-коррекцию значения перед отправкой на Arduino, а не передают линейное значение слайдера напрямую.
  • Длительная работа нескольких светодиодов на максимальной яркости одновременно увеличивает суммарное энергопотребление, что при питании всей схемы от USB-порта компьютера (ограниченного по току) или маломощного источника может вызвать нестабильность напряжения, влияющую на остальную часть схемы — для проектов с несколькими одновременно ярко горящими светодиодами и другими модулями стоит явно учитывать суммарное потребление тока относительно возможностей источника питания.