diff --git a/CMakeLists.txt b/CMakeLists.txt index 990a654..d59600c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ target_sources(app PRIVATE src/encoder_module.c src/hid_flowctrl_module.c src/keyboard_core_module.c + src/led_strip_module.c src/ui/ui_main.c src/cdc_wrapper_module.c src/protocol_module.c @@ -45,6 +46,7 @@ target_sources(app PRIVATE src/events/hid_report_sent_event.c src/events/hid_transport_state_event.c src/events/hid_tx_report_event.c + src/events/led_strip_en_event.c src/events/key_function_event.c src/mode_switch_module.c src/events/keyboard_hid_report_event.c diff --git a/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi b/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi index 04e9863..59fa96c 100644 --- a/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi +++ b/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi @@ -46,6 +46,21 @@ }; }; + spi1_ws2812_default: spi1_ws2812_default { + group1 { + psels = , + ; + }; + }; + + spi1_ws2812_sleep: spi1_ws2812_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + pwm0_default: pwm0_default { group1 { psels = ; diff --git a/boards/atguigu/mini_keyboard/mini_keyboard.dts b/boards/atguigu/mini_keyboard/mini_keyboard.dts index 71151da..487161c 100644 --- a/boards/atguigu/mini_keyboard/mini_keyboard.dts +++ b/boards/atguigu/mini_keyboard/mini_keyboard.dts @@ -2,6 +2,7 @@ #include #include "mini_keyboard-pinctrl.dtsi" #include +#include #include #include @@ -14,10 +15,12 @@ zephyr,flash = &flash0; zephyr,code-partition = &slot0_partition; zephyr,display = &screen_lcd; + zephyr,led-strip = &led_strip; }; aliases { led0 = &myled0; + led-strip = &led_strip; qdec0 = &qdec; backlight = &backlight; }; @@ -206,6 +209,28 @@ cs-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>; }; +&spi1 { + status = "okay"; + pinctrl-0 = <&spi1_ws2812_default>; + pinctrl-1 = <&spi1_ws2812_sleep>; + pinctrl-names = "default", "sleep"; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + reg = <0>; + spi-max-frequency = <6400000>; + chain-length = <17>; + color-mapping = ; + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + bits-per-symbol = <8>; + reset-delay = <8>; + supply-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; + }; +}; + &pwm0 { status = "okay"; pinctrl-0 = <&pwm0_default>; diff --git a/inc/events/led_strip_en_event.h b/inc/events/led_strip_en_event.h new file mode 100644 index 0000000..da57138 --- /dev/null +++ b/inc/events/led_strip_en_event.h @@ -0,0 +1,24 @@ +#ifndef BLINKY_LED_STRIP_EN_EVENT_H_ +#define BLINKY_LED_STRIP_EN_EVENT_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct led_strip_en_event { + struct app_event_header header; + bool enabled; +}; + +APP_EVENT_TYPE_DECLARE(led_strip_en_event); + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_LED_STRIP_EN_EVENT_H_ */ diff --git a/prj.conf b/prj.conf index de209ce..ea70a72 100644 --- a/prj.conf +++ b/prj.conf @@ -28,6 +28,8 @@ CONFIG_HEAP_MEM_POOL_SIZE=4096 CONFIG_LOG=y CONFIG_ASSERT=y CONFIG_APP_EVENT_MANAGER_MAX_EVENT_CNT=64 +CONFIG_LED_STRIP=y +CONFIG_WS2812_STRIP_SPI=y # USB HID next stack CONFIG_USB_DEVICE_STACK_NEXT=y diff --git a/src/events/led_strip_en_event.c b/src/events/led_strip_en_event.c new file mode 100644 index 0000000..68ac771 --- /dev/null +++ b/src/events/led_strip_en_event.c @@ -0,0 +1,27 @@ +#include "led_strip_en_event.h" + +static void log_led_strip_en_event(const struct app_event_header *aeh) +{ + const struct led_strip_en_event *event = cast_led_strip_en_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "enabled:%u", event->enabled); +} + +static void profile_led_strip_en_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct led_strip_en_event *event = cast_led_strip_en_event(aeh); + + nrf_profiler_log_encode_uint8(buf, event->enabled); +} + +APP_EVENT_INFO_DEFINE(led_strip_en_event, + ENCODE(NRF_PROFILER_ARG_U8), + ENCODE("enabled"), + profile_led_strip_en_event); + +APP_EVENT_TYPE_DEFINE(led_strip_en_event, + log_led_strip_en_event, + &led_strip_en_event_info, + APP_EVENT_FLAGS_CREATE( + APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/led_strip_module.c b/src/led_strip_module.c new file mode 100644 index 0000000..e4fe423 --- /dev/null +++ b/src/led_strip_module.c @@ -0,0 +1,281 @@ +#include +#include +#include + +#include + +#define MODULE led_strip_module +#include +#include + +#include +#include +#include +#include +#include + +#include "led_strip_en_event.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) + +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"); + +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 struct led_rgb pixels[LED_STRIP_NUM_PIXELS]; +static struct k_work_delayable effect_work; +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; + + err = led_strip_update_rgb(strip, pixels, ARRAY_SIZE(pixels)); + if (err) { + LOG_WRN("led_strip_update_rgb failed (%d)", err); + } + + return err; +} + +static int set_strip_power(bool on) +{ + int err; + + err = gpio_pin_set_dt(&strip_en, on ? 1 : 0); + if (err) { + LOG_WRN("LED strip EN set failed (%d)", err); + } + + return err; +} + +static void render_effect_step(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; + } +} + +static void effect_work_handler(struct k_work *work) +{ + 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); +} + +static int enable_effect_output(void) +{ + int err; + + err = set_strip_power(true); + if (err) { + return err; + } + + render_effect_step(); + err = submit_frame(); + if (err) { + return err; + } + + k_work_reschedule(&effect_work, LED_STRIP_EFFECT_INTERVAL); + return 0; +} + +static void disable_effect_output(void) +{ + k_work_cancel_delayable(&effect_work); + clear_pixels(); + (void)submit_frame(); + (void)set_strip_power(false); +} + +static int module_init(void) +{ + int err; + + if (!device_is_ready(strip)) { + LOG_ERR("LED strip device %s not ready", strip->name); + return -ENODEV; + } + + if (!gpio_is_ready_dt(&strip_en)) { + LOG_ERR("LED strip EN 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); + return err; + } + + k_work_init_delayable(&effect_work, effect_work_handler); + clear_pixels(); + effect_step = 0U; + + LOG_INF("LED strip ready len:%u", (uint32_t)led_strip_length(strip)); + return 0; +} + +static int module_start(void) +{ + int err; + + if (running) { + return 0; + } + + running = true; + + if (!enabled) { + return 0; + } + + err = enable_effect_output(); + if (err) { + running = false; + } + + return err; +} + +static void module_pause(void) +{ + if (!running) { + return; + } + + disable_effect_output(); + running = false; +} + +static bool handle_led_strip_en_event(const struct led_strip_en_event *event) +{ + enabled = event->enabled; + + if (!running) { + return false; + } + + if (enabled) { + int err = enable_effect_output(); + + if (err) { + module_set_state(MODULE_STATE_ERROR); + } + } else { + disable_effect_output(); + } + + return false; +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_led_strip_en_event(aeh)) { + return handle_led_strip_en_event(cast_led_strip_en_event(aeh)); + } + + if (is_module_state_event(aeh)) { + const struct module_state_event *event = cast_module_state_event(aeh); + + if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { + int err; + + if (!initialized) { + err = module_init(); + if (err) { + module_set_state(MODULE_STATE_ERROR); + return false; + } + + initialized = true; + } + + err = module_start(); + if (err) { + module_set_state(MODULE_STATE_ERROR); + } else { + module_set_state(MODULE_STATE_READY); + } + } + + return false; + } + + if (is_power_down_event(aeh)) { + if (initialized) { + module_pause(); + module_set_state(MODULE_STATE_STANDBY); + } + + return false; + } + + if (is_wake_up_event(aeh)) { + if (initialized) { + int err = module_start(); + + if (err) { + module_set_state(MODULE_STATE_ERROR); + } else { + module_set_state(MODULE_STATE_READY); + } + } + + return false; + } + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, led_strip_en_event); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); +APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); +APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);