基于STM32的音乐播放器

本项目通过cubeide配置freertos,实现i2s驱动max98357播放音乐,音频质量高,杂音少。可通过oled显示屏显示歌曲,按键系统实现上一首、下一首、暂停和开始功能。

播放功能演示视频

【MAX98357,stm32,音乐播放器,mp3】 https://www.bilibili.com/video/BV1to55zrEq9/?share_source=copy_web&vd_source=6503ba03f7f33203f6a7086ba0c386d9

主要任务代码 

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#include "i2s.h"
#include "oled.h"//oled
#include "oledfont.h"
#include "RGB.h"
#include "wave.h"
/* 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 Variables */

/* USER CODE END Variables */
osThreadId oledHandle;
osThreadId LEDHandle;
osThreadId Key_PressHandle;
osThreadId UARTHandle;
osThreadId Audio_PlayHandle;
osThreadId SD_InitHandle;
osSemaphoreId myBinarySem01Handle;

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
extern SemaphoreHandle_t mutexATSendOfRestlt;//esp8266
QueueHandle_t Test_Queue =NULL;//队列,按键和led
QueueHandle_t Test_Queue_audio =NULL;//队列,按键和音乐播放任务
QueueHandle_t Test_Queue_oled =NULL;//队列,按键和音乐播放任务
BaseType_t xReturn = pdPASS;

extern FIL wav_file;    // FatFs文件对象

extern void ESP01S_Init(void);
extern 	void Mount_FatFs();  //挂载文件系统	//sd卡读取任务
extern void FatFs_WriteTXTFile();
extern uint8_t scan_music_files();
extern void skip_wav_header();
extern void WS2812_Number_4();
/* USER CODE END FunctionPrototypes */

void oled_Task(void const * argument);
void LED_Task(void const * argument);
void Task_Key_Press(void const * argument);
void UART_Task(void const * argument);
void Task_Audio_Play(void const * argument);
void Task_SD_Init(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );

/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];

void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
  *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
  *ppxIdleTaskStackBuffer = &xIdleStack[0];
  *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
  /* place for user code */
}
/* USER CODE END GET_IDLE_TASK_MEMORY */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */
  /* USER CODE END Init */

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* Create the semaphores(s) */
  /* definition and creation of myBinarySem01 */
  osSemaphoreDef(myBinarySem01);
  myBinarySem01Handle = osSemaphoreCreate(osSemaphore(myBinarySem01), 1);

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  mutexATSendOfRestlt = xSemaphoreCreateBinary();//串口口等待
  Test_Queue = xQueueCreate((UBaseType_t ) 4,(UBaseType_t ) 4);//按键和播放任务
  Test_Queue_audio = xQueueCreate((UBaseType_t ) 4,(UBaseType_t ) 4);//按键和led任务
  Test_Queue_oled = xQueueCreate((UBaseType_t ) 4,(UBaseType_t ) 4);//按键和led任务

//  xDmaRxSemaphore = xSemaphoreCreateBinary();//INMP441
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */

  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* definition and creation of oled */
  osThreadDef(oled, oled_Task, osPriorityNormal, 1, 1024);
  oledHandle = osThreadCreate(osThread(oled), NULL);

  /* definition and creation of LED */
  osThreadDef(LED, LED_Task, osPriorityLow, 1, 2048);
  LEDHandle = osThreadCreate(osThread(LED), NULL);

  /* definition and creation of Key_Press */
  osThreadDef(Key_Press, Task_Key_Press, osPriorityIdle, 1, 1024);
  Key_PressHandle = osThreadCreate(osThread(Key_Press), NULL);

  /* definition and creation of UART */
  osThreadDef(UART, UART_Task, osPriorityIdle, 1, 256);
  UARTHandle = osThreadCreate(osThread(UART), NULL);

  /* definition and creation of Audio_Play */
  osThreadDef(Audio_Play, Task_Audio_Play, osPriorityIdle, 0, 4096);
  Audio_PlayHandle = osThreadCreate(osThread(Audio_Play), NULL);

  /* definition and creation of SD_Init */
  osThreadDef(SD_Init, Task_SD_Init, osPriorityIdle, 1, 1024);
  SD_InitHandle = osThreadCreate(osThread(SD_Init), NULL);

  /* USER CODE BEGIN RTOS_THREADS */

  /* add threads, ... */
  /* USER CODE END RTOS_THREADS */

}

