本项目通过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 */