编程技术分享

分享编程知识,探讨技术创新

0%

简介:该MP3播放器基于ESP32 S3微控制器,制作了一款MP3播放器。

好的,作为一名高级嵌入式软件开发工程师,我将为您详细介绍基于ESP32-S3的MP3播放器项目的代码设计架构,并提供具体的C代码实现。这个项目将遵循可靠、高效、可扩展的设计原则,并采用经过实践验证的技术和方法。
关注微信公众号,提前获取相关推文

项目概述

本项目旨在开发一款基于ESP32-S3微控制器的开源MP3播放器,如图所示,它具有以下主要功能:

  • 音频播放: 支持MP3音频文件的播放,具有播放、暂停、停止、上一曲、下一曲等基本控制功能。
  • 文件浏览: 能够浏览SD卡或Flash存储中的音频文件。
  • 用户界面: 通过显示屏提供友好的用户界面,显示播放状态、歌曲信息等。
  • 音量控制: 提供音量调节功能。
  • 可扩展性: 系统架构应具有良好的可扩展性,方便未来添加新功能,如蓝牙音频输出、网络音乐播放等。

代码设计架构

为了实现可靠、高效、可扩展的系统,我们采用分层架构设计,将系统划分为以下几个主要层次:

  1. 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • 目的: 屏蔽底层硬件差异,为上层提供统一的硬件访问接口。
    • 组件:
      • GPIO 驱动: 控制LED、按键等GPIO设备。
      • SPI 驱动: 控制SD卡、显示屏等SPI设备。
      • I2C 驱动: 控制音频解码器、触摸屏等I2C设备。
      • I2S 驱动: 用于音频数据传输到音频解码器。
      • 定时器驱动: 提供系统定时和延时功能。
      • 存储驱动: 管理SD卡或Flash存储的访问。
      • 显示驱动: 控制LCD/TFT显示屏的显示。
      • 中断管理: 处理外部中断事件。
  2. 板级支持包 (BSP - Board Support Package):

    • 目的: 提供针对特定硬件平台的高级驱动和配置,简化硬件初始化和管理。
    • 组件:
      • 时钟配置: 初始化系统时钟。
      • 内存管理: 初始化内存分配器。
      • 外设初始化: 初始化SPI、I2C、I2S等外设。
      • 电源管理: 实现低功耗模式切换。
      • 特定板载设备驱动: 例如,如果使用了特定的音频编解码器芯片,则需要提供相应的驱动。
  3. 操作系统层 (OS Layer) / 实时操作系统 (RTOS):

    • 目的: 提供任务调度、资源管理、同步机制等,简化并发编程,提高系统实时性和响应性。
    • 组件:
      • 任务管理: 创建、删除、调度任务。
      • 线程同步: 互斥锁、信号量、事件组等。
      • 消息队列: 任务间通信。
      • 内存管理: 动态内存分配 (可选,如果 HAL 层没有提供)。
      • 定时器服务: 软件定时器。
    • 选择: 本项目推荐使用 FreeRTOS,它是一个流行的开源实时操作系统,在嵌入式领域广泛应用,ESP-IDF 框架也原生支持 FreeRTOS。
  4. 中间件层 (Middleware Layer):

    • 目的: 提供通用的软件组件和服务,简化应用开发。
    • 组件:
      • 文件系统: FATFS 文件系统,用于访问SD卡或Flash存储中的文件。
      • 音频解码库: libmad 或 minimp3,用于解码MP3音频数据。
      • 图形库 (GUI Library): 例如 LVGL 或 TFT_eSPI (简化版),用于构建用户界面。
      • 网络协议栈 (可选): 如果需要网络功能,例如 TCP/IP 协议栈 (lwIP)。
      • 加密库 (可选): 如果需要安全功能,例如 mbedTLS。
  5. 应用层 (Application Layer):

    • 目的: 实现MP3播放器的核心业务逻辑和用户交互。
    • 组件:
      • 用户界面逻辑: 处理用户输入,更新显示界面。
      • 音频播放控制: 实现播放、暂停、停止、音量调节等功能。
      • 文件浏览逻辑: 实现文件列表显示和文件选择。
      • 播放列表管理: (可选) 实现播放列表功能。
      • 系统配置管理: 保存和加载系统配置。

代码实现 (C语言)

为了满足3000行代码的要求,我们将详细展开每个层次的代码实现,并加入必要的注释和说明。请注意,以下代码是示例代码,可能需要根据具体的硬件平台和需求进行调整。为了清晰起见,代码将分模块展示。

1. 硬件抽象层 (HAL)

hal_gpio.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include <stdint.h>
#include <stdbool.h>

// GPIO 方向定义
typedef enum {
GPIO_DIRECTION_INPUT,
GPIO_DIRECTION_OUTPUT
} gpio_direction_t;

// GPIO 模式定义 (例如,上拉、下拉、浮空)
typedef enum {
GPIO_MODE_INPUT_PULLUP,
GPIO_MODE_INPUT_PULLDOWN,
GPIO_MODE_INPUT_FLOATING,
GPIO_MODE_OUTPUT_PUSH_PULL,
GPIO_MODE_OUTPUT_OPEN_DRAIN
} gpio_mode_t;

// 初始化 GPIO 引脚
void hal_gpio_init(uint32_t pin_num, gpio_direction_t direction, gpio_mode_t mode);

// 设置 GPIO 引脚输出电平
void hal_gpio_set_level(uint32_t pin_num, bool level);

// 读取 GPIO 引脚输入电平
bool hal_gpio_get_level(uint32_t pin_num);

// 注册 GPIO 中断处理函数
void hal_gpio_register_isr(uint32_t pin_num, void (*isr_handler)(void), uint32_t trigger_mode);

// 使能 GPIO 中断
void hal_gpio_enable_interrupt(uint32_t pin_num);

// 禁用 GPIO 中断
void hal_gpio_disable_interrupt(uint32_t pin_num);

#endif // HAL_GPIO_H

hal_gpio.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "hal_gpio.h"
#include "esp_err.h" // ESP-IDF 错误码
#include "driver/gpio.h" // ESP-IDF GPIO 驱动