/* USER CODE BEGIN Header_oled_Task */
/**
  * @brief  Function implementing the oled thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_oled_Task */
void oled_Task(void const * argument)
{
  /* USER CODE BEGIN oled_Task */
  /* Infinite loop */
//	 HAL_I2S_Transmit_DMA(&hi2s2, (uint16_t*)dma, BUFFER_SIZE);	 // 启动I2S2发送(MAX98357)
	 OLED_Init();                           //OLED初始
	 OLED_Clear();                         //清屏
	 OLED_ShowString(20,0,"Music Player",16,0);
	 OLED_ShowString(100,3,"sta",16,0);
	 OLED_ShowString(60,3,"stp",16,0);
	 OLED_ShowString(30,3,"pr",16,0);
	 OLED_ShowString(0,3,"nt",16,0);
//	 OLED_ShowString(100,2,"sta",16,0);
//	 OLED_ShowString(60,2,"stp",16,0);
//	 OLED_ShowString(30,2,"pr",16,0);
//	 OLED_ShowString(0,2,"nt",16,0);
	//  OLED_DrawLine(0,18,127,18,1);//

	 printf("oled is ok\r\n");
	 for(;;)
	 {
		 xQueueReceive(Test_Queue_oled, &oled_state,portMAX_DELAY);
//		 printf("oled_state:%d\n",oled_state);
		 if(last_state != oled_state)
		 {
			 switch(state)
			 {
				 case 48://开始
					 OLED_ShowString(100,2,"sta",16,1);
					 OLED_ShowString(60,2,"stp",16,0);
					 OLED_ShowString(30,2,"pr",16,0);
					 OLED_ShowString(0,2,"nt",16,0);
		 	 	 	 osDelay(200);
					 break;
				 case 49://暂停
					 OLED_ShowString(100,2,"sta",16,0);
					 OLED_ShowString(60,2,"stp",16,1);
					 OLED_ShowString(30,2,"pr",16,0);
					 OLED_ShowString(0,2,"nt",16,0);
					 break;
				 case 50://上一首
					 OLED_ShowString(100,2,"sta",16,0);
					 OLED_ShowString(60,2,"stp",16,0);
					 OLED_ShowString(30,2,"pr",16,1);
					 OLED_ShowString(0,2,"nt",16,0);
					 break;
				 case 51://下一首
					 OLED_ShowString(100,2,"sta",16,0);
					 OLED_ShowString(60,2,"stp",16,0);
					 OLED_ShowString(30,2,"pr",16,0);
					 OLED_ShowString(0,2,"nt",16,1);
					 break;
				 default:
					 break;
			 }
			 last_state = state;
		 }
		 osDelay(100);//刷新一次
	 }
  /* USER CODE END oled_Task */
}

