你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32F769I-DISC1的音乐播放器

[复制链接]
lugl 发布时间:2025-3-20 18:16

【前言】

首先要感谢ST中文论坛,在春节的活动中,给了我一次试用STM32F769I-DISC的开发板的机会。经过两个多月的学习,现在作品终于可划上了句号了。现分享如下。

试用期间,我发表了好几篇的帖子:

1、【STM32F769I-DISC1】LWIP手工移植 - ST中文论坛活动 ST意法半导体中文论坛

2、【STM32F769I-DISC1】对接deepseek本地模型 - ST中文论坛活动 ST意法半导体中文论坛

3、【STM32F769I-DISC1】驱动ST7789LCD - ST中文论坛活动 ST意法半导体中文论坛

4、【STM32F769I-DISC1】硬件SPI驱动ST7789LCD - ST中文论坛活动 ST意法半导体中文论坛

5、【STM32F769I-DISC1】移植LVGL - ST中文论坛活动 ST意法半导体中文论坛

6、【STM32F769】SDRAM移植之MPU保护 - ST中文论坛活动 ST意法半导体中文论坛

7、基于STM32F769I-DISC的可调数字电源 - ST中文论坛活动 ST意法半导体中文论坛

8、STM32F769 中启用指令缓存后LVGL显示花屏的问题查找以及解决方法 - ST中文论坛活动 ST意法半导体中文论坛

9、【STM32F769】实现SD卡的FATFS - ST中文论坛活动 ST意法半导体中文论坛

10、【STM32F769I-DISC1】移植基于FreeRTOS的FATFS - ST中文论坛活动 ST意法半导体中文论坛

11、【STM32F769I-DISC1】移植基于FreeRTOS的FATFS - ST中文论坛活动 ST意法半导体中文论坛

通过上面的一步一步的学习,在这个些帖子的基础上,我设计了界面,同时实现的音乐的播放、切歌等功能。

下面分享一下,如何进行界面设计以及切歌。

【界面设计】

我使用GUI-Guider开源的界面设计:

image.png

【歌单的设计】

在歌单设计中,我使用了双向链表来对歌曲文件名进行了存取,这样就方便向前向后一曲来播放。他的链表如下:

// 定义双向链表节点结构体
typedef struct WavFileNode
{
    char filename[256];       // 存储文件名
    struct WavFileNode *prev; // 指向前一个节点
    struct WavFileNode *next; // 指向后一个节点
} WavFileNode;

这样我开点击播放时,把所有的.wav的文件都读取到这个链表中,在lvgl的三个按键就可以实现对文件的定位了。

【切歌的实现】

在lvgl的custom.c自定义函数,我注册了三个按键回调函数,以向前一首为例,这现代码如下:

void screen_btn_next_event_handler(lv_event_t *event)
{
    lv_event_code_t code = lv_event_get_code(event);
    lv_ui *ui = lv_event_get_user_data(event);
    if (code == LV_EVENT_CLICKED)
    {
        if (isWavFileListEmpty(fileList))
        {
            printf("No WAV files found.\n");
            return;
        }

        if(fileList->next == NULL)
        {
            printf("Already at the last WAV file.\n");
            return;
        }
        fileList = fileList->next;
        // 更新 UI 标签
        if (ui && ui->screen_label_play && fileList && fileList->filename)
        {
            lv_label_set_text(ui->screen_label_play, fileList->filename);
        }
        else
        {
            printf("UI element or file data is invalid.\n");
            return;
        }

        // 更新布局
        lv_obj_t *active_screen = lv_scr_act();
        if (active_screen)
        {
            lv_obj_update_layout(active_screen);
        }
        else
        {
            printf("Active screen is null.\n");
            return;
        }
        createFileProcessingTask(fileList->filename);
    }
}

按键按下后,执行了creatFilePrcessingTask,在这个函数中,对播放任务进行了重新的创建,因而结合了前面的播放音乐的逻辑进行播放,这就这现了整个项目的完整性。

收藏 评论1 发布时间:2025-3-20 18:16

举报

1个回答
lugl 回答时间:2025-3-20 18:17:47

附audio.play.c的源码:

#include "audio_play.h"
#include "main.h"
#include <stdio.h>
#include "wm8994.h"
#include "stm32f769i_discovery_audio.h"
#include "ff.h"
#include "task.h"
#include "string.h"

