JQ8900-16P语音模块在天空星HC32F4A0PITB上的串口驱动移植实战

JQ8900-16P语音模块在天空星HC32F4A0PITB上的串口驱动移植实战 JQ8900-16P语音模块在天空星HC32F4A0PITB上的串口驱动移植实战最近在做一个需要语音提示的项目用到了JQ8900-16P这个语音播报模块。它价格便宜、使用简单但要把它的驱动移植到天空星HC32F4A0PITB开发板上还是遇到了一些小坑。今天我就把完整的移植过程分享出来从硬件连接到代码编写手把手教你搞定。这篇文章适合正在使用或评估JQ8900语音模块与HC32系列MCU的嵌入式开发者。我会详细讲解如何通过串口控制这个模块包括硬件引脚配置、串口初始化、中断处理以及一线和两线控制协议的实现。跟着做一遍你就能在自己的项目中快速集成语音功能了。1. 认识JQ8900-16P语音模块在开始写代码之前咱们先了解一下要用的这个模块。JQ8900-16P是一个语音播报模块核心特点是它内置了SPI-Flash来存储语音文件而且这个Flash在电脑上可以直接识别为U盘。这意味着什么意味着你更换语音内容超级方便传统的语音芯片往往需要专用的上位机软件来烧录音频而这个模块你只需要用USB线连上电脑把新的音频文件比如MP3格式拖进去按照规则重命名就行。对于产品研发和生产来说这省去了很多麻烦。模块有几个关键参数需要记住工作电压2.8V-5.5V很宽用3.3V或5V系统都能带起来IO电压3.3V模块引脚输出的电平是3.3V和咱们的天空星开发板正好匹配控制方式支持串口控制这是我们主要要用的方式模块提供了三种控制模式单独IO控制最基础的模式每个语音文件对应一个IO引脚拉低触发播放。适合简单的固定提示音场景。一线串行控制用一根数据线通过特定的时序脉冲来发送控制指令。可以节省IO口。两线串口控制也就是我们常说的UART串口波特率固定为9600数据位8位停止位1位无校验位。这种方式最灵活可以发送各种复杂指令比如播放指定曲目、调节音量等。咱们这次主要实现的就是第三种——两线串口控制这是最常用也最推荐的方式。注意模块出厂时SPI-Flash里已经预存了10首语音。如果你要更新自己的语音记得文件命名要用5位数字比如00001.mp3、00002.mp3...以此类推。另外语音文件不要太长避免Flash空间不够。2. 硬件连接与引脚配置硬件连接很简单模块和开发板之间只需要接4根线电源、地、TX、RX。根据原始资料里的代码天空星HC32F4A0PITB开发板使用的是USART2串口2来连接JQ8900模块具体引脚分配如下功能开发板引脚对应宏定义连接模块引脚串口TXPA2BSP_JQ8900_TX_PIN模块的RX串口RXPA3BSP_JQ8900_RX_PIN模块的TX一线控制IO备用PC2GPIO_JQ8900_APP模块的BUSY/APP引脚电源3.3V-VCC地GND-GND这里有个细节要注意开发板的TX要接模块的RX开发板的RX要接模块的TX这是串口通信的基本规则接反了数据就发不出去了。如果你要用一线串行控制模式还需要把PC2引脚连接到模块的BUSY/APP引脚。不过咱们这次主要讲串口模式一线模式作为补充了解一下。3. 工程文件移植与配置移植的第一步是把必要的文件添加到你的工程中。原始资料里已经提供了完整的驱动文件我们需要做的是把这些文件正确集成到自己的项目里。3.1 添加驱动文件你需要把两个文件添加到工程中bsp_jq8900.c- 驱动的主要实现文件bsp_jq8900.h- 驱动的头文件包含引脚定义和函数声明添加文件的方法和你工程里其他外设驱动一样这里就不赘述了。重点是要确保这两个文件被正确包含在编译路径中。3.2 头文件配置详解打开bsp_jq8900.h文件咱们看看里面都定义了些什么#ifndef _BSP_JQ8900_H_ #define _BSP_JQ8900_H_ #include hc32_ll.h #include string.h #include board.h // 是否开启串口调试查看模块回传数据 #define DEBUG 1 #define JQ8900_RX_LEN_MAX 250 // 串口接收缓冲区最大长度 /**************************** 串口配置 ****************************/ #define BSP_JQ8900_FCG FCG3_PERIPH_USART2 // 串口2的时钟 #define BSP_JQ8900_TX_PORT GPIO_PORT_A // 串口TX的端口 #define BSP_JQ8900_RX_PORT GPIO_PORT_A // 串口RX的端口 #define BSP_JQ8900_FUNC GPIO_FUNC_20 // 串口2的复用功能 #define BSP_JQ8900_TX_PIN GPIO_PIN_02 // 串口TX的引脚 #define BSP_JQ8900_RX_PIN GPIO_PIN_03 // 串口RX的引脚 #define BSP_JQ8900 CM_USART2 // 串口2 // 一线控制模式用的引脚定义 #define PORT_JQ8900_APP GPIO_PORT_C #define GPIO_JQ8900_APP GPIO_PIN_02 #define SET_JQ8900_APP(x) ( x ? GPIO_SetPins(PORT_JQ8900_APP,GPIO_JQ8900_APP) : GPIO_ResetPins(PORT_JQ8900_APP,GPIO_JQ8900_APP) ) // 函数声明 void JQ8900_Init(void); void JQ8900_USART_send_String(unsigned char *str, unsigned int len); void SendData(unsigned char addr); #endif这里有几个关键点时钟配置FCG3_PERIPH_USART2表示使用FCG3时钟组的USART2外设时钟引脚复用GPIO_FUNC_20是USART2的复用功能编号这个值需要查HC32F4A0的数据手册确认一线控制宏SET_JQ8900_APP(x)是一个方便的宏用于设置一线控制引脚的电平如果你的开发板引脚分配和这里不一样只需要修改这些宏定义即可不需要改底层驱动代码。4. 串口驱动代码详解现在咱们深入到bsp_jq8900.c文件看看具体的实现。我会把关键函数拆开讲解让你明白每一行代码的作用。4.1 串口初始化函数这是最核心的函数负责配置USART2的工作参数void JQ8900_USART_Init(unsigned int bund) { stc_usart_uart_init_t stcUartInit; // 关闭寄存器外设写保护 LL_PERIPH_WE(LL_PERIPH_ALL); // TX引脚初始化 - 配置为USART2的TX功能 GPIO_SetFunc(BSP_JQ8900_TX_PORT, BSP_JQ8900_TX_PIN, BSP_JQ8900_FUNC); // RX引脚初始化 - 配置为USART2的RX功能 GPIO_SetFunc(BSP_JQ8900_RX_PORT, BSP_JQ8900_RX_PIN, BSP_JQ8900_FUNC); // 使能USART2时钟 FCG_Fcg3PeriphClockCmd(BSP_JQ8900_FCG, ENABLE); // 初始化串口配置结构体 (void)USART_UART_StructInit(stcUartInit); stcUartInit.u32ClockSrc USART_CLK_SRC_INTERNCLK; // 选择内部时钟源 stcUartInit.u32ClockDiv USART_CLK_DIV16; // 选择16分频 stcUartInit.u32CKOutput USART_CK_OUTPUT_DISABLE; // 不输出时钟 stcUartInit.u32Baudrate bund; // 波特率JQ8900固定9600 stcUartInit.u32DataWidth USART_DATA_WIDTH_8BIT; // 数据宽度8位 stcUartInit.u32StopBit USART_STOPBIT_1BIT; // 停止位1位 stcUartInit.u32OverSampleBit USART_OVER_SAMPLE_8BIT; // 8倍过采样 stcUartInit.u32FirstBit USART_FIRST_BIT_LSB; // 先发送最低位 stcUartInit.u32StartBitPolarity USART_START_BIT_FALLING; // 起始位下降沿 stcUartInit.u32HWFlowControl USART_HW_FLOWCTRL_NONE; // 禁止硬件流控 // 初始化USART USART_UART_Init(BSP_JQ8900, stcUartInit, NULL); // 配置中断错误中断和接收中断 stc_irq_signin_config_t stcIrqSigninCfg; // 错误中断配置 stcIrqSigninCfg.enIRQn INT008_IRQn; stcIrqSigninCfg.enIntSrc INT_SRC_USART2_EI; stcIrqSigninCfg.pfnCallback USART2_ERROR_IRQHandler; (void)INTC_IrqSignIn(stcIrqSigninCfg); NVIC_ClearPendingIRQ(stcIrqSigninCfg.enIRQn); NVIC_SetPriority(stcIrqSigninCfg.enIRQn, DDL_IRQ_PRIO_DEFAULT); NVIC_EnableIRQ(stcIrqSigninCfg.enIRQn); // 接收中断配置 stcIrqSigninCfg.enIRQn INT009_IRQn; stcIrqSigninCfg.enIntSrc INT_SRC_USART2_RI; stcIrqSigninCfg.pfnCallback USART2_RECV_IRQHandler; (void)INTC_IrqSignIn(stcIrqSigninCfg); NVIC_ClearPendingIRQ(stcIrqSigninCfg.enIRQn); NVIC_SetPriority(stcIrqSigninCfg.enIRQn, DDL_IRQ_PRIO_00); NVIC_EnableIRQ(stcIrqSigninCfg.enIRQn); // 开启TX和RX功能 USART_FuncCmd(BSP_JQ8900, USART_TX, ENABLE); USART_FuncCmd(BSP_JQ8900, USART_RX, ENABLE); // 开启接收中断 USART_FuncCmd(BSP_JQ8900, USART_INT_RX, ENABLE); }这里有几个需要注意的地方波特率JQ8900模块的串口波特率固定是9600所以初始化时传入9600数据格式8位数据位1位停止位无校验位这是模块要求的固定格式中断优先级接收中断的优先级设为了DDL_IRQ_PRIO_00最高确保数据能及时接收4.2 数据发送函数发送数据到模块有两个函数一个发送单个字节一个发送字符串// 发送单个字符 void JQ8900_USART_Send_Bit(unsigned char ch) { USART_WriteData(BSP_JQ8900, ch); // 等待发送完成 while( RESET USART_GetStatus(BSP_JQ8900, USART_FLAG_TX_EMPTY) ){} } // 发送字符串 void JQ8900_USART_send_String(unsigned char *str, unsigned int len) { while( len-- ) { JQ8900_USART_Send_Bit(*str); } }发送字符串函数在实际使用中很方便比如要发送一个4字节的指令直接调用这个函数就行。4.3 中断服务函数串口接收数据是通过中断方式处理的这样不会阻塞主程序// 全局接收缓冲区 unsigned char JQ8900_RX_BUFF[JQ8900_RX_LEN_MAX]; unsigned char JQ8900_RX_FLAG 0; unsigned char JQ8900_RX_LEN 0; // 串口接收中断服务函数 void USART2_RECV_IRQHandler(void) { JQ8900_RX_BUFF[JQ8900_RX_LEN] USART_ReadData(BSP_JQ8900); // 等待数据接收完成 while( SET USART_GetStatus(BSP_JQ8900, USART_FLAG_RX_FULL) ){} JQ8900_RX_LEN ( JQ8900_RX_LEN 1 ) % JQ8900_RX_LEN_MAX; JQ8900_RX_BUFF[JQ8900_RX_LEN] \0; // 字符串结尾补\0 JQ8900_RX_FLAG SET; // 设置接收完成标志 }接收中断的工作流程是当串口收到一个字节时触发中断在中断服务函数中读取数据存入缓冲区更新缓冲区索引和接收标志主程序可以通过检查JQ8900_RX_FLAG来判断是否收到新数据提示这里缓冲区大小是250字节对于JQ8900的控制指令来说完全够用。模块的反馈信息一般很短。4.4 一线串行控制实现除了串口控制驱动里还实现了一线控制模式。这种模式只用一根数据线通过特定的时序来发送指令void SendData(unsigned char addr) { unsigned char i; SET_JQ8900_APP(1); // 开始拉高 delay_us(500); SET_JQ8900_APP(0); // 开始引导码低电平 delay_ms(4); // 此处延时最少要大于2ms官方建议4ms // 发送8位数据从低位开始 for(i 0; i 8; i) { SET_JQ8900_APP(1); // 数据总是从1开始 if(addr 0x01) // 发送位1高电平1300us低电平500us { delay_us(1300); SET_JQ8900_APP(0); delay_us(500); } else // 发送位0高电平500us低电平1300us { delay_us(500); SET_JQ8900_APP(0); delay_us(1300); } addr 1; // 准备发送下一位 } SET_JQ8900_APP(1); delay_ms(10); // 两个字节之间延时建议在10ms以上 }一线控制的时序要求比较严格起始信号先拉高500us然后拉低至少2ms建议4ms数据位每个位用两个脉冲表示位1是1300us高500us低位0是500us高1300us低字节间隔至少10ms这种模式适合IO口紧张的场景但编程复杂度比串口高而且只能发送简单的控制指令。5. 主程序测试与验证驱动写好了接下来就是测试。在main函数中初始化模块然后发送控制指令#include board.h #include bsp_uart.h #include stdio.h #include bsp_jq8900.h int32_t main(void) { // 下一曲指令0xAA 0x06 0x00 0xB0 uint8_t send_buff[] {0xAA, 0x06, 0x00, 0xB0}; board_init(); // 开发板初始化 uart1_init(115200U); // 调试串口初始化用于打印信息 printf(JQ8900语音模块测试开始...\r\n); JQ8900_Init(); // 初始化JQ8900模块 while(1) { // 两线串口测试发送下一曲指令 JQ8900_USART_send_String(send_buff, 4); printf(已发送下一曲指令\r\n); delay_ms(2000); // 每隔2秒切到下一曲 } }这段测试代码的效果是模块会循环播放内部存储的所有语音文件每2秒切换到下一首。6. 常见问题与调试技巧在实际移植过程中你可能会遇到一些问题。这里分享几个我踩过的坑问题1模块没反应不播放语音检查电源确保模块的VCC接的是3.3V或5VGND接好检查接线TX-RX、RX-TX是否交叉连接正确检查波特率JQ8900只支持9600波特率确认初始化时传入的是9600用示波器或逻辑分析仪看串口波形确认数据确实发出去了问题2播放的语音不对或乱码检查指令格式JQ8900的指令是固定的4字节最后一个字节是校验和确认语音文件命名必须是5位数字如00001.mp3检查语音文件格式模块支持MP3格式确保你的文件是MP3编码问题3一线控制模式不工作检查时序用示波器测量波形确保高低电平的时间符合要求检查延时函数delay_us()和delay_ms()的精度要足够检查引脚配置一线控制引脚要配置为输出模式调试建议先测试串口通信发送简单的数据用串口助手看模块是否有返回从简单的指令开始测试比如播放指定曲目如果使用一线控制务必严格按照时序要求编写代码移植成功后你就可以根据自己的需求修改控制指令了。JQ8900支持很多指令比如播放指定曲目、暂停、停止、音量调节等具体指令格式可以参考模块的数据手册。完整的移植代码可以在立创开发板的资料下载中心找到搜索JQ8900就能找到对应的压缩包。里面包含了所有必要的文件你可以直接参考使用。