/* USER CODE BEGIN Header_LED_Task */
/**
* @brief Function implementing the LED thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_LED_Task */
void LED_Task(void const * argument)
{
  /* USER CODE BEGIN LED_Task */
  /* Infinite loop */
	WS2812_Init();
	osDelay(100);
	printf("led is ok\r\n");
  for(;;)
  {
      xQueueReceive(Test_Queue_oled, &led_state, 1);
      if(led_state==48)
      {
  		for(int i=0;i<LED_NUM;i++)  		//跑马灯
  		{
  			osDelay(500);
  			if(i%3==0)
  			{
  				WS2812_Set(i,20,0,0);
  			}
  			else if(i%3==1)
  			{
  				WS2812_Set(i,0,20,0);
  			}
  			else
  				WS2812_Set(i,0,0,20);
  			if(i==0) WS2812_Set(LED_NUM-1,0,0,0);
  			else     WS2812_Set(i-1,0,0,0);
  		}
      }
      else if(led_state == 49)
      {
    	  	for(int i=0;i<LED_NUM;i++)    	  //流水灯
    	  	{
    	  		osDelay(100);
    	  		WS2812_Set(i,(i+1),(i+1),(i+1));
    	  	}
    	  	osDelay(300);
    	  	for(int i=0;i<LED_NUM;i++)
    	  	{
    	  		WS2812_Set(i,0,0,0);
    	  	}
    	  	osDelay(100);
      }
      else if(led_state == 50)
      {
    		for(int i=0;i<LED_NUM;i++)  		//跑马灯
    		{
    			osDelay(300);
    			if(i%3==0)
    			{
    				WS2812_Set(i,0,20,0);
    			}
    			else if(i%3==1)
    			{
    				WS2812_Set(i,0,20,0);
    			}
    			else
    				WS2812_Set(i,0,20,0);
    			if(i==0) WS2812_Set(LED_NUM-1,0,0,0);
    			else     WS2812_Set(i-1,0,0,0);
    		}
      }
      else if(led_state == 51)
      {
    		for(int i=0;i<LED_NUM;i++)  		//跑马灯
    		{
    			osDelay(300);
    			if(i%3==0)
    			{
    				WS2812_Set(i,0,0,20);
    			}
    			else if(i%3==1)
    			{
    				WS2812_Set(i,0,0,20);
    			}
    			else
    				WS2812_Set(i,0,0,20);
    			if(i==0) WS2812_Set(LED_NUM-1,0,0,0);
    			else     WS2812_Set(i-1,0,0,0);
    		}
      }
      else//防止错误卡死
      {
      }
//	  xQueueSend(Test_Queue_oled,&led_state,1);//队列发送任务,oled

      osDelay(10);//防止阻塞
  }
  /* USER CODE END LED_Task */
}

/* USER CODE BEGIN Header_Task_Key_Press */
/**
* @brief Function implementing the Key_Press thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Key_Press */
void Task_Key_Press(void const * argument)
{
  /* USER CODE BEGIN Task_Key_Press */
  /* Infinite loop */
	  uint8_t lastState = 1; // 默认上拉状态
	  printf("key is ok\r\n");
	  for(;;)
	  {
		  uint8_t currentState = HAL_GPIO_ReadPin(KEY_0_GPIO_Port, KEY_0_Pin);
		  //按下高电平,不按低电平
		  uint8_t key1State = HAL_GPIO_ReadPin(KEY_1_GPIO_Port, KEY_1_Pin);//下一首
		  uint8_t key2State = HAL_GPIO_ReadPin(KEY_2_GPIO_Port, KEY_2_Pin);//上一首
		  uint8_t key3State = HAL_GPIO_ReadPin(KEY_3_GPIO_Port, KEY_3_Pin);//暂停
		  uint8_t key4State = HAL_GPIO_ReadPin(KEY_4_GPIO_Port, KEY_4_Pin);//开始
		  osDelay(20); //消抖

		  if(lastState == 1 && currentState == 0) // 下降沿,录音//INMP411
		  {
			printf("HAL_I2S_Receive_DMA\n");
			UsartPrintf(huart2,"start\r\n");
			HAL_I2S_Receive_DMA(&hi2s3,(uint16_t*)dma,4);	//开启
//			HAL_I2S_Receive_DMA(&hi2s3,(uint16_t*)data_inmp,1024);	//开启
//			while(inmp411_flag==0)
//			{
//				osDelay(1);
//			}
//			inmp411_flag = 0;
//			HAL_I2S_Transmit_DMA(&hi2s2, (uint16_t*)data_inmp,1024/2);//发送
			osDelay(20); // 消抖
		  }
		  else if(lastState == 0 && currentState == 1)// 上升沿
		  {
			printf("HAL_I2S_DMAStop\n");
			HAL_I2S_DMAStop(&hi2s3);//停止
			UsartPrintf(huart2,"end\r\n");
		  }
		  //改变状态
		  if(key4State)//开始
		  {
			  state = '0';
		  }
		  else if(key3State)//暂停
		  {
			  state = '1';
		  }
		  else if(key2State)//上一首
		  {
			  state = '2';
		  }
		  else if(key1State)//下一首
		  {
			  state = '3';
		  }
		  else//不改变
		  {
//			  state = state;
		  }
		  if(lastState != currentState)//按键状态改变,发送消息
		  {
			  xQueueSend(Test_Queue_audio,&state,1);//队列发送任务
			  xQueueSend(Test_Queue_oled,&state,1);
		  }
		  else
		  {

		  }
		  lastState = currentState;//
		  osDelay(20); //等待
	  }
  /* USER CODE END Task_Key_Press */
}

