STM32 MP3播放器实现方案,通过SPI读取SD卡中的MP3文件,使用VS1003/VS1053解码芯片进行播放。
一、系统架构
1.1 硬件连接
STM32F103C8T6 (主控)
├── SPI1 → SD卡 (FAT32文件系统)
│ ├── CS → PA4
│ ├── SCK → PA5
│ ├── MISO → PA6
│ └── MOSI → PA7
├── SPI2 → VS1053音频解码芯片
│ ├── XCS → PB12 (命令片选)
│ ├── XDCS → PB13 (数据片选)
│ ├── DREQ → PB14 (数据请求)
│ ├── SCK → PB3
│ ├── MISO → PB4
│ └── MOSI → PB5
├── I2S → 音频输出 (可选,如果VS1053使用I2S输出)
└── 控制接口
├── 播放/暂停 → PA0
├── 下一曲 → PA1
├── 上一曲 → PA2
├── 音量+ → PA3
└── 音量- → PA4
二、核心驱动代码
2.1 VS1053驱动 (vs1053.h)
c
/**
* @file vs1053.h
* @brief VS1053音频解码芯片驱动
*/
#ifndef __VS1053_H
#define __VS1053_H
#include "stm32f1xx_hal.h"
#include <stdint.h>
/* VS1053寄存器地址 */
#define VS1053_REG_MODE 0x00
#define VS1053_REG_STATUS 0x01
#define VS1053_REG_BASS 0x02
#define VS1053_REG_CLOCKF 0x03
#define VS1053_REG_DECODE_TIME 0x04
#define VS1053_REG_AUDATA 0x05
#define VS1053_REG_WRAM 0x06
#define VS1053_REG_WRAMADDR 0x07
#define VS1053_REG_HDAT0 0x08
#define VS1053_REG_HDAT1 0x09
#define VS1053_REG_VOLUME 0x0B
/* 命令定义 */
#define VS1053_CMD_READ 0x03
#define VS1053_CMD_WRITE 0x02
/* VS1053工作模式 */
#define VS1053_MODE_SM_DIFF 0x0001 // 差分模式
#define VS1053_MODE_SM_LAYER12 0x0002 // 允许MPEG Layer 1/2
#define VS1053_MODE_SM_RESET 0x0004 // 软复位
#define VS1053_MODE_SM_OUTOFWAV 0x0008 // 退出WAV模式
#define VS1053_MODE_SM_PDOWN 0x0010 // 掉电模式
#define VS1053_MODE_SM_TESTS 0x0020 // 测试模式
#define VS1053_MODE_SM_STREAM 0x0040 // 流模式
#define VS1053_MODE_SM_SDINEW 0x0800 // 使用SDI新协议
#define VS1053_MODE_SM_ADPCM 0x1000 // ADPCM录音
#define VS1053_MODE_SM_LINE1 0x4000 // Line1输入
#define VS1053_MODE_SM_CLKRANGE 0x8000 // 时钟范围加倍
/* 音量控制 (0x00最大, 0xFE最小) */
#define VS1053_VOLUME_MAX 0x00
#define VS1053_VOLUME_MIN 0xFE
/* GPIO定义 */
#define VS1053_XCS_PORT GPIOB
#define VS1053_XCS_PIN GPIO_PIN_12
#define VS1053_XDCS_PORT GPIOB
#define VS1053_XDCS_PIN GPIO_PIN_13
#define VS1053_DREQ_PORT GPIOB
#define VS1053_DREQ_PIN GPIO_PIN_14
#define VS1053_RST_PORT GPIOB
#define VS1053_RST_PIN GPIO_PIN_15
/* 函数声明 */
void VS1053_Init(void);
void VS1053_HardReset(void);
void VS1053_SoftReset(void);
void VS1053_SetVolume(uint8_t left, uint8_t right);
void VS1053_WriteRegister(uint8_t reg, uint16_t data);
uint16_t VS1053_ReadRegister(uint8_t reg);
void VS1053_SendData(uint8_t *data, uint16_t length);
void VS1053_PlayFile(const char *filename);
uint8_t VS1053_DataRequest(void);
void VS1053_SCI_Write(uint8_t reg, uint16_t data);
uint16_t VS1053_SCI_Read(uint8_t reg);
void VS1053_SDI_Write(uint8_t *data, uint16_t length);
void VS1053_TestSineWave(uint16_t freq);
void VS1053_LoadPatch(void);
/* 全局变量 */
extern SPI_HandleTypeDef hspi2;
extern uint8_t vs1053_initialized;
#endif /* __VS1053_H */
2.2 VS1053驱动实现 (vs1053.c)
c
/**
* @file vs1053.c
* @brief VS1053驱动实现
*/
#include "vs1053.h"
#include "main.h"
#include <string.h>
/* SPI句柄 */
SPI_HandleTypeDef hspi2;
uint8_t vs1053_initialized = 0;
/* VS1053测试正弦波数据 */
const uint8_t sine_test[8] = {0x53, 0xEF, 0x6E, 0x30, 0x00, 0x00, 0x00, 0x00};
/**
* @brief 初始化VS1053
*/
void VS1053_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 1. 初始化控制引脚 */
/* XCS (命令片选) */
GPIO_InitStruct.Pin = VS1053_XCS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(VS1053_XCS_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(VS1053_XCS_PORT, VS1053_XCS_PIN, GPIO_PIN_SET);
/* XDCS (数据片选) */
GPIO_InitStruct.Pin = VS1053_XDCS_PIN;
HAL_GPIO_Init(VS1053_XDCS_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(VS1053_XDCS_PORT, VS1053_XDCS_PIN, GPIO_PIN_SET);
/* DREQ (数据请求) - 输入 */
GPIO_InitStruct.Pin = VS1053_DREQ_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(VS1053_DREQ_PORT, &GPIO_InitStruct);
/* 复位引脚 (如果有) */
GPIO_InitStruct.Pin = VS1053_RST_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(VS1053_RST_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(VS1053_RST_PORT, VS1053_RST_PIN, GPIO_PIN_SET);
/* 2. 硬件复位 */
VS1053_HardReset();
HAL_Delay(100);
/* 3. 软件复位 */
VS1053_SoftReset();
HAL_Delay(100);
/* 4. 设置时钟频率 (12.288MHz晶振, 3倍频) */
VS1053_WriteRegister(VS1053_REG_CLOCKF, 0x9800); // 3x倍频
/* 5. 设置工作模式 */
VS1053_WriteRegister(VS1053_REG_MODE, VS1053_MODE_SM_SDINEW | VS1053_MODE_SM_STREAM);
/* 6. 设置音量 (默认中等音量) */
VS1053_SetVolume(0x30, 0x30); // 左右声道各0x30
/* 7. 设置重低音和超高音 */
VS1053_WriteRegister(VS1053_REG_BASS, 0x0000);
/* 8. 加载补丁 (如果需要) */
// VS1053_LoadPatch();
vs1053_initialized = 1;
printf("VS1053初始化完成\n");
}
/**
* @brief 硬件复位
*/
void VS1053_HardReset(void)
{
if(VS1053_RST_PORT)
{
HAL_GPIO_WritePin(VS1053_RST_PORT, VS1053_RST_PIN, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(VS1053_RST_PORT, VS1053_RST_PIN, GPIO_PIN_SET);
HAL_Delay(100);
}
}
/**
* @brief 软件复位
*/
void VS1053_SoftReset(void)
{
VS1053_WriteRegister(VS1053_REG_MODE, VS1053_MODE_SM_SDINEW | VS1053_MODE_SM_RESET);
HAL_Delay(100);
/* 等待复位完成 */
while(!VS1053_DataRequest());
/* 重新设置模式 */
VS1053_WriteRegister(VS1053_REG_MODE, VS1053_MODE_SM_SDINEW | VS1053_MODE_SM_STREAM);
HAL_Delay(100);
}
/**
* @brief 设置音量
* @param left 左声道音量 (0x00最大, 0xFE最小)
* @param right 右声道音量
*/
void VS1053_SetVolume(uint8_t left, uint8_t right)
{
uint16_t volume = ((uint16_t)left << 8) | right;
VS1053_WriteRegister(VS1053_REG_VOLUME, volume);
}
/**
* @brief 写入寄存器
* @param reg 寄存器地址
* @param data 16位数据
*/
void VS1053_WriteRegister(uint8_t reg, uint16_t data)
{
uint8_t tx_buffer[4];
/* 构造命令: 0x02(写) + 地址 + 数据 */
tx_buffer[0] = VS1053_CMD_WRITE;
tx_buffer[1] = reg;
tx_buffer[2] = (data >> 8) & 0xFF; // 高字节
tx_buffer[3] = data & 0xFF; // 低字节
/* 拉低XCS (选择SCI) */
HAL_GPIO_WritePin(VS1053_XCS_PORT, VS1053_XCS_PIN, GPIO_PIN_RESET);
/* 发送数据 */
HAL_SPI_Transmit(&hspi2, tx_buffer, 4, 1000);
/* 拉高XCS */
HAL_GPIO_WritePin(VS1053_XCS_PORT, VS1053_XCS_PIN, GPIO_PIN_SET);
/* 等待DREQ变高 */
while(!VS1053_DataRequest());
}
/**
* @brief 读取寄存器
* @param reg 寄存器地址
* @return 16位寄存器值
*/
uint16_t VS1053_ReadRegister(uint8_t reg)
{
uint8_t tx_buffer[4] = {0};
uint8_t rx_buffer[4] = {0};
/* 构造命令: 0x03(读) + 地址 */
tx_buffer[0] = VS1053_CMD_READ;
tx_buffer[1] = reg;
/* 拉低XCS */
HAL_GPIO_WritePin(VS1053_XCS_PORT, VS1053_XCS_PIN, GPIO_PIN_RESET);
/* 发送命令并接收数据 */
HAL_SPI_TransmitReceive(&hspi2, tx_buffer, rx_buffer, 4, 1000);
/* 拉高XCS */
HAL_GPIO_WritePin(VS1053_XCS_PORT, VS1053_XCS_PIN, GPIO_PIN_SET);
/* 返回16位数据 */
return (rx_buffer[2] << 8) | rx_buffer[3];
}
/**
* @brief 发送音频数据到VS1053
* @param data 数据指针
* @param length 数据长度
*/
void VS1053_SendData(uint8_t *data, uint16_t length)
{
/* 等待DREQ变高 (缓冲区有空闲) */
while(!VS1053_DataRequest());
/* 拉低XDCS (选择SDI) */
HAL_GPIO_WritePin(VS1053_XDCS_PORT, VS1053_XDCS_PIN, GPIO_PIN_RESET);
/* 发送数据 */
HAL_SPI_Transmit(&hspi2, data, length, 1000);
/* 拉高XDCS */
HAL_GPIO_WritePin(VS1053_XDCS_PORT, VS1053_XDCS_PIN, GPIO_PIN_SET);
}
/**
* @brief 检查DREQ引脚状态
* @return 1:可以接收数据, 0:缓冲区满
*/
uint8_t VS1053_DataRequest(void)
{
return HAL_GPIO_ReadPin(VS1053_DREQ_PORT, VS1053_DREQ_PIN);
}
/**
* @brief 播放文件
* @param filename 文件名
*/
void VS1053_PlayFile(const char *filename)
{
FIL file;
FRESULT res;
UINT bytes_read;
uint8_t buffer[512]; // 512字节缓冲区
/* 打开文件 */
res = f_open(&file, filename, FA_READ);
if(res != FR_OK)
{
printf("无法打开文件: %s\n", filename);
return;
}
printf("开始播放: %s\n", filename);
/* 读取并发送数据 */
while(1)
{
/* 读取文件数据 */
res = f_read(&file, buffer, sizeof(buffer), &bytes_read);
if(res != FR_OK || bytes_read == 0)
{
break; // 读取完成或出错
}
/* 发送到VS1053 */
VS1053_SendData(buffer, bytes_read);
/* 检查按键中断 (简化处理) */
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) // 暂停键
{
printf("播放暂停\n");
HAL_Delay(300); // 去抖
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET); // 等待释放
break;
}
}
/* 关闭文件 */
f_close(&file);
/* 发送结束标志 */
uint8_t end_bytes[32] = {0};
VS1053_SendData(end_bytes, 32);
printf("播放完成\n");
}
/**
* @brief 测试正弦波输出
* @param freq 频率 (Hz)
*/
void VS1053_TestSineWave(uint16_t freq)
{
uint8_t test_data[8];
/* 复制测试数据 */
memcpy(test_data, sine_test, 8);
/* 设置频率 */
test_data[4] = (freq >> 8) & 0xFF;
test_data[5] = freq & 0xFF;
/* 发送测试数据 */
VS1053_SendData(test_data, 8);
/* 等待1秒 */
HAL_Delay(1000);
/* 停止测试 */
test_data[0] = 0x45;
test_data[1] = 0x78;
test_data[2] = 0x69;
test_data[3] = 0x74;
memset(&test_data[4], 0, 4);
VS1053_SendData(test_data, 8);
}
2.3 SD卡驱动 (sd_card.h)
c
/**
* @file sd_card.h
* @brief SD卡驱动
*/
#ifndef __SD_CARD_H
#define __SD_CARD_H
#include "stm32f1xx_hal.h"
#include "ff.h"
#include "ffconf.h"
#include "diskio.h"
/* SD卡SPI定义 */
#define SD_SPI SPI1
#define SD_CS_PORT GPIOA
#define SD_CS_PIN GPIO_PIN_4
/* 函数声明 */
uint8_t SD_Init(void);
uint8_t SD_ReadBlock(uint8_t *buffer, uint32_t sector, uint32_t count);
uint8_t SD_WriteBlock(uint8_t *buffer, uint32_t sector, uint32_t count);
void SD_CS_High(void);
void SD_CS_Low(void);
uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc);
uint8_t SD_GetResponse(uint8_t response);
void SD_SendDummy(void);
uint8_t SD_GetStatus(void);
uint32_t SD_GetSectorCount(void);
void SD_Test(void);
/* FatFS变量 */
extern FATFS fs; // 文件系统对象
extern FIL file; // 文件对象
extern FRESULT fres; // FatFS返回结果
extern DIR dir; // 目录对象
extern FILINFO fno; // 文件信息
#endif /* __SD_CARD_H */
2.4 SD卡驱动实现 (sd_card.c)
c
/**
* @file sd_card.c
* @brief SD卡驱动实现
*/
#include "sd_card.h"
#include <string.h>
/* SPI句柄 */
SPI_HandleTypeDef hspi1;
/* SD卡命令定义 */
#define CMD0 0 // 复位SD卡
#define CMD8 8 // 检查电压范围
#define CMD16 16 // 设置块大小
#define CMD17 17 // 读单个块
#define CMD24 24 // 写单个块
#define CMD41 41 // 激活初始化进程
#define CMD55 55 // 应用命令前缀
#define CMD58 58 // 读取OCR寄存器
#define ACMD41 41 // SD卡初始化
/* 响应类型 */
#define R1 1
#define R1B 2
#define R2 3
#define R3 4
#define R7 7
/* 全局变量 */
FATFS fs;
FIL file;
FRESULT fres;
DIR dir;
FILINFO fno;
uint8_t sd_initialized = 0;
/**
* @brief 初始化SD卡
* @return 0:成功, 其他:错误码
*/
uint8_t SD_Init(void)
{
uint8_t retry = 0;
uint8_t response;
/* 1. 初始化SPI */
MX_SPI1_Init();
/* 2. 发送至少74个时钟脉冲 */
SD_CS_High();
for(int i = 0; i < 10; i++)
{
uint8_t dummy = 0xFF;
HAL_SPI_Transmit(&hspi1, &dummy, 1, 100);
}
/* 3. 发送CMD0复位 */
SD_CS_Low();
response = SD_SendCmd(CMD0, 0x00000000, 0x95);
SD_CS_High();
if(response != 0x01)
{
printf("CMD0失败: 0x%02X\n", response);
return 1;
}
/* 4. 发送CMD8检查SD卡版本 */
SD_CS_Low();
response = SD_SendCmd(CMD8, 0x000001AA, 0x87);
SD_CS_High();
uint8_t r7_response[5];
if(response == 0x01) // SD卡V2.0
{
printf("检测到SD卡V2.0\n");
/* 读取R7响应 */
SD_ReadResponse(r7_response, 5);
/* 发送ACMD41初始化 */
do
{
SD_CS_Low();
SD_SendCmd(CMD55, 0x00000000, 0xFF);
SD_CS_High();
SD_CS_Low();
response = SD_SendCmd(ACMD41, 0x40000000, 0xFF);
SD_CS_High();
HAL_Delay(10);
retry++;
} while(response != 0x00 && retry < 100);
if(retry >= 100)
{
printf("ACMD41初始化失败\n");
return 2;
}
}
else if(response == 0x05) // SD卡V1.0
{
printf("检测到SD卡V1.0\n");
/* 发送CMD41初始化 */
do
{
SD_CS_Low();
response = SD_SendCmd(CMD41, 0x00000000, 0xFF);
SD_CS_High();
HAL_Delay(10);
retry++;
} while(response != 0x00 && retry < 100);
}
else
{
printf("未知SD卡类型\n");
return 3;
}
/* 5. 设置块大小(512字节) */
SD_CS_Low();
response = SD_SendCmd(CMD16, 512, 0xFF);
SD_CS_High();
if(response != 0x00)
{
printf("设置块大小失败\n");
return 4;
}
sd_initialized = 1;
printf("SD卡初始化成功\n");
/* 6. 挂载文件系统 */
fres = f_mount(&fs, "", 1);
if(fres != FR_OK)
{
printf("文件系统挂载失败: %d\n", fres);
return 5;
}
printf("文件系统挂载成功\n");
return 0;
}
/**
* @brief 读取SD卡块
* @param buffer 数据缓冲区
* @param sector 起始扇区
* @param count 扇区数
* @return 0:成功, 其他:错误
*/
uint8_t SD_ReadBlock(uint8_t *buffer, uint32_t sector, uint32_t count)
{
uint8_t response;
if(!sd_initialized) return 1;
for(uint32_t i = 0; i < count; i++)
{
SD_CS_Low();
/* 发送CMD17读取单个块 */
response = SD_SendCmd(CMD17, sector + i, 0xFF);
if(response != 0x00)
{
SD_CS_High();
return 2;
}
/* 等待数据令牌 */
uint8_t token = 0xFF;
uint32_t timeout = 100000;
while((token = SD_ReceiveByte()) == 0xFF)
{
if(--timeout == 0)
{
SD_CS_High();
return 3;
}
}
if(token != 0xFE)
{
SD_CS_High();
return 4;
}
/* 读取512字节数据 */
HAL_SPI_Receive(&hspi1, buffer + (i * 512), 512, 1000);
/* 读取2字节CRC (忽略) */
uint8_t crc[2];
HAL_SPI_Receive(&hspi1, crc, 2, 100);
SD_CS_High();
/* 发送8个时钟脉冲 */
uint8_t dummy = 0xFF;
HAL_SPI_Transmit(&hspi1, &dummy, 1, 100);
}
return 0;
}
/**
* @brief 发送命令到SD卡
* @param cmd 命令
* @param arg 参数
* @param crc CRC值
* @return 响应
*/
uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{
uint8_t tx_buffer[6];
uint8_t response;
/* 构造命令包 */
tx_buffer[0] = cmd | 0x40; // 命令字节
tx_buffer[1] = (arg >> 24) & 0xFF; // 参数字节1
tx_buffer[2] = (arg >> 16) & 0xFF; // 参数字节2
tx_buffer[3] = (arg >> 8) & 0xFF; // 参数字节3
tx_buffer[4] = arg & 0xFF; // 参数字节4
tx_buffer[5] = crc; // CRC
/* 发送命令 */
HAL_SPI_Transmit(&hspi1, tx_buffer, 6, 1000);
/* 接收响应 (最多重试8次) */
for(int i = 0; i < 8; i++)
{
HAL_SPI_Receive(&hspi1, &response, 1, 100);
if((response & 0x80) == 0) // 最高位为0表示有效响应
break;
}
return response;
}
/**
* @brief 接收一个字节
* @return 接收到的字节
*/
uint8_t SD_ReceiveByte(void)
{
uint8_t data = 0xFF;
HAL_SPI_TransmitReceive(&hspi1, &data, &data, 1, 100);
return data;
}
/**
* @brief 拉高SD卡片选
*/
void SD_CS_High(void)
{
HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_SET);
}
/**
* @brief 拉低SD卡片选
*/
void SD_CS_Low(void)
{
HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_RESET);
}
/**
* @brief SD卡测试
*/
void SD_Test(void)
{
FRESULT res;
uint32_t free_clusters, total_sectors, free_sectors;
FATFS *fs_ptr = &fs;
/* 获取SD卡信息 */
res = f_getfree("", &free_clusters, &fs_ptr);
if(res == FR_OK)
{
total_sectors = (fs.n_fatent - 2) * fs.csize;
free_sectors = free_clusters * fs.csize;
printf("SD卡信息:\n");
printf(" 总容量: %lu KB\n", total_sectors / 2);
printf(" 可用空间: %lu KB\n", free_sectors / 2);
printf(" 已用空间: %lu KB\n", (total_sectors - free_sectors) / 2);
}
/* 列出根目录文件 */
printf("\n根目录文件列表:\n");
res = f_opendir(&dir, "");
if(res == FR_OK)
{
int file_count = 0;
while(1)
{
res = f_readdir(&dir, &fno);
if(res != FR_OK || fno.fname[0] == 0)
break;
if(fno.fattrib & AM_DIR)
{
printf(" [DIR] %s\n", fno.fname);
}
else
{
printf(" [FILE] %s (%lu bytes)\n", fno.fname, fno.fsize);
file_count++;
}
}
printf("总计: %d 个文件\n", file_count);
f_closedir(&dir);
}
}
三、主程序与播放器逻辑
3.1 主程序 (main.c)
c
/**
* @file main.c
* @brief MP3播放器主程序
*/
#include "main.h"
#include "vs1053.h"
#include "sd_card.h"
#include "ff.h"
#include <stdio.h>
#include <string.h>
/* 全局变量 */
SPI_HandleTypeDef hspi1; // SD卡SPI
SPI_HandleTypeDef hspi2; // VS1053 SPI
UART_HandleTypeDef huart1; // 调试串口
/* 播放列表 */
#define MAX_PLAYLIST_SIZE 100
char playlist[MAX_PLAYLIST_SIZE][256];
uint8_t playlist_count = 0;
uint8_t current_track = 0;
/* 播放状态 */
typedef enum {
PLAYER_STOP = 0,
PLAYER_PLAY,
PLAYER_PAUSE,
PLAYER_NEXT,
PLAYER_PREV
} PlayerState;
PlayerState player_state = PLAYER_STOP;
/**
* @brief 系统时钟配置
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/* 配置HSE 8MHz外部晶振 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz * 9 = 72MHz
HAL_RCC_OscConfig(&RCC_OscInitStruct);
/* 配置系统时钟 */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // HCLK = 72MHz
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // PCLK1 = 36MHz
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // PCLK2 = 72MHz
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}
/**
* @brief SPI1初始化 (SD卡)
*/
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 低速初始化
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
HAL_SPI_Init(&hspi1);
}
/**
* @brief SPI2初始化 (VS1053)
*/
void MX_SPI2_Init(void)
{
hspi2.Instance = SPI2;
hspi2.Init.Mode = SPI_MODE_MASTER;
hspi2.Init.Direction = SPI_DIRECTION_2LINES;
hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi2.Init.NSS = SPI_NSS_SOFT;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 9MHz
hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi2.Init.CRCPolynomial = 10;
HAL_SPI_Init(&hspi2);
}
/**
* @brief UART初始化 (调试用)
*/
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart1);
}
/**
* @brief GPIO初始化
*/
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO时钟使能 */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/* 控制按键 */
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* LED指示灯 */
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
/**
* @brief 扫描MP3文件,建立播放列表
*/
void BuildPlaylist(void)
{
FRESULT res;
DIR dir;
FILINFO fno;
printf("正在扫描MP3文件...\n");
res = f_opendir(&dir, "");
if(res == FR_OK)
{
playlist_count = 0;
while(1)
{
res = f_readdir(&dir, &fno);
if(res != FR_OK || fno.fname[0] == 0)
break;
/* 检查是否是MP3文件 */
if(!(fno.fattrib & AM_DIR))
{
char *ext = strrchr(fno.fname, '.');
if(ext != NULL && (
strcmp(ext, ".mp3") == 0 ||
strcmp(ext, ".MP3") == 0 ||
strcmp(ext, ".wav") == 0 ||
strcmp(ext, ".WAV") == 0))
{
if(playlist_count < MAX_PLAYLIST_SIZE)
{
strcpy(playlist[playlist_count], fno.fname);
printf(" [%d] %s\n", playlist_count + 1, fno.fname);
playlist_count++;
}
}
}
}
f_closedir(&dir);
printf("找到 %d 个音频文件\n", playlist_count);
}
}
/**
* @brief 播放指定曲目
* @param track 曲目编号
*/
void PlayTrack(uint8_t track)
{
if(track >= playlist_count)
return;
printf("播放: %s\n", playlist[track]);
VS1053_PlayFile(playlist[track]);
current_track = track;
}
/**
* @brief 播放下一曲
*/
void PlayNext(void)
{
if(playlist_count == 0)
return;
current_track = (current_track + 1) % playlist_count;
player_state = PLAYER_NEXT;
printf("下一曲: %d\n", current_track + 1);
}
/**
* @brief 播放上一曲
*/
void PlayPrev(void)
{
if(playlist_count == 0)
return;
current_track = (current_track == 0) ? playlist_count - 1 : current_track - 1;
player_state = PLAYER_PREV;
printf("上一曲: %d\n", current_track + 1);
}
/**
* @brief 处理按键
*/
void ProcessKeys(void)
{
static uint32_t last_key_time = 0;
uint32_t current_time = HAL_GetTick();
/* 按键去抖 */
if(current_time - last_key_time < 200)
return;
/* 播放/暂停键 */
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
last_key_time = current_time;
if(player_state == PLAYER_PLAY)
{
player_state = PLAYER_PAUSE;
printf("暂停\n");
}
else
{
player_state = PLAYER_PLAY;
printf("播放\n");
}
}
/* 下一曲键 */
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
last_key_time = current_time;
PlayNext();
}
/* 上一曲键 */
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_RESET)
{
last_key_time = current_time;
PlayPrev();
}
/* 音量+ */
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) == GPIO_PIN_RESET)
{
last_key_time = current_time;
static uint8_t volume = 0x30;
if(volume > 0x00)
{
volume -= 0x10;
VS1053_SetVolume(volume, volume);
printf("音量: %d\n", 0xFF - volume);
}
}
/* 音量- */
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET)
{
last_key_time = current_time;
static uint8_t volume = 0x30;
if(volume < 0xFE)
{
volume += 0x10;
VS1053_SetVolume(volume, volume);
printf("音量: %d\n", 0xFF - volume);
}
}
}
/**
* @brief 主函数
*/
int main(void)
{
/* 硬件初始化 */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
MX_SPI2_Init();
printf("\n=== MP3播放器系统启动 ===\n");
/* 初始化SD卡 */
printf("初始化SD卡...\n");
if(SD_Init() != 0)
{
printf("SD卡初始化失败!\n");
while(1)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(200);
}
}
/* 显示SD卡信息 */
SD_Test();
/* 初始化VS1053 */
printf("初始化VS1053...\n");
VS1053_Init();
/* 测试正弦波输出 */
printf("测试音频输出...\n");
VS1053_TestSineWave(1000);
HAL_Delay(1000);
/* 扫描MP3文件 */
BuildPlaylist();
if(playlist_count == 0)
{
printf("未找到MP3文件!\n");
while(1)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(500);
}
}
/* 主循环 */
printf("\n=== 播放器就绪 ===\n");
printf("按PA0播放/暂停\n");
printf("按PA1下一曲\n");
printf("按PA2上一曲\n");
printf("按PA3音量+\n");
printf("按PA4音量-\n");
/* 自动播放第一首 */
current_track = 0;
player_state = PLAYER_PLAY;
while(1)
{
/* 处理按键 */
ProcessKeys();
/* 状态机 */
switch(player_state)
{
case PLAYER_PLAY:
PlayTrack(current_track);
player_state = PLAYER_STOP;
break;
case PLAYER_NEXT:
PlayTrack(current_track);
player_state = PLAYER_STOP;
break;
case PLAYER_PREV:
PlayTrack(current_track);
player_state = PLAYER_STOP;
break;
case PLAYER_PAUSE:
/* 暂停逻辑 */
HAL_Delay(100);
break;
case PLAYER_STOP:
default:
/* 空闲状态,闪烁LED */
static uint32_t led_timer = 0;
if(HAL_GetTick() - led_timer > 1000)
{
led_timer = HAL_GetTick();
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
break;
}
}
}
/**
* @brief 重定向printf到串口
*/
int _write(int file, char *ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 1000);
return len;
}
四、FatFS配置文件
4.1 ffconf.h 配置
c
/* FatFS Configuration for STM32 */
#ifndef _FFCONF
#define _FFCONF 68300
/* 系统配置 */
#define FF_FS_READONLY 0 /* 0:读写模式, 1:只读模式 */
#define FF_FS_MINIMIZE 0 /* 优化级别 */
#define FF_USE_STRFUNC 2 /* 启用字符串函数 */
#define FF_USE_FIND 1 /* 启用文件查找功能 */
#define FF_USE_MKFS 1 /* 启用格式化功能 */
#define FF_USE_FASTSEEK 1 /* 启用快速查找 */
#define FF_USE_EXPAND 0 /* 启用扩展功能 */
#define FF_USE_CHMOD 0 /* 启用属性修改 */
#define FF_USE_LABEL 0 /* 启用卷标 */
#define FF_USE_FORWARD 0 /* 启用文件转发 */
/* 区域设置 */
#define FF_CODE_PAGE 936 /* 简体中文 */
#define FF_USE_LFN 2 /* 长文件名支持 (0:禁用, 1:静态缓冲区, 2:动态缓冲区) */
#define FF_MAX_LFN 255 /* 最大文件名长度 */
#define FF_LFN_UNICODE 0 /* 0:ANSI/OEM, 1:Unicode */
/* 卷/驱动配置 */
#define FF_VOLUMES 1 /* 逻辑驱动器数量 */
#define FF_STR_VOLUME_ID 0 /* 0:数字, 1:字符串 */
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
#define FF_MULTI_PARTITION 0 /* 0:单分区, 1:多分区 */
/* 系统参数 */
#define FF_MIN_SS 512 /* 最小扇区大小 */
#define FF_MAX_SS 512 /* 最大扇区大小 */
#define FF_LBA64 0 /* 0:32位LBA, 1:64位LBA */
#define FF_MIN_GPT 0x10000000 /* GPT分区最小大小 */
#define FF_USE_TRIM 0 /* ATA-TRIM功能 */
/* 缓冲区配置 */
#define FF_FS_TINY 0 /* 0:正常, 1:小内存 */
#define FF_FS_EXFAT 0 /* 0:禁用exFAT, 1:启用exFAT */
#define FF_FS_NORTC 0 /* 不使用RTC */
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2024
/* 互斥量 */
#define FF_FS_REENTRANT 0 /* 0:禁用, 1:启用 */
#define FF_FS_TIMEOUT 1000
#define FF_SYNC_t HANDLE
#endif /* _FFCONF */
4.2 diskio.c 实现
c
/**
* @file diskio.c
* @brief FatFS磁盘I/O层
*/
#include "diskio.h"
#include "sd_card.h"
/* 磁盘状态 */
DSTATUS disk_status(BYTE pdrv)
{
if(pdrv != 0) return STA_NOINIT;
return 0; /* 总是返回成功 */
}
/* 磁盘初始化 */
DSTATUS disk_initialize(BYTE pdrv)
{
if(pdrv != 0) return STA_NOINIT;
if(SD_Init() == 0)
return 0;
else
return STA_NOINIT;
}
/* 读取扇区 */
DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
if(pdrv != 0) return RES_PARERR;
if(SD_ReadBlock(buff, sector, count) == 0)
return RES_OK;
else
return RES_ERROR;
}
/* 写入扇区 */
DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
if(pdrv != 0) return RES_PARERR;
/* 写入实现 */
return RES_OK;
}
/* 其他控制 */
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff)
{
DRESULT res = RES_OK;
if(pdrv != 0) return RES_PARERR;
switch(cmd)
{
case CTRL_SYNC:
/* 确保驱动器已完成写入过程 */
break;
case GET_SECTOR_COUNT:
/* 获取总扇区数 */
*(DWORD*)buff = 0x00100000; /* 假设2GB SD卡 */
break;
case GET_SECTOR_SIZE:
/* 获取扇区大小 */
*(WORD*)buff = 512;
break;
case GET_BLOCK_SIZE:
/* 获取擦除块大小 */
*(DWORD*)buff = 1;
break;
default:
res = RES_PARERR;
break;
}
return res;
}
参考代码 MP3播放从SD卡读取程序 www.youwenfan.com/contentcsv/70657.html
五、高级功能扩展
5.1 均衡器设置
c
/**
* @brief 设置均衡器
* @param preset 预设模式
* 0: 正常
* 1: 流行
* 2: 摇滚
* 3: 爵士
* 4: 古典
* 5: 重低音
*/
void VS1053_SetEQ(uint8_t preset)
{
/* 通过SCI写入WRAM设置均衡器 */
static const uint16_t eq_presets[6][5] = {
{0, 0, 0, 0, 0}, // 正常
{8, 4, 0, -4, -8}, // 流行
{12, 8, 0, -4, -8}, // 摇滚
{4, 8, 8, 4, 0}, // 爵士
{0, 0, 0, 0, 0}, // 古典
{12, 8, 0, 0, 0} // 重低音
};
if(preset >= 6) return;
/* 设置均衡器参数 */
for(int i = 0; i < 5; i++)
{
VS1053_WriteRegister(0x06, 0x1E00 + i); // 设置WRAM地址
VS1053_WriteRegister(0x07, eq_presets[preset][i]); // 写入均衡器值
}
}
5.2 频谱显示
c
/**
* @brief 获取频谱数据
* @param spectrum 频谱数组 (16个值)
*/
void VS1053_GetSpectrum(int8_t spectrum[16])
{
/* 读取VS1053的频谱数据 */
VS1053_WriteRegister(VS1053_REG_WRAMADDR, 0x1E1C);
for(int i = 0; i < 16; i += 2)
{
uint16_t data = VS1053_ReadRegister(VS1053_REG_WRAM);
spectrum[i] = (data >> 8) & 0xFF;
spectrum[i+1] = data & 0xFF;
}
}
/**
* @brief 显示频谱
*/
void DisplaySpectrum(void)
{
int8_t spectrum[16];
VS1053_GetSpectrum(spectrum);
printf("频谱: ");
for(int i = 0; i < 16; i++)
{
int bar = (spectrum[i] + 64) / 8; // 转换为0-16
printf("%2d ", bar);
}
printf("\n");
}
六、优化与调试
6.1 性能优化
c
/* 使用DMA传输音频数据 */
void VS1053_SendData_DMA(uint8_t *data, uint16_t length)
{
/* 等待DREQ */
while(!VS1053_DataRequest());
/* 拉低XDCS */
HAL_GPIO_WritePin(VS1053_XDCS_PORT, VS1053_XDCS_PIN, GPIO_PIN_RESET);
/* 使用DMA传输 */
HAL_SPI_Transmit_DMA(&hspi2, data, length);
/* 注意:需要在传输完成中断中拉高XDCS */
}
/* DMA传输完成回调 */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI2)
{
/* 拉高XDCS */
HAL_GPIO_WritePin(VS1053_XDCS_PORT, VS1053_XDCS_PIN, GPIO_PIN_SET);
}
}
6.2 调试信息
c
/**
* @brief 显示VS1053状态
*/
void VS1053_ShowStatus(void)
{
uint16_t mode = VS1053_ReadRegister(VS1053_REG_MODE);
uint16_t status = VS1053_ReadRegister(VS1053_REG_STATUS);
uint16_t clockf = VS1053_ReadRegister(VS1053_REG_CLOCKF);
uint16_t decode_time = VS1053_ReadRegister(VS1053_REG_DECODE_TIME);
uint16_t volume = VS1053_ReadRegister(VS1053_REG_VOLUME);
printf("=== VS1053 状态 ===\n");
printf("模式寄存器: 0x%04X\n", mode);
printf("状态寄存器: 0x%04X\n", status);
printf("时钟频率: 0x%04X\n", clockf);
printf("解码时间: %d 秒\n", decode_time);
printf("音量: 左=0x%02X, 右=0x%02X\n",
(volume >> 8) & 0xFF, volume & 0xFF);
}
七、常见问题解决
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 无声音输出 | 1. VS1053未初始化 2. 晶振不起振 3. 音量设置为0 | 1. 检查初始化流程 2. 检查晶振和电容 3. 设置适当音量 |
| 播放卡顿 | 1. SPI速度太慢 2. SD卡读取慢 3. 缓冲区太小 | 1. 提高SPI速度 2. 使用Class10 SD卡 3. 增大缓冲区 |
| 文件无法读取 | 1. 文件系统损坏 2. 文件格式不支持 3. 路径错误 | 1. 格式化SD卡 2. 检查文件格式 3. 检查文件名 |
| 电流噪声 | 1. 电源干扰 2. 地线问题 3. 模拟数字混合 | 1. 增加滤波电容 2. 分开模拟数字地 3. 使用磁珠隔离 |