void hal_gpio_init(uint32_t pin_num, gpio_direction_t direction, gpio_mode_t mode) {
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; // 默认禁用中断
io_conf.pin_bit_mask = (1ULL << pin_num);
io_conf.mode = (direction == GPIO_DIRECTION_OUTPUT) ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;

// 根据模式设置 GPIO 配置
if (mode == GPIO_MODE_INPUT_PULLUP) {
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 1;
} else if (mode == GPIO_MODE_INPUT_PULLDOWN) {
io_conf.pull_down_en = 1;
io_conf.pull_up_en = 0;
} else if (mode == GPIO_MODE_INPUT_FLOATING) {
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
} else if (mode == GPIO_MODE_OUTPUT_PUSH_PULL) {
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
} else if (mode == GPIO_MODE_OUTPUT_OPEN_DRAIN) {
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
}

esp_err_t ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
// 错误处理,例如打印错误信息
printf("GPIO init error: %d\n", ret);
}
}

void hal_gpio_set_level(uint32_t pin_num, bool level) {
gpio_set_level(pin_num, level);
}

bool hal_gpio_get_level(uint32_t pin_num) {
return gpio_get_level(pin_num);
}

void hal_gpio_register_isr(uint32_t pin_num, void (*isr_handler)(void), uint32_t trigger_mode) {
// TODO: 实现 GPIO 中断注册,需要根据 ESP-IDF 中断管理机制来实现
// 例如:gpio_isr_handler_add(), gpio_set_intr_type() 等
printf("GPIO ISR registration not implemented yet.\n");
}

void hal_gpio_enable_interrupt(uint32_t pin_num) {
// TODO: 使能 GPIO 中断
printf("GPIO interrupt enable not implemented yet.\n");
}

void hal_gpio_disable_interrupt(uint32_t pin_num) {
// TODO: 禁用 GPIO 中断
printf("GPIO interrupt disable not implemented yet.\n");
}

hal_spi.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#ifndef HAL_SPI_H
#define HAL_SPI_H

#include <stdint.h>
#include <stdbool.h>

// SPI 设备句柄
typedef void* hal_spi_handle_t;

// SPI 配置结构体
typedef struct {
uint32_t clock_speed_hz; // SPI 时钟频率
uint32_t miso_pin; // MISO 引脚
uint32_t mosi_pin; // MOSI 引脚
uint32_t sclk_pin; // SCLK 引脚
uint32_t cs_pin; // CS (片选) 引脚
uint32_t dma_channel; // DMA 通道 (可选)
// ... 其他 SPI 配置参数
} hal_spi_config_t;

// 初始化 SPI 设备
hal_spi_handle_t hal_spi_init(const hal_spi_config_t *config);

// 发送数据并接收数据 (全双工)
bool hal_spi_transfer(hal_spi_handle_t handle, const uint8_t *tx_data, uint8_t *rx_data, uint32_t length);

// 发送数据 (半双工)
bool hal_spi_send(hal_spi_handle_t handle, const uint8_t *tx_data, uint32_t length);

// 接收数据 (半双工)
bool hal_spi_receive(hal_spi_handle_t handle, uint8_t *rx_data, uint32_t length);

// 释放 SPI 设备资源
void hal_spi_deinit(hal_spi_handle_t handle);

#endif // HAL_SPI_H

hal_spi.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include "hal_spi.h"
#include "esp_err.h"
#include "driver/spi_master.h" // ESP-IDF SPI 驱动

hal_spi_handle_t hal_spi_init(const hal_spi_config_t *config) {
spi_bus_config_t buscfg = {
.miso_io_num = config->miso_pin,
.mosi_io_num = config->mosi_pin,
.sclk_io_num = config->sclk_pin,
.quadwp_io_num = -1, // 禁用四线 SPI
.quadhd_io_num = -1,
.max_transfer_sz = 4096, // 最大传输大小
};
spi_device_interface_config_t devcfg = {
.clock_speed_hz = config->clock_speed_hz,
.mode = 0, // SPI 模式 0
.spics_io_num = config->cs_pin,
.queue_size = 7, // 传输队列大小
// .pre_cb = ..., // 可选的预传输回调函数
// .post_cb = ..., // 可选的后传输回调函数
};
spi_handle_t spi_handle;

esp_err_t ret = spi_bus_initialize(SPI2_HOST, &buscfg, config->dma_channel); // 使用 SPI2 主机
if (ret != ESP_OK) {
printf("SPI bus init error: %d\n", ret);
return NULL;
}

ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi_handle);
if (ret != ESP_OK) {
printf("SPI device add error: %d\n", ret);
spi_bus_free(SPI2_HOST); // 释放 SPI 总线资源
return NULL;
}

return (hal_spi_handle_t)spi_handle;
}

bool hal_spi_transfer(hal_spi_handle_t handle, const uint8_t *tx_data, uint8_t *rx_data, uint32_t length) {
spi_transaction_t trans;
memset(&trans, 0, sizeof(trans)); // 清零事务结构体
trans.length = length * 8; // 传输长度 (bit)
trans.tx_buffer = tx_data;
trans.rx_buffer = rx_data;

esp_err_t ret = spi_device_transmit((spi_device_handle_t)handle, &trans);
if (ret != ESP_OK) {
printf("SPI transfer error: %d\n", ret);
return false;
}
return true;
}

bool hal_spi_send(hal_spi_handle_t handle, const uint8_t *tx_data, uint32_t length) {
spi_transaction_t trans;
memset(&trans, 0, sizeof(trans));
trans.length = length * 8;
trans.tx_buffer = tx_data;

esp_err_t ret = spi_device_transmit((spi_device_handle_t)handle, &trans);
if (ret != ESP_OK) {
printf("SPI send error: %d\n", ret);
return false;
}
return true;
}

bool hal_spi_receive(hal_spi_handle_t handle, uint8_t *rx_data, uint32_t length) {
spi_transaction_t trans;
memset(&trans, 0, sizeof(trans));
trans.length = length * 8;
trans.rx_buffer = rx_data;

esp_err_t ret = spi_device_transmit((spi_device_handle_t)handle, &trans); // 使用 transmit 进行接收
if (ret != ESP_OK) {
printf("SPI receive error: %d\n", ret);
return false;
}
return true;
}

void hal_spi_deinit(hal_spi_handle_t handle) {
spi_bus_remove_device((spi_device_handle_t)handle);
spi_bus_free(SPI2_HOST); // 释放 SPI 总线资源
}

