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

基于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中文论坛活动

即日起开启活动话题入口,之后的活动统一都放在此处,欢迎大家的加入!


最新内容

相似分享

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