- 在CMakeLists.txt中添加SU-03T语音模块依赖。 - 在main.cpp中实现SU-03T接收回调函数,处理接收消息。 - 完善各UI源文件文档,包括动作、屏幕和字体,明确模块作用与数据流向。 - 更新主应用逻辑,初始化并启动SU-03T接收器。 - 修改过程中确保兼容性,保留原有接口。
398 lines
10 KiB
C
398 lines
10 KiB
C
/*
|
||
* 文件: 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 <ctype.h>
|
||
#include <string.h>
|
||
#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;
|
||
}
|