hal_i2c.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#ifndef HAL_I2C_H
#define HAL_I2C_H

#include <stdint.h>
#include <stdbool.h>

// I2C 设备句柄
typedef void* hal_i2c_handle_t;

// I2C 配置结构体
typedef struct {
uint32_t clock_speed_hz; // I2C 时钟频率
uint32_t sda_pin; // SDA 引脚
uint32_t scl_pin; // SCL 引脚
// ... 其他 I2C 配置参数
} hal_i2c_config_t;

// 初始化 I2C 设备
hal_i2c_handle_t hal_i2c_init(const hal_i2c_config_t *config);

// I2C 写数据
bool hal_i2c_write(hal_i2c_handle_t handle, uint8_t device_addr, const uint8_t *data, uint32_t length);

// I2C 读数据
bool hal_i2c_read(hal_i2c_handle_t handle, uint8_t device_addr, uint8_t *data, uint32_t length);

// 释放 I2C 设备资源
void hal_i2c_deinit(hal_i2c_handle_t handle);

#endif // HAL_I2C_H

hal_i2c.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "hal_i2c.h"
#include "esp_err.h"
#include "driver/i2c.h" // ESP-IDF I2C 驱动

hal_i2c_handle_t hal_i2c_init(const hal_i2c_config_t *config) {
i2c_config_t conf;
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = config->sda_pin;
conf.scl_io_num = config->scl_pin;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = config->clock_speed_hz;
conf.clk_flags = 0; // 默认时钟标志

esp_err_t ret = i2c_param_config(I2C_NUM_0, &conf); // 使用 I2C 端口 0
if (ret != ESP_OK) {
printf("I2C param config error: %d\n", ret);
return NULL;
}

ret = i2c_driver_install(I2C_NUM_0, conf.mode, 0, 0, 0);
if (ret != ESP_OK) {
printf("I2C driver install error: %d\n", ret);
return NULL;
}

return (hal_i2c_handle_t)I2C_NUM_0; // 返回 I2C 端口号作为句柄
}

bool hal_i2c_write(hal_i2c_handle_t handle, uint8_t device_addr, const uint8_t *data, uint32_t length) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (device_addr << 1) | I2C_MASTER_WRITE, true); // 设备地址 + 写标志
i2c_master_write(cmd, data, length, true);
i2c_master_stop(cmd);

esp_err_t ret = i2c_master_cmd_begin((i2c_port_t)handle, cmd, pdMS_TO_TICKS(1000)); // 超时 1 秒
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
printf("I2C write error: %d\n", ret);
return false;
}
return true;
}

bool hal_i2c_read(hal_i2c_handle_t handle, uint8_t device_addr, uint8_t *data, uint32_t length) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (device_addr << 1) | I2C_MASTER_READ, true); // 设备地址 + 读标志
i2c_master_read(cmd, data, length, I2C_MASTER_LAST_NACK); // 最后一个字节发送 NACK
i2c_master_stop(cmd);

esp_err_t ret = i2c_master_cmd_begin((i2c_port_t)handle, cmd, pdMS_TO_TICKS(1000)); // 超时 1 秒
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
printf("I2C read error: %d\n", ret);
return false;
}
return true;
}

void hal_i2c_deinit(hal_i2c_handle_t handle) {
i2c_driver_delete((i2c_port_t)handle);
}

hal_i2s.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifndef HAL_I2S_H
#define HAL_I2S_H

#include <stdint.h>
#include <stdbool.h>

// I2S 设备句柄
typedef void* hal_i2s_handle_t;

// I2S 配置结构体
typedef struct {
uint32_t sample_rate_hz; // 采样率
uint32_t bits_per_sample; // 位深度 (例如 16 位)
uint32_t mclk_pin; // MCLK 引脚 (可选)
uint32_t bclk_pin; // BCLK 引脚
uint32_t ws_pin; // WS (LRCLK) 引脚
uint32_t data_out_pin; // 数据输出引脚
uint32_t dma_channel; // DMA 通道 (可选)
// ... 其他 I2S 配置参数
} hal_i2s_config_t;

// 初始化 I2S 设备
hal_i2s_handle_t hal_i2s_init(const hal_i2s_config_t *config);

// 发送音频数据
bool hal_i2s_send_data(hal_i2s_handle_t handle, const uint8_t *data, uint32_t length_bytes);

// 释放 I2S 设备资源
void hal_i2s_deinit(hal_i2s_handle_t handle);

#endif // HAL_I2S_H

hal_i2s.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "hal_i2s.h"
#include "esp_err.h"
#include "driver/i2s.h" // ESP-IDF I2S 驱动

hal_i2s_handle_t hal_i2s_init(const hal_i2s_config_t *config) {
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX, // 发射模式
.sample_rate = config->sample_rate_hz,
.bits_per_sample = config->bits_per_sample,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 立体声
.communication_format = I2S_COMM_FORMAT_I2S_MSB, // I2S 标准格式
.dma_buf_count = 8, // DMA 缓冲区数量
.dma_buf_len = 1024, // DMA 缓冲区长度
.use_apll = false, // 不使用 APLL 时钟 (可以使用外部 MCLK)
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // 中断优先级
};
i2s_pin_config_t pin_config = {
.bck_io_num = config->bclk_pin,
.ws_io_num = config->ws_pin,
.data_out_num = config->data_out_pin,
.data_in_num = I2S_PIN_NO_CHANGE, // 接收引脚不使用
.mck_io_num = config->mclk_pin, // MCLK 引脚
};

esp_err_t ret = i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); // 使用 I2S 端口 0
if (ret != ESP_OK) {
printf("I2S driver install error: %d\n", ret);
return NULL;
}

ret = i2s_pin_config_set(I2S_NUM_0, &pin_config);
if (ret != ESP_OK) {
printf("I2S pin config error: %d\n", ret);
i2s_driver_uninstall(I2S_NUM_0); // 释放 I2S 驱动资源
return NULL;
}

return (hal_i2s_handle_t)I2S_NUM_0; // 返回 I2S 端口号作为句柄
}

bool hal_i2s_send_data(hal_i2s_handle_t handle, const uint8_t *data, uint32_t length_bytes) {
size_t bytes_written;
esp_err_t ret = i2s_write((i2s_port_t)handle, data, length_bytes, &bytes_written, portMAX_DELAY); // 阻塞式发送
if (ret != ESP_OK) {
printf("I2S write data error: %d\n", ret);
return false;
}
if (bytes_written != length_bytes) {
printf("I2S write data incomplete: written %zu, expected %u\n", bytes_written, length_bytes);
return false;
}
return true;
}

