Плавное затухание адресной светодиодной ленты (C++)
Помогите, пожалуйста, написать код эффекта для светодиодной ленты WS2812b. Хочу сделать так, чтобы плавно загорался рандомно выбранный цвет, затем плавно затухал, и дальше тоже самое, но с другим рандомно выбранным цветом Пробовала делать так, но ничего не получается:
void party()
{
if (millis() - timing > 10000)
{
timing = millis();
if (r_flag == true)
{
a = random(0, 255);
b = random(0, 255);
c = random(0, 255);
r_flag = false;
};
if ((d < a) && (e < b) && (f < c))
{
if (d < a + 1)
{
d = d + 1;
};
if (e < b + 1)
{
e = e + 1;
};
if (f < c + 1)
{
f = f + 1;
};
if (n < NUM_LEDS)
{
n = n + 1;
strip.setPixelColor(n, strip.Color(d, e, f));
strip.show();
}
n = -1;
}
else
{
if (d > 1)
{
d = d - 1;
};
if (e > 1)
{
e = e - 1;
};
if (f > 1)
{
f = f - 1;
};
if (n < NUM_LEDS)
{
n = n + 1;
strip.setPixelColor(n, strip.Color(d, e, f));
strip.show();
}
n = -1;
}
while (n < NUM_LEDS)
{
n = n + 1;
strip.setPixelColor(n, strip.Color(d, e, f));
strip.show();
}
n = -1;
}
}
Ответы (1 шт):
Тут подойдет использование цветовой модели HSV.
По этой модели цвет состоит из 3-х компонент:
- H - определяет оттенок(задается углом)
- S - задает его насыщенность(чем меньше тем бледнее и ближе к белому)
- V - определяет яркость(Больше - ярче, ноль соответствует черному)
То есть , чтобы задать цвет нужно будет генерировать значения для H и S, а изменение яркости - через изменение V.
Это сильно упростит задачку.
В библиотеке Adafruit NeoPixel для задания цвета в формате HSV существует метод Adafruit_NeoPixel::ColorHSV.
А для заполнения ленты подойдет метод Adafruit_NeoPixel::fill.
Если не задумываться о многозадачности, то в псевдокоде это выглядело бы как то так
while true:
// выбор цвета
H := select(0, 65535)
S := select(0, 255)
// нарастающая яркость
for V := 0 to 255:
color = Adafruit_NeoPixel::ColorHSV(H, S, V)
strip.fill(color)
strip.show()
wait(delay)
// убывающая яркость
for V := 255 downto 0:
color = Adafruit_NeoPixel::ColorHSV(H, S, V)
strip.fill(color)
strip.show()
wait(delay)
Для поддержки многозадачности нужно избавится от циклов, преобразовав алгоритм в машину состояний.
Это решается через выделение переменных, шагов и переходов между шагами
Выделим перечисление, которое будет определять текущий шаг:
enum class Step
{
SELECT, // выбрать цвет
RUN_UP, // нарастить яркость
WAIT_UP,
RUN_DOWN, // снизить яркость
WAIT_DOWN,
};
Объявим структуру с состоянием алгоритма
struct State
{
Step step = Step::SELECT; // текущий шаг, начинаем с выбора цвета
// текущий цвет
uint16_t H;
uint8_t S;
uint8_t V;
unsigned long time; // для замены delay
};
State state{};
добавим несколько вспомогательных функций
// Утилита для заполнения ленты
void fill_strip(const State& state)
{
auto color = Adafruit_NeoPixel::ColorHSV(state.H, state.S, state.V);
strip.fill(color);
strip.show();
}
// проверка, что ожидание завершено
bool wait_is_done(const State& state)
{
constexpr auto period = 10UL;
return millis() >= state.time + period;
}
Опишем шаги и переходы
// выбор цвета
void do_select_color(State &state)
{
state.H = random(0, 0xFFFF);
state.S = random(0, 255);
state.V = 0;
state.step = Step::RUN_UP; // цвет выбран, идем дальше
}
// увеличение яркости
void do_run_up(State &state)
{
fill_strip(state);
state.V += 1;
state.time = millis();
state.step = Step::WAIT_UP; // подождать
}
// подождать
void do_wait_up(State &state)
{
if (wait_is_done(state)) // ожидание окончено
{
auto do_run = state.V != 255; // Максимальная яркость еще не достигнута?
state.step = do_run ? Step::RUN_UP : Step::RUN_DOWN;
}
}
// уменьшение яркости
void do_run_down(State &state)
{
fill_strip(state);
state.V -= 1;
state.time = millis();
state.step = Step::WAIT_DOWN; // подождать
}
// подождать
void do_wait_down(State &state)
{
if (wait_is_done(state)) // ожидание окончено
{
auto do_run = state.V != 0; // Минимальная яркость еще не достигнута?
state.step = do_run ? Step::RUN_DOWN : Step::RUN_SELECT;
}
}
Ну и соберем их через switch
// шаг цикла
void run_show(State& state)
{
switch(state.step)
{
case Step::SELECT:
do_select_color(state);
break;
case Step::RUN_UP:
do_run_up(state);
break;
case Step::WAIT_UP:
do_wait_up(state);
break;
case Step::RUN_DOWN:
do_run_down(state);
break;
case Step::WAIT_DOWN:
do_wait_down(state);
break;
}
}