#define AUDIO_DEFAULT_VOLUME 70

#define AUDIO_START_OFFSET_ADDRESS 0 /* Offset relative to audio file header size */
#define AUDIO_BUFFER_SIZE 8096
#define AF_48K 48K

/* Private typedef -----------------------------------------------------------*/

typedef enum
{
    BUFFER_OFFSET_NONE = 0,
    BUFFER_OFFSET_HALF,
    BUFFER_OFFSET_FULL,
} BUFFER_StateTypeDef;

typedef struct
{
    uint8_t buff[AUDIO_BUFFER_SIZE];
    uint32_t fptr;
    BUFFER_StateTypeDef state;
    FIL file;                 // 添加文件句柄
    uint32_t AudioFileSize;   // 添加 AudioFileSize 成员
    uint32_t AudioSampleRate; // 添加 AudioSampleRate 成员
} AUDIO_BufferTypeDef;

typedef enum
{
    TS_ACT_NONE = 0,
    TS_ACT_VOLUME_DOWN,
    TS_ACT_VOLUME_UP,
    TS_ACT_PAUSE = 0xFE
} TS_ActionTypeDef;

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
AUDIO_BufferTypeDef buffer_ctl;
AUDIO_PLAYBACK_StateTypeDef audio_state;
__IO uint32_t uwVolume = 20;
__IO uint32_t uwPauseEnabledStatus = 0;

static uint32_t GetData(void *pdata, uint32_t offset, uint8_t *pbuf, uint32_t NbrOfData);
AUDIO_ErrorTypeDef AUDIO_Start(uint32_t *psrc_address, uint32_t file_size);
uint8_t AUDIO_Process(void);
/* Private functions ---------------------------------------------------------*/

/**
 * @brief  Audio Play demo
 * @param  None
 * @retval None
 */

/**
 * @brief  Starts Audio streaming.
 * @param  None
 * @retval Audio error
 */
void StartAudioPlayback(void *pvParameters)
{
    uint32_t AudioFreq = AF_48K;
    uint32_t *AudioFreq_ptr = &AudioFreq;
    char *filename = (char *)pvParameters;
    /* Initialize FatFs */
    FRESULT fres;
    FATFS fs;
    // 定义 FILINFO 类型变量以存储文件信息
    FILINFO fno;

    // 停止之前的播放
    if (audio_state == AUDIO_STATE_PLAYING)
    {
        BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
        audio_state = AUDIO_STATE_IDLE;

        // 关闭之前的文件句柄
        if (buffer_ctl.file.obj.fs != NULL)
        {
            printf("关闭之前的文件句柄\n");
            f_close(&buffer_ctl.file);
        }
    }

    fres = f_mount(&fs, "", 1);
    if (fres != FR_OK)
    {
        printf("无法挂载文件系统\n");
        return;
    }

    /* 添加文件存在性检查 */
    // 打印文件路径
    printf("尝试检查文件路径: %s\n", filename);

    // 检查文件是否存在
    fres = f_stat(filename, &fno);
    if (fres != FR_OK)
    {
        printf("文件不存在或无法访问: %s, 错误代码: %d\n", filename, fres);
        if (strchr(filename, '\\') != NULL)
        {
            printf("提示:请使用正斜杠(/)作为路径分隔符\n");
        }
        return;
    }

    // 打印文件信息
    printf("文件存在,大小: %lu 字节\n", fno.fsize);

    // 打开文件
    fres = f_open(&buffer_ctl.file, filename, FA_READ);
    if (fres != FR_OK)
    {
        printf("文件打开失败,错误代码: %d\n", fres);
        return;
    }

    /* Initialize Audio Codec */
    if (BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_BOTH, uwVolume, *AudioFreq_ptr) == 0)
    {
        printf("  AUDIO CODEC   OK  \n");
    }
    else
    {
        printf("  AUDIO CODEC  FAIL \n");
        printf(" Try to reset board \n");
        f_close(&buffer_ctl.file);
        return;
    }

    /* Set Audio Frame Slot */
    BSP_AUDIO_OUT_SetAudioFrameSlot(CODEC_AUDIOFRAME_SLOT_02);
    osDelay(100);

    /* Start Audio Playback */
    AUDIO_Start((uint32_t *)filename, 0); // 传递文件路径

    while (1)
    {
        if (audio_state == AUDIO_STATE_PLAYING)
        {
            AUDIO_Process();
        }

        osDelay(2);
    }
}