void hal_i2s_deinit(hal_i2s_handle_t handle) {
i2s_driver_uninstall((i2s_port_t)handle);
}

2. 板级支持包 (BSP)

bsp_board.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#ifndef BSP_BOARD_H
#define BSP_BOARD_H

#include "hal_gpio.h"
#include "hal_spi.h"
#include "hal_i2c.h"
#include "hal_i2s.h"

// 定义板载硬件引脚
#define PIN_BUTTON_PLAY_PAUSE GPIO_NUM_0
#define PIN_BUTTON_NEXT GPIO_NUM_1
#define PIN_BUTTON_PREV GPIO_NUM_2
#define PIN_BUTTON_VOL_UP GPIO_NUM_3
#define PIN_BUTTON_VOL_DOWN GPIO_NUM_4

#define PIN_SD_CS GPIO_NUM_5
#define PIN_SD_MOSI GPIO_NUM_6
#define PIN_SD_MISO GPIO_NUM_7
#define PIN_SD_SCLK GPIO_NUM_8

#define PIN_LCD_CS GPIO_NUM_9
#define PIN_LCD_DC GPIO_NUM_10
#define PIN_LCD_RST GPIO_NUM_11
#define PIN_LCD_MOSI GPIO_NUM_12
#define PIN_LCD_MISO GPIO_NUM_13
#define PIN_LCD_SCLK GPIO_NUM_14

#define PIN_AUDIO_CODEC_SDA GPIO_NUM_15
#define PIN_AUDIO_CODEC_SCL GPIO_NUM_16

#define PIN_I2S_BCLK GPIO_NUM_17
#define PIN_I2S_WS GPIO_NUM_18
#define PIN_I2S_DOUT GPIO_NUM_19
#define PIN_I2S_MCLK GPIO_NUM_20 // 可选,如果使用外部 MCLK

// 初始化板级硬件
void bsp_board_init(void);

// 初始化按键
void bsp_button_init(void);

// 读取按键状态
bool bsp_button_get_state(uint32_t button_pin);

// 初始化 SD 卡
bool bsp_sdcard_init(void);

// 初始化 LCD 显示屏
bool bsp_lcd_init(void);

// 初始化音频编解码器
bool bsp_audio_codec_init(void);

// 初始化 I2S 音频输出
bool bsp_audio_output_init(void);

#endif // BSP_BOARD_H

bsp_board.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include "bsp_board.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

void bsp_board_init(void) {
// 初始化系统时钟、内存等 (ESP-IDF 框架会自动处理大部分初始化)
printf("Board initialization...\n");
// 在这里可以添加一些板级特定的初始化代码
}

void bsp_button_init(void) {
hal_gpio_init(PIN_BUTTON_PLAY_PAUSE, GPIO_DIRECTION_INPUT, GPIO_MODE_INPUT_PULLUP);
hal_gpio_init(PIN_BUTTON_NEXT, GPIO_DIRECTION_INPUT, GPIO_MODE_INPUT_PULLUP);
hal_gpio_init(PIN_BUTTON_PREV, GPIO_DIRECTION_INPUT, GPIO_MODE_INPUT_PULLUP);
hal_gpio_init(PIN_BUTTON_VOL_UP, GPIO_DIRECTION_INPUT, GPIO_MODE_INPUT_PULLUP);
hal_gpio_init(PIN_BUTTON_VOL_DOWN, GPIO_DIRECTION_INPUT, GPIO_MODE_INPUT_PULLUP);
printf("Buttons initialized.\n");
}

bool bsp_button_get_state(uint32_t button_pin) {
return !hal_gpio_get_level(button_pin); // 按键按下通常为低电平 (PULLUP)
}

bool bsp_sdcard_init(void) {
hal_spi_config_t spi_config = {
.clock_speed_hz = 20 * 1000 * 1000, // 20MHz SPI clock
.miso_pin = PIN_SD_MISO,
.mosi_pin = PIN_SD_MOSI,
.sclk_pin = PIN_SD_SCLK,
.cs_pin = PIN_SD_CS,
.dma_channel = DMA_CHAN_AUTO,
};
hal_spi_handle_t spi_handle = hal_spi_init(&spi_config);
if (spi_handle == NULL) {
printf("SD card SPI init failed.\n");
return false;
}
// TODO: 初始化 SD 卡驱动 (例如使用 SDMMC 或 SPI 模式 FATFS)
printf("SD card initialized (SPI).\n");
return true;
}

bool bsp_lcd_init(void) {
hal_spi_config_t spi_config = {
.clock_speed_hz = 40 * 1000 * 1000, // 40MHz SPI clock
.miso_pin = PIN_LCD_MISO,
.mosi_pin = PIN_LCD_MOSI,
.sclk_pin = PIN_LCD_SCLK,
.cs_pin = PIN_LCD_CS,
.dma_channel = DMA_CHAN_AUTO,
};
hal_spi_handle_t spi_handle = hal_spi_init(&spi_config);
if (spi_handle == NULL) {
printf("LCD SPI init failed.\n");
return false;
}
hal_gpio_init(PIN_LCD_DC, GPIO_DIRECTION_OUTPUT, GPIO_MODE_OUTPUT_PUSH_PULL);
hal_gpio_init(PIN_LCD_RST, GPIO_DIRECTION_OUTPUT, GPIO_MODE_OUTPUT_PUSH_PULL);
// TODO: 初始化 LCD 驱动 (例如 ST7789 驱动)
printf("LCD initialized (SPI).\n");
return true;
}

bool bsp_audio_codec_init(void) {
hal_i2c_config_t i2c_config = {
.clock_speed_hz = 100 * 1000, // 100kHz I2C clock
.sda_pin = PIN_AUDIO_CODEC_SDA,
.scl_pin = PIN_AUDIO_CODEC_SCL,
};
hal_i2c_handle_t i2c_handle = hal_i2c_init(&i2c_config);
if (i2c_handle == NULL) {
printf("Audio codec I2C init failed.\n");
return false;
}
// TODO: 初始化音频编解码器芯片 (例如通过 I2C 配置寄存器)
printf("Audio codec initialized (I2C).\n");
return true;
}

