From 23e23f63a763728193079eb95fb1039566422300 Mon Sep 17 00:00:00 2001 From: skiinder Date: Mon, 13 Apr 2026 15:56:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(led):=20=E6=B7=BB=E5=8A=A0LED=E6=8C=89?= =?UTF-8?q?=E9=94=AE=E6=B7=A1=E5=85=A5=E6=B7=A1=E5=87=BA=E6=95=88=E6=9E=9C?= =?UTF-8?q?=E5=B9=B6=E9=87=8D=E6=9E=84LED=E6=9D=A1=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加了完整的LED效果系统架构,包括: - 新增主题颜色定义文件theme_color.h - 实现key fade LED效果算法,支持按键触发的渐变效果 - 创建LED效果注册机制和通用接口 - 配置17个LED像素与按键映射关系 - 将原有简单的周期性效果替换为基于按键事件的动态效果 CMakeLists.txt中添加了新的源文件路径和实现文件。 BREAKING CHANGE: LED效果从固定的周期性变化改为响应按键事件的动态效果。 --- CMakeLists.txt | 3 + inc/theme_color.h | 24 +++ src/display_module.c | 5 +- src/led_effect/effects/led_effect_key_fade.c | 155 +++++++++++++++++ src/led_effect/led_effect.h | 50 ++++++ src/led_effect/led_effect_registry.c | 27 +++ src/led_effect/led_effect_types.h | 24 +++ src/led_strip_module.c | 174 +++++++++++++------ 8 files changed, 407 insertions(+), 55 deletions(-) create mode 100644 inc/theme_color.h create mode 100644 src/led_effect/effects/led_effect_key_fade.c create mode 100644 src/led_effect/led_effect.h create mode 100644 src/led_effect/led_effect_registry.c create mode 100644 src/led_effect/led_effect_types.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d59600c..e0609a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ include(nanopb) zephyr_include_directories( inc inc/events + src ) add_subdirectory(drivers) @@ -29,6 +30,8 @@ target_sources(app PRIVATE src/encoder_module.c src/hid_flowctrl_module.c src/keyboard_core_module.c + src/led_effect/led_effect_registry.c + src/led_effect/effects/led_effect_key_fade.c src/led_strip_module.c src/ui/ui_main.c src/cdc_wrapper_module.c diff --git a/inc/theme_color.h b/inc/theme_color.h new file mode 100644 index 0000000..3c5368d --- /dev/null +++ b/inc/theme_color.h @@ -0,0 +1,24 @@ +#ifndef BLINKY_THEME_COLOR_H_ +#define BLINKY_THEME_COLOR_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLINKY_THEME_DEFAULT_R 0x4CU +#define BLINKY_THEME_DEFAULT_G 0x9EU +#define BLINKY_THEME_DEFAULT_B 0xF5U + +struct theme_rgb { + uint8_t r; + uint8_t g; + uint8_t b; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_THEME_COLOR_H_ */ diff --git a/src/display_module.c b/src/display_module.c index e7b6985..cab9e48 100644 --- a/src/display_module.c +++ b/src/display_module.c @@ -16,6 +16,7 @@ #include "bat_state_event.h" #include "hid_led_event.h" #include "mode_switch_event.h" +#include "theme_color.h" #include "ui/ui_main.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); @@ -30,7 +31,9 @@ static const struct device *const backlight_dev = DEVICE_DT_GET(DT_PARENT(DT_ALIAS(backlight))); static const uint32_t backlight_idx = DT_NODE_CHILD_IDX(DT_ALIAS(backlight)); static struct ui_main_model ui_model = { - .theme_color = LV_COLOR_MAKE(0x4C, 0x9E, 0xF5), + .theme_color = LV_COLOR_MAKE(BLINKY_THEME_DEFAULT_R, + BLINKY_THEME_DEFAULT_G, + BLINKY_THEME_DEFAULT_B), .inactive_border_color = LV_COLOR_MAKE(0x3A, 0x44, 0x52), .mode = MODE_SWITCH_BLE, }; diff --git a/src/led_effect/effects/led_effect_key_fade.c b/src/led_effect/effects/led_effect_key_fade.c new file mode 100644 index 0000000..0f91158 --- /dev/null +++ b/src/led_effect/effects/led_effect_key_fade.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include + +#include + +#include "led_effect/led_effect.h" + +#define KEY_FADE_LEVEL_MAX 255U +#define KEY_FADE_STEP_PER_TICK 12U + +struct led_effect_key_fade_state { + const struct led_key_map *key_map; + size_t key_map_len; + size_t pixel_count; + uint8_t default_brightness; + uint8_t level[17]; + struct theme_rgb theme; +}; + +static struct led_effect_key_fade_state key_fade_state; + +static const struct led_key_map *find_key(const struct led_effect *effect, + uint16_t key_id) +{ + const struct led_effect_key_fade_state *state = effect->state; + + for (size_t i = 0; i < state->key_map_len; i++) { + if (state->key_map[i].key_id == key_id) { + return &state->key_map[i]; + } + } + + return NULL; +} + +static int led_effect_key_fade_init(struct led_effect *effect, + const struct led_effect_config *cfg) +{ + struct led_effect_key_fade_state *state = effect->state; + + if ((effect == NULL) || (cfg == NULL) || (cfg->key_map == NULL) || + (cfg->pixel_count > ARRAY_SIZE(state->level))) { + return -EINVAL; + } + + memset(state, 0, sizeof(*state)); + state->key_map = cfg->key_map; + state->key_map_len = cfg->key_map_len; + state->pixel_count = cfg->pixel_count; + state->default_brightness = cfg->default_brightness; + state->theme.r = BLINKY_THEME_DEFAULT_R; + state->theme.g = BLINKY_THEME_DEFAULT_G; + state->theme.b = BLINKY_THEME_DEFAULT_B; + + return 0; +} + +static void led_effect_key_fade_reset(struct led_effect *effect) +{ + struct led_effect_key_fade_state *state = effect->state; + + memset(state->level, 0, sizeof(state->level)); +} + +static void led_effect_key_fade_set_theme(struct led_effect *effect, + const struct theme_rgb *theme) +{ + struct led_effect_key_fade_state *state = effect->state; + + if (theme == NULL) { + return; + } + + state->theme = *theme; +} + +static void led_effect_key_fade_on_key_press(struct led_effect *effect, + uint16_t key_id) +{ + struct led_effect_key_fade_state *state = effect->state; + const struct led_key_map *map = find_key(effect, key_id); + + if ((map == NULL) || (map->led_idx >= state->pixel_count)) { + return; + } + + state->level[map->led_idx] = + (state->default_brightness == 0U) ? KEY_FADE_LEVEL_MAX : + state->default_brightness; +} + +static bool led_effect_key_fade_tick(struct led_effect *effect, uint32_t dt_ms, + struct led_rgb *pixels, size_t pixel_count) +{ + struct led_effect_key_fade_state *state = effect->state; + bool active = false; + bool visible = false; + uint8_t decay; + + ARG_UNUSED(dt_ms); + + if ((pixels == NULL) || (pixel_count < state->pixel_count)) { + return false; + } + + memset(pixels, 0, sizeof(*pixels) * pixel_count); + decay = KEY_FADE_STEP_PER_TICK; + + for (size_t i = 0; i < state->pixel_count; i++) { + uint8_t level = state->level[i]; + + if (level == 0U) { + continue; + } + + visible = true; + pixels[i].r = (uint8_t)(((uint16_t)state->theme.r * level) / 255U); + pixels[i].g = (uint8_t)(((uint16_t)state->theme.g * level) / 255U); + pixels[i].b = (uint8_t)(((uint16_t)state->theme.b * level) / 255U); + + state->level[i] = (level > decay) ? (uint8_t)(level - decay) : 0U; + active = active || (state->level[i] > 0U); + } + + return visible || active; +} + +static bool led_effect_key_fade_is_active(const struct led_effect *effect) +{ + const struct led_effect_key_fade_state *state = effect->state; + + for (size_t i = 0; i < state->pixel_count; i++) { + if (state->level[i] > 0U) { + return true; + } + } + + return false; +} + +static const struct led_effect_ops key_fade_ops = { + .init = led_effect_key_fade_init, + .reset = led_effect_key_fade_reset, + .set_theme = led_effect_key_fade_set_theme, + .on_key_press = led_effect_key_fade_on_key_press, + .tick = led_effect_key_fade_tick, + .is_active = led_effect_key_fade_is_active, +}; + +struct led_effect led_effect_key_fade = { + .ops = &key_fade_ops, + .state = &key_fade_state, +}; diff --git a/src/led_effect/led_effect.h b/src/led_effect/led_effect.h new file mode 100644 index 0000000..7e4f45a --- /dev/null +++ b/src/led_effect/led_effect.h @@ -0,0 +1,50 @@ +#ifndef BLINKY_LED_EFFECT_H_ +#define BLINKY_LED_EFFECT_H_ + +#include +#include +#include + +#include + +#include "led_effect_types.h" +#include "theme_color.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct led_effect; + +struct led_effect_config { + const struct led_key_map *key_map; + size_t key_map_len; + size_t pixel_count; + uint8_t default_brightness; +}; + +struct led_effect_ops { + int (*init)(struct led_effect *effect, + const struct led_effect_config *cfg); + void (*reset)(struct led_effect *effect); + void (*set_theme)(struct led_effect *effect, + const struct theme_rgb *theme); + void (*on_key_press)(struct led_effect *effect, uint16_t key_id); + bool (*tick)(struct led_effect *effect, uint32_t dt_ms, + struct led_rgb *pixels, size_t pixel_count); + bool (*is_active)(const struct led_effect *effect); +}; + +struct led_effect { + const struct led_effect_ops *ops; + void *state; +}; + +const struct led_effect *led_effect_get(enum led_effect_id id); +struct led_effect *led_effect_get_mutable(enum led_effect_id id); + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_LED_EFFECT_H_ */ diff --git a/src/led_effect/led_effect_registry.c b/src/led_effect/led_effect_registry.c new file mode 100644 index 0000000..78e132d --- /dev/null +++ b/src/led_effect/led_effect_registry.c @@ -0,0 +1,27 @@ +#include + +#include "led_effect.h" + +extern struct led_effect led_effect_key_fade; + +static struct led_effect *const effect_registry[LED_EFFECT_ID_COUNT] = { + [LED_EFFECT_ID_KEY_FADE] = &led_effect_key_fade, +}; + +const struct led_effect *led_effect_get(enum led_effect_id id) +{ + if (id >= LED_EFFECT_ID_COUNT) { + return NULL; + } + + return effect_registry[id]; +} + +struct led_effect *led_effect_get_mutable(enum led_effect_id id) +{ + if (id >= LED_EFFECT_ID_COUNT) { + return NULL; + } + + return effect_registry[id]; +} diff --git a/src/led_effect/led_effect_types.h b/src/led_effect/led_effect_types.h new file mode 100644 index 0000000..2573582 --- /dev/null +++ b/src/led_effect/led_effect_types.h @@ -0,0 +1,24 @@ +#ifndef BLINKY_LED_EFFECT_TYPES_H_ +#define BLINKY_LED_EFFECT_TYPES_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct led_key_map { + uint16_t key_id; + uint8_t led_idx; +}; + +enum led_effect_id { + LED_EFFECT_ID_KEY_FADE = 0, + LED_EFFECT_ID_COUNT, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_LED_EFFECT_TYPES_H_ */ diff --git a/src/led_strip_module.c b/src/led_strip_module.c index e4fe423..5a8fbee 100644 --- a/src/led_strip_module.c +++ b/src/led_strip_module.c @@ -5,8 +5,10 @@ #include #define MODULE led_strip_module +#include #include #include +#include #include #include @@ -14,44 +16,71 @@ #include #include +#include "led_effect/led_effect.h" #include "led_strip_en_event.h" +#include "theme_color.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); #define LED_STRIP_NODE DT_CHOSEN(zephyr_led_strip) #define LED_STRIP_NUM_PIXELS DT_PROP(LED_STRIP_NODE, chain_length) -#define LED_STRIP_EFFECT_INTERVAL K_MSEC(400) +#define LED_STRIP_TICK_INTERVAL K_MSEC(20) BUILD_ASSERT(DT_NODE_HAS_STATUS(LED_STRIP_NODE, okay), "Missing zephyr,led-strip chosen node"); BUILD_ASSERT(DT_NODE_HAS_PROP(LED_STRIP_NODE, supply_gpios), "Missing supply-gpios on zephyr,led-strip node"); +BUILD_ASSERT(LED_STRIP_NUM_PIXELS == 17U, + "LED strip key map expects 17 pixels"); static const struct device *const strip = DEVICE_DT_GET(LED_STRIP_NODE); static const struct gpio_dt_spec strip_en = GPIO_DT_SPEC_GET(LED_STRIP_NODE, supply_gpios); +static const struct led_key_map led_key_map[] = { + { KEY_ID(0, 1), 0U }, + { KEY_ID(1, 1), 1U }, + { KEY_ID(2, 1), 2U }, + { KEY_ID(3, 1), 3U }, + { KEY_ID(0, 2), 4U }, + { KEY_ID(1, 2), 5U }, + { KEY_ID(2, 2), 6U }, + { KEY_ID(0, 3), 7U }, + { KEY_ID(1, 3), 8U }, + { KEY_ID(2, 3), 9U }, + { KEY_ID(3, 3), 10U }, + { KEY_ID(0, 4), 11U }, + { KEY_ID(1, 4), 12U }, + { KEY_ID(2, 4), 13U }, + { KEY_ID(0, 5), 14U }, + { KEY_ID(1, 5), 15U }, + { KEY_ID(3, 5), 16U }, +}; + +static const struct led_effect_config effect_cfg = { + .key_map = led_key_map, + .key_map_len = ARRAY_SIZE(led_key_map), + .pixel_count = LED_STRIP_NUM_PIXELS, + .default_brightness = 0xFFU, +}; + static struct led_rgb pixels[LED_STRIP_NUM_PIXELS]; static struct k_work_delayable effect_work; +static struct led_effect *effect; +static struct theme_rgb current_theme = { + .r = BLINKY_THEME_DEFAULT_R, + .g = BLINKY_THEME_DEFAULT_G, + .b = BLINKY_THEME_DEFAULT_B, +}; static bool initialized; static bool running; static bool enabled = true; -static uint8_t effect_step; static void clear_pixels(void) { memset(pixels, 0, sizeof(pixels)); } -static void fill_pixels(uint8_t r, uint8_t g, uint8_t b) -{ - for (size_t i = 0; i < ARRAY_SIZE(pixels); i++) { - pixels[i].r = r; - pixels[i].g = g; - pixels[i].b = b; - } -} - static int submit_frame(void) { int err; @@ -76,38 +105,23 @@ static int set_strip_power(bool on) return err; } -static void render_effect_step(void) +static int refresh_idle_frame(void) { - switch (effect_step) { - case 0U: - fill_pixels(0x40, 0x00, 0x00); - break; - - case 1U: - fill_pixels(0x00, 0x40, 0x00); - break; - - default: - fill_pixels(0x00, 0x00, 0x40); - break; - } + clear_pixels(); + return submit_frame(); } -static void effect_work_handler(struct k_work *work) +static void stop_effect_work(void) { - ARG_UNUSED(work); - - if (!running || !enabled) { - return; - } - - render_effect_step(); - (void)submit_frame(); - effect_step = (uint8_t)((effect_step + 1U) % 3U); - k_work_reschedule(&effect_work, LED_STRIP_EFFECT_INTERVAL); + k_work_cancel_delayable(&effect_work); } -static int enable_effect_output(void) +static void schedule_effect_tick(k_timeout_t delay) +{ + k_work_reschedule(&effect_work, delay); +} + +static int enable_strip_output(void) { int err; @@ -116,24 +130,37 @@ static int enable_effect_output(void) return err; } - render_effect_step(); - err = submit_frame(); - if (err) { - return err; - } - - k_work_reschedule(&effect_work, LED_STRIP_EFFECT_INTERVAL); - return 0; + return refresh_idle_frame(); } -static void disable_effect_output(void) +static void disable_strip_output(void) { - k_work_cancel_delayable(&effect_work); + stop_effect_work(); clear_pixels(); (void)submit_frame(); (void)set_strip_power(false); } +static void effect_work_handler(struct k_work *work) +{ + bool keep_running; + + ARG_UNUSED(work); + + if (!running || !enabled || (effect == NULL)) { + return; + } + + keep_running = effect->ops->tick(effect, k_ticks_to_ms_floor32( + LED_STRIP_TICK_INTERVAL.ticks), + pixels, ARRAY_SIZE(pixels)); + (void)submit_frame(); + + if (keep_running) { + schedule_effect_tick(LED_STRIP_TICK_INTERVAL); + } +} + static int module_init(void) { int err; @@ -144,19 +171,32 @@ static int module_init(void) } if (!gpio_is_ready_dt(&strip_en)) { - LOG_ERR("LED strip EN GPIO not ready"); + LOG_ERR("LED strip supply GPIO not ready"); return -ENODEV; } err = gpio_pin_configure_dt(&strip_en, GPIO_OUTPUT_INACTIVE); if (err) { - LOG_ERR("Failed to configure LED strip EN (%d)", err); + LOG_ERR("Failed to configure LED strip supply GPIO (%d)", err); return err; } k_work_init_delayable(&effect_work, effect_work_handler); clear_pixels(); - effect_step = 0U; + + effect = led_effect_get_mutable(LED_EFFECT_ID_KEY_FADE); + if ((effect == NULL) || (effect->ops == NULL)) { + LOG_ERR("LED effect not available"); + return -ENOENT; + } + + err = effect->ops->init(effect, &effect_cfg); + if (err) { + LOG_ERR("LED effect init failed (%d)", err); + return err; + } + + effect->ops->set_theme(effect, ¤t_theme); LOG_INF("LED strip ready len:%u", (uint32_t)led_strip_length(strip)); return 0; @@ -176,7 +216,7 @@ static int module_start(void) return 0; } - err = enable_effect_output(); + err = enable_strip_output(); if (err) { running = false; } @@ -190,10 +230,26 @@ static void module_pause(void) return; } - disable_effect_output(); + if ((effect != NULL) && (effect->ops != NULL)) { + effect->ops->reset(effect); + } + + disable_strip_output(); running = false; } +static bool handle_button_event(const struct button_event *event) +{ + if (!running || !enabled || (effect == NULL) || !event->pressed) { + return false; + } + + effect->ops->on_key_press(effect, event->key_id); + schedule_effect_tick(K_NO_WAIT); + + return false; +} + static bool handle_led_strip_en_event(const struct led_strip_en_event *event) { enabled = event->enabled; @@ -203,13 +259,18 @@ static bool handle_led_strip_en_event(const struct led_strip_en_event *event) } if (enabled) { - int err = enable_effect_output(); + int err = enable_strip_output(); if (err) { module_set_state(MODULE_STATE_ERROR); + return false; } } else { - disable_effect_output(); + if ((effect != NULL) && (effect->ops != NULL)) { + effect->ops->reset(effect); + } + + disable_strip_output(); } return false; @@ -217,6 +278,10 @@ static bool handle_led_strip_en_event(const struct led_strip_en_event *event) static bool app_event_handler(const struct app_event_header *aeh) { + if (is_button_event(aeh)) { + return handle_button_event(cast_button_event(aeh)); + } + if (is_led_strip_en_event(aeh)) { return handle_led_strip_en_event(cast_led_strip_en_event(aeh)); } @@ -275,6 +340,7 @@ static bool app_event_handler(const struct app_event_header *aeh) } APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE_EARLY(MODULE, button_event); APP_EVENT_SUBSCRIBE(MODULE, led_strip_en_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);