Compare commits
19 Commits
1012d3bb2f
...
feature/es
| Author | SHA1 | Date | |
|---|---|---|---|
| 284553f3f8 | |||
| e25971af89 | |||
| 5796b28e1a | |||
| 12a8710a2f | |||
| 468d2cba74 | |||
| d2d5d7dc4b | |||
| f1aac6611d | |||
| 733b05eaeb | |||
| 715d50c255 | |||
| 883fff95dd | |||
| 0f62418d93 | |||
| 12b8acf81c | |||
| a08dba780a | |||
| d576b4d42d | |||
| 9ef50436a4 | |||
| b1b179b5ff | |||
| 3ada494d15 | |||
| d33bda52d0 | |||
| 0dd26fdcde |
1
.gitignore
vendored
1
.gitignore
vendored
@ -291,3 +291,4 @@ dkms.conf
|
|||||||
|
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
sdkconfig.defaults
|
sdkconfig.defaults
|
||||||
|
.clangd
|
||||||
|
|||||||
11
README.md
11
README.md
@ -32,7 +32,8 @@ Professional LED controller firmware for ESP32. Designed for model aircraft with
|
|||||||
led-controller-firmware/
|
led-controller-firmware/
|
||||||
├── main/
|
├── main/
|
||||||
│ ├── main.c # Application entry point
|
│ ├── main.c # Application entry point
|
||||||
│ ├── control.c/h # NVS, initialization
|
│ ├── control.c/h # initialization
|
||||||
|
│ ├── config.c/h # NVS
|
||||||
│ ├── led.c/h # WS2812B control (RMT driver)
|
│ ├── led.c/h # WS2812B control (RMT driver)
|
||||||
│ ├── rcsignal.c/h # PWM signal reading
|
│ ├── rcsignal.c/h # PWM signal reading
|
||||||
│ ├── localbtn.c/h # Local btn reading
|
│ ├── localbtn.c/h # Local btn reading
|
||||||
@ -69,7 +70,7 @@ idf.py build
|
|||||||
idf.py -p /dev/ttyUSB0 flash monitor
|
idf.py -p /dev/ttyUSB0 flash monitor
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace `/dev/ttyUSB0` with your serial port (COM3 on Windows).
|
Replace `/dev/ttyUSB0` with your serial port.
|
||||||
|
|
||||||
## Hardware Setup
|
## Hardware Setup
|
||||||
|
|
||||||
@ -80,6 +81,7 @@ ESP32 Pin -> Component
|
|||||||
GPIO XX -> WS2812B Strip A Data
|
GPIO XX -> WS2812B Strip A Data
|
||||||
GPIO XX -> WS2812B Strip B Data
|
GPIO XX -> WS2812B Strip B Data
|
||||||
GPIO XX -> RC PWM Signal
|
GPIO XX -> RC PWM Signal
|
||||||
|
GPIO XX -> Local button Signal
|
||||||
GND -> Common Ground
|
GND -> Common Ground
|
||||||
5V -> LED Strip Power (if current < 500mA)
|
5V -> LED Strip Power (if current < 500mA)
|
||||||
```
|
```
|
||||||
@ -103,11 +105,6 @@ GND -> Common Ground
|
|||||||
1. Add mode to `animation_mode_t` enum in `animation.h`
|
1. Add mode to `animation_mode_t` enum in `animation.h`
|
||||||
2. Implement animation function in `animation.c`
|
2. Implement animation function in `animation.c`
|
||||||
3. Add case to `animation_update()` switch statement
|
3. Add case to `animation_update()` switch statement
|
||||||
4. Update `MODE_NAMES` array in `webapp/app/app.js`
|
|
||||||
|
|
||||||
### Modifying LED Count
|
|
||||||
|
|
||||||
Edit `DEFAULT_NUM_LEDS_A` and `DEFAULT_NUM_LEDS_B` in `control.c`. TODO:
|
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
|
|||||||
274
main/animation.c
274
main/animation.c
@ -15,35 +15,33 @@
|
|||||||
static const char *TAG = "ANIMATION";
|
static const char *TAG = "ANIMATION";
|
||||||
|
|
||||||
#define FRAMES_PER_SECOND 60
|
#define FRAMES_PER_SECOND 60
|
||||||
#define NUM_LEDS_DEFAULT 44 // TODO: Default from proof-of-concept
|
|
||||||
|
|
||||||
static animation_mode_t current_mode = ANIM_BLACK;
|
static animation_mode_t current_mode = ANIM_BLACK;
|
||||||
static uint8_t global_hue = 0;
|
static uint8_t global_hue = 0;
|
||||||
static uint32_t frame_counter = 0;
|
static uint32_t frame_counter = 0;
|
||||||
|
|
||||||
// Beat calculation helper (similar to FastLED beatsin16)
|
// Beat calculation helper
|
||||||
static int16_t beatsin16(uint8_t bpm, int16_t min_val, int16_t max_val)
|
static int16_t beatsin16(uint8_t bpm, int16_t min_val, int16_t max_val)
|
||||||
{
|
{
|
||||||
uint32_t ms = esp_timer_get_time() / 1000;
|
// Use uint64_t to prevent overflow
|
||||||
uint32_t beat = (ms * bpm * 256) / 60000;
|
uint64_t us = esp_timer_get_time(); // Microseconds
|
||||||
uint8_t beat8 = (beat >> 8) & 0xFF;
|
|
||||||
|
|
||||||
// Sin approximation
|
// Calculate beat phase (0-65535 repeating at BPM rate)
|
||||||
float angle = (beat8 / 255.0f) * 2.0f * M_PI;
|
// 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);
|
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 range = max_val - min_val;
|
||||||
int16_t result = min_val + (int16_t)((sin_val + 1.0f) * range / 2.0f);
|
int16_t result = min_val + (int16_t)((sin_val + 1.0f) * range / 2.0f);
|
||||||
|
|
||||||
return result;
|
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
|
// Random helper
|
||||||
static uint8_t random8(void)
|
static uint8_t random8(void)
|
||||||
{
|
{
|
||||||
@ -95,20 +93,24 @@ static void anim_white(void)
|
|||||||
|
|
||||||
static void anim_rainbow(void)
|
static void anim_rainbow(void)
|
||||||
{
|
{
|
||||||
// FastLED's built-in rainbow generator
|
// Rainbow generator
|
||||||
uint16_t num_leds_a = led_get_num_leds_a();
|
uint16_t num_leds_a = led_get_num_leds_a();
|
||||||
uint16_t num_leds_b = led_get_num_leds_b();
|
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_a; i++)
|
for (uint16_t i = 0; i < num_leds; i++)
|
||||||
{
|
{
|
||||||
hsv_t hsv = {(uint8_t)(global_hue + (i * 7)), 255, 255};
|
hsv_t hsv = {(uint8_t)(global_hue + (i * 7)), 255, 255};
|
||||||
led_set_pixel_a(i, led_hsv_to_rgb(hsv));
|
rgb_t color = led_hsv_to_rgb(hsv);
|
||||||
|
|
||||||
|
if (i < num_leds_a)
|
||||||
|
{
|
||||||
|
led_set_pixel_a(num_leds_a - i - 1, color);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
for (uint16_t i = 0; i < num_leds_b; i++)
|
|
||||||
{
|
{
|
||||||
hsv_t hsv = {(uint8_t)(global_hue + (i * 7)), 255, 255};
|
led_set_pixel_b(i - num_leds_a, color);
|
||||||
led_set_pixel_b(i, led_hsv_to_rgb(hsv));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +136,7 @@ static void add_glitter(uint8_t chance_of_glitter)
|
|||||||
static void anim_rainbow_glitter(void)
|
static void anim_rainbow_glitter(void)
|
||||||
{
|
{
|
||||||
anim_rainbow();
|
anim_rainbow();
|
||||||
add_glitter(80);
|
add_glitter(255);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void anim_confetti(void)
|
static void anim_confetti(void)
|
||||||
@ -145,16 +147,16 @@ static void anim_confetti(void)
|
|||||||
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||||
uint16_t pos = random16(num_leds);
|
uint16_t pos = random16(num_leds);
|
||||||
|
|
||||||
hsv_t hsv = {(uint8_t)(global_hue + random8()), 200, 255};
|
hsv_t hsv = {(uint8_t)(global_hue + random8()), 255, 255};
|
||||||
rgb_t color = led_hsv_to_rgb(hsv);
|
rgb_t color = led_hsv_to_rgb(hsv);
|
||||||
|
|
||||||
if (pos < led_get_num_leds_a())
|
if (pos < led_get_num_leds_a())
|
||||||
{
|
{
|
||||||
led_add_pixel_a(pos, color);
|
led_set_pixel_a(led_get_num_leds_a() - pos - 1, color);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
led_add_pixel_b(pos - led_get_num_leds_a(), color);
|
led_set_pixel_b(pos - led_get_num_leds_a(), color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,14 +166,14 @@ static void anim_sinelon(void)
|
|||||||
led_fade_to_black(20);
|
led_fade_to_black(20);
|
||||||
|
|
||||||
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||||
int16_t pos = beatsin16(13, 0, num_leds - 1);
|
int16_t pos = beatsin16(13, 0, num_leds);
|
||||||
|
|
||||||
hsv_t hsv = {global_hue, 255, 192};
|
hsv_t hsv = {global_hue, 255, 192};
|
||||||
rgb_t color = led_hsv_to_rgb(hsv);
|
rgb_t color = led_hsv_to_rgb(hsv);
|
||||||
|
|
||||||
if (pos < led_get_num_leds_a())
|
if (pos < led_get_num_leds_a())
|
||||||
{
|
{
|
||||||
led_add_pixel_a(pos, color);
|
led_add_pixel_a(led_get_num_leds_a() - pos - 1, color);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -179,42 +181,14 @@ static void anim_sinelon(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
static void anim_navigation(void)
|
||||||
{
|
{
|
||||||
// Navigation lights: left red, right green, with blinking white
|
// Aviation navigation lights with strobe overlay:
|
||||||
static uint8_t blink_state = 0;
|
// - Red: Port (left) wingtip - steady
|
||||||
|
// - Green: Starboard (right) wingtip - steady
|
||||||
|
// - White strobe: Overlays outer nav lights with bright flashes
|
||||||
|
|
||||||
|
static uint8_t strobe_counter = 0;
|
||||||
led_clear_all();
|
led_clear_all();
|
||||||
|
|
||||||
uint16_t num_leds_a = led_get_num_leds_a();
|
uint16_t num_leds_a = led_get_num_leds_a();
|
||||||
@ -224,49 +198,32 @@ static void anim_navigation(void)
|
|||||||
rgb_t green = {0, 255, 0};
|
rgb_t green = {0, 255, 0};
|
||||||
rgb_t white = {255, 255, 255};
|
rgb_t white = {255, 255, 255};
|
||||||
|
|
||||||
// Left side red (first 3 LEDs of strip A)
|
// Anti-collision strobe pattern: Double flash at ~1 Hz
|
||||||
|
// Flash duration: 3 frames (~50ms) for high-intensity effect
|
||||||
|
bool first_flash = (strobe_counter < 3);
|
||||||
|
bool second_flash = (strobe_counter >= 7 && strobe_counter < 10);
|
||||||
|
bool strobe_active = (first_flash || second_flash);
|
||||||
|
|
||||||
|
// Port (left) - Red navigation light OR white strobe (outer 3 LEDs of strip A)
|
||||||
if (num_leds_a >= 3)
|
if (num_leds_a >= 3)
|
||||||
{
|
{
|
||||||
led_set_pixel_a(0, red);
|
rgb_t color_a = strobe_active ? white : red;
|
||||||
led_set_pixel_a(1, red);
|
led_set_pixel_a(num_leds_a - 1, color_a);
|
||||||
led_set_pixel_a(2, red);
|
led_set_pixel_a(num_leds_a - 2, red);
|
||||||
|
led_set_pixel_a(num_leds_a - 3, red);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right side green (last 3 LEDs)
|
// Starboard (right) - Green navigation light OR white strobe (outer 3 LEDs of strip B)
|
||||||
if (num_leds_b >= 3)
|
if (num_leds_b >= 3)
|
||||||
{
|
{
|
||||||
led_set_pixel_b(num_leds_b - 1, green);
|
rgb_t color_b = strobe_active ? white : green;
|
||||||
|
led_set_pixel_b(num_leds_b - 1, color_b);
|
||||||
led_set_pixel_b(num_leds_b - 2, green);
|
led_set_pixel_b(num_leds_b - 2, green);
|
||||||
led_set_pixel_b(num_leds_b - 3, green);
|
led_set_pixel_b(num_leds_b - 3, green);
|
||||||
}
|
}
|
||||||
else if (num_leds_a >= 6)
|
|
||||||
{
|
|
||||||
led_set_pixel_a(num_leds_a - 1, green);
|
|
||||||
led_set_pixel_a(num_leds_a - 2, green);
|
|
||||||
led_set_pixel_a(num_leds_a - 3, green);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blinking white lights (positions 5-6 and 37-38 from original)
|
// Strobe cycle: 90 frames = 1.5 second at 60 FPS
|
||||||
if (blink_state < FRAMES_PER_SECOND / 2)
|
strobe_counter = (strobe_counter + 1) % 90;
|
||||||
{
|
|
||||||
if (num_leds_a > 6)
|
|
||||||
{
|
|
||||||
led_set_pixel_a(5, white);
|
|
||||||
led_set_pixel_a(6, white);
|
|
||||||
}
|
|
||||||
if (num_leds_b > 2)
|
|
||||||
{
|
|
||||||
led_set_pixel_b(1, white);
|
|
||||||
led_set_pixel_b(2, white);
|
|
||||||
}
|
|
||||||
else if (num_leds_a > 38)
|
|
||||||
{
|
|
||||||
led_set_pixel_a(37, white);
|
|
||||||
led_set_pixel_a(38, white);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blink_state = (blink_state + 1) % FRAMES_PER_SECOND;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void anim_chase(void)
|
static void anim_chase(void)
|
||||||
@ -274,25 +231,45 @@ static void anim_chase(void)
|
|||||||
// Red dot sweeping with trailing dots
|
// Red dot sweeping with trailing dots
|
||||||
led_clear_all();
|
led_clear_all();
|
||||||
|
|
||||||
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
uint16_t num_leds_a = led_get_num_leds_a();
|
||||||
int16_t pos = beatsin16(40, 0, num_leds - 1);
|
uint16_t num_leds_b = led_get_num_leds_b();
|
||||||
|
uint16_t total_leds = num_leds_a + num_leds_b;
|
||||||
|
|
||||||
|
// Get oscillating position across both strips
|
||||||
|
int16_t center_pos = beatsin16(40, 0, total_leds - 1);
|
||||||
|
|
||||||
rgb_t red = {255, 0, 0};
|
rgb_t red = {255, 0, 0};
|
||||||
|
|
||||||
// Set main dot and trailing dots
|
// Draw center dot with dimmed trailing dots (3 dots total: center ±1)
|
||||||
for (int offset = -2; offset <= 2; offset++)
|
for (int8_t offset = -1; offset <= 1; offset++)
|
||||||
{
|
{
|
||||||
int16_t led_pos = pos + offset;
|
int16_t led_pos = center_pos + offset;
|
||||||
if (led_pos >= 0 && led_pos < num_leds)
|
|
||||||
|
// Skip if position is out of bounds
|
||||||
|
if (led_pos < 0 || led_pos >= total_leds)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Calculate brightness based on distance from center
|
||||||
|
uint8_t brightness = (offset == 0) ? 255 : 32; // Center: full, trailing: 12%
|
||||||
|
|
||||||
|
// Create dimmed color
|
||||||
|
rgb_t dimmed_red = {
|
||||||
|
(red.r * brightness) / 255,
|
||||||
|
(red.g * brightness) / 255,
|
||||||
|
(red.b * brightness) / 255};
|
||||||
|
|
||||||
|
// Map virtual position to physical LED
|
||||||
|
if (led_pos < num_leds_a)
|
||||||
{
|
{
|
||||||
if (led_pos < led_get_num_leds_a())
|
// Strip A (mirrored: position 0 maps to last LED)
|
||||||
{
|
uint16_t strip_a_index = num_leds_a - led_pos - 1;
|
||||||
led_set_pixel_a(led_pos, red);
|
led_set_pixel_a(strip_a_index, dimmed_red);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
led_set_pixel_b(led_pos - led_get_num_leds_a(), red);
|
// Strip B (direct mapping)
|
||||||
}
|
uint16_t strip_b_index = led_pos - num_leds_a;
|
||||||
|
led_set_pixel_b(strip_b_index, dimmed_red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -302,26 +279,46 @@ static void anim_chase_rgb(void)
|
|||||||
// RGB cycling dot sweeping with trailing dots
|
// RGB cycling dot sweeping with trailing dots
|
||||||
led_clear_all();
|
led_clear_all();
|
||||||
|
|
||||||
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
uint16_t num_leds_a = led_get_num_leds_a();
|
||||||
int16_t pos = beatsin16(40, 0, num_leds - 1);
|
uint16_t num_leds_b = led_get_num_leds_b();
|
||||||
|
uint16_t total_leds = num_leds_a + num_leds_b;
|
||||||
|
|
||||||
|
// Get oscillating position across both strips
|
||||||
|
int16_t center_pos = beatsin16(40, 0, total_leds - 1);
|
||||||
|
|
||||||
hsv_t hsv = {global_hue, 255, 192};
|
hsv_t hsv = {global_hue, 255, 192};
|
||||||
rgb_t color = led_hsv_to_rgb(hsv);
|
rgb_t color = led_hsv_to_rgb(hsv);
|
||||||
|
|
||||||
// Set main dot and trailing dots
|
// Draw center dot with dimmed trailing dots (3 dots total: center ±1)
|
||||||
for (int offset = -2; offset <= 2; offset++)
|
for (int8_t offset = -1; offset <= 1; offset++)
|
||||||
{
|
{
|
||||||
int16_t led_pos = pos + offset;
|
int16_t led_pos = center_pos + offset;
|
||||||
if (led_pos >= 0 && led_pos < num_leds)
|
|
||||||
|
// Skip if position is out of bounds
|
||||||
|
if (led_pos < 0 || led_pos >= total_leds)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Calculate brightness based on distance from center
|
||||||
|
uint8_t brightness = (offset == 0) ? 255 : 32; // Center: full, trailing: 12%
|
||||||
|
|
||||||
|
// Create dimmed color
|
||||||
|
rgb_t dimmed_color = {
|
||||||
|
(color.r * brightness) / 255,
|
||||||
|
(color.g * brightness) / 255,
|
||||||
|
(color.b * brightness) / 255};
|
||||||
|
|
||||||
|
// Map virtual position to physical LED
|
||||||
|
if (led_pos < num_leds_a)
|
||||||
{
|
{
|
||||||
if (led_pos < led_get_num_leds_a())
|
// Strip A (mirrored: position 0 maps to last LED)
|
||||||
{
|
uint16_t strip_a_index = num_leds_a - led_pos - 1;
|
||||||
led_add_pixel_a(led_pos, color);
|
led_set_pixel_a(strip_a_index, dimmed_color);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
led_add_pixel_b(led_pos - led_get_num_leds_a(), color);
|
// Strip B (direct mapping)
|
||||||
}
|
uint16_t strip_b_index = led_pos - num_leds_a;
|
||||||
|
led_set_pixel_b(strip_b_index, dimmed_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,18 +329,26 @@ static void anim_random(void)
|
|||||||
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||||
uint16_t random_pos = random16(num_leds);
|
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 = {
|
rgb_t random_color = {
|
||||||
random8(),
|
0,
|
||||||
random8(),
|
0,
|
||||||
random8()};
|
0};
|
||||||
|
|
||||||
|
// Set random LED to random basis color
|
||||||
|
switch (random16(3))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
random_color.r = 255;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
random_color.g = 255;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
random_color.b = 255;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (random_pos < led_get_num_leds_a())
|
if (random_pos < led_get_num_leds_a())
|
||||||
{
|
{
|
||||||
@ -358,31 +363,26 @@ static void anim_random(void)
|
|||||||
esp_err_t animation_init(void)
|
esp_err_t animation_init(void)
|
||||||
{
|
{
|
||||||
current_mode = ANIM_BLACK;
|
current_mode = ANIM_BLACK;
|
||||||
global_hue = 0;
|
global_hue = 0U;
|
||||||
frame_counter = 0;
|
frame_counter = 0U;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Animation system initialized");
|
ESP_LOGI(TAG, "Animation initialized");
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void animation_set_mode(animation_mode_t mode)
|
void animation_set_mode(animation_mode_t mode)
|
||||||
{
|
{
|
||||||
if (mode >= ANIM_MODE_COUNT)
|
if ((mode >= ANIM_MODE_COUNT) || (mode < 0U))
|
||||||
{
|
{
|
||||||
mode = ANIM_BLACK;
|
mode = ANIM_BLACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
current_mode = mode;
|
current_mode = mode;
|
||||||
frame_counter = 0;
|
frame_counter = 0U;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Animation mode set to: %s", animation_get_mode_name(mode));
|
ESP_LOGI(TAG, "Animation mode set to: %s", animation_get_mode_name(mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
animation_mode_t animation_get_mode(void)
|
|
||||||
{
|
|
||||||
return current_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void animation_update(void)
|
void animation_update(void)
|
||||||
{
|
{
|
||||||
// Update global hue every frame (slowly cycles colors)
|
// Update global hue every frame (slowly cycles colors)
|
||||||
@ -422,9 +422,6 @@ void animation_update(void)
|
|||||||
case ANIM_SINELON:
|
case ANIM_SINELON:
|
||||||
anim_sinelon();
|
anim_sinelon();
|
||||||
break;
|
break;
|
||||||
case ANIM_BPM:
|
|
||||||
anim_bpm();
|
|
||||||
break;
|
|
||||||
case ANIM_NAVIGATION:
|
case ANIM_NAVIGATION:
|
||||||
anim_navigation();
|
anim_navigation();
|
||||||
break;
|
break;
|
||||||
@ -457,7 +454,6 @@ const char *animation_get_mode_name(animation_mode_t mode)
|
|||||||
"Rainbow with Glitter",
|
"Rainbow with Glitter",
|
||||||
"Confetti",
|
"Confetti",
|
||||||
"Sinelon",
|
"Sinelon",
|
||||||
"BPM",
|
|
||||||
"Navigation",
|
"Navigation",
|
||||||
"Chase",
|
"Chase",
|
||||||
"Chase RGB",
|
"Chase RGB",
|
||||||
|
|||||||
@ -23,11 +23,10 @@ typedef enum {
|
|||||||
ANIM_RAINBOW_GLITTER = 6, // Rainbow with glitter
|
ANIM_RAINBOW_GLITTER = 6, // Rainbow with glitter
|
||||||
ANIM_CONFETTI = 7, // Random colored speckles
|
ANIM_CONFETTI = 7, // Random colored speckles
|
||||||
ANIM_SINELON = 8, // Colored dot sweeping (RGB cycling)
|
ANIM_SINELON = 8, // Colored dot sweeping (RGB cycling)
|
||||||
ANIM_BPM = 9, // Colored stripes @ 33 BPM
|
ANIM_NAVIGATION = 9, // Navigation lights (red left, green right)
|
||||||
ANIM_NAVIGATION = 10, // Navigation lights (red left, green right)
|
ANIM_CHASE = 10, // Red dot sweeping
|
||||||
ANIM_CHASE = 11, // Red dot sweeping
|
ANIM_CHASE_RGB = 11, // RGB cycling dot sweeping
|
||||||
ANIM_CHASE_RGB = 12, // RGB cycling dot sweeping
|
ANIM_RANDOM = 12, // Random mode
|
||||||
ANIM_RANDOM = 13, // Random mode
|
|
||||||
ANIM_MODE_COUNT
|
ANIM_MODE_COUNT
|
||||||
} animation_mode_t;
|
} animation_mode_t;
|
||||||
|
|
||||||
@ -43,12 +42,6 @@ esp_err_t animation_init(void);
|
|||||||
*/
|
*/
|
||||||
void animation_set_mode(animation_mode_t mode);
|
void animation_set_mode(animation_mode_t mode);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get current animation mode
|
|
||||||
* @return Current mode
|
|
||||||
*/
|
|
||||||
animation_mode_t animation_get_mode(void);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Update animation (call periodically, e.g., 30-60 FPS)
|
* @brief Update animation (call periodically, e.g., 30-60 FPS)
|
||||||
*/
|
*/
|
||||||
|
|||||||
107
main/config.c
107
main/config.c
@ -11,21 +11,31 @@
|
|||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
#include "nvs_flash.h"
|
#include "nvs_flash.h"
|
||||||
#include "nvs.h"
|
#include "nvs.h"
|
||||||
|
#include "soc/gpio_num.h"
|
||||||
|
#include "mbedtls/sha256.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static const char *TAG = "CONFIG";
|
static const char *TAG = "CONFIG";
|
||||||
|
|
||||||
#define NVS_NAMESPACE "led_ctrl"
|
#define NVS_NAMESPACE "led_ctrl"
|
||||||
#define CONFIG_MAGIC 0xDEADBEEF
|
|
||||||
|
|
||||||
#define HARDCODED_CONFIG
|
#define HARDCODED_CONFIG
|
||||||
#ifdef HARDCODED_CONFIG
|
#ifdef HARDCODED_CONFIG
|
||||||
#define HARDCODED_CONFIG_LED_STRIP_A_PIN 12U
|
#define HARDCODED_CONFIG_LED_STRIP_A_PIN 2U
|
||||||
#define HARDCODED_CONFIG_LED_STRIP_B_PIN 14U
|
#define HARDCODED_CONFIG_LED_STRIP_B_PIN 3U
|
||||||
#define HARDCODED_CONFIG_LED_STRIP_A_COUNT 7U
|
#define HARDCODED_CONFIG_LED_STRIP_A_COUNT 1U
|
||||||
#define HARDCODED_CONFIG_LED_STRIP_B_COUNT 7U
|
#define HARDCODED_CONFIG_LED_STRIP_B_COUNT 1U
|
||||||
#define HARDCODED_CONFIG_PWM_PIN 13U
|
#define HARDCODED_CONFIG_PWM_PIN 1U
|
||||||
|
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
|
#define HARDCODED_CONFIG_LOCALBTN_PIN 9
|
||||||
|
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||||
|
#define HARDCODED_CONFIG_LOCALBTN_PIN 0
|
||||||
|
#else
|
||||||
|
#error "Unsupported target: BOOT button GPIO not defined"
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Global state
|
// Global state
|
||||||
@ -35,39 +45,59 @@ static config_t current_config = {
|
|||||||
.led_count_strip_a = -1,
|
.led_count_strip_a = -1,
|
||||||
.led_count_strip_b = -1,
|
.led_count_strip_b = -1,
|
||||||
.pwm_pin = -1,
|
.pwm_pin = -1,
|
||||||
.magic = CONFIG_MAGIC};
|
.localBtn_pin = -1};
|
||||||
|
|
||||||
|
static void calculate_config_hash(const config_t *cfg, uint8_t *out_hash);
|
||||||
|
|
||||||
// NVS Functions
|
// NVS Functions
|
||||||
static esp_err_t load_config_from_nvs(void)
|
static esp_err_t load_config_from_nvs(void)
|
||||||
{
|
{
|
||||||
nvs_handle_t nvs_handle;
|
nvs_handle_t nvs_handle;
|
||||||
|
size_t size = sizeof(config_t);
|
||||||
|
config_t tmp;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 2U; i++)
|
||||||
|
{
|
||||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
|
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
|
||||||
if (err != ESP_OK)
|
if (err != ESP_OK)
|
||||||
{
|
{
|
||||||
ESP_LOGW(TAG, "NVS not found, using defaults");
|
ESP_LOGW(TAG, "NVS not found, using defaults");
|
||||||
return ESP_ERR_NOT_FOUND;
|
config_reset_config();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t required_size = sizeof(config_t);
|
err = nvs_get_blob(nvs_handle, "config", &tmp, &size);
|
||||||
err = nvs_get_blob(nvs_handle, "config", ¤t_config, &required_size);
|
|
||||||
nvs_close(nvs_handle);
|
nvs_close(nvs_handle);
|
||||||
|
|
||||||
if (err != ESP_OK || current_config.magic != CONFIG_MAGIC)
|
uint8_t calc_hash[CONFIG_HASH_LEN];
|
||||||
|
calculate_config_hash(&tmp, calc_hash);
|
||||||
|
|
||||||
|
if (memcmp(calc_hash, tmp.hash, CONFIG_HASH_LEN) != 0)
|
||||||
{
|
{
|
||||||
ESP_LOGW(TAG, "Invalid config in NVS, using defaults");
|
ESP_LOGW(TAG, "Invalid config in NVS, using defaults");
|
||||||
return ESP_ERR_INVALID_STATE;
|
config_reset_config();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found a valid config
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Loaded config from NVS");
|
ESP_LOGI(TAG, "Loaded config from NVS");
|
||||||
ESP_LOGI(TAG, " Strip A: GPIO%d", current_config.led_pin_strip_a);
|
ESP_LOGI(TAG, " Strip A: GPIO%d", current_config.led_pin_strip_a);
|
||||||
ESP_LOGI(TAG, " Strip B: GPIO%d", current_config.led_pin_strip_b);
|
ESP_LOGI(TAG, " Strip B: GPIO%d", current_config.led_pin_strip_b);
|
||||||
|
ESP_LOGI(TAG, " Strip A LED count: %d", current_config.led_count_strip_a);
|
||||||
|
ESP_LOGI(TAG, " Strip B LED count: %d", current_config.led_count_strip_b);
|
||||||
ESP_LOGI(TAG, " PWM Pin: GPIO%d", current_config.pwm_pin);
|
ESP_LOGI(TAG, " PWM Pin: GPIO%d", current_config.pwm_pin);
|
||||||
|
ESP_LOGI(TAG, " Local btn Pin: GPIO%d", current_config.localBtn_pin);
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t save_config_to_nvs(void)
|
static esp_err_t save_config_to_nvs(void)
|
||||||
{
|
{
|
||||||
|
calculate_config_hash(¤t_config, current_config.hash);
|
||||||
|
|
||||||
nvs_handle_t nvs_handle;
|
nvs_handle_t nvs_handle;
|
||||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
|
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
|
||||||
if (err != ESP_OK)
|
if (err != ESP_OK)
|
||||||
@ -75,7 +105,6 @@ static esp_err_t save_config_to_nvs(void)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
current_config.magic = CONFIG_MAGIC;
|
|
||||||
err = nvs_set_blob(nvs_handle, "config", ¤t_config, sizeof(config_t));
|
err = nvs_set_blob(nvs_handle, "config", ¤t_config, sizeof(config_t));
|
||||||
if (err == ESP_OK)
|
if (err == ESP_OK)
|
||||||
{
|
{
|
||||||
@ -103,7 +132,7 @@ esp_err_t config_reset_config(void)
|
|||||||
current_config.led_count_strip_a = -1;
|
current_config.led_count_strip_a = -1;
|
||||||
current_config.led_count_strip_b = -1;
|
current_config.led_count_strip_b = -1;
|
||||||
current_config.pwm_pin = -1;
|
current_config.pwm_pin = -1;
|
||||||
current_config.magic = CONFIG_MAGIC;
|
current_config.localBtn_pin = -1;
|
||||||
|
|
||||||
return save_config_to_nvs();
|
return save_config_to_nvs();
|
||||||
}
|
}
|
||||||
@ -115,39 +144,14 @@ void config_get_config(config_t *const cnf)
|
|||||||
cnf->led_count_strip_a = current_config.led_count_strip_a;
|
cnf->led_count_strip_a = current_config.led_count_strip_a;
|
||||||
cnf->led_count_strip_b = current_config.led_count_strip_b;
|
cnf->led_count_strip_b = current_config.led_count_strip_b;
|
||||||
cnf->pwm_pin = current_config.pwm_pin;
|
cnf->pwm_pin = current_config.pwm_pin;
|
||||||
|
cnf->localBtn_pin = current_config.localBtn_pin;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t config_update_config(const config_t *config)
|
|
||||||
{
|
|
||||||
if (!config)
|
|
||||||
{
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reinitialize if pins changed
|
|
||||||
bool pins_changed = (current_config.led_pin_strip_a != config->led_pin_strip_a) ||
|
|
||||||
(current_config.led_pin_strip_b != config->led_pin_strip_b) ||
|
|
||||||
(current_config.pwm_pin != config->pwm_pin);
|
|
||||||
|
|
||||||
memcpy(¤t_config, config, sizeof(config_t));
|
|
||||||
esp_err_t err = save_config_to_nvs();
|
|
||||||
|
|
||||||
if (err == ESP_OK && pins_changed)
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "Restarting to apply new pin configuration...");
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
esp_restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main initialization
|
|
||||||
esp_err_t config_init(void)
|
esp_err_t config_init(void)
|
||||||
{
|
{
|
||||||
esp_err_t ret;
|
esp_err_t ret;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Initializing Config Controller...");
|
ESP_LOGI(TAG, "Initializing Config...");
|
||||||
|
|
||||||
// Initialize NVS
|
// Initialize NVS
|
||||||
ret = nvs_flash_init();
|
ret = nvs_flash_init();
|
||||||
@ -155,6 +159,7 @@ esp_err_t config_init(void)
|
|||||||
{
|
{
|
||||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||||
ret = nvs_flash_init();
|
ret = nvs_flash_init();
|
||||||
|
ESP_ERROR_CHECK(config_reset_config());
|
||||||
}
|
}
|
||||||
ESP_ERROR_CHECK(ret);
|
ESP_ERROR_CHECK(ret);
|
||||||
|
|
||||||
@ -164,7 +169,7 @@ esp_err_t config_init(void)
|
|||||||
current_config.led_count_strip_a = HARDCODED_CONFIG_LED_STRIP_A_COUNT;
|
current_config.led_count_strip_a = HARDCODED_CONFIG_LED_STRIP_A_COUNT;
|
||||||
current_config.led_count_strip_b = HARDCODED_CONFIG_LED_STRIP_B_COUNT;
|
current_config.led_count_strip_b = HARDCODED_CONFIG_LED_STRIP_B_COUNT;
|
||||||
current_config.pwm_pin = HARDCODED_CONFIG_PWM_PIN;
|
current_config.pwm_pin = HARDCODED_CONFIG_PWM_PIN;
|
||||||
current_config.magic = CONFIG_MAGIC;
|
current_config.localBtn_pin = HARDCODED_CONFIG_LOCALBTN_PIN;
|
||||||
|
|
||||||
save_config_to_nvs();
|
save_config_to_nvs();
|
||||||
#endif
|
#endif
|
||||||
@ -172,7 +177,23 @@ esp_err_t config_init(void)
|
|||||||
// Load configuration
|
// Load configuration
|
||||||
load_config_from_nvs();
|
load_config_from_nvs();
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Config system initialized successfully");
|
ESP_LOGI(TAG, "Config initialized successfully");
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void calculate_config_hash(const config_t *cfg, uint8_t *out_hash)
|
||||||
|
{
|
||||||
|
mbedtls_sha256_context ctx;
|
||||||
|
|
||||||
|
mbedtls_sha256_init(&ctx);
|
||||||
|
mbedtls_sha256_starts(&ctx, 0); // 0 = SHA-256, 1 = SHA-224
|
||||||
|
|
||||||
|
mbedtls_sha256_update(
|
||||||
|
&ctx,
|
||||||
|
(const unsigned char *)cfg,
|
||||||
|
offsetof(config_t, hash));
|
||||||
|
|
||||||
|
mbedtls_sha256_finish(&ctx, out_hash);
|
||||||
|
mbedtls_sha256_free(&ctx);
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define CONFIG_HASH_LEN 32 // SHA256
|
||||||
/**
|
/**
|
||||||
* @brief Configuration structure stored in NVS
|
* @brief Configuration structure stored in NVS
|
||||||
*/
|
*/
|
||||||
@ -21,12 +22,13 @@ typedef struct
|
|||||||
int8_t led_count_strip_a; // LED count for LED strip A (-1 = not configured)
|
int8_t led_count_strip_a; // LED count for LED strip A (-1 = not configured)
|
||||||
int8_t led_count_strip_b; // LED count for LED strip B (-1 = not configured)
|
int8_t led_count_strip_b; // LED count for LED strip B (-1 = not configured)
|
||||||
int8_t pwm_pin; // GPIO pin for PWM input (-1 = not configured)
|
int8_t pwm_pin; // GPIO pin for PWM input (-1 = not configured)
|
||||||
uint32_t magic; // Magic number to validate config (0xDEADBEEF) //TODO: use sha256
|
int8_t localBtn_pin; // GPIO pin for local btn input (-1 = not configured)
|
||||||
|
uint8_t hash[CONFIG_HASH_LEN]; // SHA256 Hash of config
|
||||||
} config_t;
|
} config_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize the config system
|
* @brief Initialize the config system
|
||||||
* Loads configuration from NVS and initializes subsystems
|
* Loads configuration from NVS
|
||||||
* @return ESP_OK on success
|
* @return ESP_OK on success
|
||||||
*/
|
*/
|
||||||
esp_err_t config_init(void);
|
esp_err_t config_init(void);
|
||||||
@ -37,13 +39,6 @@ esp_err_t config_init(void);
|
|||||||
*/
|
*/
|
||||||
void config_get_config(config_t *const cnf);
|
void config_get_config(config_t *const cnf);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Update configuration and save to NVS
|
|
||||||
* @param config New configuration
|
|
||||||
* @return ESP_OK on success
|
|
||||||
*/
|
|
||||||
esp_err_t config_update_config(const config_t *config);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reset configuration to defaults
|
* @brief Reset configuration to defaults
|
||||||
* @return ESP_OK on success
|
* @return ESP_OK on success
|
||||||
|
|||||||
@ -15,26 +15,13 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
|
||||||
static const char *TAG = "CONTROL";
|
static const char *TAG = "CONTROL";
|
||||||
|
|
||||||
static uint8_t current_animation_mode = 0;
|
static uint8_t current_animation_mode = 0;
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
static void on_mode_change(uint8_t new_mode);
|
|
||||||
|
|
||||||
// Animation mode change callback
|
// Animation mode change callback
|
||||||
static void on_mode_change(uint8_t new_mode)
|
static void on_mode_change()
|
||||||
{
|
{
|
||||||
current_animation_mode = new_mode;
|
current_animation_mode = (current_animation_mode + 1) % ANIM_MODE_COUNT;
|
||||||
animation_set_mode((animation_mode_t)new_mode);
|
animation_set_mode((animation_mode_t)current_animation_mode);
|
||||||
}
|
|
||||||
|
|
||||||
void control_set_animation_mode(uint8_t mode)
|
|
||||||
{
|
|
||||||
if (mode >= ANIM_MODE_COUNT)
|
|
||||||
{
|
|
||||||
mode = 0;
|
|
||||||
}
|
|
||||||
on_mode_change(mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t control_get_animation_mode(void)
|
uint8_t control_get_animation_mode(void)
|
||||||
@ -42,11 +29,6 @@ uint8_t control_get_animation_mode(void)
|
|||||||
return current_animation_mode;
|
return current_animation_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
void control_emulate_pwm_pulse(void)
|
|
||||||
{
|
|
||||||
rcsignal_trigger_mode_change();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main initialization
|
// Main initialization
|
||||||
esp_err_t control_init(void)
|
esp_err_t control_init(void)
|
||||||
{
|
{
|
||||||
@ -67,7 +49,7 @@ esp_err_t control_init(void)
|
|||||||
|
|
||||||
// Initialize LED strips
|
// Initialize LED strips
|
||||||
ret = led_init(current_config.led_pin_strip_a, current_config.led_pin_strip_b,
|
ret = led_init(current_config.led_pin_strip_a, current_config.led_pin_strip_b,
|
||||||
current_config.led_count_strip_a, current_config.led_count_strip_a);
|
current_config.led_count_strip_a, current_config.led_count_strip_b);
|
||||||
if (ret != ESP_OK)
|
if (ret != ESP_OK)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "LED init failed: %s", esp_err_to_name(ret));
|
ESP_LOGE(TAG, "LED init failed: %s", esp_err_to_name(ret));
|
||||||
@ -91,7 +73,7 @@ esp_err_t control_init(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize local BTN
|
// Initialize local BTN
|
||||||
ret = localbtn_init();
|
ret = localbtn_init(current_config.localBtn_pin);
|
||||||
if (ret != ESP_OK)
|
if (ret != ESP_OK)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Local BTN init failed: %s", esp_err_to_name(ret));
|
ESP_LOGE(TAG, "Local BTN init failed: %s", esp_err_to_name(ret));
|
||||||
|
|||||||
@ -18,12 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
esp_err_t control_init(void);
|
esp_err_t control_init(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set animation mode manually
|
|
||||||
* @param mode Animation mode (0-13)
|
|
||||||
*/
|
|
||||||
void control_set_animation_mode(uint8_t mode);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get current animation mode
|
* @brief Get current animation mode
|
||||||
* @return Current mode (0-13)
|
* @return Current mode (0-13)
|
||||||
|
|||||||
25
main/led.c
25
main/led.c
@ -194,7 +194,13 @@ static esp_err_t init_strip(led_strip_t *strip, int8_t pin, uint16_t num_leds)
|
|||||||
rmt_tx_channel_config_t tx_chan_config = {
|
rmt_tx_channel_config_t tx_chan_config = {
|
||||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
.gpio_num = pin,
|
.gpio_num = pin,
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
|
.mem_block_symbols = 48,
|
||||||
|
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||||
.mem_block_symbols = 64,
|
.mem_block_symbols = 64,
|
||||||
|
#else
|
||||||
|
#error "Unsupported target: rmt block symbols undefined"
|
||||||
|
#endif
|
||||||
.resolution_hz = 80000000, // 80MHz
|
.resolution_hz = 80000000, // 80MHz
|
||||||
.trans_queue_depth = 4,
|
.trans_queue_depth = 4,
|
||||||
};
|
};
|
||||||
@ -321,7 +327,10 @@ static void show_strip(led_strip_t *strip)
|
|||||||
// Convert RGB to GRB for WS2812B
|
// Convert RGB to GRB for WS2812B
|
||||||
uint8_t *grb_data = malloc(strip->num_leds * 3);
|
uint8_t *grb_data = malloc(strip->num_leds * 3);
|
||||||
if (!grb_data)
|
if (!grb_data)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate GRB buffer");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (uint16_t i = 0; i < strip->num_leds; i++)
|
for (uint16_t i = 0; i < strip->num_leds; i++)
|
||||||
{
|
{
|
||||||
@ -334,7 +343,21 @@ static void show_strip(led_strip_t *strip)
|
|||||||
.loop_count = 0,
|
.loop_count = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
rmt_transmit(strip->rmt_channel, strip->encoder, grb_data, strip->num_leds * 3, &tx_config);
|
esp_err_t ret = rmt_transmit(strip->rmt_channel, strip->encoder, grb_data, strip->num_leds * 3, &tx_config);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "RMT transmit failed: %s", esp_err_to_name(ret));
|
||||||
|
free(grb_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for transmission to complete before freeing buffer
|
||||||
|
ret = rmt_tx_wait_all_done(strip->rmt_channel, pdMS_TO_TICKS(100));
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "RMT wait timeout");
|
||||||
|
}
|
||||||
|
|
||||||
free(grb_data);
|
free(grb_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
199
main/localbtn.c
199
main/localbtn.c
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @file localbtn.c
|
* @file localbtn.c
|
||||||
* @brief Local GPIO0 BTN reading implementation using edge capture
|
* @brief Local GPIO button reading using interrupt-based edge detection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "localbtn.h"
|
#include "localbtn.h"
|
||||||
@ -10,78 +10,199 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "soc/gpio_num.h"
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static const char *TAG = "LOCALBTN";
|
static const char *TAG = "LOCALBTN";
|
||||||
uint8_t current_mode;
|
|
||||||
localbtn_mode_change_callback_t callback;
|
|
||||||
|
|
||||||
#define BOOT_BTN GPIO_NUM_0
|
#define DEBOUNCE_TIME_MS 50 // Debounce time in milliseconds
|
||||||
#define MAX_MODES 14 // TODO: get from control
|
|
||||||
|
|
||||||
bool boot_button_pressed(void)
|
// Button state
|
||||||
|
static struct
|
||||||
{
|
{
|
||||||
return gpio_get_level(BOOT_BTN) == 0; // active LOW
|
int8_t gpio_pin;
|
||||||
|
bool initialized;
|
||||||
|
TaskHandle_t task_handle;
|
||||||
|
QueueHandle_t event_queue;
|
||||||
|
localbtn_mode_change_callback_t callback;
|
||||||
|
int64_t last_press_time; // For debouncing
|
||||||
|
} button_state = {
|
||||||
|
.gpio_pin = -1,
|
||||||
|
.initialized = false,
|
||||||
|
.task_handle = NULL,
|
||||||
|
.event_queue = NULL,
|
||||||
|
.callback = NULL,
|
||||||
|
.last_press_time = 0};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GPIO interrupt handler (ISR)
|
||||||
|
* Minimal work in ISR - just send event to task
|
||||||
|
*/
|
||||||
|
static void IRAM_ATTR gpio_isr_handler(void *arg)
|
||||||
|
{
|
||||||
|
int64_t now = esp_timer_get_time();
|
||||||
|
|
||||||
|
// Send timestamp to queue for debouncing in task
|
||||||
|
BaseType_t high_priority_task_woken = pdFALSE;
|
||||||
|
xQueueSendFromISR(button_state.event_queue, &now, &high_priority_task_woken);
|
||||||
|
|
||||||
|
if (high_priority_task_woken)
|
||||||
|
{
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void monitor_task(void *arg)
|
/**
|
||||||
|
* @brief Button handling task
|
||||||
|
* Handles debouncing and callback execution
|
||||||
|
*/
|
||||||
|
static void localbtn_task(void *arg)
|
||||||
{
|
{
|
||||||
bool lastState = false;
|
int64_t event_time;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Button task started, monitoring GPIO%d", button_state.gpio_pin);
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
// Wait for button press event from ISR
|
||||||
|
if (xQueueReceive(button_state.event_queue, &event_time, portMAX_DELAY))
|
||||||
bool currentState = boot_button_pressed();
|
|
||||||
if ((currentState) && (lastState != currentState))
|
|
||||||
{
|
{
|
||||||
printf("BOOT button pressed\n");
|
// Debouncing: Check if enough time has passed since last press
|
||||||
|
int64_t time_since_last_press = (event_time - button_state.last_press_time) / 1000; // Convert to ms
|
||||||
|
|
||||||
current_mode = (current_mode + 1) % MAX_MODES;
|
if (time_since_last_press >= DEBOUNCE_TIME_MS)
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Mode changed to: %d ", current_mode);
|
|
||||||
|
|
||||||
if (callback)
|
|
||||||
{
|
{
|
||||||
callback(current_mode);
|
// Valid button press - verify button is still pressed
|
||||||
}
|
vTaskDelay(pdMS_TO_TICKS(10)); // Small delay to ensure stable state
|
||||||
}
|
|
||||||
|
|
||||||
lastState = currentState;
|
if (gpio_get_level(button_state.gpio_pin) == 0)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t localbtn_init()
|
|
||||||
{
|
{
|
||||||
|
ESP_LOGI(TAG, "Button press detected on GPIO%d", button_state.gpio_pin);
|
||||||
|
|
||||||
|
button_state.last_press_time = event_time;
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (button_state.callback)
|
||||||
|
{
|
||||||
|
button_state.callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t localbtn_init(int8_t pin_localbtn)
|
||||||
|
{
|
||||||
|
if (pin_localbtn < 0)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Button disabled (invalid pin: %d)", pin_localbtn);
|
||||||
|
return ESP_ERR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button_state.initialized)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Button already initialized");
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
button_state.gpio_pin = pin_localbtn;
|
||||||
|
button_state.last_press_time = 0U;
|
||||||
|
|
||||||
|
// Create event queue for ISR->Task communication
|
||||||
|
button_state.event_queue = xQueueCreate(10, sizeof(int64_t));
|
||||||
|
if (button_state.event_queue == NULL)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to create event queue");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure GPIO
|
||||||
gpio_config_t io_conf = {
|
gpio_config_t io_conf = {
|
||||||
.pin_bit_mask = 1ULL << BOOT_BTN,
|
.pin_bit_mask = (1ULL << pin_localbtn),
|
||||||
.mode = GPIO_MODE_INPUT,
|
.mode = GPIO_MODE_INPUT,
|
||||||
.pull_up_en = GPIO_PULLUP_ENABLE, // safe even if external pull-up exists
|
.pull_up_en = GPIO_PULLUP_ENABLE, // Enable internal pull-up (safe even with external)
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
.intr_type = GPIO_INTR_DISABLE};
|
.intr_type = GPIO_INTR_NEGEDGE // Interrupt on falling edge (button press)
|
||||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
};
|
||||||
|
|
||||||
// Create monitor task
|
esp_err_t ret = gpio_config(&io_conf);
|
||||||
BaseType_t ret = xTaskCreate(monitor_task, "localbtn_monitor", 2048, NULL, 5, NULL);
|
if (ret != ESP_OK)
|
||||||
if (ret != pdPASS)
|
|
||||||
{
|
{
|
||||||
|
ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
|
||||||
|
vQueueDelete(button_state.event_queue);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ISR handler for this GPIO
|
||||||
|
ret = gpio_isr_handler_add(pin_localbtn, gpio_isr_handler, NULL);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "ISR handler add failed: %s", esp_err_to_name(ret));
|
||||||
|
vQueueDelete(button_state.event_queue);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create button handling task
|
||||||
|
BaseType_t task_ret = xTaskCreate(
|
||||||
|
localbtn_task,
|
||||||
|
"localbtn_task",
|
||||||
|
2048,
|
||||||
|
NULL,
|
||||||
|
5, // Priority 5 (same as other tasks)
|
||||||
|
&button_state.task_handle);
|
||||||
|
|
||||||
|
if (task_ret != pdPASS)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to create button task");
|
||||||
|
gpio_isr_handler_remove(pin_localbtn);
|
||||||
|
vQueueDelete(button_state.event_queue);
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: rcsignal.initialized = true;
|
button_state.initialized = true;
|
||||||
ESP_LOGI(TAG, "local btn initialized on GPIO%d", BOOT_BTN);
|
ESP_LOGI(TAG, "Button initialized on GPIO%d with interrupt-based detection", pin_localbtn);
|
||||||
|
ESP_LOGI(TAG, "Debounce time: %d ms", DEBOUNCE_TIME_MS);
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void localbtn_deinit(void)
|
void localbtn_deinit(void)
|
||||||
{
|
{
|
||||||
// TODO:
|
if (!button_state.initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove ISR handler
|
||||||
|
if (button_state.gpio_pin >= 0)
|
||||||
|
{
|
||||||
|
gpio_isr_handler_remove(button_state.gpio_pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete task
|
||||||
|
if (button_state.task_handle)
|
||||||
|
{
|
||||||
|
vTaskDelete(button_state.task_handle);
|
||||||
|
button_state.task_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete queue
|
||||||
|
if (button_state.event_queue)
|
||||||
|
{
|
||||||
|
vQueueDelete(button_state.event_queue);
|
||||||
|
button_state.event_queue = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
button_state.initialized = false;
|
||||||
|
button_state.callback = NULL;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Button deinitialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
void localbtn_register_callback(localbtn_mode_change_callback_t cb)
|
void localbtn_register_callback(localbtn_mode_change_callback_t cb)
|
||||||
{
|
{
|
||||||
callback = cb;
|
button_state.callback = cb;
|
||||||
|
ESP_LOGI(TAG, "Callback registered");
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @file localbtn.h
|
* @file localbtn.h
|
||||||
* @brief Local GPIO0 BTN reading implementation using edge capture
|
* @brief Local GPIO button reading using interrupt-based edge detection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef LOCALBTN_H
|
#ifndef LOCALBTN_H
|
||||||
@ -13,24 +13,24 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Callback function type for mode changes
|
* @brief Callback function type for mode changes
|
||||||
* @param new_mode New animation mode (0-13)
|
|
||||||
*/
|
*/
|
||||||
typedef void (*localbtn_mode_change_callback_t)(uint8_t new_mode);
|
typedef void (*localbtn_mode_change_callback_t)();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize local btn reading
|
* @brief Initialize local button with interrupt-based detection
|
||||||
|
* @param pin_localbtn GPIO pin number for button (active low)
|
||||||
* @return ESP_OK on success
|
* @return ESP_OK on success
|
||||||
*/
|
*/
|
||||||
esp_err_t localbtn_init();
|
esp_err_t localbtn_init(int8_t pin_localbtn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Deinitialize local btn reading
|
* @brief Deinitialize local button reading
|
||||||
*/
|
*/
|
||||||
void localbtn_deinit(void);
|
void localbtn_deinit(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register callback for mode changes
|
* @brief Register callback for mode changes
|
||||||
* @param callback Callback function
|
* @param cb Callback function
|
||||||
*/
|
*/
|
||||||
void localbtn_register_callback(localbtn_mode_change_callback_t cb);
|
void localbtn_register_callback(localbtn_mode_change_callback_t cb);
|
||||||
|
|
||||||
|
|||||||
@ -76,6 +76,8 @@ void app_main(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
animation_set_mode((animation_mode_t)control_get_animation_mode());
|
||||||
|
|
||||||
ESP_LOGI(TAG, "System initialized successfully");
|
ESP_LOGI(TAG, "System initialized successfully");
|
||||||
|
|
||||||
// Main loop - just monitor system status
|
// Main loop - just monitor system status
|
||||||
@ -84,6 +86,6 @@ void app_main(void)
|
|||||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||||
|
|
||||||
// Periodic status logging
|
// Periodic status logging
|
||||||
ESP_LOGI(TAG, "Status - Mode: %d", control_get_animation_mode());
|
ESP_LOGI(TAG, "Animation Mode set to: %s", animation_get_mode_name(control_get_animation_mode()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
static const char *TAG = "RCSIGNAL";
|
static const char *TAG = "RCSIGNAL";
|
||||||
|
|
||||||
#define MAX_MODES 14 //TODO: Get from config
|
|
||||||
#define PULSE_THRESHOLD_US 1500
|
#define PULSE_THRESHOLD_US 1500
|
||||||
#define SIGNAL_TIMEOUT_MS 100
|
#define SIGNAL_TIMEOUT_MS 100
|
||||||
static struct
|
static struct
|
||||||
@ -99,14 +98,10 @@ static void monitor_task(void *arg)
|
|||||||
{
|
{
|
||||||
// Mode change detected
|
// Mode change detected
|
||||||
rcsignal.pull_detected = false;
|
rcsignal.pull_detected = false;
|
||||||
rcsignal.current_mode = (rcsignal.current_mode + 1) % MAX_MODES;
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Mode changed to: %d (pulse: %lu us)",
|
|
||||||
rcsignal.current_mode, rcsignal.pulse_width_us);
|
|
||||||
|
|
||||||
if (rcsignal.callback)
|
if (rcsignal.callback)
|
||||||
{
|
{
|
||||||
rcsignal.callback(rcsignal.current_mode);
|
rcsignal.callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,17 +181,6 @@ bool rcsignal_is_active(void)
|
|||||||
return rcsignal.signal_active;
|
return rcsignal.signal_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rcsignal_trigger_mode_change(void)
|
|
||||||
{
|
|
||||||
rcsignal.current_mode = (rcsignal.current_mode + 1) % MAX_MODES;
|
|
||||||
ESP_LOGI(TAG, "Manual mode change to: %d", rcsignal.current_mode);
|
|
||||||
|
|
||||||
if (rcsignal.callback)
|
|
||||||
{
|
|
||||||
rcsignal.callback(rcsignal.current_mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t rcsignal_get_current_mode(void)
|
uint8_t rcsignal_get_current_mode(void)
|
||||||
{
|
{
|
||||||
return rcsignal.current_mode;
|
return rcsignal.current_mode;
|
||||||
|
|||||||
@ -13,9 +13,8 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Callback function type for mode changes
|
* @brief Callback function type for mode changes
|
||||||
* @param new_mode New animation mode (0-13)
|
|
||||||
*/
|
*/
|
||||||
typedef void (*rcsignal_mode_change_callback_t)(uint8_t new_mode);
|
typedef void (*rcsignal_mode_change_callback_t)();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize RC signal reading
|
* @brief Initialize RC signal reading
|
||||||
@ -47,11 +46,6 @@ uint32_t rcsignal_get_pulse_width(void);
|
|||||||
*/
|
*/
|
||||||
bool rcsignal_is_active(void);
|
bool rcsignal_is_active(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Manually trigger mode change (for emulation)
|
|
||||||
*/
|
|
||||||
void rcsignal_trigger_mode_change(void);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get current mode
|
* @brief Get current mode
|
||||||
* @return Current animation mode (0-13)
|
* @return Current animation mode (0-13)
|
||||||
|
|||||||
Reference in New Issue
Block a user