bool bsp_audio_output_init(void) {
hal_i2s_config_t i2s_config = {
.sample_rate_hz = 44100, // 默认采样率
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.mclk_pin = PIN_I2S_MCLK,
.bclk_pin = PIN_I2S_BCLK,
.ws_pin = PIN_I2S_WS,
.data_out_pin = PIN_I2S_DOUT,
.dma_channel = DMA_CHAN_AUTO,
};
hal_i2s_handle_t i2s_handle = hal_i2s_init(&i2s_config);
if (i2s_handle == NULL) {
printf("I2S audio output init failed.\n");
return false;
}
printf("I2S audio output initialized.\n");
return true;
}

3. 操作系统层 (OS Layer) - FreeRTOS

由于 ESP-IDF 框架已经集成了 FreeRTOS,我们不需要手动编写 OS 层的代码,只需要利用 FreeRTOS 提供的 API 进行任务创建、同步等操作。

4. 中间件层 (Middleware)

middleware_fs.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef MIDDLEWARE_FS_H
#define MIDDLEWARE_FS_H

#include <stdint.h>
#include <stdbool.h>

// 文件系统初始化
bool middleware_fs_init(void);

// 打开文件
void* middleware_fs_open_file(const char *path, const char *mode);

// 读取文件
uint32_t middleware_fs_read_file(void *file_handle, void *buffer, uint32_t size);

// 关闭文件
bool middleware_fs_close_file(void *file_handle);

// 获取文件大小
uint32_t middleware_fs_get_file_size(const char *path);

// 扫描目录
bool middleware_fs_scan_dir(const char *path, void (*callback)(const char *filename));

#endif // MIDDLEWARE_FS_H

middleware_fs.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include "middleware_fs.h"
#include "ff.h" // FATFS 头文件
#include "diskio.h" // FATFS 磁盘 I/O 头文件
#include "string.h"
#include "esp_vfs_fatfs.h" // ESP-IDF FATFS VFS 组件

#define MOUNT_POINT "/sdcard" // SD 卡挂载点

bool middleware_fs_init(void) {
esp_vfs_fatfs_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false, // 如果挂载失败是否格式化
.max_files = 5, // 最大打开文件数
.allocation_unit_size = 16 * 1024, // 分配单元大小
};
sdmmc_card_t* card;
const char mount_point[] = MOUNT_POINT;
ESP_LOGI("FS", "Initializing SD card");

esp_err_t ret = esp_vfs_fatfs_register_sdmmc("/sdcard", &host, &mount_config, &card); // 使用 SDMMC 主机
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE("FS", "Failed to mount filesystem.");
} else {
ESP_LOGE("FS", "Failed to initialize the card (%s)", esp_err_to_name(ret));
}
return false;
}
ESP_LOGI("FS", "SD card mounted, speed=%lu MHz, size=%lluMB",
card->max_freq_khz / 1000, card->csd.capacity / (1024 * 1024));
return true;
}

void* middleware_fs_open_file(const char *path, const char *mode) {
char full_path[256];
sprintf(full_path, "%s/%s", MOUNT_POINT, path);
FIL *fp = (FIL *)malloc(sizeof(FIL));
if (fp == NULL) {
ESP_LOGE("FS", "Failed to allocate FIL structure");
return NULL;
}
FRESULT res = f_open(fp, full_path, FA_READ); // 只读模式
if (res != FR_OK) {
ESP_LOGE("FS", "Failed to open file %s, error: %d", full_path, res);
free(fp);
return NULL;
}
return (void*)fp;
}

uint32_t middleware_fs_read_file(void *file_handle, void *buffer, uint32_t size) {
FIL *fp = (FIL *)file_handle;
UINT bytes_read;
FRESULT res = f_read(fp, buffer, size, &bytes_read);
if (res != FR_OK) {
ESP_LOGE("FS", "File read error: %d", res);
return 0;
}
return bytes_read;
}

bool middleware_fs_close_file(void *file_handle) {
FIL *fp = (FIL *)file_handle;
FRESULT res = f_close(fp);
free(fp);
if (res != FR_OK) {
ESP_LOGE("FS", "File close error: %d", res);
return false;
}
return true;
}

uint32_t middleware_fs_get_file_size(const char *path) {
char full_path[256];
sprintf(full_path, "%s/%s", MOUNT_POINT, path);
FILINFO fno;
FRESULT res = f_stat(full_path, &fno);
if (res != FR_OK) {
ESP_LOGE("FS", "Failed to get file size for %s, error: %d", full_path, res);
return 0;
}
return fno.fsize;
}

bool middleware_fs_scan_dir(const char *path, void (*callback)(const char *filename)) {
char full_path[256];
sprintf(full_path, "%s/%s", MOUNT_POINT, path);
DIR dir;
FILINFO fno;
FRESULT res;

res = f_opendir(&dir, full_path); /* 打开目录 */
if (res == FR_OK) {
while (true) {
res = f_readdir(&dir, &fno); /* 读取目录下的条目 */
if (res != FR_OK || fno.fname[0] == 0) break; /* 出错或目录结束则退出 */
if (fno.fattrib & AM_DIR) { /* 是目录 */
// ESP_LOGI("FS", "DIR %s", fno.fname); // 可以选择处理子目录
} else { /* 是文件 */
// ESP_LOGI("FS", "FILE %s", fno.fname);
callback(fno.fname); // 调用回调函数处理文件名
}
}
f_closedir(&dir);
} else {
ESP_LOGE("FS", "Failed to open directory %s, error: %d", full_path, res);
return false;
}
return true;
}

middleware_audio_decoder.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef MIDDLEWARE_AUDIO_DECODER_H
#define MIDDLEWARE_AUDIO_DECODER_H

#include <stdint.h>
#include <stdbool.h>

// 音频解码器句柄
typedef void* middleware_audio_decoder_handle_t;

// 初始化音频解码器
middleware_audio_decoder_handle_t middleware_audio_decoder_init(void);

// 解码音频数据
uint32_t middleware_audio_decoder_decode(middleware_audio_decoder_handle_t handle, const uint8_t *input_data, uint32_t input_size, int16_t *output_buffer, uint32_t output_buffer_size);

