STM32 进阶封神之路(十四):语音交互实战 ------SU03T 语音识别模块从固件制作到 STM32 控制(串口通信 + 命令响应)
上一篇我们实现了空气质量传感器的串口数据采集与解析,这一篇聚焦嵌入式系统中极具实用性的 "人机交互层"------SU03T 语音识别模块。SU03T 是低成本、高识别率的离线语音模块,支持自定义命令词,通过串口与 STM32 通信,可实现 "语音控制硬件""语音查询数据" 等场景,是入门语音交互的最佳选择。
本文基于实战资料,从语音模块核心认知、固件制作(命令词配置)、硬件连接,到 STM32 代码实现(语音识别 + 命令响应 + 联动传感器),手把手带你打造 "语音控制空气质量监测系统",让你掌握语音模块与 MCU 的串口交互逻辑!
一、复习回顾:语音模块通信核心基础
在深入实战前,衔接关键基础,确保知识连贯:
- 语音模块通信方式 :SU03T 采用串口 UART通信(TTL 电平,默认 9600bps 8N1),与之前的传感器、串口实战逻辑一致;
- 交互核心逻辑:离线语音模块→识别命令词→串口输出对应指令码→STM32 接收解析→执行操作(控制 LED / 查询传感器数据);
- 命令词与指令码映射:需通过固件工具自定义命令词(如 "打开 LED""查询空气质量"),并绑定唯一串口输出码(如 0x01、0x02);
- 联动核心:结合上一篇的 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:工具安装与模块连接(烧录模式)
- 安装 SU-Utils 工具,打开后选择 "固件制作" 功能;
- 连接 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(进入烧录模式);
- 将 USB-TTL 模块插入电脑,设备管理器识别到串口(如 COM3)。
步骤 2:自定义命令词与指令码映射
- 在 SU-Utils 中加载固件模板("SU03T_V1.0.bin");
- 进入 "命令词配置" 界面,删除默认命令词,添加自定义命令(最多 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,重置状态 |
- 配置唤醒方式(可选):选择 "语音唤醒"(如唤醒词 "小安小安")或 "按键唤醒"(按下 KEY 引脚唤醒);
- 确认串口参数(默认 9600bps 8N1,与 STM32 保持一致)。
步骤 3:固件烧录
- 点击工具 "生成固件",保存为 "my_su03t.bin";
- 选择烧录串口(如 COM3),点击 "开始烧录",烧录成功后提示 "烧录完成";
- 断开 KEY 与 GND 的连接,模块退出烧录模式,切换为正常工作模式。
3. 固件验证(可选)
- 重新连接模块与 USB-TTL(正常工作模式,KEY 悬空);
- 打开串口助手(9600bps 8N1),对着模块说出命令词(如 "打开 LED");
- 串口助手接收到 "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(模块) ←→ 悬空(语音唤醒模式)
- 关键注意:
- STM32 USART1 同时与 SU03T 和 KQM6600 通信?不!实际是 "分时复用"------SU03T 输出指令码,STM32 接收后执行对应操作(如查询传感器时,STM32 通过 USART1 向 KQM6600 发送指令,接收数据);
- 共地原则:所有模块必须共地,否则串口数据乱码;
- 供电保障:若同时连接多个模块,建议使用外部 3.3V 电源供电(避免 STM32 引脚供电不足)。
3. 模块工作逻辑
- SU03T 识别语音命令→通过串口发送指令码(如 "查询空气质量"→"03\r\n");
- STM32 USART1 接收指令码→解析命令;
- 执行对应操作:
- 控制 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 的交互,让数据可视化!