Files
Smart-granary-code/components/su-03t/su-03t.c
Wang Beihong ffdb7065e3 功能:集成SU-03T语音模块,完善UI代码文档
- 在CMakeLists.txt中添加SU-03T语音模块依赖。
- 在main.cpp中实现SU-03T接收回调函数,处理接收消息。
- 完善各UI源文件文档,包括动作、屏幕和字体,明确模块作用与数据流向。
- 更新主应用逻辑,初始化并启动SU-03T接收器。
- 修改过程中确保兼容性,保留原有接口。
2026-04-22 01:06:10 +08:00

398 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 文件: 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;
}