/// // // leds.h defines structs and functions for addressing an HD107s ledstrip over serial using the GPIO pins. // /// #ifndef _potion_leds_h #define _potion_leds_h #include #include #include #include #include "shared.h" #include "esp8266/gpio_register.h" #include "esp_system.h" #include "rom/ets_sys.h" #include "driver/gpio.h" enum leds_send_state_t { LEDS_SEND_WAITING, LEDS_SEND_REQUESTED, LEDS_SENDING, }; // pack the struct to match exactly 8 * 4 = 32bits struct __attribute__((__packed__)) led_components_t { // RGB component values uint8_t red; uint8_t green; uint8_t blue; uint8_t global; // global baseline brightness, highest 3 bits should always be ones }; // union of components and their representation as a u32 // allows for easier sending of data over serial union led_t { struct led_components_t components; uint32_t bits; }; // point on a gradient struct gradient_point_t { union led_t led; // value of the led at this point size_t offset; // offset (measured in leds) from the beginning short movement; // direction of movement over time }; struct gradient_t { struct gradient_point_t points[16]; // array of gradient points, support at most 16 points in a gradient size_t points_len; // number of used gradient points float duration; // amount of time to allow this gradient to last // positive means an amount in second 0 or negative means indefinitely until further notice }; // buffer that will be written out to the led strip over serial uint32_t g_serial_out_buffer[122]; // 120-long slice of the out buffer that represents the first few leds union led_t* g_leds = ((union led_t*)g_serial_out_buffer + 1); struct gradient_t g_default_gradient; struct gradient_t g_current_gradient; int g_leds_are_default = 1; enum leds_send_state_t 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 #define CLOCK 4 #define DATA 5 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); } static 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); } 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(union led_t* out, const union led_t* from, const union led_t* 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 struct gradient_point_t from, const struct gradient_point_t 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 set_led_range(int start, int end, union led_t value) { for(int i = start; i < end; ++i) { g_leds[i] = value; } } static void leds_set_default_gradient(const struct gradient_t* gradient) { g_default_gradient = *gradient; } static void leds_set_current_gradient(const struct gradient_t* gradient, int defer_send) { struct gradient_point_t from = gradient->points[0]; struct gradient_point_t 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 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) { struct gradient_point_t* point = g_current_gradient.points + i; if(point->movement > 0) { point->offset = min(point->offset + 1, 120); LOGLN("move result %i", point->offset); LOGLN("next %d", (point+1)->offset); if(point->offset > (point+1)->offset) { memswap(point, point+1, sizeof(struct gradient_point_t)); LOGLN("swap down"); } } else if(point->movement < 0) { point->offset = max(0, point->offset - 1); LOGLN("move result %i", point->offset); LOGLN("next %d", (point-1)->offset); if(point->offset < (point-1)->offset) { memswap(point, point-1, sizeof(struct gradient_point_t)); LOGLN("swap up"); } } } leds_set_current_gradient(&g_current_gradient, 1); } struct led_thread_data_t { TaskHandle_t task; TaskFunction_t func; }; static void leds_thread(); static struct led_thread_data_t _thread_data = { .task = 0, .func = &leds_thread }; static void leds_thread() { send_leds(); for(;;) { vTaskDelay(10); xSemaphoreTake(g_led_mutex, portMAX_DELAY); send_leds(); leds_animate(); xSemaphoreGive(g_led_mutex); } } static void leds_init() { g_serial_out_buffer[0] = 0u; g_serial_out_buffer[61] = ~0u; set_led_range(0, 120, (union led_t){.components = (struct led_components_t) { .red = 0, .green = 0, .blue = 0, .global = GLOBAL(5) }} ); g_led_mutex = xSemaphoreCreateMutex(); xTaskCreate(_thread_data.func, "Leds", 1024, NULL, 1, &_thread_data.task); // initialize mutex for leds data leds_config_gpio(); } #endif // !_leds_h