STM32 进阶封神之路(十四):语音交互实战 ——SU03T 语音识别模块从固件制作到 STM32 控制(串口通信 + 命令响应)

STM32 进阶封神之路(十四):语音交互实战 ------SU03T 语音识别模块从固件制作到 STM32 控制(串口通信 + 命令响应)

上一篇我们实现了空气质量传感器的串口数据采集与解析,这一篇聚焦嵌入式系统中极具实用性的 "人机交互层"------SU03T 语音识别模块。SU03T 是低成本、高识别率的离线语音模块,支持自定义命令词,通过串口与 STM32 通信,可实现 "语音控制硬件""语音查询数据" 等场景,是入门语音交互的最佳选择。

本文基于实战资料,从语音模块核心认知、固件制作(命令词配置)、硬件连接,到 STM32 代码实现(语音识别 + 命令响应 + 联动传感器),手把手带你打造 "语音控制空气质量监测系统",让你掌握语音模块与 MCU 的串口交互逻辑!

一、复习回顾:语音模块通信核心基础

在深入实战前,衔接关键基础,确保知识连贯:

  1. 语音模块通信方式 :SU03T 采用串口 UART通信(TTL 电平,默认 9600bps 8N1),与之前的传感器、串口实战逻辑一致;
  2. 交互核心逻辑:离线语音模块→识别命令词→串口输出对应指令码→STM32 接收解析→执行操作(控制 LED / 查询传感器数据);
  3. 命令词与指令码映射:需通过固件工具自定义命令词(如 "打开 LED""查询空气质量"),并绑定唯一串口输出码(如 0x01、0x02);
  4. 联动核心:结合上一篇的 KQM6600 传感器,实现 "语音查询→STM32 读取传感器数据→串口打印 + 语音播报(可选)" 的完整闭环。

二、语音模块基础认知:分类与 SU03T 核心优势

嵌入式语音模块按工作方式可分为 "离线语音" 和 "在线语音",不同类型适配不同场景,SU03T 属于离线语音模块,是新手入门的首选。

1. 语音模块核心分类

表格

类型 核心特点 依赖条件 响应速度 典型应用
离线语音模块(SU03T) 无需网络、成本低、隐私性强、配置简单 内置离线识别算法,无需云服务 快(≤300ms) 智能家居控制、设备本地语音交互
在线语音模块(如科大讯飞 AIUI) 识别率高、支持自然语言、可更新词汇 依赖网络(WiFi/4G)、云服务 中等(≥500ms) 智能音箱、远程语音控制

2. SU03T 模块核心优势与参数

(1)核心优势
  • 离线识别:无需网络,上电即可工作,适配无网络场景;
  • 自定义命令词:支持最多 15 个自定义命令词,可按需配置;
  • 串口输出:识别成功后通过串口输出固定指令码(ASCII / 十六进制),易解析;
  • 宽电压供电:3.3V~5V 兼容,直接与 STM32 对接(无需电平转换);
  • 识别距离远:无遮挡环境下识别距离可达 3~5 米,满足日常场景。
(2)关键参数(实战必知)
  • 通信参数:默认 9600bps、8 位数据位、无校验、1 位停止位(8N1);
  • 输出格式:识别成功后输出 "指令码 + 回车换行"(如 "01\r\n""02\r\n");
  • 唤醒方式:支持 "按键唤醒" 或 "语音唤醒"(需固件配置);
  • 工作电流:待机≤5mA,识别时≤20mA,低功耗适配电池供电;
  • 引脚定义:VCC(电源)、GND(地)、TX(模块发送)、RX(模块接收)、KEY(唤醒 / 配置引脚)。

三、SU03T 固件制作:自定义命令词与指令码(核心步骤)

SU03T 的核心是 "自定义命令词",需通过官方工具制作固件并烧录到模块,这是实现语音控制的前提 ------ 没有固件配置,模块无法识别自定义命令。

1. 固件制作工具与准备

  • 工具名称:SU-Utils(官方免费工具,支持 Windows 系统);
  • 准备文件:SU03T 模块固件模板(官网下载,如 "SU03T_V1.0.bin");
  • 硬件准备:USB-TTL 模块(CH340G)、SU03T 模块、杜邦线、电脑。

2. 固件制作步骤(自定义命令词)

