当前位置: 首页 > news >正文

JQ8900-16P语音模块串口驱动移植与天空星STM32F407实战应用

JQ8900-16P语音模块串口驱动移植与天空星STM32F407实战应用

最近在做一个智能设备的项目,需要加入语音提示功能,比如“欢迎光临”、“操作成功”之类的。找了一圈,发现JQ8900-16P这个语音模块挺合适,价格便宜,控制简单,最关键的是它内置了SPI Flash,更新语音就像往U盘里拷贝文件一样方便,不用折腾什么专用上位机软件。正好手头有块天空星的STM32F407开发板,就决定用它来驱动这个模块。

今天这篇文章,我就来手把手带你把这个JQ8900语音模块,通过串口接到咱们的天空星开发板上,从硬件连接到驱动代码编写,再到最后播放测试,走一遍完整的流程。就算你之前没玩过语音模块,跟着做下来也能搞定。

1. 认识JQ8900-16P语音模块

在动手写代码之前,咱们先得搞清楚要控制的“对象”是个啥。JQ8900-16P模块,你可以把它理解成一个自带小喇叭和存储卡的MP3播放器,但它比MP3更简单,专门为嵌入式设备设计。

模块核心特点:

  • 供电简单:工作电压范围是2.8V到5.5V,咱们的开发板上的3.3V或者5V都能直接用。
  • 功耗低:额定电流在500微安到10毫安之间,非常省电。
  • 存储方便:这是它最大的亮点!模块上的SPI Flash芯片在电脑上会被识别成一个U盘。你想换语音,直接把新的音频文件(比如MP3格式)拖进去,按规则重命名就行,完全不需要任何烧录工具。
  • 控制灵活:支持三种控制模式,适合不同场景:
    • 单独IO控制:每个语音文件对应一个引脚,拉低引脚就播放,适合按键直接触发。
    • 一线串行控制:用一根数据线,按照特定的时序脉冲发送指令,节省IO口。
    • 两线串口控制:就是我们今天要用的方式,通过串口发送指令,功能最全,也最常用。

注意:模块出厂时,SPI Flash里已经预存了10首测试语音。你自己添加的语音文件不能太大,否则存储空间可能不够。

串口通信参数(务必记准):我们选择“两线串口控制”模式,它的通信格式是固定的:

  • 波特率:9600
  • 数据位:8位
  • 停止位:1位
  • 校验位:无

这个参数就像两个人对话的语速和规则,单片机和模块必须设置成一样的才能正常交流。

2. 硬件连接:把模块“插”到开发板上

硬件连接是第一步,错了后面全白搭。我们用的是串口控制,所以只需要连接4根线。

模块引脚天空星STM32F407引脚连接说明
VCC3.3V 或 5V电源正极,开发板上任意的3.3V或5V引脚
GNDGND电源地,和开发板共地
TXPA3 (USART2_RX)模块的TX接单片机的RX,模块发送数据给单片机听
RXPA2 (USART2_TX)模块的RX接单片机的TX,单片机发送指令给模块

重要提示:串口连接时,一定要交叉!即发送端(TX)接接收端(RX)。很多新手在这里栽跟头,如果接反了,通信肯定失败。

为什么选PA2和PA3?因为这两个引脚是STM32F407的USART2串口默认功能引脚,用起来最方便,不需要重映射。当然,你也可以用其他串口,但代码里的引脚定义就得跟着改。

3. 驱动代码移植与解析

硬件连好了,接下来就是软件部分。我们需要创建两个文件:bsp_jq8900.c(驱动源文件)和bsp_jq8900.h(驱动头文件)。下面我逐段解释关键代码。

3.1 头文件配置 (bsp_jq8900.h)

头文件主要做两件事:定义硬件连接和声明函数。

#ifndef _BSP_JQ8900_H_ #define _BSP_JQ8900_H_ #include "stm32f4xx.h" #include "string.h" #include "board.h" // 你的开发板基础头文件 // 是否开启调试,开启后会在串口0打印接收到的数据 #define DEBUG 1 #define JQ8900_RX_LEN_MAX 250 // 串口接收缓冲区最大长度 /**************************** 串口配置 ****************************/ // 以下宏定义将代码与具体的硬件引脚绑定 #define BSP_JQ8900_TX_RCC RCC_AHB1Periph_GPIOA // TX引脚所在GPIO端口时钟 #define BSP_JQ8900_RX_RCC RCC_AHB1Periph_GPIOA // RX引脚所在GPIO端口时钟 #define BSP_JQ8900_RCC RCC_APB1Periph_USART2 // 使用USART2 #define BSP_JQ8900_TX_PORT GPIOA // TX引脚端口 #define BSP_JQ8900_RX_PORT GPIOA // RX引脚端口 #define BSP_JQ8900_AF GPIO_AF_USART2 // 复用功能为USART2 #define BSP_JQ8900_TX_PIN GPIO_Pin_2 // TX对应PA2 #define BSP_JQ8900_TX_SOURCE GPIO_PinSource2 #define BSP_JQ8900_RX_PIN GPIO_Pin_3 // RX对应PA3 #define BSP_JQ8900_RX_SOURCE GPIO_PinSource3 #define BSP_JQ8900 USART2 // 使用的串口 #define BSP_JQ8900_IRQ USART2_IRQn // 串口中断号 #define BSP_JQ8900_IRQHandler USART2_IRQHandler // 中断服务函数名 // 如果你要用一线串行控制模式,才需要定义这个APP引脚(这里用PC2举例) #define RCC_JQ8900_APP RCC_AHB1Periph_GPIOC #define PORT_JQ8900_APP GPIOC #define GPIO_JQ8900_APP GPIO_Pin_2 #define SET_JQ8900_APP(x) GPIO_WriteBit(PORT_JQ8900_APP, GPIO_JQ8900_APP, x?Bit_SET:Bit_RESET); // 函数声明 void JQ8900_Init(void); void JQ8900_USART_send_String(unsigned char *str, unsigned int len); void SendData ( unsigned char addr ); // 一线串行控制函数 #endif

这里把串口2(USART2)和引脚PA2、PA3绑定好了。如果你想换到其他串口(比如USART1),只需要修改这些宏定义即可。

3.2 串口初始化与发送函数 (bsp_jq8900.c)

驱动文件里函数不少,我们挑最核心的几个讲。

首先是串口初始化函数JQ8900_USART_Init。这个函数负责把STM32的USART2配置成9600波特率、8N1格式,并开启接收中断。

void JQ8900_USART_Init(unsigned int bund) { GPIO_InitTypeDef GPIO_InitStructure; // 1. 使能GPIO时钟 RCC_AHB1PeriphClockCmd(BSP_JQ8900_TX_RCC, ENABLE); RCC_AHB1PeriphClockCmd(BSP_JQ8900_RX_RCC, ENABLE); // 2. 配置GPIO为串口复用功能 GPIO_PinAFConfig(BSP_JQ8900_TX_PORT, BSP_JQ8900_TX_SOURCE, BSP_JQ8900_AF); GPIO_PinAFConfig(BSP_JQ8900_RX_PORT, BSP_JQ8900_RX_SOURCE, BSP_JQ8900_AF); GPIO_InitStructure.GPIO_Pin = BSP_JQ8900_TX_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 复用模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉 GPIO_Init(BSP_JQ8900_TX_PORT, &GPIO_InitStructure); // RX引脚的配置类似,略... // 3. 使能USART2时钟并配置参数 RCC_APB1PeriphClockCmd(BSP_JQ8900_RCC, ENABLE); USART_InitTypeDef USART_InitStructure; USART_StructInit(&USART_InitStructure); USART_InitStructure.USART_BaudRate = bund; // 波特率,我们传9600 USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(BSP_JQ8900, &USART_InitStructure); // 4. 使能接收中断和空闲中断 USART_ITConfig(BSP_JQ8900, USART_IT_RXNE, ENABLE); // 接收缓冲区非空中断 USART_ITConfig(BSP_JQ8900, USART_IT_IDLE, ENABLE); // 空闲中断,用于判断一帧数据接收完成 USART_Cmd(BSP_JQ8900, ENABLE); // 使能串口 // 5. 配置NVIC(嵌套向量中断控制器),设置中断优先级 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = BSP_JQ8900_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }

这里用到了空闲中断(IDLE),这是个好东西。当串口总线上一段时间没有新数据时,就会产生这个中断。我们可以用它来判断一包数据是否接收完毕,比单纯用接收中断更可靠。

然后是数据发送函数,很简单,就是循环发送字符串里的每个字节。

void JQ8900_USART_send_String(unsigned char *str, unsigned int len) { while( len-- ) { // 调用发送单字节函数 USART_SendData(BSP_JQ8900, (uint8_t)(*str++)); while( RESET == USART_GetFlagStatus(BSP_JQ8900, USART_FLAG_TXE) ){} // 等待发送完成 } }

3.3 中断服务函数:接收模块的回复

我们发送指令后,模块有时会回复状态(比如播放完成信号),所以需要接收数据。这里用中断方式接收,不占用主程序时间。

// 定义接收缓冲区和标志 unsigned char JQ8900_RX_BUFF[JQ8900_RX_LEN_MAX]; unsigned char JQ8900_RX_FLAG = 0; unsigned char JQ8900_RX_LEN = 0; void JQ8900_USART_IRQHandler(void) { // 1. 处理接收中断(RXNE):来了一个新字节 if(USART_GetITStatus(BSP_JQ8900, USART_IT_RXNE) != RESET) { // 读取这个字节,存到缓冲区 JQ8900_RX_BUFF[ JQ8900_RX_LEN ] = USART_ReceiveData(BSP_JQ8900); // 如果定义了DEBUG,可以通过printf打印出来看看 #if DEBUG printf("%c", JQ8900_RX_BUFF[ JQ8900_RX_LEN ]); #endif // 缓冲区索引增加,防止溢出 JQ8900_RX_LEN = ( JQ8900_RX_LEN + 1 ) % JQ8900_RX_LEN_MAX; USART_ClearITPendingBit(BSP_JQ8900, USART_IT_RXNE); // 清除中断标志 } // 2. 处理空闲中断(IDLE):一帧数据收完了 if(USART_GetITStatus(BSP_JQ8900, USART_IT_IDLE) == SET) { // 读SR和DR寄存器是为了清除IDLE标志位,这是STM32库函数的要求 volatile uint32_t temp; temp = BSP_JQ8900->SR; temp = BSP_JQ8900->DR; // 给接收到的字符串加上结束符'\0',方便后续用字符串函数处理 JQ8900_RX_BUFF[JQ8900_RX_LEN] = '\0'; JQ8900_RX_FLAG = 1; // 设置接收完成标志,主函数里可以查询这个标志来处理数据 USART_ClearITPendingBit(BSP_JQ8900, USART_IT_IDLE); } }

这个中断服务函数是驱动接收部分的核心。它实现了“来一个字节存一个,数据流停了就通知主程序”的机制。

3.4 模块初始化函数

最后,我们把所有初始化工作封装成一个函数,方便主程序调用。

void JQ8900_Init(void) { JQ8900_USART_Init(9600); // 初始化串口,波特率9600 // 以下是一线串行控制模式所需的GPIO初始化(如果只用串口模式,这部分可以不要) GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_JQ8900_APP, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_JQ8900_APP; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(PORT_JQ8900_APP, &GPIO_InitStructure); GPIO_SetBits(PORT_JQ8900_APP, GPIO_JQ8900_APP); // 默认拉高 }

4. 主程序测试:让模块“开口说话”

驱动写好了,现在来写个主程序测试一下。我们让模块循环播放“下一曲”。

#include "board.h" #include "bsp_uart.h" // 用于printf调试 #include <stdio.h> #include "bsp_jq8900.h" int main(void) { // JQ8900串口控制指令:下一曲 // 指令格式:0xAA (帧头), 0x06 (命令), 0x00 (数据高字节), 0xB0 (数据低字节+校验) uint8_t send_buff[4] = {0xAA, 0x06, 0x00, 0xB0}; board_init(); // 开发板基础初始化(系统时钟、滴答定时器等) uart1_init(115200U); // 初始化调试串口,用于打印信息 printf("JQ8900 Test Start...\r\n"); JQ8900_Init(); // 初始化JQ8900模块(串口、GPIO等) while(1) { // 每隔2秒发送一次“下一曲”指令 JQ8900_USART_send_String(send_buff, 4); printf("Send 'Next' command.\r\n"); delay_ms(2000); // 等待2秒 } }

这段代码干了啥?

  1. 定义了一个指令数组send_buff,里面是让模块播放“下一曲”的串口指令码。
  2. 初始化系统和调试串口。
  3. 调用JQ8900_Init()初始化我们刚写好的驱动。
  4. 在主循环里,每隔2秒就通过串口发送一次这个指令。

上电后的现象应该是:模块会从第一首语音开始播放,播放完后等2秒(我们延迟的时间),自动播放第二首,依次类推,直到播放完存储的所有语音文件,然后又会从第一首开始循环。

5. 进阶与排错

如何播放指定曲目?JQ8900有完整的串口指令集,在它的数据手册里。比如播放第5首语音,指令可能是{0xAA, 0x07, 0x00, 0x05, 0xB2}(具体以手册为准)。你需要把指令码替换掉上面测试代码里的send_buff即可。

如果没声音,怎么排查?

  1. 查硬件:首先确认VCC和GND没接反、接牢。重点检查TX和RX是否交叉连接。可以用万用表测一下电压。
  2. 查代码:确认波特率是不是9600。确认发送的指令码是否正确。可以尝试在JQ8900_USART_send_String函数里加个printf,把发送的每个字节的十六进制打印出来看看。
  3. 听提示音:有些模块上电或收到错误指令时会有一个“嘀”的提示音。如果有提示音但播放没声音,可能是语音文件格式或命名不对。
  4. 用USB-TTL调试:如果你有一个USB转TTL模块,可以把它连接到电脑,用串口助手直接发送指令给JQ8900,这样可以排除单片机代码的问题。

关于语音文件: 把模块用USB线连接到电脑,它会弹出一个U盘。把你要的MP3或WAV文件放进去,并重命名为5位数字,例如00001.mp300002.mp3。模块就是根据这个文件名编号来播放对应曲目的。

好了,整个移植和应用的流程就是这样。代码我已经在实际的天空星开发板上跑通了。你只要跟着步骤,注意硬件连接和指令格式,让JQ8900在STM32上工作起来并不难。遇到问题,多利用串口打印调试信息,慢慢分析,总能解决的。

http://www.jsqmd.com/news/473366/

相关文章:

  • 基于Vue3的Nano-Banana Studio前端控制台开发
  • 面试题|MySQL InnoDB索引不选择hash的原因
  • C 头文件
  • 紧急!MCP v3.6升级后Sampling调用流中断?2小时内恢复方案:5步回滚检查清单 + 4个兼容性补丁 + 1份经CNCF SIG-Observability认证的验证脚本
  • 面试题|MySQL InnoDB B+树内部节点为什么存储索引健值不存储数据行
  • go面经(1)
  • gte-base-zh部署SLA保障:99.9%可用性设计——双活Xinference节点方案
  • MVC 控制器
  • 紧急预警:PHP 8.3已废弃ReflectionProperty::setAccessible()!你的低代码表单动态赋值逻辑正在 silently 失效(附向后兼容热补丁)
  • 解锁yolov8全能力:借助快马平台ai助手玩转分割与姿态估计
  • C++20 auto 写法
  • 历史一轮复习大纲
  • DeepSeek-OCR开源模型教程:基于<|grounding|>提示词的空间感知调用
  • 突破Windows版本限制:MediaCreationTool.bat全场景介质创建应用指南
  • figmaCN插件全攻略:从安装到定制的设计师本地化解决方案
  • wpf canvas 移动 缩放
  • YOLO-V5目标检测实战:识别图片中物体位置,附完整代码示例
  • 【CVPR26-孙栩-北京大学】Conan:像侦探一样对多尺度视觉证据进行渐进式学习推理
  • 打破PDF笔记壁垒:Obsidian PDF Plus让文献管理效率提升300%的秘密
  • 2026 深度审计:ChatGPT Plus 国内充值封锁现状与“免密码”激活方案实测
  • translategemma-12b-it效果实测:技术文档扫描件翻译准确率惊人
  • Hotkey Detective:Windows热键冲突的智能诊断解决方案
  • Ostrakon-VL-8B MySQL数据可视化:将图片分析结果转化为商业洞察
  • 南北阁 Nanbeige 4.1-3B Streamlit定制:支持暗色模式与字体大小调节
  • LangGraph实战:AI从此‘过目不忘’——知识库+记忆宫殿实现持续学习,拒绝信息丢失!
  • 文墨共鸣作品展示:当政务文件遇见水墨风AI的惊艳分析效果
  • 5-4分析活动投票情况
  • 当Android Studio遇上AI:用快马解决图片处理中的内存优化难题
  • WAN2.2文生视频零基础教程:5分钟用中文提示词生成你的第一个AI视频
  • Z-Image-GGUF模型推理性能测试:不同GPU配置下的速度对比