К Новому году я обещал себе гирлянду на всё окно: радуга по карнизу и тёплое звёздное небо на откосе. Первая попытка была наивной: я подал обе ленты одним проводом данных, рассчитывая «потом развести в коде». Обе, конечно, светили одно и то же — данные-то общие, и каждый пиксель второй ленты слушал ту же очередь, что и первый. Вторая попытка — второй Arduino — принесла лишнюю плату, лишний блок питания и два скетча, которые приходится синхронизировать вручную. Оба тупика закрылись в один вечер, когда я дописал в setup() второй FastLED.addLeds.
Разберём: два буфера и два addLeds в одном скетче, один show() на обе ленты, независимые и синхронные сцены, время кадра и питание большой сборки. Схему подключения ленты с резистором 330 Ом и конденсатором 1000 мкФ я здесь не повторяю — она целиком в уроке про питание WS2812B.
Второй addLeds — второй контроллер
Каждый вызов FastLED.addLeds<WS2812B, PIN>(buf, N) — паспорт одного контроллера: чип, пин данных, буфер и длина. FastLED складывает паспорта в список и обслуживает их всех сразу; на AVR библиотека спокойно держит до восьми таких вызовов. Для двух лент достаточно двух массивов и двух строк в setup(): пины 6 и 9, буферы ledsA и ledsB. Если самый первый скетч ещё не собирали — вернитесь к уроку про установку и первый запуск. Порядок вызовов не пустая формальность: show() обходит контроллеры в той же последовательности, в какой вы их зарегистрировали — сцены это не касается, а вот при замере времени кадра и отладке полезно помнить, что сначала всегда уходит первая лента.
Главное принять сразу: show(), setBrightness() и clear() — глобальные. Один FastLED.show() выталкивает все зарегистрированные буферы в свои провода подряд, по 30 микросекунд на пиксель каждой ленты. Управлять лентами порознь нужно их буферами, а не вызовами отрисовки. Это не ограничение, а экономия: одна точка синхронизации на весь скетч — всё, что налито в буферы до show(), выйдет на ленты в один и тот же момент.
Пины для лент годятся любые цифровые, но у меня есть привычка брать соседние с землёй — 6 и 9 хороши тем, что рядом шина питания макетки, и провода данных не тянутся через всю плату. На ESP32 картина другая: там логика 3,3 вольта, а WS2812 при питании 5 В ждёт на входе уровень выше 3,5 — первый пиксель может молчать. Народное лечение — диод в разрыв питания ленты, чтобы просадить её до 4,3 В, либо первый пиксель в роли буфера уровня. На Uno этих танцев нет: пять вольт на пять вольт.
Бонус смешанных сборок: раз каждый addLeds — отдельный паспорт, ленты могут быть и разных моделей. На одном пине — WS2812B, на другом APA102 с тактовым проводом: FastLED сам разрулит тайминги, а show() останется один. Симулятор рисует только WS2812, поэтому смешанные схемы проверяйте на живой плате — скелет кода при этом не меняется ни на строку.
| Вызов | Что делает | Кого касается |
|---|---|---|
| FastLED.show() | выталкивает буферы в провода | все ленты сразу, последовательно |
| FastLED.setBrightness(100) | общий множитель яркости | все ленты |
| FastLED.clear() | обнуляет буферы | все ленты |
| fill_solid(ledsA, N_A, c) | заливает один буфер | только ledsA |
#include <FastLED.h> #define PIN_A 6#define PIN_B 9#define N_A 30 // карниз#define N_B 16 // откос CRGB ledsA[N_A];CRGB ledsB[N_B]; byte hue = 0;int pos = 0;unsigned long lastFrame = 0; void setup() { FastLED.addLeds<WS2812B, PIN_A, GRB>(ledsA, N_A); FastLED.addLeds<WS2812B, PIN_B, GRB>(ledsB, N_B); FastLED.setBrightness(140);} void loop() { if (millis() - lastFrame >= 30) { lastFrame = millis(); hue++; // сцена A: комета на карнизе fadeToBlackBy(ledsA, N_A, 80); ledsA[pos] = CHSV(hue, 200, 255); pos = (pos + 1) % N_A; // сцена B: своя радуга на откосе fill_rainbow(ledsB, N_B, hue, 16); FastLED.show(); // один вызов — обе ленты }}Независимые сцены — это просто два непересекающихся набора переменных. Комете на карнизе нужны свой pos и своё затухание, радуге на откосе — свой стартовый оттенок; заливки и палитры берите готовые из урока про CRGB и fill_solid и урока про fill_rainbow. Рисуете каждый буфер своими строками кода — и показываете всё одним show(). В симуляторе ниже лента на пине 6 живёт кометой, лента на пине 9 — радугой: два мира в одном loop(). Заметьте, что и скорость у сцен разная: комета делает круг за 640 мс, радуга меняет оттенок каждые 40 мс — независимость распространяется и на темп, не только на картинку.
Синхронные сцены — наоборот: обе ленты кормятся от одной переменной. Общий hue — и радуга идёт по обеим лентам согласованно; один бегущий центр — волна перетекает с карниза на откос без шва. Полусинхронный вариант тоже хорош: общий hue, но у второй ленты сдвинут на половину круга — оттенок словно передаёт эстафету с одной ленты на другую. Так собирают зеркальные композиции: два крыла подсветки, две стены аквариума, обвес монитора из двух полос — одна анимация, размазанная на несколько лент.
Когда сцен становится больше двух, я выношу каждую в свою функцию — drawComet(), drawStars() — и выбираю режим переменной с switch. Получается плейлист: кнопка листает эффекты, каждый рисует в свой буфер, show() остаётся один. Это ровно та развилка, с которой начинаются умные гирлянды с переключением режимов: не новый код, а порядок в старом.
Классика жанра — ambilight у монитора: две полосы по бокам экрана — одна сцена, а не две. Кадр один: посчитали цвет с края изображения, залили оба буфера, причём правая полоса зеркальна, с индексом N - i. Обе ленты показывают свои половины одной картинки, и show() обновляет их в один и тот же миг. Разница между «две сцены» и «одна сцена на два буфера» — ровно в том, чьи переменные кормят цвета.
Питание и время кадра
Питание двух лент считается так же, только умножайте на два. Пиксель на максимуме ест до 60 мА; 46 пикселей белым — почти 2,8 А, а метр 60-пиксельной ленты в белом — 3,6 А. О питании от пина 5V Arduino речи нет: отдельный блок на 5 В с запасом, общая земля с платой обязательна, иначе данные читаются как шум и ленты показывают снег. Провода питания берите сечением от 0,75 мм²: три ампера по тонкой скрутке греются, а на разъёмах и под пайкой просаживают вольты. На длинных сборках добавляйте инжекцию — подпитку 5 В каждые примерно метр. И оставляйте запас блока процентов тридцать: гирлянда, работающая на пределе, просаживает напряжение на дальних пикселях, и красный начинает желтить — это первый симптом нехватки тока.
Время кадра тоже удваивается: show() идёт по лентам последовательно. 46 пикселей — это 46 × 30 мкс, около 1,4 мс на кадр; 128 пикселей — уже 3,8 мс, что при кадре 30 мс съедает 13% бюджета времени. Дальше круче: лента в 300 пикселей отнимет 9 мс — почти целый кадр 30 мс, вот почему на больших сборках интервал поднимают до 50–60 мс. Для средних я поднимаю кадр до 40 мс: глаз разницы не видит, а освободившееся время получают кнопки, Serial и датчики. Каркас кадров тот же, что в уроке про эффекты. Кстати, эти самые 30 мкс на пиксель — время, когда библиотека без остановки гонит биты по проводу и глушит прерывания; что это значит для соседних задач, я разбирал в уроке про однопроводной протокол.
Эксперименты для закрепления
Три ленты? Пять? После второго addLeds каждый следующий — одна строка в setup(). Каждую новую сцену сначала проверьте в симуляторе: там видно обе ленты и занятые пины сразу. Задачи по возрастанию:
- Третья лента на пине 10: массив, addLeds, своя сцена — например, бегущий по одиночным пикселям счётчик кадров.
- Синхронная волна: один центр на обе ленты — сгусток уходит с карниза и приходит на откос без паузы.
- Разная яркость сцен: setBrightness общий, поэтому приглушайте одну сцену яркостью v в CHSV — комета на 120, радуга на 255.
- Замер бюджета: раз в секунду печатайте millis() в Serial и убедитесь, что кадр с двумя лентами не съедает больше 15% времени.
- Один addLeds — один контроллер: свой пин и свой буфер; show() обновляет все ленты разом
- setBrightness и clear глобальные; сцены разделяются на уровне буферов, а не вызовов
- Независимые сцены = свои переменные у каждой ленты; синхронные = общий hue или общий центр
- 30 мкс на пиксель: 46 пикселей — около 1,4 мс кадра, 128 — уже 3,8 мс
- Питание двух лент — отдельный блок 5 В, общая земля и сечение от 0,75 мм²; 60 пикселей белым — до 3,6 А
- 1. Сколько вызовов addLeds нужно для двух лент на разных пинах?
- 2. Что сделает один FastLED.show() в скетче с двумя лентами?
- 3. Обе ленты светят одинаково, хотя сцены «разные». Типовая причина?
- 4. Сколько примерно занимает show() для 128 пикселей?
- 5. Что из этого действует на все ленты сразу?
Две ленты — это уже не поделка, а установка: сцены, синхронизация, бюджет питания и время кадра считаются всерьёз. Моё окно в итоге получило четыре полосы: карниз, два откоса и подоконник — после второго addLeds третья и четвёртая добавлялись каждая одной строкой и одним куском провода. На такой базе по-новому читаются и матричные уроки: бегущая строка на сборке 8x32 — та же прокрутка с окном шире в четыре раза, а змейке хватает места для честного поля. Вернитесь к тексту на матрице с длинной лентой и посмотрите, как строка оживает на широком экране.
Частые вопросы по теме урока
- Сколько вызовов addLeds нужно для двух лент на разных пинах?
- Каждый addLeds регистрирует один контроллер: чип, пин, буфер и длину. Две ленты — два вызова и два массива. Два — по одному на каждую ленту со своим буфером
- Что сделает один FastLED.show() в скетче с двумя лентами?
- show() обходит список зарегистрированных контроллеров и выталкивает каждый буфер, по 30 мкс на пиксель. Обновит обе ленты, последовательно выдав их буферы в провода
- Обе ленты светят одинаково, хотя сцены «разные». Типовая причина?
- Второй addLeds с чужим массивом выводит тот же буфер на второй пин. Лечится выделением отдельного массива. Обе ленты зарегистрированы с одним и тем же буфером
- Сколько примерно занимает show() для 128 пикселей?
- 128 × 30 мкс = 3,84 мс. При кадре 30 мс это уже 13% бюджета — повод поднять интервал кадра. Около 3,8 мс — по 30 мкс на пиксель
- Что из этого действует на все ленты сразу?
- clear() обнуляет все зарегистрированные буферы. Заливки и переменные сцен касаются только тех буферов, что им переданы. FastLED.clear()