2023-11-04 13:30:53 +01:00
|
|
|
/*
|
2023-11-15 14:57:12 +01:00
|
|
|
* Light (WS2812 + LED) control
|
2023-11-04 13:30:53 +01:00
|
|
|
* WHowe <github.com/whowechina>
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2023-11-15 14:57:12 +01:00
|
|
|
#include "light.h"
|
2023-11-04 13:30:53 +01:00
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
|
|
|
#include "hardware/pio.h"
|
|
|
|
#include "hardware/timer.h"
|
2023-11-15 14:57:12 +01:00
|
|
|
#include "hardware/pwm.h"
|
2023-11-04 13:30:53 +01:00
|
|
|
|
|
|
|
#include "ws2812.pio.h"
|
|
|
|
|
|
|
|
#include "board_defs.h"
|
|
|
|
#include "config.h"
|
|
|
|
|
2024-02-25 03:29:27 +01:00
|
|
|
static uint32_t rgb_buf[64];
|
2023-11-15 14:57:12 +01:00
|
|
|
static uint8_t led_gpio[] = LED_DEF;
|
|
|
|
#define RGB_NUM (sizeof(rgb_buf) / sizeof(rgb_buf[0]))
|
|
|
|
#define LED_NUM (sizeof(led_gpio))
|
2023-11-18 04:11:36 +01:00
|
|
|
static uint8_t led_buf[LED_NUM];
|
|
|
|
|
2023-11-04 13:30:53 +01:00
|
|
|
|
|
|
|
#define _MAP_LED(x) _MAKE_MAPPER(x)
|
|
|
|
#define _MAKE_MAPPER(x) MAP_LED_##x
|
|
|
|
#define MAP_LED_RGB { c1 = r; c2 = g; c3 = b; }
|
|
|
|
#define MAP_LED_GRB { c1 = g; c2 = r; c3 = b; }
|
|
|
|
|
|
|
|
#define REMAP_BUTTON_RGB _MAP_LED(BUTTON_RGB_ORDER)
|
|
|
|
#define REMAP_TT_RGB _MAP_LED(TT_RGB_ORDER)
|
|
|
|
|
|
|
|
static inline uint32_t _rgb32(uint32_t c1, uint32_t c2, uint32_t c3, bool gamma_fix)
|
|
|
|
{
|
|
|
|
if (gamma_fix) {
|
|
|
|
c1 = ((c1 + 1) * (c1 + 1) - 1) >> 8;
|
|
|
|
c2 = ((c2 + 1) * (c2 + 1) - 1) >> 8;
|
|
|
|
c3 = ((c3 + 1) * (c3 + 1) - 1) >> 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (c1 << 16) | (c2 << 8) | (c3 << 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix)
|
|
|
|
{
|
|
|
|
#if BUTTON_RGB_ORDER == GRB
|
|
|
|
return _rgb32(g, r, b, gamma_fix);
|
|
|
|
#else
|
|
|
|
return _rgb32(r, g, b, gamma_fix);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t rgb32_from_hsv(uint8_t h, uint8_t s, uint8_t v)
|
|
|
|
{
|
|
|
|
uint32_t region, remainder, p, q, t;
|
|
|
|
|
|
|
|
if (s == 0) {
|
|
|
|
return v << 16 | v << 8 | v;
|
|
|
|
}
|
|
|
|
|
|
|
|
region = h / 43;
|
|
|
|
remainder = (h % 43) * 6;
|
|
|
|
|
|
|
|
p = (v * (255 - s)) >> 8;
|
|
|
|
q = (v * (255 - ((s * remainder) >> 8))) >> 8;
|
|
|
|
t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8;
|
|
|
|
|
|
|
|
switch (region) {
|
|
|
|
case 0:
|
|
|
|
return v << 16 | t << 8 | p;
|
|
|
|
case 1:
|
|
|
|
return q << 16 | v << 8 | p;
|
|
|
|
case 2:
|
|
|
|
return p << 16 | v << 8 | t;
|
|
|
|
case 3:
|
|
|
|
return p << 16 | q << 8 | v;
|
|
|
|
case 4:
|
|
|
|
return t << 16 | p << 8 | v;
|
|
|
|
default:
|
|
|
|
return v << 16 | p << 8 | q;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-06 14:31:24 +02:00
|
|
|
static inline uint32_t apply_level_by(uint32_t color, uint8_t level)
|
2023-11-05 12:18:30 +01:00
|
|
|
{
|
|
|
|
unsigned r = (color >> 16) & 0xff;
|
|
|
|
unsigned g = (color >> 8) & 0xff;
|
|
|
|
unsigned b = color & 0xff;
|
|
|
|
|
2024-05-06 14:31:24 +02:00
|
|
|
r = r * level / 255;
|
|
|
|
g = g * level / 255;
|
|
|
|
b = b * level / 255;
|
2023-11-05 12:18:30 +01:00
|
|
|
|
|
|
|
return r << 16 | g << 8 | b;
|
|
|
|
}
|
|
|
|
|
2024-05-06 14:31:24 +02:00
|
|
|
static uint8_t curr_level = 0;
|
|
|
|
static inline uint32_t apply_level(uint32_t color)
|
|
|
|
{
|
|
|
|
return apply_level_by(color, curr_level);
|
|
|
|
}
|
|
|
|
|
2023-11-05 12:18:30 +01:00
|
|
|
/* 6 segment regular hsv color wheel, better color cycle
|
|
|
|
* https://www.arnevogel.com/rgb-rainbow/
|
|
|
|
* https://www.instructables.com/How-to-Make-Proper-Rainbow-and-Random-Colors-With-/
|
|
|
|
*/
|
|
|
|
#define COLOR_WHEEL_SIZE 256
|
|
|
|
static uint32_t color_wheel[COLOR_WHEEL_SIZE];
|
|
|
|
static void generate_color_wheel()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < COLOR_WHEEL_SIZE; i++) {
|
2023-11-05 14:23:28 +01:00
|
|
|
color_wheel[i] = rgb32_from_hsv(i, 208, 255);
|
2023-11-05 12:18:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define RAINBOW_PITCH 37
|
|
|
|
#define RAINBOW_MIN_SPEED 1
|
2023-11-18 03:45:47 +01:00
|
|
|
static uint32_t curr_speed = RAINBOW_MIN_SPEED;
|
2023-11-05 12:18:30 +01:00
|
|
|
|
|
|
|
static void rainbow_update()
|
2023-11-04 13:30:53 +01:00
|
|
|
{
|
|
|
|
static uint64_t last = 0;
|
|
|
|
uint64_t now = time_us_64();
|
2023-11-05 12:18:30 +01:00
|
|
|
if (now - last < 33333) { // no faster than 30Hz
|
2023-11-04 13:30:53 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
last = now;
|
|
|
|
|
2023-11-05 12:18:30 +01:00
|
|
|
static uint32_t rotator = 0;
|
2023-11-18 03:45:47 +01:00
|
|
|
rotator = (rotator + curr_speed) % COLOR_WHEEL_SIZE;
|
2023-11-05 12:18:30 +01:00
|
|
|
|
2023-11-15 14:57:12 +01:00
|
|
|
for (int i = 0; i < RGB_NUM; i++) {
|
2023-11-05 12:18:30 +01:00
|
|
|
uint32_t index = (rotator + RAINBOW_PITCH * i) % COLOR_WHEEL_SIZE;
|
2023-11-18 03:45:47 +01:00
|
|
|
rgb_buf[i] = apply_level(color_wheel[index]);
|
2023-11-04 13:30:53 +01:00
|
|
|
}
|
2023-11-15 14:57:12 +01:00
|
|
|
|
2023-11-18 04:11:36 +01:00
|
|
|
for (int i = 0; i < LED_NUM; i++) {
|
2023-11-18 03:45:47 +01:00
|
|
|
uint32_t index = (rotator + RAINBOW_PITCH * 2 * i) % COLOR_WHEEL_SIZE;
|
2023-11-18 04:11:36 +01:00
|
|
|
led_buf[i] = apply_level(color_wheel[index]) & 0xff;
|
2023-11-15 14:57:12 +01:00
|
|
|
}
|
2023-11-04 13:30:53 +01:00
|
|
|
}
|
|
|
|
|
2023-11-18 03:45:47 +01:00
|
|
|
void light_stimulate()
|
2023-11-04 13:30:53 +01:00
|
|
|
{
|
2023-11-18 03:45:47 +01:00
|
|
|
curr_speed = 48;
|
|
|
|
curr_level = aic_cfg->light.max;
|
2023-11-05 12:18:30 +01:00
|
|
|
}
|
2023-11-04 13:30:53 +01:00
|
|
|
|
2023-11-18 03:45:47 +01:00
|
|
|
static void rainbow_fade()
|
2023-11-05 12:18:30 +01:00
|
|
|
{
|
|
|
|
static uint64_t last = 0;
|
|
|
|
uint64_t now = time_us_64();
|
|
|
|
if (now - last < 200000) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
last = now;
|
2023-11-04 13:30:53 +01:00
|
|
|
|
2023-11-18 03:45:47 +01:00
|
|
|
if (curr_speed > RAINBOW_MIN_SPEED) {
|
2024-03-10 15:45:20 +01:00
|
|
|
curr_speed = curr_speed * 90 / 100;
|
2023-11-18 03:45:47 +01:00
|
|
|
}
|
|
|
|
if (curr_level > aic_cfg->light.min) {
|
|
|
|
curr_level -= (curr_level - aic_cfg->light.min) / 10 + 1;
|
|
|
|
} else if (curr_level < aic_cfg->light.min) {
|
|
|
|
curr_level += (aic_cfg->light.min - curr_level) / 10 + 1;
|
2023-11-05 12:18:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void drive_led()
|
|
|
|
{
|
|
|
|
static uint64_t last = 0;
|
|
|
|
uint64_t now = time_us_64();
|
|
|
|
if (now - last < 4000) { // no faster than 250Hz
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
last = now;
|
|
|
|
|
2023-11-15 14:57:12 +01:00
|
|
|
for (int i = 0; i < RGB_NUM; i++) {
|
2023-11-18 03:45:47 +01:00
|
|
|
uint32_t color = aic_cfg->light.rgb ? rgb_buf[i] << 8u : 0;
|
|
|
|
pio_sm_put_blocking(pio0, 0, color);
|
2023-11-05 12:18:30 +01:00
|
|
|
}
|
2023-11-18 04:11:36 +01:00
|
|
|
|
|
|
|
for (int i = 0; i < LED_NUM; i++) {
|
|
|
|
uint8_t level = aic_cfg->light.led ? led_buf[i] : 0;
|
|
|
|
pwm_set_gpio_level(led_gpio[i], level);
|
|
|
|
}
|
2023-11-04 13:30:53 +01:00
|
|
|
}
|
|
|
|
|
2023-11-15 14:57:12 +01:00
|
|
|
void light_set_color(unsigned index, uint32_t color)
|
2023-11-04 13:30:53 +01:00
|
|
|
{
|
2023-11-15 14:57:12 +01:00
|
|
|
if (index >= RGB_NUM) {
|
2023-11-04 13:30:53 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
rgb_buf[index] = apply_level(color);
|
|
|
|
}
|
|
|
|
|
2023-11-15 14:57:12 +01:00
|
|
|
void light_set_color_all(uint32_t color)
|
2023-11-04 13:30:53 +01:00
|
|
|
{
|
2023-11-15 14:57:12 +01:00
|
|
|
for (int i = 0; i < RGB_NUM; i++) {
|
2024-05-06 14:31:24 +02:00
|
|
|
rgb_buf[i] = apply_level_by(color, aic_cfg->light.max);
|
2023-11-04 13:30:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-29 14:15:28 +02:00
|
|
|
static uint64_t last_hid = 0;
|
|
|
|
void light_hid_light(uint8_t r, uint8_t g, uint8_t b)
|
|
|
|
{
|
|
|
|
light_set_color_all(rgb32(r, g, b, false));
|
|
|
|
last_hid = time_us_64();
|
|
|
|
}
|
|
|
|
|
2023-11-15 14:57:12 +01:00
|
|
|
void light_set_brg(unsigned index, const uint8_t *brg_array, size_t num)
|
2023-11-04 13:30:53 +01:00
|
|
|
{
|
2023-11-15 14:57:12 +01:00
|
|
|
if (index >= RGB_NUM) {
|
2023-11-04 13:30:53 +01:00
|
|
|
return;
|
|
|
|
}
|
2023-11-15 14:57:12 +01:00
|
|
|
if (index + num > RGB_NUM) {
|
|
|
|
num = RGB_NUM - index;
|
2023-11-04 13:30:53 +01:00
|
|
|
}
|
|
|
|
for (int i = 0; i < num; i++) {
|
|
|
|
uint8_t b = brg_array[i * 3 + 0];
|
|
|
|
uint8_t r = brg_array[i * 3 + 1];
|
|
|
|
uint8_t g = brg_array[i * 3 + 2];
|
|
|
|
rgb_buf[index + i] = apply_level(rgb32(r, g, b, false));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-15 14:57:12 +01:00
|
|
|
void light_init()
|
2023-11-04 13:30:53 +01:00
|
|
|
{
|
|
|
|
uint pio0_offset = pio_add_program(pio0, &ws2812_program);
|
|
|
|
gpio_set_drive_strength(RGB_PIN, GPIO_DRIVE_STRENGTH_2MA);
|
|
|
|
ws2812_program_init(pio0, 0, pio0_offset, RGB_PIN, 800000, false);
|
2023-11-15 14:57:12 +01:00
|
|
|
|
|
|
|
for (int i = 0; i < LED_NUM; i++) {
|
|
|
|
gpio_init(led_gpio[i]);
|
|
|
|
gpio_set_dir(led_gpio[i], GPIO_OUT);
|
|
|
|
gpio_set_function(led_gpio[i], GPIO_FUNC_PWM);
|
|
|
|
|
|
|
|
int slice = pwm_gpio_to_slice_num(led_gpio[i]);
|
|
|
|
|
|
|
|
pwm_config cfg = pwm_get_default_config();
|
|
|
|
pwm_config_set_clkdiv(&cfg, 4.f);
|
|
|
|
pwm_init(slice, &cfg, true);
|
|
|
|
}
|
2024-04-27 08:44:33 +02:00
|
|
|
|
|
|
|
curr_level = aic_cfg->light.min;
|
2024-05-06 14:31:24 +02:00
|
|
|
generate_color_wheel();
|
2023-11-04 13:30:53 +01:00
|
|
|
}
|
|
|
|
|
2024-04-21 10:03:32 +02:00
|
|
|
static bool rainbow = true;
|
|
|
|
void light_set_rainbow(bool enable)
|
|
|
|
{
|
|
|
|
rainbow = enable;
|
|
|
|
}
|
|
|
|
|
2023-11-15 14:57:12 +01:00
|
|
|
void light_update()
|
2023-11-04 13:30:53 +01:00
|
|
|
{
|
2024-04-29 14:15:28 +02:00
|
|
|
if (rainbow && (time_us_64() > last_hid + 1000000)) {
|
2024-04-21 10:03:32 +02:00
|
|
|
rainbow_update();
|
|
|
|
}
|
2024-05-06 14:31:24 +02:00
|
|
|
rainbow_fade();
|
2023-11-04 13:30:53 +01:00
|
|
|
drive_led();
|
|
|
|
}
|