STM32 + VS1003/VS1053 MP3播放器SD卡读取程序

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. 使用磁珠隔离
相关推荐
念一不念二2 小时前
[SSD]SSD主控
嵌入式硬件
xiangw@GZ2 小时前
DDR3 颗粒信号定义解析
单片机·嵌入式硬件
小+不通文墨2 小时前
在树莓派中部署emqx
经验分享·笔记·单片机·学习
Deitymoon2 小时前
STM32——oled显示字符串和数字
stm32·单片机·嵌入式硬件
深圳市晨芯阳科技有限公司3 小时前
带延时功能的电压检测系列晨芯阳HC809
单片机·嵌入式硬件·电源芯片·深圳市晨芯阳科技有限公司
xiangw@GZ3 小时前
DDR2 / DDR3 / DDR4 颗粒信号差异对照表
单片机·嵌入式硬件
科芯创展3 小时前
1A,60VIN,1MHz,XZ4116,降压恒流LED驱动芯片 输入电压:5V-60V
stm32·单片机·嵌入式硬件
振浩微433射频芯片4 小时前
工业环境下的“硬核”选择:如何科学评估国产433芯片的可靠性?
网络·人工智能·科技·单片机·物联网·学习
天天爱吃肉82185 小时前
新能源汽车单级车载电源及高频高密度DCDC设计开发技术入门指南
大数据·人工智能·功能测试·嵌入式硬件·汽车