// 释放音频解码器资源
void middleware_audio_decoder_deinit(middleware_audio_decoder_handle_t handle);

#endif // MIDDLEWARE_AUDIO_DECODER_H

middleware_audio_decoder.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "middleware_audio_decoder.h"
#include "minimp3.h" // minimp3 头文件
#include "stdlib.h"
#include "string.h"

typedef struct {
mp3dec_t mp3d; // minimp3 解码器实例
} decoder_context_t;

middleware_audio_decoder_handle_t middleware_audio_decoder_init(void) {
decoder_context_t *ctx = (decoder_context_t *)malloc(sizeof(decoder_context_t));
if (ctx == NULL) {
ESP_LOGE("DECODER", "Failed to allocate decoder context");
return NULL;
}
mp3dec_init(&ctx->mp3d); // 初始化 minimp3 解码器
return (middleware_audio_decoder_handle_t)ctx;
}

uint32_t middleware_audio_decoder_decode(middleware_audio_decoder_handle_t handle, const uint8_t *input_data, uint32_t input_size, int16_t *output_buffer, uint32_t output_buffer_size) {
decoder_context_t *ctx = (decoder_context_t *)handle;
int frame_size;
int samples;
int bytes_decoded = 0;
uint8_t *current_input = (uint8_t *)input_data;
int16_t *current_output = output_buffer;
uint32_t remaining_output_bytes = output_buffer_size;

while (bytes_decoded < input_size && remaining_output_bytes >= MINIMP3_MAX_SAMPLES_PER_FRAME * 2 * 2) { // 保证输出缓冲区足够
frame_size = mp3dec_decode_frame(&ctx->mp3d, current_input, input_size - bytes_decoded, current_output, &samples);
if (frame_size == 0) { // 没有解码到帧,可能文件结束或错误
break;
}
bytes_decoded += frame_size;
current_input += frame_size;
current_output += samples * 2; // 立体声,每个 sample 2 个 int16_t
remaining_output_bytes -= samples * 2 * 2;
}
return (output_buffer_size - remaining_output_bytes); // 返回实际解码的字节数 (输出缓冲区使用的字节数)
}

void middleware_audio_decoder_deinit(middleware_audio_decoder_handle_t handle) {
decoder_context_t *ctx = (decoder_context_t *)handle;
if (ctx != NULL) {
free(ctx);
}
}

middleware_gui.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#ifndef MIDDLEWARE_GUI_H
#define MIDDLEWARE_GUI_H

#include <stdint.h>
#include <stdbool.h>

// GUI 初始化
bool middleware_gui_init(void);

// 清屏
void middleware_gui_clear_screen(uint16_t color);

// 画点
void middleware_gui_draw_pixel(int16_t x, int16_t y, uint16_t color);

// 画线
void middleware_gui_draw_line(int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color);

// 画矩形
void middleware_gui_draw_rect(int16_t x, int16_t y, uint16_t width, uint16_t height, uint16_t color);

// 填充矩形
void middleware_gui_fill_rect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t color);

// 显示字符
void middleware_gui_draw_char(int16_t x, int16_t y, char chr, uint16_t color, uint16_t bgcolor, uint8_t size);

// 显示字符串
void middleware_gui_draw_string(int16_t x, int16_t y, const char *str, uint16_t color, uint16_t bgcolor, uint8_t size);

// 设置背景颜色
void middleware_gui_set_background_color(uint16_t color);

// 设置前景色
void middleware_gui_set_foreground_color(uint16_t color);

// ... 其他 GUI 功能函数 (例如画圆、显示图片等)

#endif // MIDDLEWARE_GUI_H

middleware_gui.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "middleware_gui.h"
#include "st7789.h" // 假设使用 ST7789 LCD 驱动
#include "string.h"

bool middleware_gui_init(void) {
st7789_init(); // 初始化 ST7789 LCD
middleware_gui_clear_screen(ST7789_BLACK); // 默认黑色背景
return true;
}

void middleware_gui_clear_screen(uint16_t color) {
st7789_fill_color(color);
}

void middleware_gui_draw_pixel(int16_t x, int16_t y, uint16_t color) {
st7789_draw_pixel(x, y, color);
}

void middleware_gui_draw_line(int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color) {
st7789_draw_line(x1, y1, x2, y2, color);
}

void middleware_gui_draw_rect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t color) {
st7789_draw_rect(x, y, width, height, color);
}

void middleware_gui_fill_rect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t color) {
st7789_fill_rect(x, y, width, height, color);
}

void middleware_gui_draw_char(int16_t x, int16_t y, char chr, uint16_t color, uint16_t bgcolor, uint8_t size) {
st7789_draw_char(x, y, chr, color, bgcolor, size);
}

void middleware_gui_draw_string(int16_t x, int16_t y, const char *str, uint16_t color, uint16_t bgcolor, uint8_t size) {
st7789_drawString(x, y, (char *)str, color, bgcolor, size); // 注意类型转换
}

void middleware_gui_set_background_color(uint16_t color) {
// ST7789 驱动中可能没有直接设置背景色的函数,可以记录下来,在清屏时使用
// 或在填充矩形时使用
}

void middleware_gui_set_foreground_color(uint16_t color) {
// ST7789 驱动中可能没有直接设置前景色函数,直接在绘图函数中使用颜色参数
}

5. 应用层 (Application)

app_mp3_player.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef APP_MP3_PLAYER_H
#define APP_MP3_PLAYER_H

#include <stdint.h>
#include <stdbool.h>

// MP3 播放器应用初始化
bool app_mp3_player_init(void);

// MP3 播放器主循环
void app_mp3_player_run(void);

#endif // APP_MP3_PLAYER_H

app_mp3_player.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#include "app_mp3_player.h"
#include "bsp_board.h"
#include "middleware_fs.h"
#include "middleware_audio_decoder.h"
#include "middleware_gui.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "string.h"

#define AUDIO_BUFFER_SIZE (1024 * 4) // 音频缓冲区大小
#define DECODE_BUFFER_SIZE (1024 * 2) // 解码缓冲区大小

typedef enum {
PLAYER_STATE_STOPPED,
PLAYER_STATE_PLAYING,
PLAYER_STATE_PAUSED
} player_state_t;

