Гирлянда-«настроение» на восемь режимов была моим самым утомительным проектом. Восемь кнопочных состояний — и под каждое я подобрал цвет в RGB: switch на восемь кейсов, в каждом тройка чисел, плюс бумажка над столом с той же таблицей, потому что числа в скетче без контекста ничего не говорят. Захотелось девятый режим — и я полчаса подбирал третью тройку, пока друг, глянув через плечо, не переписал мне всё на две строки: hue = hue + 32 на каждое нажатие и CHSV(hue, 255, 255) в заливке. Бумажка ушла в мусор в тот же вечер.
С тех пор всё, что «переключается по цвету» или «плавно меняет оттенок», я делаю только через HSV. В уроке про HSV и палитры мы уже попробовали CHSV и собрали первую радугу; здесь разложу колесо оттенка по полочкам: почему оттенок хранится в одном байте, чем насыщенность отличается от яркости на реальной ленте, как пересчитывать привычные 360 градусов и почему восемь режимов гирлянды — это одна строка вместо switch на восемь кейсов.
Колесо оттенка: один байт по кругу
Оттенок — hue — это точка на цветовом круге. FastLED хранит её в одном байте, поэтому круг разбит на 256 шагов от 0 до 255, а не на привычные графическим редакторам 360 градусов. Шаг 32 — новый цвет: 0 красный, 32 оранжевый, 64 жёлтый, 96 зелёный, 128 аква, 160 синий, 192 пурпурный, 224 розовый. Запоминать таблицу не нужно — достаточно помнить мнемонику «32 на цвет» и точку отсчёта: красный в нуле.
Круг замкнут, и это не метафора, а арифметика байта: оттенок 255 почти совпадает с оттенком 0, потому что после розового круг приходит к красному. Переменная типа byte при hue = 255 и прибавлении единицы честно переполнится и даст 0 — там, где в других задачах переполнение баг, здесь оно превращается в фичу: анимация оттенка замыкается сама, без единого if. Тот же приём мы использовали в первом знакомстве с CHSV, и я до сих пор не написал ни одного «закольцовывателя оттенков» — байт делает это бесплатно.
Почему не 360: один байт вмещает ровно 256 значений, и FastLED гонится за скоростью — оттенок, насыщенность и яркость занимают три байта, столько же, сколько и RGB, но ручки при этом человеческие. Практический вывод простой: эффекты считайте сразу в FastLED-оттенках, без промежуточных градусов, иначе половина границ поедет мимо. Если оттенок всё же пришёл в градусах — из датчика, из JSON, из макета дизайнера — пересчёт одной строкой: map(hue360, 0, 360, 0, 255). Функцию map мы разбирали в уроке про потенциометр, здесь она работает так же: масштабирует один диапазон в другой.
Вторая и третья ручки — насыщенность s и яркость v, обе тоже 0–255. Насыщенность — чистота цвета: 255 — максимально едкий оттенок без примесей, 200–230 — тот же оттенок с лёгкой белизной, комфортный для жилой комнаты, 0 — цвет исчезает, остаётся серый свет (белый при v = 255). Яркость v — мощность: 0 гасит пиксель в чёрный при любом оттенке, 255 — вся мощь выбранного цвета. Важно не путать v с глобальным FastLED.setBrightness(): первое — свойство конкретного пикселя, которым анимируют дыхание и затухания; второе — общий множитель на всю ленту, который ставят один раз в setup(). Это различие мы проговаривали в прошлом уроке раздела, и оно работает в HSV точно так же.
Мнение с границей: для жилых комнат я почти всегда пишу s в диапазоне 200–230 — примешанный белый убирает «кислотность», от которой к вечеру рябит в глазах. Для витрин, клубного антуража и уличных декораций — наоборот, 255: на расстоянии и днём приглушённые цвета выглядят пыльными. Граница простая: ближе трёх метров к ленте — приглушайте; дальше — жгите на полную.
| Задача | Путь в CRGB | Путь в HSV |
|---|---|---|
| Тревога, красный сигнал | CRGB(255, 0, 0) | CHSV(0, 255, 255) |
| «Всё в порядке», зелёный | CRGB(0, 255, 0) | CHSV(96, 255, 255) |
| Тёплый белый ночника | CRGB(255, 147, 41) | CHSV(25, 190, 255) |
| Пастельно-розовый | CRGB(255, 135, 135) | CHSV(0, 120, 255) |
| Восемь режимов гирлянды | switch на восемь кейсов с тройками | hue = hue + 32 на каждое нажатие |
Восемь режимов без switch и таблиц
Вернёмся к гирлянде из начала урока и соберём её честно. Кнопка на пине 2 с подтяжкой — чтение и защита от дребезга мы проходили в уроке про кнопку. На каждое нажатие hue = hue + 32 — и лента перекрашивается в следующий цвет колеса: красный, оранжевый, жёлтый, зелёный, аква, синий, пурпурный, розовый — ровно восемь режимов, потому что 256 делится на 32 без остатка. Девятый клик возвращает красный: круг замкнулся сам. Захотели двенадцать режимов — меняете шаг на 21–22 и правите одну константу, а не двенадцать кейсов.
Здесь и живёт главное преимущество HSV: количество состояний может меняться в любой момент без переписывания структуры программы. В RGB добавление режима — это новая тройка чисел и новый кейс; в HSV — новое значение шага. Моё правило: если цвета в проекте перебираются по кругу — пишу только HSV, без вариантов.
Тот же ход работает и в обратную сторону — не перебор, а дрейф внутри узкого отрезка круга. В скетче ниже ночник, у которого оттенок медленно гуляет между 15 и 40: колесо радуги ночнику не нужно, нужно «живое» пламя свечи. Граница 40 с возвратом на 15 — это уже не замыкание круга, а обычный if с диапазоном: глазу переход через 40 в 15 не заметен, потому что оба конца тёплые. Кадр раз в 120 мс — дрейф медленный, около трёх шагов в секунду, движение похоже на колебание пламени, а не на перелив. Общий вывод: колесо оттенка — это и палитра, и счётчик режимов, и ось анимации одновременно; одна переменная byte решает задачи, на которые в RGB уходили switch, таблицы и бумажка над столом.
#include <FastLED.h> #define LED_PIN 6#define NUM_LEDS 12 CRGB leds[NUM_LEDS];byte hue = 25; void setup() { FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS); FastLED.setBrightness(60);} void loop() { fill_solid(leds, NUM_LEDS, CHSV(hue, 190, 255)); FastLED.show(); hue = hue + 1; if (hue > 40) { hue = 15; // оттенок гуляет по тёплому отрезку 15–40, а не по всему кругу } delay(120);}Задачи на закрепление — все в симуляторах выше. Первая: в симуляторе колеса заведите вторую половину ленты с оттенком hue + 128 — получите двухцветную ленту-антитезу, где половинки всегда противоположны по кругу. Вторая: превратите переключатель режимов в «тёплый спектр» — шаг 8 вместо 32, и лента начнёт листать близкие оттенки, как кольца на палитре. Третья: соберите индикатор тревоги, где оттенок медленно уезжает от 96 (зелёный) к 0 (красный) со скоростью один шаг в полсекунды, — и обратно. Такие индикаторы я делаю только в HSV: переходы получаются честными без единой тройки чисел.
Дальше по разделу — эффекты. Урок про эффекты на millis-кадрах держится на том же каркасе таймера из урока про millis вместо delay, и именно там дрейфующий оттенок превращается в волны, огонь и кометы. А следующий урок этого раздела — про самую известную функцию FastLED, fill_rainbow, которая красит ленту радугой одной строкой: колеса оттенка мы уже понимаем достаточно, чтобы разобрать её по винтикам.
- Оттенок hue — точка на круге из 256 шагов; шаг 32 — новый цвет; красный в нуле; байт сам замыкает круг 255 + 1 = 0
- Насыщенность s — чистота цвета: 255 едкий, 200–230 комфортный для жилой комнаты, 0 — серый (белый при v = 255)
- Яркость v — свойство пикселя, setBrightness — общий множитель; v = 0 гасит пиксель при любом оттенке
- Белого оттенка в HSV нет: это s = 0; CHSV(255, 255, 255) — красно-розовый, а не белый
- Перебор цветов по кругу — hue + шаг: 32 даёт восемь режимов; в HSV новые режимы — это новое число, а не новый кейс
- 1. На сколько шагов разбит цветовой круг оттенка в FastLED?
- 2. Какой цвет даёт CHSV(96, 255, 255)?
- 3. Как в HSV получить белый свет?
- 4. byte hue = 250; hue = hue + 10; что окажется в переменной?
- 5. Чем насыщенность s отличается от яркости v?
Частые вопросы по теме урока
- На сколько шагов разбит цветовой круг оттенка в FastLED?
- Один байт — 256 значений от 0 до 255. Круг замкнут: 255 стоит рядом с 0, поэтому переливы не имеют швов. 256 — оттенок хранится в одном байте
- Какой цвет даёт CHSV(96, 255, 255)?
- 96 — зелёный: каждые 32 шага новый цвет, 64 жёлтый, 96 зелёный, 128 аква. Полная насыщенность и яркость дают самый едкий вариант. Зелёный
- Как в HSV получить белый свет?
- Белый — это отсутствие цвета: s = 0, v = 255. А CHSV(255, 255, 255) — красно-розовый: оттенок 255 почти красный конец круга. Нулевой насыщенностью при полной яркости, например CHSV(0, 0, 255)
- byte hue = 250; hue = hue + 10; что окажется в переменной?
- 250 + 10 = 260, байт вмещает 0–255, остаток от деления на 256 — это 4. Для оттенка это фича: анимация замыкается без кода. 4 — байт переполнится и круг замкнётся
- Чем насыщенность s отличается от яркости v?
- Насыщенность — сколько в цвете серой примеси, яркость — сила свечения. v = 0 гасит пиксель, s = 0 убирает цвет, оставляя белый свет. s — чистота цвета от серого к едкому, v — мощность от чёрного к полной