我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

STM32+WT588D公交语音播报系统:串口时序与LCD显示详解

STM32+WT588D公交语音播报系统:串口时序与LCD显示详解 简介基于STM32的公交语音播报系统毕业设计论文及配套资料面向电子科学与技术、物联网及嵌入式方向的本科毕业生和项目开发者。设计以STM32单片机为控制核心集成按键输入、WT588D语音芯片、移动连接模块与LCD液晶显示模块实现公交站台到站语音提示与屏幕信息同步显示系统结构简洁、稳定性好具备较强的工程参考价值。压缩包内共1个doc文档大小约1.86MB内容涵盖完整课题论文、中英文摘要、目录、绪论、系统总体方案、硬件电路设计、软件流程图与调试说明等模块可直接用于毕业设计撰写参考或作为实物开发基础。文档还详细介绍了按键K1/K2触发、语音录制播放、12864液晶显示等关键环节的实现逻辑。已有80人学习查看适合需要快速搭建STM32语音播报类毕设框架并想深入理解单片机、语音芯片与液晶显示协同工作原理的读者。1. 公交报站为什么选STM32F103C8T6加WT588D这套组合很多人拿 stm32 做毕业设计时第一反应是点灯、温湿度、OLED 显示这类方案真正落到公共交通场景的却不多。这套基于 stm32 的公交语音播报系统核心是把语音芯片、液晶显示和按键输入三件事串起来K1 看上一站、K2 看下一站按下后 WT588D 播报站名LCD12864 同步显示汉字站名。主控选的是 STM32F103C8T6而不是 51 系列理由是它 64KB Flash、20KB RAM 的余量对语音地址管理和后续扩展更从容而且 Cortex-M3 内核的 GPIO 翻转速度和定时器资源足够支撑软件模拟 SPI 时序不需要额外加硬件 SPI 外设。这套组合对嵌入式刚入门的人友好对已经在做产品的工程师也有参考价值——WT588D 的地址映射和三线串口时序处理换到任何语音播报场景都成立。2. WT588D 三线串口时序报站语音触发的核心链路2.1 WT588D 的选型理由与工作模式WT588D 是广州唯创与华邦合作推出的语音芯片本质上是一个带 SPI-Flash 接口的 MCU 语音方案支持重复擦写录音采样率 6kHz 到 22kHz 可调内置 0.5W/8Ω 功放可以直接推小喇叭不需要外加音频功放电路。它的语音时长取决于外挂 Flash 容量16Mbit 的 Flash 差不多能存 500 到 1000 秒语音对公交报站这种每段只有几秒的短语音来说绰绰有余。相比 ISD1700 系列模拟直存方案WT588D 的优势在于音频格式支持 WAV、MP3、WMA而且可以通过上位机软件把语音文件下载到 Flash 里改站名不用换芯片重新烧录一次 Flash 就行。WT588D 有七种控制模式MP3 管理模式、按键控制模式、三线串口控制模式、并行控制模式等。公交报站场景推荐用三线串口控制模式因为它只占用三个 GPIO且地址寻址范围是 0 到 219完全覆盖公交线路的站点数量需求。三线串口模式下所有按键无效控制完全由 STM32 通过 CS、DATA、CLK 三条线完成这对嵌入式软件来说等于把语音播放变成了一次外设写入操作。2.2 三线串口时序的软件模拟实现三线串口控制模式的时序标准是CS 拉低 5ms 到 10ms 唤醒芯片然后 CLK 上升沿采集 DATA 上的数据数据位从高位开始发送。发送完命令或地址字节后CS 拉高芯片开始解析并输出 BUSY 信号。时钟频率建议控制在 300μs 到 1ms 之间软件模拟时用延时函数拉长 CLK 周期比硬件 SPI 更稳定因为不受 SPI 分频系数限制。#define WT588D_CS_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_7) #define WT588D_CS_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_7) #define WT588D_CLK_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_3) #define WT588D_CLK_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_3) #define WT588D_DATA_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_5) #define WT588D_DATA_HIGH()GPIO_SetBits(GPIOB, GPIO_Pin_5) void WT588D_SendByte(uint8_t dat) { for (uint8_t i 0; i 8; i) { WT588D_CLK_LOW(); if (dat 0x80) { WT588D_DATA_HIGH(); } else { WT588D_DATA_LOW(); } dat 1; delay_us(500); WT588D_CLK_HIGH(); delay_us(500); } } void WT588D_PlayAddr(uint8_t addr) { WT588D_CS_LOW(); delay_ms(5); WT588D_SendByte(addr); WT588D_CS_HIGH(); delay_ms(20); }这段代码里WT588D_SendByte负责把一个字节按从高位到低位的顺序发出去CLK 低电平时改变 DATA 电平CLK 高电平时芯片采集数据所以 DATA 的电平必须在 CLK 跳变之前稳定下来这就是为什么 CLK_LOW 之后先拉 DATA 再延时。WT588D_PlayAddr传的是语音地址比如地址 0x00 对应第一段报站语音0x01 对应第二段。CS 拉低 5ms 是为了让芯片从休眠状态唤醒如果这个时间太短芯片可能还没准备好接收数据首字节会丢失。发送完地址后 CS 拉高芯片开始从 Flash 读取对应音频并播放20ms 的延时是给芯片内部解析指令留的时间。2.3 指令码与地址映射的关系WT588D 的指令码有明确的分配0x00 到 0xDB 是语音地址0xE0 到 0xE7 是音量控制指令0xFE 是停止播放0xF2 是循环播放。这里有一个容易踩的坑发送音量指令时0xE0 到 0xE7 的低三位就是音量等级0xE0 表示最小音量0xE7 表示最大音量不需要再单独发音量数值字节。而循环播放指令 0xF2 必须在语音地址指令发送后 30ms 内发出否则芯片可能已经进入播放状态循环指令会被忽略。停止播放指令 0xFE 则随时有效发送后 1ms 内生效。音量、地址和控制的对应关系可以整理成下面这张表方便做驱动层封装功能指令/地址范围说明语音地址0x00 - 0xDB对应 Flash 中的语音片段音量控制0xE0 - 0xE70xE0 最小0xE7 最大循环播放0xF2需在地址指令后 30ms 内发送停止播放0xFE发送后 1ms 内停止端口扩展输出0xF5 / 0xF6发送 F5 进扩展模式F6 退出注意音量指令发送后如果要调整地址播放必须等待 90ms 以上否则芯片还在处理音量切换的内部状态地址指令会被吞掉。这个时序约束在软件设计时要用延时或状态机保证不能连续快速调用。3. LCD12864 串行驱动与站点汉字显示3.1 为什么选带字库的 LCD12864显示模块有三个候选方案1602 只能显示英文和数字汉字站名需要自己造字模公交站名几乎全是汉字直接排除不带字库的 12864比如 KS0108 控制器需要软件取模要把每个汉字的 16×16 点阵数据提前烧进单片机 Flash换站名就得重新取模重新编译维护成本高带字库的 LCD12864 内置 8192 个汉字16×16 点阵直接写 GB2312 内码就能显示汉字比如“火车站”三个字往显存里写三个内码字节序列就出来了。所以原设计选第三种是合理的对应硬件型号是 JM12864M-2逻辑工作电压 4.5V 到 5.5V内置升压电路不需要负压。串行模式相比并行模式节省 5 个 GPIO对 STM32F103C8T6 这种 48 脚的芯片来说省出来的引脚可以做按键、接 BUSY 检测、留串口调试。JM12864M-2 的 PSB 引脚接地就是串行模式此时 RS 引脚变成片选 CSR/W 变成数据线 SIDE 变成时钟线 CLK。3.2 串行时序的字节级实现串行模式下LCD12864 的通信协议是先发送 5 个连续的 1 作为同步头然后跟一个 RW 位读写选择写操作为 0、一个 RS 位指令/数据选择指令为 0数据为 1最后补一个 0组成完整的 8 位控制字节。控制字节发完后再分两次发送真正的数据第一次发高 4 位低 4 位补 0第二次发低 4 位左移到高 4 位位置。void LCD12864_WriteByte(uint8_t dat) { for (uint8_t i 0; i 8; i) { LCD12864_CLK_LOW(); if (dat 0x80) { LCD12864_SID_HIGH(); } else { LCD12864_SID_LOW(); } dat 1; delay_us(2); LCD12864_CLK_HIGH(); delay_us(2); } } void LCD12864_WriteCmd(uint8_t cmd) { LCD12864_CS_LOW(); LCD12864_WriteByte(0xF8); LCD12864_WriteByte(cmd 0xF0); LCD12864_WriteByte((cmd 0x0F) 4); LCD12864_CS_HIGH(); delay_us(50); } void LCD12864_WriteData(uint8_t dat) { LCD12864_CS_LOW(); LCD12864_WriteByte(0xFA); LCD12864_WriteByte(dat 0xF0); LCD12864_WriteByte((dat 0x0F) 4); LCD12864_CS_HIGH(); delay_us(50); }0xF8这个控制字节拆开看二进制是 11111 0 0 0前五位 1 是同步头第六位 0 是 RW 写操作第七位 0 是 RS 指令选择第八位 0 是结束位。0xFA对应 11111 0 1 0第七位变成 1表示接下来发的是显示数据。发送命令或数据时cmd 0xF0取出高 4 位放到字节高位(cmd 0x0F) 4把低 4 位移到高位两个字节都补零填充LCD 模块只采样每个字节的高 4 位。CS在整个传输过程中保持低电平传输结束后拉高表示一帧数据完成。3.3 汉字显示坐标与站点名渲染带字库 LCD12864 的汉字显示坐标是有固定规律的第一行地址是 0x80 到 0x87第二行 0x90 到 0x97第三行 0x88 到 0x8F第四行 0x98 到 0x9F。每行最多显示 8 个汉字因为每个汉字占 16×16 点阵128 像素宽除以 16 等于 8。公交站名一般 4 到 6 个字一行完全放得下。void LCD12864_ShowStation(const char *name) { LCD12864_WriteCmd(0x80); for (uint8_t i 0; i 8; i) { if (name[i] \0) { break; } LCD12864_WriteData(name[i]); } }传入的站名字符串是 GB2312 编码的汉字内码LCD12864_WriteData会把每个字节直接写入显示 RAM。0x80是第一行首地址的指令后续每个数据字节自动向后推进一个地址所以只需在开头设置一次坐标。这里有个细节站名做成const char *数组放在 Flash 里不占 RAM对 STM32F103C8T6 这种 20KB RAM 的芯片很重要因为后续还要给 WT588D 的播报逻辑和按键状态机留内存。显示时如果只想清空某一行可以用 0x01 清屏指令但清屏会让整个屏幕闪烁公交报站场景建议用空格覆盖旧站名视觉上更平滑。4. K1/K2 按键输入与站点播报状态机的软件实现4.1 站点数据结构的组织方式把站名和语音地址绑定在一起是整套软件设计里最值得参考的部分。用一个结构体数组来维护站点数据每个站点有显示名称和对应的语音地址这样按键切换、语音播报、液晶显示都围绕同一个索引操作不会出现站名和语音对不上的问题。typedef struct { const char *name; uint8_t voice_addr; } StationInfo; static const StationInfo station_list[] { {中央广场, 0x00}, {人民公园, 0x01}, {火车站, 0x02}, {科技园, 0x03}, }; #define STATION_TOTAL (sizeof(station_list) / sizeof(station_list[0])) static uint8_t current_station 0;StationInfo结构体把显示名和语音地址打包station_list数组用const修饰数据全部存放于 Flash不占 RAM。STATION_TOTAL用sizeof计算数组长度以后增加站点只需要在数组里加一行不用改其他代码。current_station是当前站点索引按键操作本质上就是对它的加减和边界判断。4.2 按键扫描与消抖处理的工程写法按键不能直接放在主循环里做延时消抖因为延时期间 CPU 干等着WT588D 的播报状态和屏幕刷新都会被卡住。工程上常用的做法是轮询加状态位主循环里每 10ms 扫描一次按键读到电平变化后连续确认两次确认是稳定按下才执行动作。void Station_Next(void) { if (current_station STATION_TOTAL - 1) { current_station; } else { current_station 0; } Station_PlayCurrent(); } void Station_Prev(void) { if (current_station 0) { current_station--; } else { current_station STATION_TOTAL - 1; } Station_PlayCurrent(); } void Station_PlayCurrent(void) { LCD12864_ShowStation(station_list[current_station].name); WT588D_PlayAddr(station_list[current_station].voice_addr); }Station_Next和Station_Prev分别是 K2 和 K1 对应的处理函数。current_station到达数组末尾时回绕到 0到达开头时跳到最后一站这样处理在测试阶段方便不用来回按很多次才能验证首尾站点的播报边界。回绕逻辑也可以改成停在首尾不动作但实际驾驶场景中司机偶尔会按过头回绕比停住更符合使用习惯。Station_PlayCurrent先把站名写到 LCD12864再触发 WT588D 播报先显示后出声乘客先看到站名再听到语音信息感知更自然。按键扫描函数放到定时器中断里做 10ms 间隔轮询主循环只处理事件标志这样按键响应不会受到语音播报期间延时的影响。中断里用状态机记录按键的按下和释放只有检测到从高电平到低电平的下降沿且连续两次采样都是低电平时才置位按键事件标志。主循环发现标志后执行对应的 Station 函数。4.3 BUSY 信号与播报节奏的控制WT588D 播报期间有个 BUSY 引脚播放时输出低电平播报结束后恢复高电平。如果系统需要在播报期间做其他事比如显示到站信息、等待按键预输入可以通过查询 BUSY 电平来判断当前是否处于播放状态。void Station_PlayBlocking(uint8_t index) { LCD12864_ShowStation(station_list[index].name); WT588D_PlayAddr(station_list[index].voice_addr); while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) RESET) { // 等待 BUSY 拉高即播报结束 } }阻塞等待的写法适合调试阶段验证语音是否正常播放但如果要在播报期间响应按键操作就得改成非阻塞方式主循环里轮询 BUSY播报过程中仍然扫描按键按键只更新current_station和显示内容不打断当前语音。需要打断时调用WT588D_StopPlay()发送 0xFE 指令再立刻触发新站点的播报。非阻塞的好处是司机连续按 K2 时屏幕上的站名会跟着按键快速切换但语音只播最后一次的结果不会出现多段语音叠在一起的情况。程序主体推荐用状态机来组织而不是顺序执行后再回到主循环。状态可以划分为空闲态、播报态、切换态按键在空闲态和播报态都能触发切换但在切换态要屏蔽新按键因为芯片还在处理上一条指令连续发地址字节可能丢失。状态机的具体实现不复杂关键是每个状态里只处理自己该做的事空转的周期让给延时函数和按键扫描。5. 语音地址表规划与调试技巧WT588D 的语音地址不是自动分配的它依赖烧录时 Flash 里存放的语音片段顺序。在上位机软件里每导入一段音频文件它会占用一个递增的地址编号第一段是 0x00第二段是 0x01依此类推。这个地址必须和代码里station_list数组的voice_addr严格一一对应不然会出现屏幕显示“火车站”、喇叭里却播报“人民公园”的错位问题。规划时有三个约束值得注意一是音频素材的时长不要超过 Flash 容量对应的总时长比如 16Mbit Flash 按 16kHz 采样率大约能存 500 秒二是语音格式建议统一用 WAV 或 MP3采样率转换在上位机完成不要混用格式三是站名播报语音不要做静音消隐因为这会影响地址的连续性判断。调试时最容易踩的坑集中在三个地方现象原因解决办法首字节丢失播报无反应CS 拉低唤醒时间不足拉低后至少延时 5ms音量调整后播报地址错乱音量指令后 90ms 内发地址被吞音量设置后延时 100ms 再操作语音断断续续CLK 频率过快或代码里 GPIO 翻转过慢每个电平保持 500μs 以上最后一个值得分享的技巧是用逻辑分析仪抓三线串口的波形。把 CH1 接 CLK、CH2 接 DATA、CH3 接 CS触发条件设为 CS 下降沿然后按一次 K2分析仪上就能清楚看到一帧完整的传输过程CS 拉低CLK 开始输出方波DATA 在 CLK 上升沿之前完成电平变化8 个时钟周期对应一个字节。如果发现 DATA 跳变和 CLK 上升沿重叠说明 GPIO 初始化时没有配置成推挽输出或者代码里 CLK_LOW 之后没有给足够延时。这套验证方法同样适用于调试其他 SPI 类外设比在代码里加无数个串口打印要高效得多。本文还有配套的精品资源点击获取
返回列表