// 定义一个全局变量来存储任务句柄
TaskHandle_t FileProcessingTaskHandle = NULL;

void createFileProcessingTask(const char *filename)
{
    // 检查任务是否已经创建
    if (FileProcessingTaskHandle != NULL)
    {
        printf("任务已经创建\n");
        // 停止并删除任务
        vTaskSuspend(FileProcessingTaskHandle);
        FileProcessingTaskHandle = NULL; // 清空句柄
    }

    // 动态创建任务
    xTaskCreate(
        StartAudioPlayback,           // 任务函数
        "FileProcessingTask",         // 任务名称
        configMINIMAL_STACK_SIZE , // 调整栈大小以适应需求
        (void *)filename,             // 任务参数
        osPriorityLow,                // 任务优先级
        &FileProcessingTaskHandle     // 存储任务句柄
    );

    if (FileProcessingTaskHandle != NULL)
    {
        // 启动任务
        vTaskResume(FileProcessingTaskHandle);
    }
    else
    {
        printf("任务创建失败\n");
    }
}

/**
 * @brief  Starts Audio streaming.
 * @param  None
 * @retval Audio error
 */
AUDIO_ErrorTypeDef AUDIO_Start(uint32_t *psrc_address, uint32_t file_size)
{
    FRESULT fres;

    // 读取 WAV 文件头部信息
    uint8_t wav_header[44];
    fres = f_read(&buffer_ctl.file, wav_header, sizeof(wav_header), NULL);
    if (fres != FR_OK)
    {
        printf("无法读取 WAV 文件头部信息\n");
        f_close(&buffer_ctl.file);
        return AUDIO_ERROR_IO;
    }

    // 检查 WAV 文件头部信息
    if (memcmp(wav_header, "RIFF", 4) != 0 || memcmp(wav_header + 8, "WAVE", 4) != 0)
    {
        printf("不是有效的 WAV 文件\n");
        f_close(&buffer_ctl.file);
        return AUDIO_ERROR_IO;
    }

    // 获取采样率
    buffer_ctl.AudioSampleRate = (wav_header[24] << 0) |
                                 (wav_header[25] << 8) |
                                 (wav_header[26] << 16) |
                                 (wav_header[27] << 24);

    // 初始化音频编解码器,使用 WAV 文件的采样率
    if (BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_BOTH, uwVolume, buffer_ctl.AudioSampleRate) != 0)
    {
        printf("  AUDIO CODEC  FAIL \n");
        printf(" Try to reset board \n");
        f_close(&buffer_ctl.file);
        return AUDIO_ERROR_IO;
    }

    // 获取文件大小
    buffer_ctl.AudioFileSize = f_size(&buffer_ctl.file);
    buffer_ctl.state = BUFFER_OFFSET_NONE;
    buffer_ctl.fptr = 0;

    // 重新设置文件指针到数据块开始位置
    uint32_t data_chunk_offset = (wav_header[36] << 0) |
                                 (wav_header[37] << 8) |
                                 (wav_header[38] << 16) |
                                 (wav_header[39] << 24);
    fres = f_lseek(&buffer_ctl.file, data_chunk_offset);
    if (fres != FR_OK)
    {
        printf("无法设置文件指针到数据块开始位置\n");
        f_close(&buffer_ctl.file);
        return AUDIO_ERROR_IO;
    }

    uint32_t bytesread = GetData(NULL, 0, &buffer_ctl.buff[0], AUDIO_BUFFER_SIZE);
    if (bytesread > 0)
    {
        BSP_AUDIO_OUT_Play((uint16_t *)&buffer_ctl.buff[0], AUDIO_BUFFER_SIZE);
        audio_state = AUDIO_STATE_PLAYING;
        buffer_ctl.fptr = bytesread;
        return AUDIO_ERROR_NONE;
    }
    else
    {
        f_close(&buffer_ctl.file);
        return AUDIO_ERROR_IO;
    }
}

/**
 * @brief  Manages Audio process.
 * @param  None
 * @retval Audio error
 */
