Files
WS2812B-LED-RC-Controller/main/animation.c
2026-01-06 22:06:02 +01:00

445 lines
10 KiB
C

/**
* @file animation.c
* @brief LED animation patterns implementation
*/
#include "animation.h"
#include "led.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_random.h"
#include <math.h>
static const char *TAG = "ANIMATION";
#define FRAMES_PER_SECOND 60
static animation_mode_t current_mode = ANIM_BLACK;
static uint8_t global_hue = 0;
static uint32_t frame_counter = 0;
// Beat calculation helper
static int16_t beatsin16(uint8_t bpm, int16_t min_val, int16_t max_val)
{
// Use uint64_t to prevent overflow
uint64_t us = esp_timer_get_time(); // Microseconds
// Calculate beat phase (0-65535 repeating at BPM rate)
// beats_per_minute → beats_per_microsecond = bpm / 60,000,000
uint64_t beat_phase = (us * (uint64_t)bpm * 65536ULL) / 60000000ULL;
uint16_t beat16 = (uint16_t)(beat_phase & 0xFFFF);
// Convert to angle (0 to 2π)
float angle = (beat16 / 65535.0f) * 2.0f * M_PI;
float sin_val = sinf(angle);
// Map sin (-1 to +1) to output range (min_val to max_val)
int16_t range = max_val - min_val;
int16_t result = min_val + (int16_t)((sin_val + 1.0f) * range / 2.0f);
return result;
}
// Beat calculation helper (beatsin8 variant)
static uint8_t beatsin8(uint8_t bpm, uint8_t min_val, uint8_t max_val)
{
return (uint8_t)beatsin16(bpm, min_val, max_val);
}
// Random helper
static uint8_t random8(void)
{
return esp_random() & 0xFF;
}
static uint16_t random16(uint16_t max)
{
if (max == 0)
return 0;
return esp_random() % max;
}
// Animation implementations
static void anim_black(void)
{
rgb_t black = {0, 0, 0};
led_fill_a(black);
led_fill_b(black);
}
static void anim_red(void)
{
rgb_t red = {255, 0, 0};
led_fill_a(red);
led_fill_b(red);
}
static void anim_blue(void)
{
rgb_t blue = {0, 0, 255};
led_fill_a(blue);
led_fill_b(blue);
}
static void anim_green(void)
{
rgb_t green = {0, 255, 0};
led_fill_a(green);
led_fill_b(green);
}
static void anim_white(void)
{
rgb_t white = {255, 255, 255};
led_fill_a(white);
led_fill_b(white);
}
static void anim_rainbow(void)
{
// Rainbow generator
uint16_t num_leds_a = led_get_num_leds_a();
uint16_t num_leds_b = led_get_num_leds_b();
uint16_t num_leds = num_leds_a + num_leds_b;
for (uint16_t i = 0; i < num_leds; i++)
{
hsv_t hsv = {(uint8_t)(global_hue + (i * 7)), 255, 255};
rgb_t color = led_hsv_to_rgb(hsv);
if (i < num_leds_a)
{
led_set_pixel_a(num_leds_a - i - 1, color);
}
else
{
led_set_pixel_b(i - num_leds_a, color);
}
}
}
static void add_glitter(uint8_t chance_of_glitter)
{
if (random8() < chance_of_glitter)
{
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
uint16_t pos = random16(num_leds);
rgb_t white = {255, 255, 255};
if (pos < led_get_num_leds_a())
{
led_add_pixel_a(pos, white);
}
else
{
led_add_pixel_b(pos - led_get_num_leds_a(), white);
}
}
}
static void anim_rainbow_glitter(void)
{
anim_rainbow();
add_glitter(255);
}
static void anim_confetti(void)
{
// Random colored speckles that blink in and fade smoothly
led_fade_to_black(10);
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
uint16_t pos = random16(num_leds);
hsv_t hsv = {(uint8_t)(global_hue + random8()), 255, 255};
rgb_t color = led_hsv_to_rgb(hsv);
if (pos < led_get_num_leds_a())
{
led_set_pixel_a(led_get_num_leds_a() - pos - 1, color);
}
else
{
led_set_pixel_b(pos - led_get_num_leds_a(), color);
}
}
static void anim_sinelon(void)
{
// A colored dot sweeping back and forth, with fading trails
led_fade_to_black(20);
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
int16_t pos = beatsin16(13, 0, num_leds);
hsv_t hsv = {global_hue, 255, 192};
rgb_t color = led_hsv_to_rgb(hsv);
if (pos < led_get_num_leds_a())
{
led_add_pixel_a(led_get_num_leds_a() - pos - 1, color);
}
else
{
led_add_pixel_b(pos - led_get_num_leds_a(), color);
}
}
static void anim_bpm(void)
{
// Colored stripes pulsing at 33 BPM
uint8_t bpm = 33;
uint8_t beat = beatsin8(bpm, 64, 255);
uint16_t num_leds_a = led_get_num_leds_a();
uint16_t num_leds_b = led_get_num_leds_b();
// PartyColors palette simulation
const uint8_t palette_colors[] = {
170, 240, 90, 150, 210, 30, 180, 0,
210, 255, 150, 240, 255, 60, 255, 120};
for (uint16_t i = 0; i < num_leds_a; i++)
{
uint8_t color_index = (global_hue + (i * 2)) & 0x0F;
uint8_t brightness = beat - global_hue + (i * 10);
hsv_t hsv = {palette_colors[color_index], 255, brightness};
led_set_pixel_a(i, led_hsv_to_rgb(hsv));
}
for (uint16_t i = 0; i < num_leds_b; i++)
{
uint8_t color_index = (global_hue + ((i + num_leds_a) * 2)) & 0x0F;
uint8_t brightness = beat - global_hue + ((i + num_leds_a) * 10);
hsv_t hsv = {palette_colors[color_index], 255, brightness};
led_set_pixel_b(i, led_hsv_to_rgb(hsv));
}
}
static void anim_navigation(void)
{
// Navigation lights: left red, right green
led_clear_all();
uint16_t num_leds_a = led_get_num_leds_a();
uint16_t num_leds_b = led_get_num_leds_b();
rgb_t red = {255, 0, 0};
rgb_t green = {0, 255, 0};
// Side red (last 3 LEDs of strip A)
if (num_leds_a >= 3)
{
led_set_pixel_a(num_leds_a - 1, red);
led_set_pixel_a(num_leds_a - 2, red);
led_set_pixel_a(num_leds_a - 3, red);
}
// Side green (last 3 LEDs of strip B)
if (num_leds_b >= 3)
{
led_set_pixel_b(num_leds_b - 1, green);
led_set_pixel_b(num_leds_b - 2, green);
led_set_pixel_b(num_leds_b - 3, green);
}
}
static void anim_chase(void)
{
// Red dot sweeping with trailing dots
led_clear_all();
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
int16_t pos = beatsin16(40, 0, num_leds - 1);
rgb_t red = {255, 0, 0};
// Set main dot and trailing dots
for (int offset = -2; offset <= 2; offset++)
{
int16_t led_pos = pos + offset;
if (led_pos >= 0 && led_pos < num_leds)
{
if (led_pos < led_get_num_leds_a())
{
led_set_pixel_a(led_pos, red);
}
else
{
led_set_pixel_b(led_pos - led_get_num_leds_a(), red);
}
}
}
}
static void anim_chase_rgb(void)
{
// RGB cycling dot sweeping with trailing dots
led_clear_all();
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
int16_t pos = beatsin16(40, 0, num_leds - 1);
hsv_t hsv = {global_hue, 255, 192};
rgb_t color = led_hsv_to_rgb(hsv);
// Set main dot and trailing dots
for (int offset = -2; offset <= 2; offset++)
{
int16_t led_pos = pos + offset;
if (led_pos >= 0 && led_pos < num_leds)
{
if (led_pos < led_get_num_leds_a())
{
led_add_pixel_a(led_pos, color);
}
else
{
led_add_pixel_b(led_pos - led_get_num_leds_a(), color);
}
}
}
}
static void anim_random(void)
{
// Random LEDs get random colors
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
uint16_t random_pos = random16(num_leds);
// Randomly clear all (rare event)
if (random_pos == num_leds - 1 && random8() > 200)
{
led_clear_all();
return;
}
// Set random LED to random color
rgb_t random_color = {
random8(),
random8(),
random8()};
if (random_pos < led_get_num_leds_a())
{
led_set_pixel_a(random_pos, random_color);
}
else
{
led_set_pixel_b(random_pos - led_get_num_leds_a(), random_color);
}
}
esp_err_t animation_init(void)
{
current_mode = ANIM_BLACK;
global_hue = 0U;
frame_counter = 0U;
ESP_LOGI(TAG, "Animation initialized");
return ESP_OK;
}
void animation_set_mode(animation_mode_t mode)
{
if ((mode >= ANIM_MODE_COUNT) || (mode < 0U))
{
mode = ANIM_BLACK;
}
current_mode = mode;
frame_counter = 0U;
ESP_LOGI(TAG, "Animation mode set to: %s", animation_get_mode_name(mode));
}
void animation_update(void)
{
// Update global hue every frame (slowly cycles colors)
frame_counter++;
if (frame_counter % 3 == 0)
{
global_hue++;
}
// Execute current animation
switch (current_mode)
{
case ANIM_BLACK:
anim_black();
break;
case ANIM_RED:
anim_red();
break;
case ANIM_BLUE:
anim_blue();
break;
case ANIM_GREEN:
anim_green();
break;
case ANIM_WHITE:
anim_white();
break;
case ANIM_RAINBOW:
anim_rainbow();
break;
case ANIM_RAINBOW_GLITTER:
anim_rainbow_glitter();
break;
case ANIM_CONFETTI:
anim_confetti();
break;
case ANIM_SINELON:
anim_sinelon();
break;
case ANIM_BPM:
// anim_bpm();
break;
case ANIM_NAVIGATION:
anim_navigation();
break;
case ANIM_CHASE:
// anim_chase();
break;
case ANIM_CHASE_RGB:
// anim_chase_rgb();
break;
case ANIM_RANDOM:
// anim_random();
break;
default:
anim_black();
break;
}
led_show();
}
const char *animation_get_mode_name(animation_mode_t mode)
{
static const char *mode_names[] = {
"Black",
"Red",
"Blue",
"Green",
"White",
"Rainbow",
"Rainbow with Glitter",
"Confetti",
"Sinelon",
"33BPM",
"Navigation",
"Chase",
"Chase RGB",
"Random"};
if (mode >= ANIM_MODE_COUNT)
{
return "Unknown";
}
return mode_names[mode];
}