/* * 文件: components/su-03t/su-03t.c * 角色: SU-03T 语音模块串口协议适配 * 说明: * - 本文件用于实现当前模块的核心功能或接口定义。 * - 修改前请先确认该模块与其它任务/外设之间的数据流关系。 * - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。 */ /* * SU-03T 语音模块串口适配层 * * 设计目标: * 1) 提供统一的 UART2 初始化接口(RX=IO41, TX=IO42, 115200)。 * 2) 支持两类发送方式: * - 结构化帧发送: 头(2) + msgno(1) + params(N) + 尾(2) * - 原始十六进制串发送: 方便调试工具或协议快速验证。 * 3) 支持同步收帧与后台异步收帧回调,满足“主控主动发、模块随时上报”的场景。 */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "driver/uart.h" #include "esp_log.h" #include "su-03t.h" static const char *TAG = "su03t"; static bool s_inited = false; static TaskHandle_t s_rx_task = NULL; static volatile bool s_rx_running = false; static su03t_rx_callback_t s_rx_callback = NULL; static void *s_rx_user_ctx = NULL; static su03t_frame_format_t s_fmt = { .head_h = SU03T_DEFAULT_HEAD_H, .head_l = SU03T_DEFAULT_HEAD_L, .tail_h = SU03T_DEFAULT_TAIL_H, .tail_l = SU03T_DEFAULT_TAIL_L, }; /* * 将单个十六进制字符转换为 4bit 数值。 * 返回值: * - 0~15: 有效十六进制字符 * - -1 : 非法字符 */ /* 函数: hex_to_nibble * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static int hex_to_nibble(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } /* * SU-03T 后台接收任务。 * 逻辑: * 1) 循环调用 su03t_recv_frame() 解析帧。 * 2) 解析成功后,回调上层业务。 * 3) 超时属于正常空闲,不打印告警;仅对非超时错误告警。 */ /* 函数: su03t_rx_task * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void su03t_rx_task(void *arg) { (void)arg; su03t_frame_t frame; while (s_rx_running) { esp_err_t ret = su03t_recv_frame(&frame, 200); if (ret == ESP_OK) { if (s_rx_callback != NULL) { s_rx_callback(&frame, s_rx_user_ctx); } } else if (ret != ESP_ERR_TIMEOUT) { ESP_LOGW(TAG, "recv frame failed: %s", esp_err_to_name(ret)); } } s_rx_task = NULL; vTaskDelete(NULL); } /* * 初始化 UART2 驱动并配置 SU-03T 引脚。 * 注意: 若驱动已安装,uart_driver_install 可能返回 ESP_ERR_INVALID_STATE, * 这里将其视为“可继续配置”的情况。 */ /* 函数: su03t_init * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t su03t_init(void) { const uart_config_t uart_cfg = { .baud_rate = SU03T_UART_BAUD, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT, }; esp_err_t ret = uart_driver_install(SU03T_UART_PORT, 1024, 1024, 0, NULL, 0); if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { return ret; } ret = uart_param_config(SU03T_UART_PORT, &uart_cfg); if (ret != ESP_OK) { return ret; } ret = uart_set_pin(SU03T_UART_PORT, SU03T_UART_TX_IO, SU03T_UART_RX_IO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); if (ret != ESP_OK) { return ret; } s_inited = true; ESP_LOGI(TAG, "init UART2 done, TX=%d RX=%d baud=%d", SU03T_UART_TX_IO, SU03T_UART_RX_IO, SU03T_UART_BAUD); return ESP_OK; } /* 设置当前帧头帧尾格式,后续发送与解析都按该格式执行。 */ /* 函数: su03t_set_frame_format * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t su03t_set_frame_format(const su03t_frame_format_t *fmt) { if (fmt == NULL) { return ESP_ERR_INVALID_ARG; } s_fmt = *fmt; return ESP_OK; } /* 获取当前帧格式,便于业务层调试或回显配置。 */ /* 函数: su03t_get_frame_format * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t su03t_get_frame_format(su03t_frame_format_t *fmt_out) { if (fmt_out == NULL) { return ESP_ERR_INVALID_ARG; } *fmt_out = s_fmt; return ESP_OK; } /* * 按协议帧格式发送: * [head_h head_l msgno params... tail_h tail_l] */ /* 函数: su03t_send_frame * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t su03t_send_frame(uint8_t msgno, const uint8_t *params, size_t params_len) { if (!s_inited) { return ESP_ERR_INVALID_STATE; } if (params_len > SU03T_MAX_PARAM_LEN) { return ESP_ERR_INVALID_SIZE; } if (params_len > 0 && params == NULL) { return ESP_ERR_INVALID_ARG; } uint8_t tx_buf[2 + 1 + SU03T_MAX_PARAM_LEN + 2]; size_t idx = 0; tx_buf[idx++] = s_fmt.head_h; tx_buf[idx++] = s_fmt.head_l; tx_buf[idx++] = msgno; if (params_len > 0) { memcpy(&tx_buf[idx], params, params_len); idx += params_len; } tx_buf[idx++] = s_fmt.tail_h; tx_buf[idx++] = s_fmt.tail_l; int written = uart_write_bytes(SU03T_UART_PORT, (const char *)tx_buf, idx); if (written < 0 || (size_t)written != idx) { return ESP_FAIL; } return uart_wait_tx_done(SU03T_UART_PORT, pdMS_TO_TICKS(100)); } /* * 发送十六进制字符串。 * 输入示例: "AA 55 01 11 22 55 AA" * 规则: * 1) 允许空白字符。 * 2) 必须是偶数个十六进制字符。 * 3) 遇到非法字符直接返回参数错误。 */ /* 函数: su03t_send_hex_string * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t su03t_send_hex_string(const char *hex_string) { if (!s_inited) { return ESP_ERR_INVALID_STATE; } if (hex_string == NULL) { return ESP_ERR_INVALID_ARG; } uint8_t tx_buf[2 + 1 + SU03T_MAX_PARAM_LEN + 2]; size_t out_len = 0; int high = -1; for (const char *p = hex_string; *p != '\0'; ++p) { if (isspace((unsigned char)*p)) { continue; } int n = hex_to_nibble(*p); if (n < 0) { return ESP_ERR_INVALID_ARG; } if (high < 0) { high = n; } else { if (out_len >= sizeof(tx_buf)) { return ESP_ERR_INVALID_SIZE; } tx_buf[out_len++] = (uint8_t)((high << 4) | n); high = -1; } } if (high >= 0) { return ESP_ERR_INVALID_ARG; } if (out_len == 0) { return ESP_ERR_INVALID_ARG; } int written = uart_write_bytes(SU03T_UART_PORT, (const char *)tx_buf, out_len); if (written < 0 || (size_t)written != out_len) { return ESP_FAIL; } return uart_wait_tx_done(SU03T_UART_PORT, pdMS_TO_TICKS(100)); } /* * 在超时时间内解析一帧。 * 协议格式: * head_h head_l msgno param... tail_h tail_l * 返回: * - ESP_OK: 成功拿到完整帧 * - ESP_ERR_TIMEOUT: 超时未收到完整帧 * - 其它: 参数或协议错误 */ /* 函数: su03t_recv_frame * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t su03t_recv_frame(su03t_frame_t *out_frame, uint32_t timeout_ms) { if (!s_inited) { return ESP_ERR_INVALID_STATE; } if (out_frame == NULL) { return ESP_ERR_INVALID_ARG; } memset(out_frame, 0, sizeof(*out_frame)); const TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms); const TickType_t start_tick = xTaskGetTickCount(); uint8_t byte = 0; uint8_t payload[1 + SU03T_MAX_PARAM_LEN]; size_t payload_len = 0; bool head_found = false; while ((xTaskGetTickCount() - start_tick) < timeout_ticks) { int n = uart_read_bytes(SU03T_UART_PORT, &byte, 1, pdMS_TO_TICKS(20)); if (n <= 0) { continue; } if (!head_found) { if (byte == s_fmt.head_h) { int n2 = uart_read_bytes(SU03T_UART_PORT, &byte, 1, pdMS_TO_TICKS(20)); if (n2 > 0 && byte == s_fmt.head_l) { head_found = true; payload_len = 0; } } continue; } if (payload_len >= sizeof(payload)) { head_found = false; payload_len = 0; continue; } payload[payload_len++] = byte; if (payload_len >= 3 && payload[payload_len - 2] == s_fmt.tail_h && payload[payload_len - 1] == s_fmt.tail_l) { size_t body_len = payload_len - 2; if (body_len < 1) { return ESP_ERR_INVALID_RESPONSE; } out_frame->msgno = payload[0]; out_frame->params_len = body_len - 1; if (out_frame->params_len > 0) { memcpy(out_frame->params, &payload[1], out_frame->params_len); } return ESP_OK; } } return ESP_ERR_TIMEOUT; } /* * 启动异步接收: * - callback: 每收到一帧就回调一次 * - task_stack_size/task_priority: 允许上层按系统负载调优 */ /* 函数: su03t_start_receiver * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t su03t_start_receiver(su03t_rx_callback_t callback, void *user_ctx, uint32_t task_stack_size, UBaseType_t task_priority) { if (!s_inited) { return ESP_ERR_INVALID_STATE; } if (callback == NULL) { return ESP_ERR_INVALID_ARG; } if (s_rx_task != NULL) { return ESP_ERR_INVALID_STATE; } if (task_stack_size == 0) { task_stack_size = 4096; } if (task_priority == 0) { task_priority = 5; } s_rx_callback = callback; s_rx_user_ctx = user_ctx; s_rx_running = true; BaseType_t ok = xTaskCreate(su03t_rx_task, "su03t_rx", task_stack_size, NULL, task_priority, &s_rx_task); if (ok != pdPASS) { s_rx_running = false; s_rx_callback = NULL; s_rx_user_ctx = NULL; return ESP_ERR_NO_MEM; } return ESP_OK; } /* * 停止异步接收。 * 该实现通过标志位让接收任务自行退出,避免跨任务强制删除导致资源状态不一致。 */ /* 函数: su03t_stop_receiver * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t su03t_stop_receiver(void) { if (s_rx_task == NULL) { return ESP_OK; } s_rx_running = false; return ESP_OK; }