步骤 1:工具安装与模块连接(烧录模式)
  1. 安装 SU-Utils 工具,打开后选择 "固件制作" 功能;
  2. 连接 SU03T 与 USB-TTL 模块(烧录模式):
    • SU03T VCC → USB-TTL 3.3V;
    • SU03T GND → USB-TTL GND;
    • SU03T TX → USB-TTL RX;
    • SU03T RX → USB-TTL TX;
    • SU03T KEY → GND(进入烧录模式);
  3. 将 USB-TTL 模块插入电脑,设备管理器识别到串口(如 COM3)。
步骤 2:自定义命令词与指令码映射
  1. 在 SU-Utils 中加载固件模板("SU03T_V1.0.bin");
  2. 进入 "命令词配置" 界面,删除默认命令词,添加自定义命令(最多 15 个),示例配置如下:| 命令词 ID | 命令词内容 | 串口输出指令码(ASCII 格式) | 功能描述 ||----------|------------|----------------------------|----------|| 1 | 打开 LED | 01\r\n | 控制 PB0 LED 点亮 || 2 | 关闭 LED | 02\r\n | 控制 PB0 LED 熄灭 || 3 | 查询空气质量 | 03\r\n | 读取 KQM6600 数据并打印 || 4 | 检测甲醛 | 04\r\n | 读取甲醛浓度并打印 || 5 | 关闭所有设备 | 05\r\n | 熄灭 LED,重置状态 |
  3. 配置唤醒方式(可选):选择 "语音唤醒"(如唤醒词 "小安小安")或 "按键唤醒"(按下 KEY 引脚唤醒);
  4. 确认串口参数(默认 9600bps 8N1,与 STM32 保持一致)。
步骤 3:固件烧录
  1. 点击工具 "生成固件",保存为 "my_su03t.bin";
  2. 选择烧录串口(如 COM3),点击 "开始烧录",烧录成功后提示 "烧录完成";
  3. 断开 KEY 与 GND 的连接,模块退出烧录模式,切换为正常工作模式。

3. 固件验证(可选)

  1. 重新连接模块与 USB-TTL(正常工作模式,KEY 悬空);
  2. 打开串口助手(9600bps 8N1),对着模块说出命令词(如 "打开 LED");
  3. 串口助手接收到 "01\r\n",说明固件配置成功,模块识别正常。

四、硬件连接:STM32+SU03T+KQM6600 联动

1. 核心硬件清单

  • 主控:STM32F103C8T6 最小系统板;
  • 语音模块:SU03T(已烧录自定义固件);
  • 传感器:KQM6600 空气质量传感器(上一篇实战模块);
  • 其他:LED(含 1KΩ 限流电阻)、USB-TTL 模块(供电 + 调试)、杜邦线。

2. 完整硬件连接图

plaintext

复制代码
STM32F103C8T6          SU03T语音模块          KQM6600传感器          辅助电路
PA9(USART1_TX)  ←→  RX(模块接收)          RX(传感器接收)       PB0 → 1KΩ电阻 → LED → GND
PA10(USART1_RX) ←→  TX(模块发送)          TX(传感器发送)       3.3V → 传感器VCC
3.3V              ←→  VCC                    VCC
GND               ←→  GND                    GND
KEY(模块)       ←→  悬空(语音唤醒模式)
  • 关键注意:
    1. STM32 USART1 同时与 SU03T 和 KQM6600 通信?不!实际是 "分时复用"------SU03T 输出指令码,STM32 接收后执行对应操作(如查询传感器时,STM32 通过 USART1 向 KQM6600 发送指令,接收数据);
    2. 共地原则:所有模块必须共地,否则串口数据乱码;
    3. 供电保障:若同时连接多个模块,建议使用外部 3.3V 电源供电(避免 STM32 引脚供电不足)。

3. 模块工作逻辑

  1. SU03T 识别语音命令→通过串口发送指令码(如 "查询空气质量"→"03\r\n");
  2. STM32 USART1 接收指令码→解析命令;
  3. 执行对应操作:
    • 控制 LED:直接操作 GPIO;
    • 查询传感器:STM32 通过 USART1 向 KQM6600 发送读取指令(部分传感器默认主动上报,无需发送指令),接收数据后解析并打印。

五、STM32 实战代码:语音识别 + 命令响应 + 传感器联动

核心流程:串口初始化(双模块适配)→ 语音指令接收→ 指令解析→ 执行操作(LED 控制 / 传感器查询)→ 数据输出,代码基于上一篇 KQM6600 传感器代码扩展,保持兼容性。