/* USER CODE BEGIN Header_UART_Task */
/**
* @brief Function implementing the UART thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_UART_Task */
void UART_Task(void const * argument)
{
  /* USER CODE BEGIN UART_Task */
  /* Infinite loop */
	//ESP8266接收
	HAL_UART_Receive_IT(&huart2,(uint8_t *)RxTemp, REC_LENGTH);	//串口接收函数,监听任务,esp8266
	printf("uart2 is ok\r\n");
	for(;;)
	{
		osSemaphoreWait(myBinarySem01Handle,100);						//等待二值信号量,进入串口
		if(RxFlag == 1)													//数据接收完成
		{
			for(int i = 0; i<RxCounter; i++)							//打印接收数组存储的内容
			{
				printf("%c",RxBuffer[i]);
				ESP01S_buf[i] = RxBuffer[i];//收到的数据
//				printf("%c",ESP01S_buf[i]);
			}

			if(ESP01S_buf[RxCounter-3]=='K'&&ESP01S_buf[RxCounter-4]=='O')
			{
				xSemaphoreGive(mutexATSendOfRestlt);//释放
			}
			else if(ESP01S_buf[RxCounter-3]=='>')
			{
				xSemaphoreGive(mutexATSendOfRestlt);//释放
			}
			else
			{

			}
			RxFlag = 0;													//接收标志清零
			RxCounter = 0;												//接收计数清零
			memset(RxBuffer ,0, MAX_REC_LENGTH);						//清空接收数组
		}
		osDelay(1);
	}
  /* USER CODE END UART_Task */
}

