diff --git a/components/emm_v5/emm_v5.c b/components/emm_v5/emm_v5.c index 3388412..6f987ba 100644 --- a/components/emm_v5/emm_v5.c +++ b/components/emm_v5/emm_v5.c @@ -42,12 +42,8 @@ void Emm_V5_Init(uart_port_t port, int tx_pin, int rx_pin) UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - gpio_reset_pin(EMM_V5_LED_GPIO); - gpio_set_direction(EMM_V5_LED_GPIO, GPIO_MODE_OUTPUT); - gpio_set_level(EMM_V5_LED_GPIO, 0); - - ESP_LOGI(TAG, "初始化完成 (UART%d, TX=%d, RX=%d, LED=GPIO%d)", - s_uart_port, tx_pin, rx_pin, EMM_V5_LED_GPIO); + ESP_LOGI(TAG, "初始化完成 (UART%d, TX=%d, RX=%d)", + s_uart_port, tx_pin, rx_pin); } /* ============================================================ diff --git a/components/emm_v5/emm_v5.h b/components/emm_v5/emm_v5.h index 182e5cd..b19da66 100644 --- a/components/emm_v5/emm_v5.h +++ b/components/emm_v5/emm_v5.h @@ -27,9 +27,6 @@ extern "C" { #define EMM_V5_UART_BAUDRATE 115200 #define EMM_V5_UART_BUF_SIZE 256 -/* LED 指示 (GPIO2 为多数 ESP32 板载 LED) */ -#define EMM_V5_LED_GPIO GPIO_NUM_2 - /* 接收超时时间 (毫秒) */ #define EMM_V5_RX_TIMEOUT_MS 100 diff --git a/components/rgb/CMakeLists.txt b/components/rgb/CMakeLists.txt new file mode 100644 index 0000000..08fb5ce --- /dev/null +++ b/components/rgb/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "rgb.c" + INCLUDE_DIRS "." + REQUIRES esp_driver_rmt driver) diff --git a/components/rgb/rgb.c b/components/rgb/rgb.c new file mode 100644 index 0000000..c0dd7a9 --- /dev/null +++ b/components/rgb/rgb.c @@ -0,0 +1,223 @@ +/** + * @file rgb.c + * @brief 单颗 WS2812 RGB LED 控制实现 (RMT) + * + * 基于 ESP-IDF RMT 外设, 使用 bytes encoder 编码 WS2812 时序: + * T0H=0.3µs T0L=0.9µs + * T1H=0.9µs T1L=0.3µs + * RESET > 50µs + * 数据格式: GRB (MSB first) + */ + +#include "rgb.h" +#include "esp_check.h" +#include "esp_log.h" +#include "driver/rmt_tx.h" +#include "freertos/FreeRTOS.h" +#include + +static const char *TAG = "RGB"; + +#define RESOLUTION_HZ 10000000 /* 10MHz, 1 tick = 0.1µs */ + +/* ── LED Strip Encoder (参考 esp-idf 官方 led_strip 示例) ── */ + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + int state; + rmt_symbol_word_t reset_code; +} rmt_led_strip_encoder_t; + +static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, + rmt_channel_handle_t channel, + const void *primary_data, + size_t data_size, + rmt_encode_state_t *ret_state) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; + rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; + rmt_encode_state_t session_state = RMT_ENCODING_RESET; + rmt_encode_state_t state = RMT_ENCODING_RESET; + size_t encoded_symbols = 0; + + switch (led_encoder->state) { + case 0: /* 发送 GRB 数据 */ + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, + primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 1; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; + } + /* fall-through */ + case 1: /* 发送 RESET 码 */ + encoded_symbols += copy_encoder->encode(copy_encoder, channel, + &led_encoder->reset_code, + sizeof(led_encoder->reset_code), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = RMT_ENCODING_RESET; + state |= RMT_ENCODING_COMPLETE; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; + } + break; + } + +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_del_encoder(led_encoder->bytes_encoder); + rmt_del_encoder(led_encoder->copy_encoder); + free(led_encoder); + return ESP_OK; +} + +static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_reset(led_encoder->bytes_encoder); + rmt_encoder_reset(led_encoder->copy_encoder); + led_encoder->state = RMT_ENCODING_RESET; + return ESP_OK; +} + +static esp_err_t rmt_new_led_strip_encoder(rmt_encoder_handle_t *ret_encoder) +{ + rmt_led_strip_encoder_t *led_encoder = NULL; + ESP_RETURN_ON_FALSE(ret_encoder, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + led_encoder = rmt_alloc_encoder_mem(sizeof(rmt_led_strip_encoder_t)); + ESP_RETURN_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, TAG, "no mem for led strip encoder"); + + led_encoder->base.encode = rmt_encode_led_strip; + led_encoder->base.del = rmt_del_led_strip_encoder; + led_encoder->base.reset = rmt_led_strip_encoder_reset; + + /* WS2812 时序 (10MHz 分辨率下 duration 直接等于 0.1µs 单位) */ + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 3, /* T0H = 0.3µs */ + .level1 = 0, + .duration1 = 9, /* T0L = 0.9µs */ + }, + .bit1 = { + .level0 = 1, + .duration0 = 9, /* T1H = 0.9µs */ + .level1 = 0, + .duration1 = 3, /* T1L = 0.3µs */ + }, + .flags.msb_first = 1, /* WS2812 高位在前, 顺序: G7..G0 R7..R0 B7..B0 */ + }; + ESP_RETURN_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, + &led_encoder->bytes_encoder), TAG, "create bytes encoder failed"); + + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_RETURN_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, + &led_encoder->copy_encoder), TAG, "create copy encoder failed"); + + /* 复位码: 低电平 >50µs */ + uint32_t reset_ticks = RESOLUTION_HZ / 1000000 * 50 / 2; /* 250 ticks */ + led_encoder->reset_code = (rmt_symbol_word_t){ + .level0 = 0, + .duration0 = reset_ticks, + .level1 = 0, + .duration1 = reset_ticks, + }; + + *ret_encoder = &led_encoder->base; + return ESP_OK; +} + +/* ── 模块静态变量 ── */ + +static rmt_channel_handle_t s_led_chan = NULL; +static rmt_encoder_handle_t s_led_encoder = NULL; + +/* ── 内部: 发送 GRB 数据 ── */ + +static void rgb_transmit(uint8_t g, uint8_t r, uint8_t b) +{ + uint8_t grb[3] = { g, r, b }; + + rmt_transmit_config_t tx_config = { .loop_count = 0 }; + ESP_ERROR_CHECK(rmt_transmit(s_led_chan, s_led_encoder, + grb, sizeof(grb), &tx_config)); + ESP_ERROR_CHECK(rmt_tx_wait_all_done(s_led_chan, portMAX_DELAY)); +} + +/* ── 公开接口 ── */ + +void rgb_init(int gpio) +{ + if (s_led_chan) { + ESP_LOGW(TAG, "已初始化, 跳过"); + return; + } + + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = gpio, + .mem_block_symbols = 64, + .resolution_hz = RESOLUTION_HZ, + .trans_queue_depth = 4, + }; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &s_led_chan)); + + ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&s_led_encoder)); + + ESP_ERROR_CHECK(rmt_enable(s_led_chan)); + + ESP_LOGI(TAG, "初始化完成 (GPIO%d)", gpio); +} + +void rgb_set(uint8_t r, uint8_t g, uint8_t b) +{ + rgb_transmit(g, r, b); +} + +void rgb_set_hsv(uint16_t h, uint8_t s, uint8_t v) +{ + h %= 360; + + uint32_t rgb_max = (uint32_t)(v * 2.55f); + uint32_t rgb_min = rgb_max * (100 - s) / 100; + + uint32_t i = h / 60; + uint32_t diff = h % 60; + uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60; + + uint32_t r = 0, g = 0, b = 0; + + switch (i) { + case 0: r = rgb_max; g = rgb_min + rgb_adj; b = rgb_min; break; + case 1: r = rgb_max - rgb_adj; g = rgb_max; b = rgb_min; break; + case 2: r = rgb_min; g = rgb_max; b = rgb_min + rgb_adj; break; + case 3: r = rgb_min; g = rgb_max - rgb_adj; b = rgb_max; break; + case 4: r = rgb_min + rgb_adj; g = rgb_min; b = rgb_max; break; + default: r = rgb_max; g = rgb_min; b = rgb_max - rgb_adj; break; + } + + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + + rgb_transmit((uint8_t)g, (uint8_t)r, (uint8_t)b); +} + +void rgb_off(void) +{ + rgb_transmit(0, 0, 0); +} diff --git a/components/rgb/rgb.h b/components/rgb/rgb.h new file mode 100644 index 0000000..03ff8f4 --- /dev/null +++ b/components/rgb/rgb.h @@ -0,0 +1,49 @@ +/** + * @file rgb.h + * @brief 单颗 WS2812 RGB LED 控制 (RMT) + * + * 使用 RMT 外设驱动一颗 WS2812 / SK6812 等可寻址 RGB LED。 + * 提供 RGB 直接设色与 HSV 设色, 调用即刷新, 无需手动 flush。 + */ + +#ifndef RGB_H +#define RGB_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 初始化 RGB LED + * @param gpio 数据引脚编号 + */ +void rgb_init(int gpio); + +/** + * @brief 设置 RGB 颜色 (立即生效) + * @param r 红色 0~255 + * @param g 绿色 0~255 + * @param b 蓝色 0~255 + */ +void rgb_set(uint8_t r, uint8_t g, uint8_t b); + +/** + * @brief 以 HSV 色彩设置颜色 (立即生效) + * @param h 色相 0~359 + * @param s 饱和度 0~100 + * @param v 明度 0~100 + */ +void rgb_set_hsv(uint16_t h, uint8_t s, uint8_t v); + +/** + * @brief 熄灭 LED + */ +void rgb_off(void); + +#ifdef __cplusplus +} +#endif + +#endif /* RGB_H */ diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 4b3bc9a..45e44d6 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRCS "main.c" INCLUDE_DIRS "." - PRIV_REQUIRES emm_v5 magnet servo web_ctrl protocol) + PRIV_REQUIRES emm_v5 magnet servo web_ctrl protocol rgb) diff --git a/main/main.c b/main/main.c index 3f9073c..ab5858b 100644 --- a/main/main.c +++ b/main/main.c @@ -11,6 +11,7 @@ * ESP32 GPIO12 -> 协议 UART RX (← MPU) (UART0) * ESP32 GPIO2 -> I2C SDA (← MPU) * ESP32 GPIO3 -> I2C SCL + * ESP32 GPIO27 -> RGB LED (WS2812) * ESP32 GND -> 电机 GND / MPU GND * * 注: 日志控制台已切换到 USB Serial/JTAG (COM 口监视), @@ -24,6 +25,7 @@ #include "servo.h" #include "web_ctrl.h" #include "protocol.h" +#include "rgb.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" @@ -32,6 +34,10 @@ static const char *TAG = "MAIN"; void app_main(void) { + /* 初始化 RGB LED (GPIO27, 最先启动, 指示系统状态) */ + rgb_init(GPIO_NUM_27); + rgb_set(32, 32, 0); /* 黄色 (低亮), 表示启动中 */ + /* 初始化电机驱动 (UART1, TX=GPIO6, RX=GPIO7, LED=GPIO2) */ Emm_V5_Init(UART_NUM_1, GPIO_NUM_6, GPIO_NUM_7); @@ -54,9 +60,16 @@ void app_main(void) /* 启动 WiFi + HTTP 网页控制服务器 */ web_ctrl_init(); + /* 系统就绪: 绿灯 (低亮) */ + rgb_set(0, 32, 0); ESP_LOGI(TAG, "系统就绪, 等待网页指令..."); + while (1) { - vTaskDelay(pdMS_TO_TICKS(1000)); + /* 心跳指示: 绿灯慢闪 (低亮) */ + rgb_set(0, 32, 0); + vTaskDelay(pdMS_TO_TICKS(500)); + rgb_off(); + vTaskDelay(pdMS_TO_TICKS(500)); } }