1. 核心头文件与数据结构

c

运行

复制代码
#include "stm32f10x.h"
#include <stdio.h>
#include <string.h>

// 空气质量数据结构体(复用上一篇代码)
typedef struct {
    uint16_t pm25;
    float hcho;
    float tvoc;
    int8_t temperature;
    uint8_t humidity;
    uint8_t data_valid;
} AirQuality_TypeDef;

// 语音指令枚举(与固件命令词一一对应)
typedef enum {
    VOICE_CMD_NONE = 0,
    VOICE_CMD_LED_ON = 1,    // 01→打开LED
    VOICE_CMD_LED_OFF = 2,   // 02→关闭LED
    VOICE_CMD_AIR_QUERY = 3, // 03→查询空气质量
    VOICE_CMD_HCHO_QUERY = 4,// 04→检测甲醛
    VOICE_CMD_ALL_OFF = 5    // 05→关闭所有设备
} Voice_CmdTypeDef;

// 全局变量
AirQuality_TypeDef air_data;
uint8_t rx_buffer[32];      // 串口接收缓冲区(接收语音指令/传感器数据)
uint16_t rx_index = 0;       // 缓冲区索引
Voice_CmdTypeDef current_cmd = VOICE_CMD_NONE; // 当前语音指令

2. 串口初始化(USART1,9600bps 8N1,中断接收)

c

运行

复制代码
void USART1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    // 1. 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    // 2. 配置GPIO(PA9=TX,PA10=RX)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 3. 配置串口参数(适配SU03T和KQM6600,均为9600bps 8N1)
    USART_InitStruct.USART_BaudRate = 9600;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(USART1, &USART_InitStruct);

    // 4. 配置接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    // 5. 使能USART1
    USART_Cmd(USART1, ENABLE);

    // 初始化数据结构体
    memset(&air_data, 0, sizeof(AirQuality_TypeDef));
    air_data.data_valid = 0;
}

3. GPIO 初始化(LED 控制)

c

运行

复制代码
void GPIO_Init_Config(void) {
    GPIO_InitTypeDef GPIO_InitStruct;

    // 使能GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    // 配置PB0为推挽输出(控制LED)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // 初始状态:LED熄灭
    GPIO_SetBits(GPIOB, GPIO_Pin_0);
}

4. 串口中断接收(区分语音指令与传感器数据)

核心难点:STM32 通过 USART1 同时接收 SU03T 的语音指令和 KQM6600 的传感器数据,需通过 "帧格式" 区分两者:

  • 语音指令:2 字节 ASCII 码 + 帧尾(如 "01\r\n""03\r\n");
  • 传感器数据:帧头 "KQ"+ 数据段 + 帧尾 "\r\n"(复用上一篇协议)。

c

运行

复制代码
void USART1_IRQHandler(void) {
    uint8_t rx_byte;
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        rx_byte = (uint8_t)USART_ReceiveData(USART1);

        // 缓冲区循环存储
        if(rx_index < 31) {
            rx_buffer[rx_index++] = rx_byte;

            // 检测帧尾"\r\n",触发解析
            if(rx_byte == 0x0A && rx_index >=2 && rx_buffer[rx_index-2] == 0x0D) {
                rx_buffer[rx_index] = '\0'; // 字符串结束符

                // 区分语音指令和传感器数据:语音指令为2字节数字(如"01""03")
                if(rx_index == 4 && rx_buffer[0] >= '0' && rx_buffer[0] <= '9' && rx_buffer[1] >= '0' && rx_buffer[1] <= '9') {
                    // 解析语音指令(ASCII转数字)
                    current_cmd = (Voice_CmdTypeDef)(atoi((char*)rx_buffer));
                } else if(rx_index > 4 && rx_buffer[0] == 'K' && rx_buffer[1] == 'Q') {
                    // 解析传感器数据(复用上一篇解析函数)
                    air_data.data_valid = 2;
                }

                rx_index = 0; // 重置缓冲区
            }
        } else {
            rx_index = 0; // 缓冲区溢出,重置
        }

        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

5. 命令响应函数(执行语音指令)

c

运行

复制代码
// 串口发送函数(复用)
void USART1_SendByte(uint8_t data) {
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, data);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}