/* USER CODE BEGIN Header_Task_Audio_Play */
/**
* @brief Function implementing the Audio_Play thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_Audio_Play */
void Task_Audio_Play(void const * argument)
{
  /* USER CODE BEGIN Task_Audio_Play */

	 ESP01S_Init();//esp8266初始化
//    char music_files[MAX_MUSIC_NUM][MAX_FILENAME_LEN];// 定义音乐文件名数组,每个元素存储一个wav文件的完整路径
	char name[MAX_MUSIC_NUM][MAX_FILENAME_LEN];
    uint8_t music_count = 0;    // 记录music文件夹下wav文件的数量
    uint8_t current_index = 0;    // 当前播放的音乐索引
//    FIL wav_file;    // FatFs文件对象
//    UINT br;    // FatFs读取字节数
//    uint8_t audio_buffer[AUDIO_BUFFER_SIZE];    // 音频数据缓冲区
    uint8_t playing = 0;    // 播放状态标志,1为正在播放,0为暂停或未播放
    // 1. 挂载SD卡文件系统,并扫描music文件夹下所有wav文件,存入music_files数组
    Mount_FatFs();
    music_count = scan_music_files("0:/music", music_files);
    for(int j=0;j<music_count;j++)
    {
    	for(int i=0;music_files[j][i+9]!='.'&&i<16;i++)
    	{
    		name[j][i] = music_files[j][i+9];
    	}
    }
	for(int i=0;i<music_count;i++)
	{
		printf("%d:%s\r\n",i,name[i]);//输出歌曲名
	}
    printf("music_count:%d\r\n",music_count);
    for(;;)
    {
    	 last_cmd = cmd;
         // 阻塞等待队列消息(按键任务发送的控制命令),cmd为'0'(播放)、'1'(暂停)、'2'(上一首)、'3'(下一首)
         xQueueReceive(Test_Queue_audio, &cmd, portMAX_DELAY);//等待消息
         if(last_cmd != cmd)
         {
//        	 OLED_Clear();
        	 switch(cmd)
        	{
				case 48: //播放// 如果当前未播放,则打开当前索引的wav文件,并跳过wav头
					if(!playing)
					{
						printf("play\r\n");
//					    OLED_ShowString(0,6,name[current_index],16,0);
//					    OLED_Some_HorizontalShift(1,0,6);//水平滚动
						playing = 1;
						f_open(&wav_file, music_files[current_index], FA_READ);//打开文件
//        				fread(&wave,1,44,&br);wav_solve(wave);//输出解码
						skip_wav_header(&wav_file); // 跳过wav头44字节
					}
					//播放循环:不断读取音频数据并通过I2S发送到功放
					while(playing)
					{
						// 检查是否有新命令(如暂停、切歌),有则跳出播放循环
//						printf("playing\r\n");
						if(HAL_GPIO_ReadPin(KEY_3_GPIO_Port, KEY_3_Pin))//结束
						{
							printf("play_stop\r\n");
							break;
						}
						else
						{
							f_read(&wav_file, audio_buffer, AUDIO_BUFFER_SIZE, &br); // 读取一段音频数据
							if(br == 0)
							{
								printf("br\r\n");
								break; // 文件读完,跳出循环
							}
							else
							{
								while(i2s2_dma_tx_done == 0); // 等待上次DMA完成
								HAL_I2S_Transmit_DMA(&hi2s2, (uint16_t*)audio_buffer,br/2);//发送
								i2s2_dma_tx_done = 0;
							}
						}
					}
					break;
				case 49: // 暂停
					printf("stop\r\n");
        			playing = 0; // 设置播放标志为0
        			HAL_I2S_DMAStop(&hi2s2); // 停止I2S DMA传输
					break;
				case 50: // 上一首
					printf("pre:%d\r\n",current_index);
					if(current_index > 0) current_index--; // 索引减一,切换到上一首
					else if(current_index == 0)
					{
						current_index = music_count-1;
					}
        			playing = 0; // 停止当前播放
					f_close(&wav_file); // 关闭当前文件
					break;
				case 51: // 下一首
					printf("next:%d\r\n",current_index);
					if(current_index < music_count-1)
					{
						current_index++; // 索引加一,切换到下一首
					}
					else if(current_index == music_count-1)
					{
						current_index = 0;//回到第一首
					}
        			playing = 0; // 停止当前播放
					f_close(&wav_file); // 关闭当前文件
					break;
				default:
					break;
        	}
//			 OLED_ShowString(10,6,name[current_index],16,0);
        	 oled_clear_te(5,8);//清除显示
        	 osDelay(100);
//    		 OLED_Some_HorizontalShift(0x27,6,7);//水平滚动
    		 OLED_ShowString(0,6,name[current_index],16,0);//显示歌名
//    		 OLED_WR_CMD(0x2F);//开始滚动指令
        	 printf("%d,%d\r\n",last_cmd,cmd);
        	 printf("%s\r\n",name[current_index]);
//			 printf("%s\r\n",music_files[current_index]);
         }
         osDelay(10); // 延时,防止任务占用过多CPU
    }
  /* USER CODE END Task_Audio_Play */
}

/* USER CODE BEGIN Header_Task_SD_Init */
/**
* @brief Function implementing the SD_Init thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_SD_Init */
void Task_SD_Init(void const * argument)
{
  /* USER CODE BEGIN Task_SD_Init */
  /* Infinite loop */
  for(;;)
  {
	  	LED(1);
	  	osDelay(500);
	  	LED(0);
	  	osDelay(500);
  }
  /* USER CODE END Task_SD_Init */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */

/* USER CODE END Application */

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值