STM32驱动JQ6500语音模块实现按键切歌与OLED状态显示
最近做语音播报类的小项目拿到一块 JQ6500 语音模块。这模块本身自带按键触发、音频解码、功放输出单独接个喇叭和 TF 卡就能响。但问题也跟着来了播放到第几首了音量是多少当前是暂停还是停止这些状态全得靠耳朵和记忆去猜。项目一复杂光靠模块自带的 IO 触发根本不够用。所以我把 STM32 拉了进来做成“STM32 驱动 JQ6500 语音模块 按键切换播放 OLED 状态显示”的小系统。整体思路很直接STM32 通过串口给 JQ6500 发控制命令按键接在 STM32 的 GPIO 上OLED 走 I2C 显示当前曲目、音量、播放状态。这套方案非常适合语音播报终端、车载语音提示、工业设备人机交互、教学演示设备这类场景。下文从硬件接线、串口协议、驱动代码到实际调坑完整拆一遍新手照着做也能跑起来。1. 项目概述与整体设计思路1.1 JQ6500 语音模块到底能干什么JQ6500 是一款很常见的 MP3 解码芯片很多开发板上都带这个方案。它可以直接读取 TF 卡、U 盘或者 SPI Flash 里的音频文件解码后通过 DAC 输出也可以直接驱动小功率喇叭。模块周边电路做得很完善供电、音频滤波、按键接口都给出来了属于“拿到就能用”的语音方案。模块本身支持两类控制方式一类是直接用 IO 触发按键模块内部有相应引脚短接一下就能播指定序号的音频另一类是通过串口 UART 发送指令实现上一曲、下一曲、指定曲目、暂停、停止、音量调节这些操作。串口控制的优势很明显MCU 可以主动控制播放流程也能把状态数据拿回来做上层逻辑。它的弱项也很明显模块没有显示界面按键触发逻辑固定没法告诉使用者“现在正在播什么”。如果只是固定播一句“欢迎光临”那直接 IO 触发就够了。但如果要做一个多语种语音引导、多曲目切换的终端就需要外部 MCU 来做协调。1.2 为什么让 STM32 做“大脑”而不是直连按键一开始我也想过偷懒直接用 JQ6500 的物理按键引脚接几个轻触开关完事。可实际排需求的时候发现我要的不只是“切歌”还要有按键防抖、播放模式切换、音量加减、OLED 状态同步。这些东西如果全用硬件逻辑去堆手写触发电路会非常痛苦而且想改一个播放规则就得重新焊接。STM32 在这类项目里的角色本质上是一个“逻辑大脑”。它负责读按键负责维护当前曲目号、音量、播放状态这些变量再通过串口把具体动作下发给 JQ6500。OLED 只是把大脑里的这些变量可视化出来。这样做的最大好处是显示、按键、语音播报三者解耦。哪怕后面把 OLED 换成段码屏或者把按键改成触摸按键主逻辑都不用大改。按键切换播放这个需求听起来简单真正做好需要三点按键消抖可靠、切换命令不重复发送、UI 状态与语音模块状态保持一致。这三件事用模块自带按键很难做到但用 STM32 来做就是常规操作。1.3 按键切换 OLED 显示的核心应用场景这套方案的应用范围比想象中广。我身边的实际例子包括语音报站/导览设备参观展厅时按“上一首”“下一首”切换不同展品讲解。工业设备人机交互面板设备出现不同故障时操作人员按按键播放对应报警语音液晶屏显示故障编号。老人或儿童语音学习机切换字母、单词、古诗音频同时显示当前内容。多语种播报终端通过按键切换中英文语音文件屏幕同步显示当前语言。在这些场景里语音只是输出环节真正重要是“当前处于哪个状态”“下一步可以切到什么状态”。这就是 OLED 状态显示存在的意义。它不负责复杂绘图只要稳定显示几行关键信息就能极大降低使用者的理解成本。2. 硬件选型与接线详解2.1 核心器件清单器件型号/规格用途主控 MCUSTM32F103C8T6 最小系统板逻辑控制、按键读取、I2C/串口输出语音模块JQ6500-16P 或同系列带串口的模块MP3 解码与功放输出显示屏0.96 寸 SSD1306 驱动 I2C OLED状态显示按键轻触开关 4 个或 5 个播放控制喇叭3W/4Ω 或 3W/8Ω 小喇叭声音输出电源USB 5V AMS1117-3.3 或直接 3.3V 面包板电源供电STM32 我用了最保守的 F103C8T6HAL 库和标准外设库都能跑。实际上只要有一个 UART、一个 I2C、几个 GPIO 的 MCU 都能做逻辑不依赖某款芯片特有外设。OLED 选 0.96 寸 128x64 的 I2C 版本很合适接线只有 4 根不占 GPIO显示几行状态文字绰绰有余。2.2 JQ6500 与 STM32 接线表JQ6500 模块的引脚功能不同批次略有差异但核心引脚基本一致。以我手上的 16P 模块为例与 STM32 的连接方式如下JQ6500 引脚连接目标说明VCC5V 电源确认模块允许范围模块内部可能有稳压但别超规格GND系统 GND必须和 STM32 共地RXSTM32 PA9USART1_TX串口发送给语音模块TXSTM32 PA10USART1_RX可选用于读取模块状态DAC_L / DAC_R功放输入端或直接接小喇叭不同模块叫法可能不同SPK / SPK-喇叭正负极如果模块带功放输出直接接接线时记住一句话发送接接收接收发送。STM32 的 PA9 是串口发送脚必须接 JQ6500 的 RX如果接了反模块完全没反应排查时第一反应先检查这里。关于电平匹配很多 JQ6500 模块工作在 3.3V 逻辑STM32F103 的 TX 输出 3.3V接上去没问题。如果你手里那块模块明确要求 5V 逻辑那 STM32 输出高电平只有 3.3V可能会有识别不稳的问题。这种情况下我习惯在串口线上串一个 1k 电阻或者用三极管做一次电平转换比较省事的是直接买 3.3V 版本模块。2.3 OLED、按键与电源电路OLED 的 I2C 接口四根线分别是 VCC、GND、SCL、SDA。VCC 接 3.3V不要接 5VSSD1306 核心逻辑虽然是 3.3V但有些模块板载了 LDO 可以接受 5V具体看模块丝印。保险起见统一用 3.3V。I2C 我接在 STM32 的 PB6、PB7 上这是 I2C1 的默认引脚。如果使用其他引脚做软件 I2C也可以只是代码里对应改一下引脚。按键电路更简单每个按键一端接 STM32 的 GPIO另一端接 GNDMCU 内部开上拉电阻。按键按下时 GPIO 读到低电平释放时读到高电平。四个按键可以定义为上一曲、下一曲、暂停/播放、音量模式切换。如果不做音量三个按键也够用。电源部分注意一个容易踩的坑语音模块播放大音量时电流波动比较大如果和 STM32、OLED 共用一根劣质面包板电源线可能出现 OLED 闪烁甚至语音模块重启。我在实际项目中会在语音模块电源引脚附近并联 100uF 电解电容和 0.1uF 陶瓷电容电源会稳定很多。功放输出的地线最好单独走线再和数字地单点汇合能明显降低底噪。2.4 连线时最容易犯的错误第一次做这类系统时我犯过一个很低级但很典型的错误JQ6500 的电源我用了一个独立的小电源板STM32 用 USB 供电两个板子没有共地。结果串口指令发出去全部无效OLED 倒是正常。折腾了半天才反应过来串口通信是参考同一个地电平的不共地电平标准就是乱的。另外 OLED 的 SCL 和 SDA 千万别接反。有些模块丝印会把 SCL、SDA 放在同一侧但如果不小心接反屏幕不亮而且 I2C 扫描不到设备。遇到这种问题先用万用表或者逻辑分析仪量一下引脚再检查代码别上来就怀疑固件。3. 串口协议与底层驱动封装3.1 通信参数与帧格式JQ6500 的串口默认波特率是 9600数据位 8无校验停止位 1。很多模块在上电时会短暂输出一条启动信息这是正常现象。如果发给它的指令没反应先把串口参数确认一遍部分模块可能通过拨码开关调到 115200但绝大多数默认 9600。串口控制命令不是简单的发一个字节而是要按照固定帧格式发送。我使用的 JQ6500-16P 模块兼容的 MP3 串口协议帧格式如下字节位置内容说明00x7E帧起始10xFF版本号20x06后面有效数据长度3CMD命令码40x00是否需要模块回传状态5参数高字节如曲目号高 8 位6参数低字节如曲目号低 8 位7校验高字节校验和8校验低字节校验和90xEF帧结束校验算法常用的是0xFFFF - 从字节 1 到字节 6 的累加和 1再取低 16 位。假如我要设置音量为 20参数的十六进制就是 0x00 0x14把命令、参数带入帧结构发送十字节数据就能完成。这里有个细节曲目号、音量这类参数在不同命令下含义不一样下一曲、上一曲这种命令参数直接填 0。3.2 常用指令说明不同厂家、不同批次的 JQ6500 模块命令码确实可能不完全一样。我第一次买到的模块和后来的兼容模块就有细微差别。所以下面这份命令表建议理解为“参考 实测基准”而不是拿一个命令码去通吃所有模块。功能命令码参数说明播放指定曲目0x03参数高字节、低字节为曲目号下一曲0x01参数为 0上一曲0x02参数为 0播放/继续0x0C参数为 0暂停0x0D参数为 0停止0x0E参数为 0设置音量0x06参数低字节 0x00~0x1E0~30音量加0x04参数为 0音量减0x05参数为 0使用前最好用一个 USB 转串口工具先把模块单独接电脑用串口助手直接发帧测试。如果 0x03 无法播放指定曲目就查一下对应模块的数据手册把命令码改掉即可。底层发送代码不变只改宏定义这是我把所有命令码封装成宏的原因。3.3 STM32 发送串口命令的代码封装在 STM32 的 HAL 库工程里如果当前工程已经初始化好 USART1发送一个命令帧可以写成下面这样#define JQ_CMD_PLAY_INDEX 0x03 #define JQ_CMD_NEXT 0x01 #define JQ_CMD_PREV 0x02 #define JQ_CMD_PLAY 0x0C #define JQ_CMD_PAUSE 0x0D #define JQ_CMD_STOP 0x0E #define JQ_CMD_SET_VOL 0x06 #define JQ_CMD_VOL_UP 0x04 #define JQ_CMD_VOL_DOWN 0x05 void JQ_SendCommand(uint8_t cmd, uint8_t paramH, uint8_t paramL) { uint8_t frame[10]; uint16_t checksum; uint16_t i; frame[0] 0x7E; frame[1] 0xFF; frame[2] 0x06; frame[3] cmd; frame[4] 0x00; frame[5] paramH; frame[6] paramL; checksum 0xFFFF - (frame[1] frame[2] frame[3] frame[4] frame[5] frame[6]) 1; frame[7] (uint8_t)(checksum 8); frame[8] (uint8_t)(checksum 0xFF); frame[9] 0xEF; HAL_UART_Transmit(huart1, frame, 10, 100); }这段代码里最重要的不是哪一行而是整个发送过程必须一次发完十字节中间不能停顿太久。JQ6500 对帧间延时有一定容忍度但我遇到过中间被任务打断后模块解析失败的情况。如果工程里还用到了中断优先级很高的部分可以在发送前关掉不必要的中断或者干脆用 DMA 发送十字节然后等发送完成标志。播放指定曲目时曲目号从 0 开始算还是从 1 开始算不同固件也有差异。我手里的模块曲目号从 1 开始也就是文件命名为 0001.mp3、0002.mp3 这样的顺序发送 1 就播第一个文件。新模块到手建议先发一个JQ_SendCommand(JQ_CMD_PLAY_INDEX, 0, 1)点一下能响就说明基本通路已经通了。3.4 OLED 驱动与状态显示OLED 部分我用的是 I2C SSD13060.96 寸128x64。I2C 地址通常是 0x3C也有少数屏是 0x3D。为了保险代码里可以先做一个 I2C 扫描把扫描到的地址打印到串口。用 HAL 库发送一字节命令和一字节数据的代码如下#define OLED_ADDR 0x3C // 注意看自己的屏幕地址 void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2]; buf[0] 0x00; buf[1] cmd; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 50); } void OLED_WriteData(uint8_t data) { uint8_t buf[2]; buf[0] 0x40; buf[1] data; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 50); }注意 HAL 库 I2C 地址参数不同版本写法有差异。有些例程要求传 8 位地址 0x78有些要求传 7 位地址 0x3C。我的代码里按 7 位地址 0x3C 来写如果你移植后一直发 I2C 错误就把地址改成 0x78 再试一次。SSD1306 上电后需要初始化最核心的几步是开启内部电荷泵、设置列地址寻址模式、设置显示时钟、点亮屏幕。有一家淘宝店的小批量 OLED初始化序列和标准 SSD1306 有点差异表现为“批量点不亮”最后发现是缺少0x8D 0x14开启电荷泵这一步补上之后就好了。状态显示我用两行文本就够了。第一行显示当前曲目号第二行显示音量和播放状态。void OLED_ShowStatus(uint16_t track, uint8_t vol, uint8_t state) { char line1[20]; char line2[20]; const char* stateName Stop; if (state 0) stateName Play; else if (state 1) stateName Pause; sprintf(line1, Track:%03d, track); sprintf(line2, Vol:%02d %s, vol, stateName); OLED_Clear(); OLED_ShowString(0, 0, line1); OLED_ShowString(0, 2, line2); }这里有个取舍问题。清屏再显示会简单但刷新频率稍高就会出现闪烁。我建议在正式项目里给 OLED 开一块 128x8 的显存数组先把文字写到显存里再一次性把有变化的页面刷新到屏幕。只有页面内容变化时才更新不然就等下一轮不会闪也不会占用大量 I2C 带宽。3.5 按键消抖与状态保持按键如果不做处理按一次可能触发好几次切换听感上就是“切一下跳了两三首”。简单可靠的办法是“去抖计数”读到一个低电平后不要立刻执行而是连续在多轮扫描中确认几次都读到低电平才认为按键按下。我一般在定时器中断里每 10ms 扫描一次按键连续 3~5 次稳定为低才生成一次按键事件。按键消抖代码骨架如下typedef struct { GPIO_TypeDef* port; uint16_t pin; uint8_t stableLevel; uint8_t lastLevel; uint16_t stableCnt; } Key_t; void Key_ScanOne(Key_t* key) { uint8_t level HAL_GPIO_ReadPin(key-port, key-pin); if (level ! key-lastLevel) { key-lastLevel level; key-stableCnt 0; return; } if (key-stableCnt 5) { key-stableCnt; if (key-stableCnt 5) { key-stableLevel level; if (level 0) { // 这里产生“按下”事件 } } } }状态保持的核心变量就三个当前曲目号currentTrack、当前音量currentVol、播放状态枚举playState。按键每触发一次先改这三个变量再根据变量发送对应命令最后在 OLED 显示函数里读取变量并刷新。这样即使 OLED 刷新频率不高也不会因为自己忘了当前状态而出错。4. 完整流程与主逻辑实现4.1 工程初始化配置用 STM32CubeMX 建工程时我建议最少配置以下外设USART1波特率 96008 位数据无校验1 位停止。I2C1标准模式 100kHz 或快速模式 400kHzOLED 都能跑。GPIO按键引脚设为输入选择上拉。定时器 TIM2配置成 10ms 中断用于按键扫描和周期任务。串口波特率、GPIO 起始电平、I2C 时钟这三项是最容易出问题的。特别是波特率如果 STM32 系统时钟不是按默认 72MHz 配置HAL 库的波特率初始化也可能偏差结果就是 JQ6500 收不到正确指令。用标准库的朋友重点检查USART_InitStructure.USART_BaudRate 9600时钟树如果改过要一起核对。系统上电后给 JQ6500 模块留几百毫秒的初始化时间。模块刚上电时内部存储介质还没就绪立刻发命令很容易丢。我通常在主函数里先 HAL_Delay(500)然后设置一次音量和播放指定曲目再进入主循环。4.2 按键事件处理与播放控制逻辑主循环里不能一直去读按键容易受到干扰。我采用定时器中断里扫键用事件标志通知主循环处理。这样按键响应速度快也不会因为主循环里的 OLED 显示函数占用太长时间而丢键。按键事件处理函数可以这样写void HandleKeyEvent(uint8_t key) { switch (key) { case KEY_NEXT: if (currentTrack TRACK_TOTAL) { currentTrack; } else { currentTrack 1; } JQ_SendCommand(JQ_CMD_PLAY_INDEX, (uint8_t)(currentTrack 8), (uint8_t)(currentTrack 0xFF)); playState STATE_PLAYING; break; case KEY_PREV: if (currentTrack 1) { currentTrack--; } else { currentTrack TRACK_TOTAL; } JQ_SendCommand(JQ_CMD_PLAY_INDEX, (uint8_t)(currentTrack 8), (uint8_t)(currentTrack 0xFF)); playState STATE_PLAYING; break; case KEY_PLAY_PAUSE: if (playState STATE_PLAYING) { JQ_SendCommand(JQ_CMD_PAUSE, 0, 0); playState STATE_PAUSED; } else if (playState STATE_PAUSED) { JQ_SendCommand(JQ_CMD_PLAY, 0, 0); playState STATE_PLAYING; } break; case KEY_VOL_UP: if (currentVol 30) { currentVol; JQ_SendCommand(JQ_CMD_SET_VOL, 0, currentVol); } break; case KEY_VOL_DOWN: if (currentVol 0) { currentVol--; JQ_SendCommand(JQ_CMD_SET_VOL, 0, currentVol); } break; } }曲目总数TRACK_TOTAL需要根据你 SD 卡里的文件数量自己定义或者做一个配置文件放到 SD 卡里。JQ6500 在回读状态下也可以通过返回参数读出曲目数但如果模块不支持就用宏定义手动维护。我习惯在上电启动时把当前曲目号恢复为 1避免上次断电前停留在某个曲目号上OLED 显示和实际播放不一致。按键切换播放的体验要做好关键在“边界处理”。比如第一首按上一曲很多模块 FW 自己会停在第一首但有些模块会直接跳到最后。我在上层先判断了边界再发指定播放命令而不是依赖模块内部的边界逻辑这样 OLED 显示永远不会乱。4.3 OLED 显示刷新策略OLED 显示刷新不要无脑在主循环里清屏。因为清屏一次要往 I2C 写大量 0x00 数据如果同时还在处理按键、发送串口命令会增加时间占用。我通常的做法是每一百毫秒刷新一次显示并且只显示当前状态变量uint32_t oledTick HAL_GetTick(); uint32_t lastRefresh 0; while (1) { HandleKeyEvent(GetKeyEvent()); if (HAL_GetTick() - lastRefresh 100) { OLED_ShowStatus(currentTrack, currentVol, playState); lastRefresh HAL_GetTick(); } HAL_Delay(5); }如果你做的产品需要和用户更频繁交互比如按下按键立刻看到反馈那可以把刷新间隔缩短到 30ms。OLED 本身的响应速度没问题瓶颈主要在 I2C 传输和文字绘制函数效率。如果文字绘制函数每显示一个字符都重新设置列地址、页地址那即使不闪烁也会觉得“卡”。建议在 OLED 底层驱动里做好光标的行列映射比如设置坐标后直接连续写数据不要每个字符都重发完整坐标命令。4.4 完整主循环示例把前面功能拼起来一个可以直接改的主函数逻辑如下int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_I2C1_Init(); MX_TIM2_Init(); OLED_Init(); OLED_Clear(); HAL_Delay(500); currentVol 20; currentTrack 1; playState STATE_STOPPED; JQ_SendCommand(JQ_CMD_SET_VOL, 0, currentVol); JQ_SendCommand(JQ_CMD_PLAY_INDEX, 0, currentTrack); playState STATE_PLAYING; while (1) { HandleKeyEvent(GetKeyEvent()); if (HAL_GetTick() - lastRefresh 100) { OLED_ShowStatus(currentTrack, currentVol, playState); lastRefresh HAL_GetTick(); } HAL_Delay(5); } }实际运行起来上电后喇叭会播放 0001.mp3OLED 第一行显示Track:001第二行显示Vol:20 Play。按下一曲OLED 变成 002同时喇叭切到 002。按键响应时间大约几十毫秒体感上没有延迟。4.5 实测效果与小优化我在面包板上先跑通了全部功能后续又移植到一块自制的 PCB 上。一个很小的但很有价值的优化是按住音量键连续加减时不要每按一次就发一条指令而是在长按状态下每 200ms 发一条到最大或最小值后自动停止。这样既不会把 JQ6500 的命令队列塞满也不会让 OLED 刷新频繁闪烁。另外OLED 显示当前模式时如果显示Play、Pause、Stop这样的英文看起来还可以。如果把模式用中文显示需要自己准备中文汉字取模。如果你不想折腾字体就先用 ASCII省出来的 flash 空间还能放更多业务逻辑。5. 调试实录与避坑指南5.1 OLED 不亮、花屏、“批量点不亮”这是最多人遇到的问题。我总结了几个层次现象可能原因处理方式完全不亮I2C 扫描不到供电不对、SDA/SCL 接反、地址不对先测 VCC/GND再交换 SDA/SCL地址 0x3C/0x3D 都试亮一下但很快黑屏电荷泵未开启或初始化时序不对检查初始化命令确认有0x8D 0x14屏幕只显示半屏SSD1306 列地址偏移检查初始化里0xA1/0xA0、0xC8/0xC0是否匹配显示乱码、闪烁I2C 速度过高、线太长、没有上拉降低到 100kHz缩短杜邦线加上拉电阻“批量点不亮”这种问题往往是屏幕初始化代码缺少某条命令。SSD1306 基本都能跑标准初始化但兼容屏批次不同对部分命令时序敏感。我建议把初始化做成一个函数调试时试着把0x81 0xCF这组对比度命令省略看是否有变化。逐条注释初始化命令能很快定位是哪条导致黑屏。5.2 语音模块没声音、杂音大、播放乱序语音模块最容易踩的是文件命名和格式。JQ6500 播放 TF 卡里的文件文件名最好用纯数字前缀比如0001.mp3、0002.mp3。如果文件名里有中文或特殊符号模块可能找不到曲目或者播放顺序和预期不一致。音频格式方面建议直接用最常见的 MP3 格式44.1kHz 采样率、128kbps 或 192kbps 都没问题。有些 WMA、WAV 文件虽然模块可能支持但压缩格式兼容性不如 MP3。杂音大的时候先检查喇叭是否匹配。模块功放直接驱动 3W 小喇叭如果换成大功率无源喇叭声音会破音。另外JQ6500 的电源地和功放地如果混在一起播放时会听到低频噪声。我试过把喇叭负极线和模块供电地分开走噪声立刻小了很多。如果按下播放后完全没声音但 OLED 显示状态正常多半是串口命令已经发出模块没成功解码。先用 USB 转串口模块直连 JQ6500用串口助手发一帧7E FF 06 03 00 00 01 校验 校验 EF如果能响说明 STM32 发出的帧有问题如果不能响检查模块供电、喇叭、TF 卡文件。5.3 按键失灵、重复触发、和 OLED 不同步按键按键失灵最常见原因是 GPIO 没有开上拉。STM32 的 GPIO 结构里如果配置成浮空输入按键不按时的引脚电平是不确定的这时读到的数据很随机。代码里要设置GPIO_PULLUP。重复触发则基本是消抖没做够。有些轻触开关弹跳时间可能长达 20ms只消抖 5ms 不够。我习惯在定时器中断里每 10ms 扫一次连续 3~5 次确认这样总消抖时间在 30~50ms手感也还跟手。OLED 显示和实际播放不同步多数是因为按键直接调用JQ_SendCommand后立刻更新了变量但发串口命令时模块没收到导致 OLED 显示的是“期望状态”而模块实际是“旧状态”。解决方法有两个一是把串口发送返回值检查起来发送失败时不更新显示变量二是用 JQ6500 的 TX 回读状态根据模块返回的播放状态再刷新 OLED。第一种方法简单第二种更稳。5.4 程序卡死、串口无响应的排查思路整个系统最容易卡住的位置是 OLED 的 I2C 发送。如果 OLED 接线松了或者地址不对HAL_I2C_Master_Transmit在阻塞模式下会一直等待总线空闲看起来就像程序死机。解决办法是给 I2C 发送函数加超时时间比如 50ms超时后直接跳过本次显示不要让 OLED 拖死主循环。JQ6500 串口发送也会出现类似问题。HAL_UART_Transmit的最后一个参数是超时时间如果模块没接好串口发送其实不会卡因为发送到 DR 寄存器就返回了但你用中断发送或者 DMA 时如果没有清除完成标志可能后面再也发不了。建议先用最简单的阻塞发送把功能跑通再考虑优化成中断或 DMA。还有一个容易忽略的坑JQ6500 的 TX 引脚如果不使用最好配置成浮空输入。有些 STM32 工程会默认把不用的引脚复用成其他功能或者拉低这样 TX 引脚上的噪声可能影响模块。反正我只要发现播放时偶尔“咯哒”一声就会先检查这两个引脚的配置。5.5 个人经验怎么让这套系统更省心做这类“语音 显示 按键”的项目我最深刻的体会是底层协议不用纠结到字节级别但要封装好。JQ6500 的串口帧格式、校验算法封装成JQ_SendCommand一个函数之后后面所有业务代码都不需要关心帧结构。OLED 驱动也是一样封装成OLED_ShowString和OLED_ShowStatus主循环代码会非常干净出问题时定位很快。调试顺序也非常重要。我一般先把 OLED 点亮再测按键再接语音模块。如果一开始就把四五个模块全接上出问题根本分不清是哪个环节。OLED 点亮后键盘每个按键打印到串口确认然后再用按键控制语音模块。这样每一层都是可信的最后组合起来才会顺手。最后再分享一个小技巧TF 卡里尽量多放几个测试音频命名和正式项目保持一致。调试按键切换的时候用“001 嘀”、“002 嘟嘟”、“003 嘟嘟嘟”这种有辨识度的文件远比放三首同样的音乐管用。听到第二声能直接确认切换命令和曲目排序都正确。等项目逻辑稳定后再换成正式音频文件整个开发周期能省下不少时间。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →