MP3 单曲循环播放器 — 最简版说明
STM32F103C8T6 + MP3-TF-16P,固定重复播放一首歌。
对应代码:User/main.c(最简版,无 ESP / OLED / 按键)。
一、硬件清单
| 物品 | 说明 |
|---|
| STM32F103C8T6 蓝板 | 主控,USB 或 ST-Link 供电 |
| MP3-TF-16P 模块 | 带底板版,底部 5 针:VCC / GND / RXD / TXD / BUSY |
| TF 卡 | ≤32GB,FAT32 格式(64G 默认 exFAT 模块不认) |
| 小喇叭 | 8Ω / 1~3W,接模块"喇叭±"焊盘,不分正负 |
| 杜邦线 | 3~4 根(公对母) |
二、接线表(只要 3 根线)
| MP3-TF-16P 引脚 | 接到 STM32 | 说明 |
|---|
| VCC | 5V(板上 5V 排针) | ⚠️ 别接 3.3V,供电不足会卡顿/重启 |
| GND | GND | 必须共地,否则指令全丢 |
| RXD | PA9(USART1_TX) | 交叉:单片机 TX → 模块 RX |
| TXD | (可不接) | 模块返回数据,本版代码不读 |
| BUSY | 默认悬空;备用接PB0 | 见下文"USE_BUSY 开关" |
电源提醒:蓝板用 USB 线供电时,板上5V针即 USB 直通 5V,直接给模块;若只用 ST-Link(只有 3.3V),需另找一路 5V 给模块,且两路电源GND 连在一起。
三、TF 卡准备
TF卡根目录 └─ 01 ← 文件夹名:两位数字 01~99 └─ 001.mp3 ← 文件名:三位数字 .mp3,不能有其他前缀字符
- 格式化选FAT32(不是 exFAT / NTFS)。
- 只放这一首歌:文件夹里只有
001.mp3,"全部循环"就是单曲循环。
四、代码说明(main.c)
初始化流程
上电 → 延时 1s(模块稳定) → 0x09, 0x02 指定设备 = TF 卡 → 延时 500ms(等卡挂载,太短会吞命令) → 0x06, 20 音量 20(范围 0~30,改这个数调音量) → 0x08, 0x00 全部循环(主流固件认) → 0x11, 0x01 循环播放(另一批固件认,双保险) → 0x0F, 0x0101 播放 01 文件夹的 001.mp3 → 主循环空转,重复播放由模块自己完成
DFPlayer 命令帧格式(10 字节)
7E FF 06 [CMD] 00 [参数高] [参数低] [校验高] [校验低] EF
- 波特率9600,USART1 只发不收;
- 校验和 =
0 - (0xFF + 0x06 + CMD + 参数高 + 参数低)的 16 位补码(代码已算好)。
USE_BUSY 开关(备用方案)
#defineUSE_BUSY0/* 默认 0:不接 BUSY,靠模块固件循环 */
| 现象 | 处理 |
|---|
| 能自动循环(默认情况) | 什么都不用改 |
| 播完一遍真停了、不循环 | 说明固件不支持循环命令:接BUSY→PB0,把USE_BUSY改1重新编译 |
USE_BUSY=1 时原理:BUSY 播放时为低、停止时跳高,STM32 检测到变高(消抖 200ms)就重发播放命令,不依赖固件。
五、烧录步骤
- Keil 打开工程(
MP3目录,标准库 V3.5.0); - 无需增删文件,直接Rebuild;
- ST-Link / 串口烧录
Objects下的 HEX; - 复位上电,喇叭应开始播放并无限循环。
六、故障排查(按顺序过)
| 现象 | 排查 |
|---|
| 完全没声 | ① 共地?② TF 卡 FAT32?③ 文件名严格001.mp3?④ 模块接的是 5V?⑤ 喇叭焊点牢? |
| 有咔哒声/重启 | 模块供电不足,换 5V 直供 |
| 没播完就重播 | BUSY 悬空被误读,确认USE_BUSY 0(本版已默认关) |
| 播完不循环 | 接BUSY→PB0,USE_BUSY改1 |
| 找不到文件 | 文件夹/文件命名不严格,或卡是 exFAT |
#include"stm32f10x.h"#include"Delay.h"/* * 最简版:固定重复播放一首歌 * ========================= * 接线(只要 3 根线): * MP3-TF-16P VCC -> 5V * MP3-TF-16P GND -> GND(共地) * MP3-TF-16P RXD -> PA9 (USART1_TX) * 喇叭接模块"喇叭±"焊盘 * * TF 卡(FAT32):01 文件夹里放 001.mp3(一首歌) * * 如果烧录后发现播完一遍就停、不自动循环(说明固件不支持循环命令), * 再把 BUSY 接到 PB0,并把下面的 USE_BUSY 改成 1 重新编译烧录。 */#defineUSE_BUSY0/* 0 = 不用 BUSY 引脚(默认);1 = BUSY 接 PB0 后启用 */voidMP3_Init(void);voidMP3_SendCmd(uint8_tcmd,uint16_tparam);voidBusy_Init(void);intmain(void){MP3_Init();#ifUSE_BUSYBusy_Init();#endifDelay_ms(1000);/* 等模块上电稳定 */MP3_SendCmd(0x09,0x02);/* 指定设备:TF 卡 */Delay_ms(500);/* 等卡挂载 */MP3_SendCmd(0x06,20);/* 音量 20(0~30),按需改 */Delay_ms(100);MP3_SendCmd(0x08,0x00);/* 全部循环(主流固件认) */Delay_ms(100);MP3_SendCmd(0x11,0x01);/* 循环播放(另一批固件认,双保险) */Delay_ms(100);MP3_SendCmd(0x0F,0x0101);/* 播放 01/001.mp3,文件夹只有一首 = 单曲循环 */while(1){#ifUSE_BUSY/* BUSY 兜底:模块停止时 BUSY 变高 -> 重新播放 */if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)==1){Delay_ms(200);/* 消抖,避开切歌间隙 */if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)==1){MP3_SendCmd(0x0F,0x0101);Delay_ms(500);}}#elseDelay_ms(100);/* 空转,一切交给模块自己循环 */#endif}}/* ---------------- USART1 初始化(9600,发命令给 MP3 模块) ---------------- */voidMP3_Init(void){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;/* PA9 = TX */GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);USART_InitStructure.USART_BaudRate=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_HardwareFlowControl=USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode=USART_Mode_Tx;/* 只发不收 */USART_Init(USART1,&USART_InitStructure);USART_Cmd(USART1,ENABLE);}/* ---------------- BUSY 引脚(PB0 输入,播放=低,停止=高) ---------------- */voidBusy_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_Init(GPIOB,&GPIO_InitStructure);}/* ---------------- 发送一条 MP3 命令帧(10 字节) ---------------- */voidMP3_SendCmd(uint8_tcmd,uint16_tparam){uint8_tframe[10];uint16_tsum,checksum;uint8_ti;frame[0]=0x7E;frame[1]=0xFF;frame[2]=0x06;frame[3]=cmd;frame[4]=0x00;frame[5]=(uint8_t)(param>>8);frame[6]=(uint8_t)(param&0xFF);sum=0xFF+0x06+cmd+frame[5]+frame[6];checksum=(uint16_t)(0-sum);/* 16 位补码 */frame[7]=(uint8_t)(checksum>>8);frame[8]=(uint8_t)(checksum&0xFF);frame[9]=0xEF;for(i=0;i<10;i++){USART_SendData(USART1,frame[i]);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);}}