Урок 1.12. Программирование адресной светодиодной ленты

Базовый курс "Программирование микроконтроллеров"
Модуль 1. Введение в Arduino. Работа с цифровым и аналоговым сигналом

Модуль WS2812b

В одном из предыдущих уроков мы уже познакомились с обычным светодиодом и с его более комплексной версией — RGB-светодиодом. Зажигать и гасить светодиод можно и безо всякого контроллера, подавая на него ток от обычной пальчиковой батарейки. Однако, чтобы управлять сразу группой светодиодов, да ещё и не просто включать/выключать, а менять их яркость, потребуется немного больше знаний и навыков.

Всё ещё более усложняется, когда нам нужно управлять не просто множеством светодиодов, но еще и разными цветами и с разной интенсивностью свечения. Это бывает необходимо, когда из светодиодов строится огромный уличный дисплей. Тут в дело идёт тяжелая артиллерия: мощные системы на основе STM32 старших серий либо контроллеры на основе ПЛИС.

К счастью, есть более простое и дешевое решение, которое позволяет DIY-энтузиасту без глубоких знаний в программировании управлять RGB-светодиодами, причём адресно и с возможностью менять яркость каждого светодиода в сборке. Светодиоды, а точнее, светодиодные модули, которые позволяют всё это делать, называются адресными. Самый популярный на данный момент представитель этих «умных» RGB-модулей называется WS2812b.

Такие светодиоды обычно соединяют в ленту или матрицу:

Каждый модуль WS2812b представляет собой сложное устройство, в котором имеется три светодиода красного, синего и зелёного свечения, а самое главное — специализированная микросхема.

Перед нами корпус известного светодиода типоразмера 5050, внутри которого виднеется большой черный прямоугольник — микросхема. Вот еще снимок самого чипа под микроскопом.

Алгоритм протокола ws2812b для нескольких соединенных светодиодов выглядит следующим образом:

1. Контроллер отправляет 3 байта, кодирующих цвет первого ws2812b;
2. Первый ws2812b в цепочке принимает цветовой код и на его основе генерирует ШИМ-сигнал для каждого из RGB-светодиодов;
3. Затем первый светодиод соединяет вход с выходом, чтобы данные проходили через него без изменений;
4. Контроллер отправляет 3 байта для следующего светодиода;
5. Данные свободно минуют первый светодиод и поступают на второй
и так далее, пока контроллер не сделает паузу 50 мкс, после которой первый светодиод готов будет вновь принимать новый цвет.

Вот так в реальности выглядит передача данных на один светодиод:

Примеры проектов с адресными светодиодами:

Обратите внимание: внутри каждого модуля уживаются вместе три отдельных светодиода, каждый из которых потребляет ток около 15мА. Таким образом, одному модулю WS2812b необходимо 50мА при максимальной яркости всех трёх светодиодов (RGB). Это значит, что для отрезка адресной ленты с 30 светодиодными модулями потребуется ток около 1,5 Ампер! Для понимания масштабов: на контакте +5V Arduino Uno можно получить только 800 мА.

Схема подключения и программа включения нескольких светодиодов

Для работы с адресными светодиодами существует множество библиотек, мы будем использовать библиотеку FastLED от Daniel Garcia.

Напишем простую программу для включения нескольких светодиодов разными цветами:

#define LED_PIN 3     // пин управления
#define LED_NUM 12    // количество светодиодов
#include "FastLED.h"  // подключаем библиотеку FastLED
CRGB leds[LED_NUM];   // создание массива светодиодов


void setup() {
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM);
  // устанавливаем базовую яркость светодиодов на максимум
  FastLED.setBrightness(255);
}

void loop() {
  // устанавливаем светодиод "0" в красный цвет
  leds[0].setRGB(255, 0, 0);
  leds[1].setRGB(255, 255, 0);
  leds[2].setRGB(0, 255, 0);
  leds[3].setRGB(0, 255, 255);
  leds[4].setRGB(0, 0, 255);
  // обновляем ленту, это необходимо для того, что бы включить светодиоды
  FastLED.show();
}

Управление оттенками в цветовой модели HSV

Если мы захотим сделать какие то красивые анимации с переливаниями цветов и прочим, то нам придётся попотеть! Вспомните как мы делали переливание цветов радуги используя RGB-светодиод -это довольно сложная процедура требующая внимательности и большого числа строк кода. В

Выйти из этого положения нам поможет другая цветовая система, которая называется HSV. Обратите внимание на два рисунка, представленных ниже:

Обратите внимание на то, как построены две эти цветовые модели. Главным отличием и особенностью является то, что в RGB-системе для получения оттенка цвета придётся двигаться во всех трех измерениях, а в системе HSV только по одному измерению – Hue.

Иными словами, используя систему HSV, мы можем изменять оттенок цвета регулируя только одно значение. Перепишем нашу программу на использование этой системы:

#define LED_PIN 3     // пин управления
#define LED_NUM 12    // количество светодиодов
#include "FastLED.h"  // подключаем библиотеку FastLED
CRGB leds[LED_NUM];   // создание массива светодиодов


void setup() {
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM);
  // устанавливаем базовую яркость светодиодов на максимум
  FastLED.setBrightness(255);
}

void loop() {
  // устанавливаем светодиод "0" в красный цвет, 
  // насыщенность и яркость оставляем на максимум
  leds[0].setHSV(0, 255, 255);
  leds[1].setHSV(20, 255, 255);
  leds[2].setHSV(40, 255, 255);
  leds[3].setHSV(60, 255, 255);
  leds[4].setHSV(80, 255, 255);
  leds[5].setHSV(100, 255, 255);
  leds[6].setHSV(120, 255, 255);
  leds[7].setHSV(140, 255, 255);
  leds[8].setHSV(160, 255, 255);
  leds[9].setHSV(180, 255, 255);
  leds[10].setHSV(200, 255, 255);
  leds[11].setHSV(220, 255, 255);
  // обновляем ленту, это необходимо для того,
  // что бы включить светодиоды
  FastLED.show();
}

