方案背景
智能家电、充电桩、医疗设备的语音提示功能,已经成为产品体验的一项标配。过去工程师习惯用OTP语音芯片,但OTP容量固定,换音必须要换芯片重新烧录,虽然otp语音芯片便宜,但是产品迭代的成本不低。WT2003Hx系列MP3语音芯片内置32位120MHz处理器,支持MP3和WAV解码,通过标准UART串口就能播放海量音频,芯片还集成0.5W D类功放与16位DAC,一颗芯片就能独立完成语音播报。本文以STM32为主控,手把手搭建一套可直接移植的UART控制工程。
硬件连接
WT2003Hx的串口是3.3V TTL电平,和STM32的USART外设直接对接即可,无需电平转换。以SOP16封装为例,关键引脚如下:
• 芯片TX(16脚)连接STM32的RX引脚
• 芯片RX(6脚)连接STM32的TX引脚
• 芯片BUSY(15脚)连接STM32任意GPIO输入,用来检测播放状态
• 电源VCC接2.6V到5.0V,建议和STM32共用3.3V或独立5V,电源纹波控制在5%以内
• 喇叭接PWM+(13脚)与PWM-(14脚),推荐8欧0.5W喇叭(内置0.5W D类功放,工作在SPK输出模式)
• 芯片与STM32两侧GND必须共地
如果你的STM32是5V系统,芯片RX端需要串接限流电阻或加电平转换,避免3.3V接收端被5V直驱损坏。BUSY是输出脚,平时为低电平,播放时拉高,非常适合用中断或轮询判断一曲是否播完。
通信协议
WT2003Hx所有控制都走同一套UART帧。默认波特率9600,数据位8、停止位1、无校验。整条指令帧格式如下:
7E 长度 命令码 参数 校验和 EF
长度字段等于命令码、参数、校验和这三段加起来的字节数,再加上长度字段自身那1个字节,也就是参数长度加3。校验和等于长度、命令码、全部参数三者累加之后取低8位。
举一个规格书里的真实例子,播放片内Flash中WCZY1文件包索引1的音频,完整帧是:
7E 0A 95 57 43 5A 59 31 00 01 1E EF
其中57 43 5A 59 31是WCZY1这五个字符的ASCII码,00 01是索引地址,1E是校验和。我们来验算:0A加95加57加43加5A加59加31加00加01等于21E,低字节正好是1E,和帧里一致。芯片收到指令后会回一帧,返回码00表示执行成功。
完整工程代码
下面是基于STM32 HAL库的发送封装,核心是自动算长度与校验和,业务层只管传命令码和参数。
#include main.h
extern UART_HandleTypeDef huart2; // 连接WT2003Hx的串口
// 计算校验和并发送完整指令帧
void WT2003_SendCmd(uint8_t cmd, uint8_t *param, uint8_t param_len)
{
uint8_t frame[32];
uint8_t len = param_len + 3; // 长度字段自身 + 命令码 + 参数 + 校验和
uint8_t checksum = len + cmd; // 长度字节参与累加
uint8_t i;
frame[0] = 0x7E;
frame[1] = len;
frame[2] = cmd;
for (i = 0; i < param_len; i++)
{
frame[3 + i] = param[i];
checksum += param[i];
}
frame[3 + param_len] = checksum & 0xFF;
frame[4 + param_len] = 0xEF;
HAL_UART_Transmit(&huart2, frame, 5 + param_len, 100);
}
// 播放片内Flash文件包中指定索引的音频
// pkg_name: 5字节文件包名ASCII index: 2字节索引地址
void WT2003_PlayPackageIndex(uint8_t *pkg_name, uint16_t index)
{
uint8_t param[7];
param[0] = pkg_name[0];
param[1] = pkg_name[1];
param[2] = pkg_name[2];
param[3] = pkg_name[3];
param[4] = pkg_name[4];
param[5] = (uint8_t)(index >> 8);
param[6] = (uint8_t)(index & 0xFF);
WT2003_SendCmd(0x95, param, 7);
}
// 停止播放
void WT2003_Stop(void)
{
WT2003_SendCmd(0xAB, NULL, 0);
}
// 设置音量 0到31级,掉电记忆
void WT2003_SetVolume(uint8_t vol)
{
if (vol > 31) vol = 31;
WT2003_SendCmd(0xAE, &vol, 1);
}
// 切换音频输出 0为SPK 1为DAC,切换后等待再发其他指令
void WT2003_SwitchOutput(uint8_t mode)
{
WT2003_SendCmd(0xB6, &mode, 1);
HAL_Delay(200);
}
// 插播Flash中指定索引音频,可在背景音中插入紧急语音
void WT2003_InsertPlay(uint16_t index)
{
uint8_t param[3] = {0x00, (uint8_t)(index >> 8), (uint8_t)(index & 0xFF)};
WT2003_SendCmd(0xB1, param, 3);
}
// BUSY高电平表示正在播放
uint8_t WT2003_IsBusy(void)
{
return HAL_GPIO_ReadPin(BUSY_GPIO_Port, BUSY_Pin);
}
```
在主函数里先延时1秒等待芯片挂载文件系统,再发播放指令:
```c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
HAL_Delay(1000); // 等待WT2003Hx上电挂载文件系统
uint8_t pkg[5] = {'W', 'C', 'Z', 'Y', '1'}; // 文件包名WCZY1
WT2003_PlayPackageIndex(pkg, 0x0001); // 播放索引1
while (1)
{
if (WT2003_IsBusy() == 0) // 播放结束,可做后续业务逻辑
{
// 例如触发下一曲或切换状态
}
HAL_Delay(50);
}
}
```
在主函数里先延时1秒等待芯片挂载文件系统,再发播放指令:
int main(void){ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART2_UART_Init(); HAL_Delay(1000); // 等待WT2003Hx上电挂载文件系统 uint8_t pkg[5] = {'W', 'C', 'Z', 'Y', '1'}; // 文件包名WCZY1 WT2003_PlayPackageIndex(pkg, 0x0001); // 播放索引1 while (1) { if (WT2003_IsBusy() == 0) // 播放结束,可做后续业务逻辑 { // 例如触发下一曲或切换状态 } HAL_Delay(50); }}
常用指令速查
掌握下面几条,大部分播报场景都能覆盖:
• 播放文件包索引(95):参数为5字节包名加2字节索引
• 停止(AB):无参数
• 音量(AE):1字节0到31,掉电记忆
• 输出切换(B6):00切SPK,01切DAC,切换后等200毫秒再发下一条
• 插播(B1):3字节,标示字加2字节索引,可在背景音中插入紧急语音
• 查询状态(C2):读取当前是播放、暂停还是停止
开发注意事项
第一,上电初始化要等。WT2003Hx走PC模式,需要挂载文件系统,上电后建议等500毫秒到1秒再发码,否则芯片不响应。
第二,BUSY信号对同步很有用。平时低电平,播放时拉高,用中断或轮询都能判断一曲是否播完,避免重复发码。
第三,USB换音后必须拔线。通过USB把电脑上的音频拷进盘符后,一定要先断开USB线再发串口指令,否则发码无响应。
第四,外挂Flash认品牌。规格书建议用唯创自家Flash,第三方Flash可能出现杂音、爆音甚至无法播放。
第五,发码要有间隔。规格书要求每串命令之间留出200到300毫秒,发完可通过返回码确认执行结果;切换输出这类指令后同样建议等200毫秒再发下一条。
结语
WT2003Hx加STM32的组合,把语音播报的硬件成本和开发门槛都降了下来。一块SOP16小芯片,UART几行代码就能驱动,PC模式还能像U盘一样随时换音。如果你正在做家电、充电桩、门锁或者工业设备,这套工程可以直接套用,先把协议帧和校验和对通,再叠加业务指令就能快速量产。
303