uint8_t AUDIO_Process(void)
{
    uint32_t bytesread;
    AUDIO_ErrorTypeDef error_state = AUDIO_ERROR_NONE;

    switch (audio_state)
    {
    case AUDIO_STATE_PLAYING:

        if (buffer_ctl.fptr >= buffer_ctl.AudioFileSize)
        {
            // Play audio sample again ...
            buffer_ctl.fptr = 0;
            error_state = AUDIO_ERROR_EOF;
            audio_state = AUDIO_STATE_IDLE; // 重置状态
            f_close(&buffer_ctl.file); // 关闭文件
            break;
        }

        // 1st half buffer played; so fill it and continue playing from bottom
        if (buffer_ctl.state == BUFFER_OFFSET_HALF)
        {
            bytesread = GetData(NULL, buffer_ctl.fptr, &buffer_ctl.buff[0], AUDIO_BUFFER_SIZE / 2);

            if (bytesread > 0)
            {
                buffer_ctl.state = BUFFER_OFFSET_NONE;
                buffer_ctl.fptr += bytesread;
            }
            else
            {
                // Play audio sample again...
                buffer_ctl.fptr = 0;
                error_state = AUDIO_ERROR_EOF;
                audio_state = AUDIO_STATE_IDLE; // 重置状态
                f_close(&buffer_ctl.file); // 关闭文件
            }
        }

        // 2nd half buffer played; so fill it and continue playing from top
        if (buffer_ctl.state == BUFFER_OFFSET_FULL)
        {
            bytesread = GetData(NULL, buffer_ctl.fptr, &buffer_ctl.buff[AUDIO_BUFFER_SIZE / 2], AUDIO_BUFFER_SIZE / 2);
            if (bytesread > 0)
            {
                buffer_ctl.state = BUFFER_OFFSET_NONE;
                buffer_ctl.fptr += bytesread;
            }
            else
            {
                // Play audio sample again...
                buffer_ctl.fptr = 0;
                error_state = AUDIO_ERROR_EOF;
                audio_state = AUDIO_STATE_IDLE; // 重置状态
                f_close(&buffer_ctl.file); // 关闭文件
            }
        }
        break;

    default:
        error_state = AUDIO_ERROR_NOTREADY;
        break;
    }
    return (uint8_t)error_state;
}

/**
 * @brief  Gets Data from storage unit.
 * @param  None
 * @retval None
 */
static uint32_t GetData(void *pdata, uint32_t offset, uint8_t *pbuf, uint32_t NbrOfData)
{
    FRESULT fres;
    UINT bytesRead;

    fres = f_lseek(&buffer_ctl.file, offset);
    if (fres != FR_OK)
    {
        printf("Failed to set the file pointer to the offset. %lu\n", offset);
        return 0;
    }

    fres = f_read(&buffer_ctl.file, pbuf, NbrOfData, &bytesRead);
    if (fres != FR_OK)
    {
        printf("无法读取文件\n");
        return 0;
    }

    return bytesRead;
}

/*------------------------------------------------------------------------------
       Callbacks implementation:
           the callbacks API are defined __weak in the stm32769i_discovery_audio.c file
           and their implementation should be done the user code if they are needed.
           Below some examples of callback implementations.
  ----------------------------------------------------------------------------*/

/**
 * @brief  Manages the full Transfer complete event.
 * @param  None
 * @retval None
 */
void BSP_AUDIO_OUT_TransferComplete_CallBack(void)
{
    if (audio_state == AUDIO_STATE_PLAYING)
    {
        /* allows AUDIO_Process() to refill 2nd part of the buffer  */
        buffer_ctl.state = BUFFER_OFFSET_FULL;
    }
}

/**
 * @brief  Manages the DMA Half Transfer complete event.
 * @param  None
 * @retval None
 */
void BSP_AUDIO_OUT_HalfTransfer_CallBack(void)
{
    if (audio_state == AUDIO_STATE_PLAYING)
    {
        /* allows AUDIO_Process() to refill 1st part of the buffer  */
        buffer_ctl.state = BUFFER_OFFSET_HALF;
    }
}

/**
 * @brief  Manages the DMA FIFO error event.
 * @param  None
 * @retval None
 */
void BSP_AUDIO_OUT_Error_CallBack(void)
{
    printf("       DMA  ERROR     \n");
}
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版