potion_party_leds/main/leds.c

229 lines
6.2 KiB
C
Raw Normal View History

#include "leds.h"
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include "shared.h"
#include "esp8266/gpio_register.h"
#include "rom/ets_sys.h"
#include "driver/gpio.h"
uint32_t g_serial_out_buffer[122];
Led* g_leds = ((Led*)g_serial_out_buffer + 1);
Gradient g_default_gradient;
Gradient g_current_gradient;
int g_leds_are_default = 1;
LedsSendStatus g_leds_send_state = LEDS_SEND_WAITING;
SemaphoreHandle_t g_led_mutex; // mutex governing access to data for leds
// use for all global data defined in this header
LedThreadData g_led_thread_data = {
.task = 0,
.func = &leds_thread
};
/// =======================================
// leds.h internal helper functions
/// =======================================
static inline
uint8_t lerp_uint8(uint8_t a, uint8_t b, float t) {
if(t <= 0) return a;
else if(t >= 1.0) return b;
else {
int dir = b - a;
return a + dir * t;
}
}
static inline
void lerp_led(Led* out, const Led* from, const Led* to, float t) {
out->components.red = lerp_uint8(from->components.red, to->components.red, t);
out->components.green = lerp_uint8(from->components.green, to->components.green, t);
out->components.blue = lerp_uint8(from->components.blue, to->components.blue, t);
uint8_t glob_from = from->components.global & ~0xE0;
uint8_t glob_to = to->components.global & ~0xE0;
out->components.global = GLOBAL(lerp_uint8(glob_from, glob_to, t));
}
static inline
void lerp_points_between(const GradientPoint from, const GradientPoint to) {
const int dif = to.offset - from.offset;
float t = 0.f;
for(int led = from.offset; led <= to.offset; ++led) {
t = (float)(led - from.offset) / (float)dif;
lerp_led(g_leds + led, &from.led, &to.led, t);
}
}
static
void leds_config_gpio() {
gpio_config_t config = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 0x18030,
.pull_up_en = 0,
.pull_down_en = 0,
};
gpio_config(&config);
}
static
void serial_write(int high) {
// set clock out to high, triggering a rising edge
gpio_set_level(CLOCK, 1);
// write bit to data out
gpio_set_level(DATA, high);
ets_delay_us(1);
// set clock to low, triggering a falling edge, shifting the LEDs shift register
gpio_set_level(CLOCK, 0);
}
/// =======================================
// leds.h external interface functions
/// =======================================
void send_leds() {
// index of the bit being written
// fixed point number where the first 5 bits are the bit of a 32bit integger, and the rest is the integer
int write_bit = 0;
int write_next = 0;
gpio_set_level(CLOCK, 0);
while(write_bit < sizeof(g_serial_out_buffer) * 8) {
// fetch the bit being addressed
write_next = 0x1 & (g_serial_out_buffer[write_bit >> 5] >> (32 - (write_bit & 0x1F)));
serial_write(write_next);
write_bit++;
}
gpio_set_level(CLOCK, 0);
gpio_set_level(DATA, 0);
}
void set_led_range(int start, int end, Led value) {
for(int i = start; i < end; ++i) {
g_leds[i] = value;
}
}
void leds_set_default_gradient(const Gradient* gradient) {
g_default_gradient = *gradient;
}
void leds_set_current_gradient(const Gradient* gradient, int defer_send) {
GradientPoint from = gradient->points[0];
GradientPoint to;
set_led_range(0, gradient->points[0].offset, gradient->points[0].led);
set_led_range(gradient->points[gradient->points_len-1].offset, 120, gradient->points[gradient->points_len-1].led);
g_current_gradient = *gradient;
for(int i = 1; i < gradient->points_len; ++i) {
to = gradient->points[i];
lerp_points_between(from, to);
from = to;
}
if(!defer_send) {
send_leds();
}
}
void leds_reset_gradient(int defer_send) {
leds_set_current_gradient(&g_default_gradient, defer_send);
}
// swap to ranges of memory using a temporary block
static
void memswap(void* d, void* s, size_t n) {
void* tmp = malloc(n);
memcpy(tmp, s, n);
memcpy(s, d, n);
memcpy(d, tmp, n);
free(tmp);
}
void leds_animate() {
for(size_t i = 0; i < g_current_gradient.points_len; ++i) {
// The gradient point at i
GradientPoint* point = g_current_gradient.points + i;
// move towards end
if(point->movement > 0) {
// without moving past it
point->offset = min(point->offset + 1, 120);
// swap with next point if we pass it
if(point->offset > (point+1)->offset) {
memswap(point, point+1, sizeof(GradientPoint));
}
// move towards start
} else if(point->movement < 0) {
// without passing it
point->offset = max(0, point->offset - 1);
// swap with previous point if we fall below it
if(point->offset < (point-1)->offset) {
memswap(point, point-1, sizeof(GradientPoint));
}
}
}
leds_set_current_gradient(&g_current_gradient, 1);
}
void leds_thread() {
send_leds();
float timer = 0;
for(;;) {
// wait for 10 milliseconds,
// giving FreeRTOS time to run other tasks
vTaskDelay(10 / portTICK_RATE_MS);
// tick timer by 10ms
timer += 0.01;
xSemaphoreTake(g_led_mutex, portMAX_DELAY);
{
send_leds();
leds_animate();
// reset gradient, defer send until next frame
if(timer > g_current_gradient.duration) {
timer = 0.f;
leds_reset_gradient(1);
}
}
xSemaphoreGive(g_led_mutex);
}
}
void leds_init() {
g_serial_out_buffer[0] = 0u;
g_serial_out_buffer[61] = ~0u;
set_led_range(0, 120,
(Led){.components =
(LedComponents) {
.red = 0,
.green = 0,
.blue = 0,
.global = GLOBAL(5)
}}
);
g_led_mutex = xSemaphoreCreateMutex();
xTaskCreate(g_led_thread_data.func, "Leds", 1024, NULL, 1, &g_led_thread_data.task);
// initialize mutex for leds data
leds_config_gpio();
}