// 字符串发送函数(复用)
void USART1_SendString(uint8_t *str) {
    while(*str != '\0') {
        USART1_SendByte(*str);
        str++;
    }
    USART1_SendByte('\r');
    USART1_SendByte('\n');
}

// 传感器数据解析函数(复用上一篇KQM6600解析逻辑)
void KQM6600_ParseData(void);

// 语音指令响应函数
void Voice_Cmd_Response(void) {
    if(current_cmd == VOICE_CMD_NONE) return;

    switch(current_cmd) {
        case VOICE_CMD_LED_ON:
            // 打开LED
            GPIO_ResetBits(GPIOB, GPIO_Pin_0);
            USART1_SendString("Voice Cmd: LED ON");
            break;

        case VOICE_CMD_LED_OFF:
            // 关闭LED
            GPIO_SetBits(GPIOB, GPIO_Pin_0);
            USART1_SendString("Voice Cmd: LED OFF");
            break;

        case VOICE_CMD_AIR_QUERY:
            // 查询空气质量(等待传感器数据有效)
            USART1_SendString("Query Air Quality...");
            if(air_data.data_valid == 1) {
                USART1_SendString("=== Air Quality Data ===");
                printf("PM2.5: %d μg/m³\r\n", air_data.pm25);
                printf("HCHO: %.2f mg/m³\r\n", air_data.hcho);
                printf("TVOC: %.2f mg/m³\r\n", air_data.tvoc);
                printf("Temperature: %d ℃\r\n", air_data.temperature);
                printf("Humidity: %d %%RH\r\n", air_data.humidity);
                USART1_SendString("=======================");
            } else {
                USART1_SendString("Air Data Not Ready!");
            }
            break;

        case VOICE_CMD_HCHO_QUERY:
            // 检测甲醛
            USART1_SendString("Query HCHO...");
            if(air_data.data_valid == 1) {
                printf("HCHO Concentration: %.2f mg/m³\r\n", air_data.hcho);
            } else {
                USART1_SendString("HCHO Data Not Ready!");
            }
            break;

        case VOICE_CMD_ALL_OFF:
            // 关闭所有设备
            GPIO_SetBits(GPIOB, GPIO_Pin_0);
            air_data.data_valid = 0;
            USART1_SendString("Voice Cmd: All Devices OFF");
            break;

        default:
            USART1_SendString("Unknown Voice Cmd!");
            break;
    }

    current_cmd = VOICE_CMD_NONE; // 重置指令
}

6. 主函数:完整联动逻辑

c

运行

复制代码
// printf重定向(复用)
int fputc(int ch, FILE *f) {
    USART1_SendByte((uint8_t)ch);
    return ch;
}

// 延时函数(复用)
void delay_ms(uint32_t ms) {
    uint32_t i, j;
    for(i = 0; i < ms; i++) {
        for(j = 0; j < 1000; j++);
    }
}

int main(void) {
    // 初始化
    USART1_Init();
    GPIO_Init_Config();

    USART1_SendString("STM32 Voice Control System Init Success!");
    USART1_SendString("Please Speak Command (e.g., 打开LED, 查询空气质量)");

    while(1) {
        // 解析传感器数据(若有)
        if(air_data.data_valid == 2) {
            KQM6600_ParseData();
        }

        // 响应语音指令
        if(current_cmd != VOICE_CMD_NONE) {
            Voice_Cmd_Response();
            delay_ms(500); // 避免指令重复响应
        }

        // 主循环空闲,可添加其他任务
    }
}

六、实验现象与验证

1. 硬件启动

  • 所有模块供电正常,STM32、SU03T、KQM6600 电源指示灯亮起;
  • 串口助手(9600bps 8N1)接收初始化信息:"STM32 Voice Control System Init Success!"。

2. 语音指令响应

表格

语音命令词 串口输出指令码 STM32 执行结果 串口打印信息
打开 LED 01\r\n PB0 LED 点亮 Voice Cmd: LED ON
关闭 LED 02\r\n PB0 LED 熄灭 Voice Cmd: LED OFF
查询空气质量 03\r\n 读取 KQM6600 数据 完整空气质量参数列表
检测甲醛 04\r\n 读取甲醛浓度 HCHO Concentration: 0.02 mg/m³
关闭所有设备 05\r\n LED 熄灭,数据重置 Voice Cmd: All Devices OFF

