首先去下载miniMP3解码库
项目地址
https://github.com/lieff/minimp3
使用
minimp3 的使用十分简单,基础解码功能使用的话只需要两个步骤,并且只需要调用以下两个函数就可以完成解码。
void mp3dec_init(mp3dec_t *dec);
int mp3dec_decode_frame(mp3dec_t *dec, const uint8_t *mp3, int mp3_bytes, mp3d_sample_t *pcm, mp3dec_frame_info_t *info);
使用CubeMAX配置SDIO
选择sd 1bit,我不确定是我sd卡是低速卡的原因还是hal库有bug,我4bit模式初始化不过
为sdio配置DMA开启中断注意sdio的中断优先级一定要高于dma
配置串口用来看调试信息
配置I2s,选择全双工模式,使用飞利浦标准,16位模式,44Khz标准音频采样率
配置I2S的DMA,使能中断(注意I2S的DMA中断优先级要低于SDIO的DMA中断优先级)
配置FATFS文件系统
选择utf-8字符集支持中文,使能长文件名,扇区512默认
配置内存到内存的DMA,用于解码器和缓冲区的数据填充,不需要使能DMA中断
配置按键和LED灯
中断配置如上
时钟配置
配置定时器,播放音乐
拷贝所有库,生成单独的.c.h文件
将栈区和堆区开大些,以免解码过程中栈溢出导致程序卡死
FIL file;
FRESULT f_res;
DWORD tolt,freesize,datasize;
UINT fnum;
BYTE Readebuff[512]={0};
BYTE Write_buff[512]={
"This is STM32F407 write"
};
int main(void)
{
/* USER CODE BEGIN 1 */
FATFS *fs=&SDFatFS;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_SDIO_SD_Init();
MX_I2S2_Init();
MX_FATFS_Init();
MX_USART1_UART_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
f_res = f_mount(&SDFatFS,"0:",1);
HAL_Delay(100);
f_res =f_open(&SDFile,"0:/holle.txt",FA_CREATE_ALWAYS|FA_WRITE|FA_READ);
if(f_res==FR_OK){
printf("创建成功\n");
}
f_res =f_write(&SDFile,Write_buff,sizeof(Write_buff),&fnum);
if(f_res==FR_OK){
printf("写入成功\n");
}
f_res =f_sync(&SDFile);
HAL_Delay(100);
f_res =f_read(&SDFile,Readebuff,sizeof(Readebuff),&fnum);
if(f_res==FR_OK){
printf("读入成功\n");
}
f_res =f_close(&SDFile);
f_res=f_getfree("0:",&tolt,&fs);
if(f_res==FR_OK){
datasize=(fs->n_fatent-2)*(fs->csize/2);
freesize=tolt*(fs->csize/2);
printf("总容量: %ldMB\n",datasize/1024);
printf("剩余容量:%ldMB\n",freesize/1024);
}
先测试sd卡的读写是否异常
#include "minimp3.h"
#include "minimp3_interface.h"
#include "tim.h"
#include "stdbool.h"
extern I2S_HandleTypeDef hi2s2;
extern DMA_HandleTypeDef hdma_spi2_tx;
extern DMA_HandleTypeDef hdma_memtomem_dma2_stream0;
extern DMA_HandleTypeDef hdma_memtomem_dma2_stream1;
extern TIM_HandleTypeDef htim6;
// 增大缓冲区大小,避免帧跨缓冲区问题(MP3帧最大约1440字节,8192更安全)
#define MP3_BUFFER_SIZE 8192
#define MP3_FIFO_SIZE 12
#define MP3_HIGH_BIT 10
#define MP3_LOW_BIT 8
mp3dec_t mp3d;
mp3dec_frame_info_t info;
short pcm[MINIMP3_MAX_SAMPLES_PER_FRAME];
short pcmbuf1[MINIMP3_MAX_SAMPLES_PER_FRAME];
short pcmbuf2[MINIMP3_MAX_SAMPLES_PER_FRAME];
BYTE buffer[MP3_BUFFER_SIZE]; // 增大缓冲区
FRESULT fr;
UINT bytes_read;
UINT bytes_remaining = 0; // 重命名变量,更清晰表示"剩余未处理数据"
const BYTE *data_ptr; // 重命名变量,明确为数据指针
UINT byte_data;//文件剩余大小
short pcm_fifo_buf[MINIMP3_MAX_SAMPLES_PER_FRAME*MP3_FIFO_SIZE]; //fifo缓冲区
typedef struct Pcm_fifo{
short *handbuf;
short *tailbuf;
short *buftailaddr;
UINT number;
}PCM_Fifo;
PCM_Fifo pcm_back;
bool Play_stop_bit = false;
volatile uint16_t snubber = 0; // 0:快速写缓冲区 1:等待缓冲区数据拿走
volatile UINT play_stage = 0; // 0:无状态 2:播放中
volatile uint16_t key_mode = 0; // 0:播放/暂停 1:上一曲 2:下一曲
volatile uint16_t up_play = 0;
volatile uint16_t down_play = 0;
static uint32_t audio_sample_rate = 0;
static uint16_t audio_channels = 0;
建立一些变量,为MP3解码开辟缓冲区和FIFO队列
//fifo 初始化
void Fifo_Init(PCM_Fifo *pcm_fifo){
pcm_fifo->handbuf=pcm_fifo_buf;
pcm_fifo->tailbuf=pcm_fifo_buf;
pcm_fifo->buftailaddr=&pcm_fifo_buf[MINIMP3_MAX_SAMPLES_PER_FRAME*MP3_FIFO_SIZE];
pcm_fifo->number=0;
}
void Fifo_wirte_data(PCM_Fifo *pcm_fifo,short *data){
if(pcm_fifo->handbuf>=pcm_fifo->buftailaddr){
pcm_fifo->handbuf=pcm_fifo_buf;
}
HAL_DMA_Start(&hdma_memtomem_dma2_stream0,(uint32_t)data,(uint32_t)pcm_fifo->handbuf,MINIMP3_MAX_SAMPLES_PER_FRAME);
HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_stream0,HAL_DMA_FULL_TRANSFER,10);
HAL_DMA_Abort(&hdma_memtomem_dma2_stream0);
// memmove(pcm_fifo->handbuf,data,MINIMP3_MAX_SAMPLES_PER_FRAME*sizeof(short));
pcm_fifo->handbuf+=MINIMP3_MAX_SAMPLES_PER_FRAME;
pcm_fifo->number++;
}
void Fifo_read_data(PCM_Fifo *pcm_fifo,short *data){
if(pcm_fifo->number>0){
if(pcm_fifo->tailbuf>=pcm_fifo->buftailaddr){
pcm_fifo->tailbuf=pcm_fifo_buf;
}
HAL_DMA_Start(&hdma_memtomem_dma2_stream1,(uint32_t)pcm_fifo->tailbuf,(uint32_t)data,MINIMP3_MAX_SAMPLES_PER_FRAME);
HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_stream1,HAL_DMA_FULL_TRANSFER,10);
HAL_DMA_Abort(&hdma_memtomem_dma2_stream1);
// memmove(data,pcm_fifo->tailbuf,MINIMP3_MAX_SAMPLES_PER_FRAME*sizeof(short));
pcm_fifo->tailbuf+=MINIMP3_MAX_SAMPLES_PER_FRAME;
pcm_fifo->number--;
}
}
FIFO的实现,使用FIFO可以缓存一部分解码完成的数据,避免卡的情况发生
// 配置定时器频率以匹配音频采样率
void ConfigureTimerForSampleRate(uint32_t sample_rate) {
if (sample_rate == 0) return;
// 计算定时器周期:每个音频帧的播放时间(ms)
// MINIMP3_MAX_SAMPLES_PER_FRAME通常是1152
uint32_t frame_duration = (MINIMP3_MAX_SAMPLES_PER_FRAME * 1000) / sample_rate;
// 配置定时器周期
TIM_HandleTypeDef *htim = &htim6;
HAL_TIM_Base_Stop_IT(htim);
htim->Init.Period = ((((SystemCoreClock) / (htim->Init.Prescaler+1) / 1000) * frame_duration ));
HAL_TIM_Base_Init(htim);
HAL_TIM_Base_Start_IT(htim);
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim->Instance==TIM6){
if(pcm_back.number>0&&play_stage==2){
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
// Fifo_read_data(&pcm_back,pcmbuf1);
// HAL_I2S_Transmit_DMA(&hi2s2,pcmbuf1,MINIMP3_MAX_SAMPLES_PER_FRAME);
HAL_I2S_Transmit_DMAEx(&hi2s2,pcmbuf1,pcmbuf2,MINIMP3_MAX_SAMPLES_PER_FRAME);
__HAL_TIM_CLEAR_FLAG(&htim6,TIM_FLAG_UPDATE);
}
}
}
根据解码信息配置定时器的时间匹配播放帧率
void DMAEx_XferCpltCallback(DMA_HandleTypeDef *hdma){
Fifo_read_data(&pcm_back,pcmbuf1);
//HAL_I2S_DMAStop(&hi2s2);
}
void DMAEx_XferM1CpltCallback(DMA_HandleTypeDef *hdma){
Fifo_read_data(&pcm_back,pcmbuf2);
//HAL_I2S_DMAStop(&hi2s2);
}
void DMAEx_XferErrorCallback(DMA_HandleTypeDef *hdma){
}
HAL_StatusTypeDef HAL_I2S_Transmit_DMAEx(I2S_HandleTypeDef *hi2s, uint16_t *FirstBuff,uint16_t *SecondBuff,uint16_t Size)
{
uint32_t tmpreg_cfgr;
if ((FirstBuff == NULL) || (Size == 0U) || (SecondBuff == NULL))
{
return HAL_ERROR;
}
if (hi2s->State != HAL_I2S_STATE_READY)
{
return HAL_BUSY;
}
/* Process Locked */
__HAL_LOCK(hi2s);
/* Set state and reset error code */
hi2s->State = HAL_I2S_STATE_BUSY_TX;
hi2s->ErrorCode = HAL_I2S_ERROR_NONE;
hi2s->pTxBuffPtr = FirstBuff;
tmpreg_cfgr = hi2s->Instance->I2SCFGR & (SPI_I2SCFGR_DATLEN | SPI_I2SCFGR_CHLEN);
if ((tmpreg_cfgr == I2S_DATAFORMAT_24B) || (tmpreg_cfgr == I2S_DATAFORMAT_32B))
{
hi2s->TxXferSize = (Size << 1U);
hi2s->TxXferCount = (Size << 1U);
}
else
{
hi2s->TxXferSize = Size;
hi2s->TxXferCount = Size;
}
/* Set the I2S Tx DMA Half transfer complete callback */
hi2s->hdmatx->XferHalfCpltCallback = NULL;
hi2s->hdmatx->XferM1HalfCpltCallback=NULL;
/* Set the I2S Tx DMA transfer complete callback */
hi2s->hdmatx->XferCpltCallback = DMAEx_XferCpltCallback;
hi2s->hdmatx->XferM1CpltCallback=DMAEx_XferM1CpltCallback;
/* Set the DMA error callback */
hi2s->hdmatx->XferErrorCallback = DMAEx_XferErrorCallback;
/* Enable the Tx DMA Stream/Channel */
if (HAL_OK != HAL_DMAEx_MultiBufferStart_IT(hi2s->hdmatx,
(uint32_t)FirstBuff,
(uint32_t)&hi2s->Instance->DR,
(uint32_t)SecondBuff,
hi2s->TxXferSize))
{
/* Update SPI error code */
SET_BIT(hi2s->ErrorCode, HAL_I2S_ERROR_DMA);
hi2s->State = HAL_I2S_STATE_READY;
__HAL_UNLOCK(hi2s);
return HAL_ERROR;
}
__HAL_UNLOCK(hi2s);
/* Check if the I2S Tx request is already enabled */
if (HAL_IS_BIT_CLR(hi2s->Instance->CR2, SPI_CR2_TXDMAEN))
{
/* Enable Tx DMA Request */
SET_BIT(hi2s->Instance->CR2, SPI_CR2_TXDMAEN);
}
/* Check if the I2S is already enabled */
if (HAL_IS_BIT_CLR(hi2s->Instance->I2SCFGR, SPI_I2SCFGR_I2SE))
{
/* Enable I2S peripheral */
__HAL_I2S_ENABLE(hi2s);
}
return HAL_OK;
}
我参考野火的DMA双缓冲区,重写HAL库的I2s的DMA双缓冲区发送函数,实现连续不间断的音频数据播放
// MP3解码函数
void doc_mp3(const char *filename) {
//初始化fifo
__HAL_I2S_ENABLE(&hi2s2);
Fifo_Init(&pcm_back);//初始化fifo
audio_sample_rate=0;
up_play=0; // 清除上一曲状态
down_play=0; // 清除下一曲状态
play_stage=0; // 清除播放状态
Play_stop_bit=false;//清除暂停状态
// 打开MP3文件
fr = f_open(&SDFile, filename, FA_OPEN_EXISTING | FA_READ);
if (fr != FR_OK) {
printf("文件打开失败: %d\n", fr); // 输出错误码,便于调试
return;
}
byte_data=f_size(&SDFile);
printf("文件大小%d byte\n",byte_data);
printf("文件打开成功\n");
fr=f_lseek(&SDFile,0);
// 初始化解码器
mp3dec_init(&mp3d);
printf("MP3解码器初始化成功\n");
// 初始化缓冲区
bytes_remaining = 0;
memset(buffer, 0, MP3_BUFFER_SIZE);
do {
if(up_play||down_play){
break;
}
// 读取数据到缓冲区剩余空间
// __disable_irq();
fr = f_read(&SDFile, buffer + bytes_remaining, sizeof(buffer) - bytes_remaining, &bytes_read);
// __enable_irq();
if (fr != FR_OK) {
printf("文件读取错误: %d\n", fr);
break;
}
// 更新总可用数据量(原有剩余数据 + 新读取数据)
bytes_remaining += bytes_read;
data_ptr = buffer;
byte_data-=bytes_read;
// 循环解码缓冲区中的所有完整帧
while (bytes_remaining > 0) {
if(up_play||down_play){
break;
}
// 解码一帧
int samples = mp3dec_decode_frame(&mp3d, data_ptr, bytes_remaining, pcm, &info);
// 处理解码结果
if (info.frame_bytes == 0) {
// 帧字节数为0的可能情况:
// 1. 数据不足(需继续读数据)
// 2. 真正的错误或文件结束
break;
}
// 解码成功,处理PCM数据(播放等操作)
if (samples > 0) {
if(pcm_back.number<MP3_FIFO_SIZE && snubber==0){
Fifo_wirte_data(&pcm_back,pcm);//开始缓冲
if(pcm_back.number>MP3_HIGH_BIT){
snubber=1;//缓冲结束
}
}
if(pcm_back.number<MP3_FIFO_SIZE && snubber==1){
while(1){
play_stage=2;//开始播放
if(pcm_back.number<MP3_LOW_BIT){
snubber=0;//开始缓冲
break;
}
if(up_play||down_play){
break;
}
}
}
if (audio_sample_rate == 0 && info.hz > 0) {
audio_sample_rate = info.hz;
audio_channels = info.channels;
ConfigureTimerForSampleRate(audio_sample_rate);
printf("配置音频: 采样率=%dHz, 声道数=%d\n", audio_sample_rate, audio_channels);
}
// printf("解码成功: 采样数=%d, 采样率=%dHz, 声道数=%d\n",
// samples, info.hz, info.channels);
// HAL_I2S_Transmit_DMA(&hi2s2,pcm,MINIMP3_MAX_SAMPLES_PER_FRAME);
// HAL_Delay(26);
// 这里添加播放PCM数据的代码(根据你的音频输出接口实现)
}
// 移动指针,处理剩余数据
bytes_remaining -= info.frame_bytes;
data_ptr += info.frame_bytes;
}
// 将剩余未解码数据移动到缓冲区头部
if (bytes_remaining > 0) {
memmove(buffer, data_ptr, bytes_remaining);
}
} while(byte_data >0); // 直到文件结束且无剩余数据
//
__HAL_I2S_DISABLE(&hi2s2);
HAL_TIM_Base_Stop_IT(&htim6);
// 关闭文件
f_close(&SDFile);
printf("MP3解码完成,文件已关闭\n");
}
MP3解码流程,我采用流式解码,在FIFO缓冲区没有低于低水位时,快速读取sd卡的MP3原数据并调用miniMP3解码函数完成一帧的解码,并加载到FIFO缓冲区,当FIFO缓冲区数据到达高水位时,等待数据下降到低水位,再次重复以上操作,直到音乐播放完成
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
if(GPIO_Pin==GPIO_PIN_8){
if(++key_mode>=3){
key_mode=0;
}
}
if(GPIO_Pin==GPIO_PIN_9){
switch(key_mode){
case 0:
Play_stop_bit=!Play_stop_bit;
if(Play_stop_bit){
play_stage=0;
__HAL_I2S_DISABLE(&hi2s2);
HAL_TIM_Base_Stop_IT(&htim6);
}else{
play_stage=2;
__HAL_I2S_ENABLE(&hi2s2);
HAL_TIM_Base_Start_IT(&htim6);
}
break;
case 1:
up_play=1;
break;
case 2:
down_play=1;
break;
}
}
}
按键使用外部中断方式触发,控制上一曲,下一曲,播放,暂停操作
//获取根目录列表
uint16_t get_mulu_list(const char *path,char (*psr)[40],uint16_t maxlen){
DIR dpath;
FRESULT rse;
FILINFO info;
uint16_t index = 0;
rse = f_opendir(&dpath,path);
if(rse == FR_OK){
printf("目录打开成功\n");
while(1){
rse=f_readdir(&dpath,&info);
if(rse!=FR_OK||info.fname[0]==0){//文件读取到末尾
break;
}
if(info.fattrib && AM_DIR){
printf("0:/%s\n",info.fname);
}else{
printf("%s\n",info.fname);
}
sprintf(psr,"0:/%s",info.fname);
psr++;
if(++index>=maxlen){
break;
}
}
printf ("目录条数%d\n",index);
f_closedir(&dpath);
}else{
printf("目录打开失败\n");
f_closedir(&dpath);
}
return index;
}
//获取音乐列表
uint16_t get_music_list(const char *path,char (*psr)[40],uint16_t maxlen,uint16_t scanum){
DIR dpath;
FRESULT rse;
FILINFO info;
TCHAR *Path=path;
uint16_t index = 0;
for(int Path_num=0;Path_num<scanum;Path_num++){
rse = f_opendir(&dpath,Path);
if(rse == FR_OK){
printf("目录打开成功\n");
while(1){
rse=f_readdir(&dpath,&info);
if(rse!=FR_OK||info.fname[0]==0){//文件读取到末尾
break;
}
if(info.fattrib && AM_DIR){
printf("0:/%s\n",info.fname);
}else{
printf("%s\n",info.fname);
}
if(strstr(info.fname,"mp3")!=NULL){
sprintf(psr,"%s/%s",Path,info.fname);
psr++;
if(++index>=maxlen){
break;
}
}
}
printf ("音乐条数%d\n",index);
f_closedir(&dpath);
}else{
printf("目录打开失败\n");
f_closedir(&dpath);
}
Path+=40;
}
return index;
}
读取播放列表,和根目录文件夹操作
#ifndef __MP3_INTERFACE_H_
#define __MP3_INTERFACE_H_
#include "fatfs.h"
void doc_mp3(const char *filenames);
uint16_t get_mulu_list(const char *path,char (*psr)[40],uint16_t maxlen);
uint16_t get_music_list(const char *path,char (*psr)[40],uint16_t maxlen,uint16_t scanum);
#endif
函数声明
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "fatfs.h"
#include "i2s.h"
#include "sdio.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "minimp3_interface.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#define MAX_list 20
#define MU_list 100
FIL file;
FRESULT f_res;
DWORD tolt,freesize,datasize;
UINT fnum;
BYTE Readebuff[512]={0};
BYTE Write_buff[512]={
"This is STM32F407 write"
};
char Path[] = {"0:/"}; //根目录
char Pathlist[MAX_list][40]={0};
char Mulist[MU_list][40]={0};
int Path_num;
int Music_num;
int Music_index;
extern volatile uint16_t up_play;
extern volatile uint16_t down_play;
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
FATFS *fs=&SDFatFS;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_SDIO_SD_Init();
MX_I2S2_Init();
MX_FATFS_Init();
MX_USART1_UART_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
f_res = f_mount(&SDFatFS,"0:",1);
HAL_Delay(100);
f_res =f_open(&SDFile,"0:/holle.txt",FA_CREATE_ALWAYS|FA_WRITE|FA_READ);
if(f_res==FR_OK){
printf("创建成功\n");
}
f_res =f_write(&SDFile,Write_buff,sizeof(Write_buff),&fnum);
if(f_res==FR_OK){
printf("写入成功\n");
}
f_res =f_sync(&SDFile);
HAL_Delay(100);
f_res =f_read(&SDFile,Readebuff,sizeof(Readebuff),&fnum);
if(f_res==FR_OK){
printf("读入成功\n");
}
f_res =f_close(&SDFile);
f_res=f_getfree("0:",&tolt,&fs);
if(f_res==FR_OK){
datasize=(fs->n_fatent-2)*(fs->csize/2);
freesize=tolt*(fs->csize/2);
printf("总容量: %ldMB\n",datasize/1024);
printf("剩余容量:%ldMB\n",freesize/1024);
}
Path_num=get_mulu_list(Path,Pathlist,MAX_list);
Music_num=get_music_list(Pathlist[0],Mulist,MU_list,Path_num);//更新列表
for(int i = 0;i<Music_num;i++){
printf("path %s\n",Mulist[i]);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
printf("Music_index%d\n",Music_index);
if(Music_index>=Music_num){
Music_index=0;
}
// for(int i=0;i<Music_num;i++){
doc_mp3(Mulist[Music_index]);//根据列表播放音乐
if(up_play == 0 && down_play == 0){
HAL_Delay(20);
Music_index++;
}
if(up_play>=1 && down_play == 0){
Music_index-=1;
if(Music_index<0){
Music_index=Music_num-1;
}
HAL_Delay(20);
}else if(down_play>=1 && up_play == 0){
Music_index+=1;
if(Music_index>=Music_num){
Music_index=0;
}
HAL_Delay(20);
}
// }
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
代码整体逻辑控制,通过控制mp3路径的索引,来控制上一曲,下一曲
调试信息打印