typedef struct {
player_state_t state;
char current_file[256];
uint32_t current_volume; // 音量值 (0-100)
middleware_audio_decoder_handle_t decoder_handle;
hal_i2s_handle_t i2s_handle;
void *file_handle;
} mp3_player_context_t;

static mp3_player_context_t player_ctx;

bool app_mp3_player_init(void) {
bsp_board_init();
bsp_button_init();
if (!bsp_sdcard_init()) {
ESP_LOGE("APP", "SD card init failed.");
return false;
}
if (!bsp_lcd_init()) {
ESP_LOGE("APP", "LCD init failed.");
return false;
}
if (!bsp_audio_codec_init()) {
ESP_LOGE("APP", "Audio codec init failed.");
return false;
}
if (!bsp_audio_output_init()) {
ESP_LOGE("APP", "Audio output init failed.");
return false;
}
if (!middleware_fs_init()) {
ESP_LOGE("APP", "File system init failed.");
return false;
}
if (!middleware_gui_init()) {
ESP_LOGE("APP", "GUI init failed.");
return false;
}

player_ctx.state = PLAYER_STATE_STOPPED;
player_ctx.current_volume = 50; // 默认音量
player_ctx.decoder_handle = middleware_audio_decoder_init();
if (player_ctx.decoder_handle == NULL) {
ESP_LOGE("APP", "Audio decoder init failed.");
return false;
}
player_ctx.i2s_handle = hal_i2s_init(NULL); // I2S handle already initialized in bsp_audio_output_init
if (player_ctx.i2s_handle == NULL) {
ESP_LOGE("APP", "I2S init failed.");
return false;
}
player_ctx.file_handle = NULL;
memset(player_ctx.current_file, 0, sizeof(player_ctx.current_file));

middleware_gui_clear_screen(ST7789_BLUE);
middleware_gui_draw_string(10, 20, "MP3 Player", ST7789_WHITE, ST7789_BLUE, 2);
middleware_gui_draw_string(10, 50, "Initializing...", ST7789_WHITE, ST7789_BLUE, 1);

return true;
}

// 音频播放任务
static void audio_play_task(void *pvParameters) {
uint8_t *audio_buffer = (uint8_t *)malloc(AUDIO_BUFFER_SIZE);
int16_t *decode_buffer = (int16_t *)malloc(DECODE_BUFFER_SIZE);
if (audio_buffer == NULL || decode_buffer == NULL) {
ESP_LOGE("APP", "Failed to allocate audio buffers.");
vTaskDelete(NULL);
return;
}

while (1) {
if (player_ctx.state == PLAYER_STATE_PLAYING && player_ctx.file_handle != NULL) {
uint32_t bytes_read = middleware_fs_read_file(player_ctx.file_handle, audio_buffer, AUDIO_BUFFER_SIZE);
if (bytes_read > 0) {
uint32_t decoded_bytes = middleware_audio_decoder_decode(player_ctx.decoder_handle, audio_buffer, bytes_read, decode_buffer, DECODE_BUFFER_SIZE);
if (decoded_bytes > 0) {
hal_i2s_send_data(player_ctx.i2s_handle, (uint8_t *)decode_buffer, decoded_bytes);
}
} else {
// 文件播放结束
player_ctx.state = PLAYER_STATE_STOPPED;
middleware_fs_close_file(player_ctx.file_handle);
player_ctx.file_handle = NULL;
ESP_LOGI("APP", "File playback finished.");
middleware_gui_draw_string(10, 50, "Playback finished.", ST7789_WHITE, ST7789_BLUE, 1);
}
} else {
vTaskDelay(pdMS_TO_TICKS(100)); // 非播放状态,降低 CPU 占用
}
}
free(audio_buffer);
free(decode_buffer);
vTaskDelete(NULL);
}

// 用户界面任务
static void ui_task(void *pvParameters) {
char file_list[10][32]; // 假设最多显示 10 个文件名,每个文件名 32 字节
int file_count = 0;
int selected_file_index = 0;

// 文件列表扫描回调函数
void file_list_callback(const char *filename) {
if (strstr(filename, ".mp3") != NULL || strstr(filename, ".MP3") != NULL) { // 只显示 MP3 文件
if (file_count < 10) {
strncpy(file_list[file_count], filename, 31);
file_list[file_count][31] = '\0'; // 确保字符串结尾
file_count++;
}
}
}

middleware_fs_scan_dir("/", file_list_callback); // 扫描 SD 卡根目录

while (1) {
middleware_gui_clear_screen(ST7789_BLUE);
middleware_gui_draw_string(10, 20, "MP3 Player", ST7789_WHITE, ST7789_BLUE, 2);

middleware_gui_draw_string(10, 40, "Files:", ST7789_WHITE, ST7789_BLUE, 1);
for (int i = 0; i < file_count; i++) {
uint16_t text_color = (i == selected_file_index) ? ST7789_YELLOW : ST7789_WHITE; // 选中文件高亮显示
middleware_gui_draw_string(20, 60 + i * 15, file_list[i], text_color, ST7789_BLUE, 1);
}

// 按键处理
if (bsp_button_get_state(PIN_BUTTON_PLAY_PAUSE)) {
vTaskDelay(pdMS_TO_TICKS(200)); // 按钮消抖
if (player_ctx.state == PLAYER_STATE_PLAYING) {
player_ctx.state = PLAYER_STATE_PAUSED;
ESP_LOGI("APP", "Playback paused.");
middleware_gui_draw_string(10, 50, "Playback paused.", ST7789_WHITE, ST7789_BLUE, 1);
} else if (player_ctx.state == PLAYER_STATE_PAUSED) {
player_ctx.state = PLAYER_STATE_PLAYING;
ESP_LOGI("APP", "Playback resumed.");
middleware_gui_draw_string(10, 50, "Playback resumed.", ST7789_WHITE, ST7789_BLUE, 1);
} else if (player_ctx.state == PLAYER_STATE_STOPPED) {
// 开始播放选中的文件
if (file_count > 0) {
if (player_ctx.file_handle != NULL) {
middleware_fs_close_file(player_ctx.file_handle); // 关闭之前的文件
}
sprintf(player_ctx.current_file, "%s", file_list[selected_file_index]);
player_ctx.file_handle = middleware_fs_open_file(player_ctx.current_file, "r");
if (player_ctx.file_handle != NULL) {
player_ctx.state = PLAYER_STATE_PLAYING;
ESP_LOGI("APP", "Start playing: %s", player_ctx.current_file);
middleware_gui_draw_string(10, 50, "Playing: ", ST7789_WHITE, ST7789_BLUE, 1);
middleware_gui_draw_string(70, 50, player_ctx.current_file, ST7789_YELLOW, ST7789_BLUE, 1);
} else {
ESP_LOGE("APP", "Failed to open file: %s", player_ctx.current_file);
middleware_gui_draw_string(10, 50, "File open error.", ST7789_WHITE, ST7789_BLUE, 1);
}
}
}
}

if (bsp_button_get_state(PIN_BUTTON_NEXT)) {
vTaskDelay(pdMS_TO_TICKS(200));
selected_file_index++;
if (selected_file_index >= file_count) {
selected_file_index = 0;
}
}

if (bsp_button_get_state(PIN_BUTTON_PREV)) {
vTaskDelay(pdMS_TO_TICKS(200));
selected_file_index--;
if (selected_file_index < 0) {
selected_file_index = file_count - 1;
}
}

if (bsp_button_get_state(PIN_BUTTON_VOL_UP)) {
vTaskDelay(pdMS_TO_TICKS(200));
player_ctx.current_volume += 5;
if (player_ctx.current_volume > 100) {
player_ctx.current_volume = 100;
}
ESP_LOGI("APP", "Volume up: %d", player_ctx.current_volume);
// TODO: 设置音量 (需要音频编解码器驱动支持)
}

if (bsp_button_get_state(PIN_BUTTON_VOL_DOWN)) {
vTaskDelay(pdMS_TO_TICKS(200));
player_ctx.current_volume -= 5;
if (player_ctx.current_volume < 0) {
player_ctx.current_volume = 0;
}
ESP_LOGI("APP", "Volume down: %d", player_ctx.current_volume);
// TODO: 设置音量 (需要音频编解码器驱动支持)
}

vTaskDelay(pdMS_TO_TICKS(50)); // UI 刷新频率
}
vTaskDelete(NULL);
}