Усложним задачу, разукрасим наши светодиоды более компактно и правильно:

#define LED_PIN 3     // пин управления
#define LED_NUM 12    // количество светодиодов
#include "FastLED.h"  // подключаем библиотеку FastLED
CRGB leds[LED_NUM];
// переменная времени задержки анимации
int t = 300;

void setup() {
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM);
  FastLED.setBrightness(255);
}

void loop() {
  for (int i = 0; i < LED_NUM; i++) {
    leds[i].setHSV(255 * i / LED_NUM, 255, 100);
    FastLED.show();
    delay(t);
  }
  FastLED.clear();
}

Анимации адресной светодиодной ленты

"Последовательное включение и выключение светодиодов"

#define LED_PIN 3 // пин управления
#define LED_NUM 15 // количество светодиодов #include "FastLED.h" // подключаем библиотеку FastLED CRGB leds[LED_NUM]; int t1 = 200; // переменная времени задержки анимации включения int t2 = 100; // переменная времени задержки анимации выключения void setup() { FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM); FastLED.setBrightness(255); } void loop() { for (int i = 0; i < LED_NUM; i++) { leds[i].setHSV(255 * i / LED_NUM, 255, 100); FastLED.show(); delay(t1); } for (int i = LED_NUM - 1 ; i >= 0; i--) { leds[i].setHSV(0, 0, 0); FastLED.show(); delay(t2); } }
"Бегущий цветной огонек"

#define LED_PIN 3 // пин управления
#define LED_NUM 12 // количество светодиодов #include "FastLED.h" // подключаем библиотеку FastLED CRGB leds[LED_NUM]; // переменная времени задержки анимации int t1 = 250; int t2 = 100; void setup() { FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM); FastLED.setBrightness(255); } void loop() { for (int i = 0; i < LED_NUM; i++) { leds[i].setHSV(255 * i / LED_NUM, 255, 100); FastLED.show(); delay(t1); leds[i].setHSV(0, 0, 0); FastLED.show(); } }
"Бегущий огонек туда и обратно"

#define LED_PIN 3 // пин управления
#define LED_NUM 12 // количество светодиодов #include "FastLED.h" // подключаем библиотеку FastLED CRGB leds[LED_NUM]; // переменная времени задержки анимации int t = 200; void setup() { FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM); FastLED.setBrightness(255); } void loop() { for (int i = 0; i < LED_NUM; i++) { leds[i].setHSV(255 * i / LED_NUM, 255, 100); FastLED.show(); delay(t); leds[i].setHSV(0, 0, 0); FastLED.show(); } for (int i = LED_NUM - 2; i > 0; i--) { leds[i].setHSV(255 * i / LED_NUM, 255, 100); FastLED.show(); delay(t); leds[i].setHSV(0, 0, 0); FastLED.show(); } }
"Бегущий огонек с хвостиком"

#define LED_PIN 3 // пин управления
#define LED_NUM 12 // количество светодиодов #include "FastLED.h" // подключаем библиотеку FastLED CRGB leds[LED_NUM]; // переменная времени задержки анимации int t = 100; int j = 0; void setup() { FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM); } void loop() { leds[j % LED_NUM].setHSV(255 * j / LED_NUM, 255, 200); leds[(j - 1) % LED_NUM].setHSV(255 * j / LED_NUM, 255, 80); leds[(j - 2) % LED_NUM].setHSV(255 * j / LED_NUM, 255, 40); FastLED.show(); delay(50); FastLED.clear(); FastLED.show(); j++; }
"Подсвечивание светодиодов вспышкой"

#define LED_PIN 3 // пин управления
#define LED_NUM 12 // количество светодиодов #include "FastLED.h" // подключаем библиотеку FastLED CRGB leds[LED_NUM]; // переменная времени задержки анимации int t = 50; int h = 0; int i = 0; void setup() { FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM); FastLED.setBrightness(255); } void loop() { h += 255 * (i % LED_NUM) / 100; leds[i % LED_NUM].setHSV(h % 255, 255, 220); FastLED.show(); delay(t); leds[i % LED_NUM].setHSV(h % 255, 255, 40); FastLED.show(); i++; }
"Конфетти"

#define LED_PIN 3 // пин управления
#define LED_NUM 12 // количество светодиодов #include "FastLED.h" // подключаем библиотеку FastLED CRGB leds[LED_NUM]; // переменная времени задержки анимации int t = 20; int i = 0; int h = 0; int leds_now[LED_NUM][2]; unsigned long timer, timer2, timer_cur; int period; int period2 = 5; void setup() { FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM); FastLED.setBrightness(255); randomSeed(analogRead(3)); period = random(100, 3000); timer = millis() + period; timer2 = millis() + period2; } void loop() { timer_cur = millis(); if (timer_cur >= timer) { i = random(LED_NUM); h = random(255); leds_now[i][0] = h; leds_now[i][1] = 100; leds[i].setHSV(h, 255, 100); FastLED.show(); period = random(100, 200); timer = timer_cur + period; } if (timer_cur >= timer2) { for (int j = 0; j < LED_NUM; j++) { if (leds_now[j][1] > 0) { leds_now[j][1]--; if (leds_now[j][1] < 0) leds_now[j][1] = 0; leds[j].setHSV(leds_now[j][0], 255, leds_now[j][1]); } } FastLED.show(); timer2 = timer_cur + period2; } }