3. 关键验证点

  • 语音识别率:无遮挡环境下识别率≥90%,响应时间≤300ms;
  • 数据联动:查询空气质量时,STM32 成功读取 KQM6600 数据并打印,实现 "语音 - 传感器 - 串口" 闭环;
  • 指令区分:STM32 正确区分语音指令和传感器数据,无解析冲突。

七、语音模块实战避坑指南(10 + 高频错误)

1. 语音模块无响应(不识别命令词)

  • 原因 1:固件未烧录或烧录失败;解决:重新按步骤烧录固件,确保 KEY 引脚接 GND 进入烧录模式,工具提示 "烧录完成";
  • 原因 2:模块未唤醒(语音唤醒未配置);解决:配置语音唤醒词,或按下 KEY 引脚唤醒模块后再说话;
  • 原因 3:命令词发音不标准(离线模块对发音敏感);解决:录制命令词时使用标准普通话,避免方言或语速过快。

2. STM32 无法解析语音指令

  • 原因 1:串口参数不匹配(模块默认 9600bps,误设为 115200);解决:统一串口参数为 9600bps 8N1;
  • 原因 2:指令码格式错误(固件配置为十六进制,代码按 ASCII 解析);解决:固件中明确指令码输出格式(推荐 ASCII,易解析),代码解析逻辑与之匹配;
  • 原因 3:缓冲区溢出(指令码未及时解析);解决:优化中断接收逻辑,缩短缓冲区处理时间,避免数据覆盖。

3. 传感器与语音指令冲突

  • 原因:STM32 同时接收两种数据,解析逻辑混淆;解决:通过帧头区分(语音指令为数字,传感器数据帧头为 "KQ"),在中断中添加帧头判断。

八、总结:语音交互实战核心要点与进阶方向

1. 核心要点回顾

  • 语音模块实战核心:固件配置(命令词 + 指令码)+ 串口解析 + 命令响应
  • 数据区分关键:通过 "帧头 / 格式特征" 区分不同模块的串口数据,避免解析冲突;
  • 联动逻辑:复用已有传感器代码,实现 "语音控制硬件""语音查询数据" 的实用场景;
  • 避坑核心:固件烧录正确、串口参数一致、指令解析逻辑与固件格式匹配。

2. 进阶学习方向

  • 语音播报:添加 SYN6288 语音合成模块(串口对接),实现 "查询结果语音播报"(如 "甲醛浓度 0.02 毫克每立方米");
  • 多命令扩展:配置更多命令词(如 "调节 LED 亮度""查询温度"),扩展 PWM 调光功能;
  • 按键唤醒优化:将 KEY 引脚连接到 STM32 GPIO,通过 STM32 控制模块唤醒(如长按按键唤醒语音识别);
  • 抗干扰设计:模块远离电源模块和高频电路,减少电磁干扰导致的识别率下降。

掌握 SU03T 语音模块实战后,你已具备嵌入式 "语音人机交互" 的核心能力,结合之前的传感器、串口、GPIO 知识,可搭建更复杂的智能系统(如语音控制智能家居、语音报警系统)。下一篇我们将学习 I2C 通信,实现 STM32 与 OLED 屏幕、EEPROM 的交互,让数据可视化!

相关推荐
m0_528174452 小时前
C++中的策略模式实战
开发语言·c++·算法
REDcker2 小时前
Android MediaCodec 架构与实现解析
android·架构
计算机安禾2 小时前
【C语言程序设计】第30篇:指针与字符串
c语言·开发语言·c++·算法·visualstudio·visual studio code·visual studio
信奥胡老师2 小时前
GESP 2026年3月C++三级(二进制回文串)
开发语言·c++·算法
小年糕是糕手2 小时前
【35天从0开始备战蓝桥杯 -- 刷题包】
c语言·jvm·数据结构·c++·算法·蓝桥杯
卤炖阑尾炎2 小时前
LNMP/LNAMP 架构部署实战:从环境搭建到 Discuz 论坛与动静分离实现
架构·php
lauo2 小时前
从“安全孤岛”到“信任基石”:ibbot智体机灵如何重新定义AI智能体的安全范式
人工智能·安全·智能手机·架构·开源·github
liuyao_xianhui2 小时前
动态规划_最长递增子序列_C++
java·开发语言·数据结构·c++·算法·链表·动态规划
luoganttcc2 小时前
华为 昇腾 架构怎么 解决这个 NCCL 通信问题
华为·架构