void app_mp3_player_run(void) {
if (app_mp3_player_init()) {
ESP_LOGI("APP", "MP3 Player initialized successfully.");
xTaskCreatePinnedToCore(audio_play_task, "audio_play_task", 4096, NULL, 2, NULL, APP_CPU_NUM); // 创建音频播放任务
xTaskCreatePinnedToCore(ui_task, "ui_task", 4096, NULL, 3, NULL, PRO_CPU_NUM); // 创建 UI 任务
} else {
ESP_LOGE("APP", "MP3 Player initialization failed.");
}
}

main.c:

1
2
3
4
5
#include "app_mp3_player.h"

void app_main(void) {
app_mp3_player_run();
}

项目编译和运行

  1. 环境搭建: 确保您已经安装了 ESP-IDF 开发环境,并配置好 ESP32-S3 的开发工具链。
  2. 项目配置: 创建一个新的 ESP-IDF 项目,并将以上代码文件添加到项目中。根据您的硬件连接,修改 bsp_board.h 中的引脚定义。
  3. 依赖库: 确保项目中包含了必要的依赖库,例如 minimp3 (音频解码)、FATFS (文件系统)、ST7789 (LCD 驱动) 等。您可以将这些库添加到 components 目录下,并在 CMakeLists.txt 中添加依赖。
  4. 编译: 使用 ESP-IDF 的编译命令 (idf.py build) 编译项目。
  5. 烧录: 将编译生成的固件烧录到 ESP32-S3 开发板。
  6. 运行: 连接电源,MP3 播放器应该开始运行。

代码说明和扩展

  • 分层架构: 代码严格按照分层架构组织,HAL 层负责硬件操作,BSP 层提供板级支持,Middleware 层提供通用服务,Application 层实现业务逻辑,结构清晰,易于维护和扩展。
  • 模块化设计: 每个层次和模块都尽可能独立,通过头文件暴露接口,降低模块间的耦合性。
  • FreeRTOS 任务: 使用 FreeRTOS 将音频播放和 UI 界面分别放在不同的任务中运行,提高系统的并发性和响应性。
  • 错误处理: 代码中包含了基本的错误处理,例如在 HAL 层和 Middleware 层检查函数返回值,并打印错误信息。在实际项目中,需要更完善的错误处理机制。
  • 代码注释: 代码中添加了详细的注释,方便理解代码逻辑和功能。
  • 可扩展性: 架构设计考虑了可扩展性,例如可以很容易地添加蓝牙音频输出、网络音乐播放等功能,只需要在 Middleware 层和 Application 层添加相应的模块即可。

未来改进方向

  • 更完善的 HAL 层: HAL 层可以进一步抽象,支持更多的硬件平台,并提供更丰富的硬件操作接口。
  • 音频编解码器驱动: 需要根据实际使用的音频编解码器芯片编写驱动,实现音量控制、EQ 调节等功能。
  • 更强大的 GUI 库: 可以使用更成熟的 GUI 库,例如 LVGL,提供更丰富的 UI 元素和交互方式。
  • 网络功能: 可以添加 Wi-Fi 或蓝牙模块,实现网络音乐播放、在线升级等功能。
  • 低功耗优化: 可以加入电源管理模块,实现低功耗模式切换,延长电池续航时间。
  • 测试和验证: 需要进行充分的单元测试、集成测试和系统测试,确保系统的可靠性和稳定性。

总结

这个基于 ESP32-S3 的 MP3 播放器项目展示了一个典型的嵌入式系统开发流程和代码架构。通过分层设计、模块化开发和使用 RTOS,我们构建了一个可靠、高效、可扩展的系统平台。提供的 C 代码示例涵盖了 HAL、BSP、Middleware 和 Application 各个层次,并包含了音频播放、文件系统、GUI 等关键模块的实现。您可以基于此代码框架进行扩展和完善,打造功能更强大的 MP3 播放器产品。

请注意,由于篇幅限制,代码中部分功能 (例如 GPIO 中断、SD 卡驱动、LCD 驱动、音频编解码器驱动、音量控制等) 只是提供了框架和注释,需要您根据具体的硬件平台和需求进行补充和完善。

希望这个详细的解答能够帮助您理解嵌入式 MP3 播放器的开发过程和代码架构设计。如果您有任何问题,欢迎随时提问。

欢迎关注我的其它发布渠道