发帖
6 0 0

Ai-M61-32S开发板编写MAX98357A驱动

沈夜
论坛元老

88

主题

209

回帖

1万

积分

论坛元老

积分
11393
Ai-M61系列 412 6 2025-10-22 04:34:16
[i=s] 本帖最后由 沈夜 于 2025-10-22 04:35 编辑 [/i]

本来想挑战一下自己,从零开始写一个完整的NES游戏机。前几天刚搞定USB手柄的接入,满心欢喜。今天打算测试声音播放,结果发现手头上的开发板不支持ES8388……瞬间感觉天塌了😭

一路走来真的不容易,一步步学了:
🎮 按键的使用
🖥️ 屏幕驱动
📦 NES模拟器的移植
🗂️ 文件管理系统
🔌 GPIO的使用
🕹️ USB Host的驱动
🎵 声音播放

就差临门一脚了,没想到硬件居然不支持……只能自己写了💪

5116a2bfc4ba83eb242ad4f8e1f7b42.jpg

78442e09bb194dbfc8915ee70dfd9c3.png

✅ 一、硬件连接(重点)

MAX98357A 的接口如下(常见模块引脚标号):

MAX98357A 引脚 连接到 BL618 说明
DIN (SD) I2S_DO(GPIO15) I²S 数据输出
BCLK I2S_BCLK(GPIO20) I²S 时钟
LRC (LRCLK) I2S_FS(GPIO13) 帧时钟
GND GND 电源地
VIN 3.3V 电源正
SD_MODE 拉高(使能) 可以固定拉 3.3V
GAIN0 / GAIN1 可选 控制音量,默认悬空即可

注意:MAX98357A 不需要 I²C;只要 I²S 信号和电源正确,就会自动工作。


✅ 二、软件部分:最简 I²S + DMA 播放 demo

以下代码可直接在 BL618 上播放嵌入的 PCM 数据(一段短音频)。(示例中用一个固定数组代替文件;后面我会告诉你如何换成音频文件数据)

工程基础要求

  • SDK 为 BouffaloLab 官方 BL618(如 bl_mcu_sdk
  • 已包含驱动:bflb_i2s, bflb_dma, bflb_gpio, bflb_mtimer
#include "board.h"
#include "bflb_i2s.h"
#include "bflb_gpio.h"
#include "bflb_dma.h"
#include "bflb_mtimer.h"

/* 简单单声道16bit PCM 音频(示例数据, 16KHz 采样率) */
extern const int16_t sound_sample[];
extern const uint32_t sound_sample_len;

struct bflb_device_s *i2s0;
struct bflb_device_s *dma0_ch0;
static struct bflb_dma_channel_lli_pool_s tx_llipool[20];

volatile uint8_t dma_done_flag = 0;

void dma_tx_isr(void *arg)
{
    dma_done_flag = 1;
    printf("DMA TX Done\r\n");
}

/* GPIO Function: setup pins for I2S0 */
void i2s_gpio_init(void)
{
    struct bflb_device_s *gpio = bflb_device_get_by_name("gpio");

    /* I2S FS -> GPIO13 */
    bflb_gpio_init(gpio, GPIO_PIN_13, GPIO_FUNC_I2S |
                   GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);
    /* I2S DO -> GPIO15 */
    bflb_gpio_init(gpio, GPIO_PIN_15, GPIO_FUNC_I2S |
                   GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);
    /* I2S BCLK -> GPIO20 */
    bflb_gpio_init(gpio, GPIO_PIN_20, GPIO_FUNC_I2S |
                   GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);
}

/* Init I2S and DMA */
void i2s_dma_init(void)
{
    struct bflb_i2s_config_s i2s_cfg = {
        .bclk_freq_hz = 16000 * 16 * 2, /* 16bit, stereo frame */
        .role = I2S_ROLE_MASTER,
        .format_mode = I2S_MODE_LEFT_JUSTIFIED,
        .channel_mode = I2S_CHANNEL_MODE_NUM_1,
        .frame_width = I2S_SLOT_WIDTH_16,
        .data_width = I2S_SLOT_WIDTH_16,
        .fs_offset_cycle = 0,
        .tx_fifo_threshold = 0,
        .rx_fifo_threshold = 0,
    };

    struct bflb_dma_channel_config_s tx_config = {
        .direction = DMA_MEMORY_TO_PERIPH,
        .src_req = DMA_REQUEST_NONE,
        .dst_req = DMA_REQUEST_I2S_TX,
        .src_addr_inc = DMA_ADDR_INCREMENT_ENABLE,
        .dst_addr_inc = DMA_ADDR_INCREMENT_DISABLE,
        .src_burst_count = DMA_BURST_INCR1,
        .dst_burst_count = DMA_BURST_INCR1,
        .src_width = DMA_DATA_WIDTH_16BIT,
        .dst_width = DMA_DATA_WIDTH_16BIT,
    };

    i2s0 = bflb_device_get_by_name("i2s0");
    bflb_i2s_init(i2s0, &i2s_cfg);
    bflb_i2s_link_txdma(i2s0, true);

    dma0_ch0 = bflb_device_get_by_name("dma0_ch0");
    bflb_dma_channel_init(dma0_ch0, &tx_config);
    bflb_dma_channel_irq_attach(dma0_ch0, dma_tx_isr, NULL);

    struct bflb_dma_channel_lli_transfer_s tx_trans = {
        .src_addr = (uint32_t)sound_sample,
        .dst_addr = (uint32_t)DMA_ADDR_I2S_TDR,
        .nbytes = sound_sample_len,
    };

    uint32_t num = bflb_dma_channel_lli_reload(dma0_ch0, tx_llipool,
                         sizeof(tx_llipool) / sizeof(tx_llipool[0]),
                         &tx_trans, 1);

    bflb_dma_channel_lli_link_head(dma0_ch0, tx_llipool, num);
}

int main(void)
{
    board_init();

    printf("BL618 + MAX98357A I2S DMA Sound Demo\r\n");

    i2s_gpio_init();
    i2s_dma_init();

    bflb_i2s_feature_control(i2s0, I2S_CMD_DATA_ENABLE,
                             I2S_CMD_DATA_ENABLE_TX);

    printf("Start DMA transmit\r\n");
    bflb_dma_channel_start(dma0_ch0);

    while (1) {
        if (dma_done_flag) {
            dma_done_flag = 0;
            printf("Playback finished\r\n");
            bflb_mtimer_delay_ms(1000);
            /* 重新播放 */
            bflb_dma_channel_start(dma0_ch0);
        }
    }
}

🔊 简单的声音数据(示例)

你可以放在另一个 sound_data.c 文件中:

#include <stdint.h>

const int16_t sound_sample[] = {
    0, 5000, 10000, 5000, 0, -5000, -10000, -5000,
    0, 5000, 10000, 5000, 0, -5000, -10000, -5000,
    /* 这里可以放更多采样点,用音频编辑器导出 PCM16 格式 */
};

const uint32_t sound_sample_len = sizeof(sound_sample);

编译烧录后,你将听到简单的「嗡嗡声」。
如果 MAX98357A 输出到喇叭,上电后应该能听见。


✅ 三、播放声音文件(WAV 文件)

如果你想播放「真实声音」:

  1. 用音频软件(如 Audacity)导出:
    • 单声道(Mono)
    • 采样率 16 000 Hz
    • 16 bit PCM
  2. 生成 .wav,然后去掉 WAV 文件头(前 44 字节);
  3. 把纯 PCM 数据转成数组(可以用 Python 脚本生成);
  4. 替换上面的 sound_sample[]

或者更高级的:

  • 存在 SPI Flash 或 SD 卡上;
  • 从文件系统中读取 PCM 数据;
  • 分段用 DMA 送到 I²S → 即支持连续播放(需要环形 DMA 或 double buffer)。

✅ 四、总结

功能 状态
不需要 I²C
可驱动 MAX98357A
DMA 连续播放
可嵌入 PCM 声音文件
可进一步扩展成 WAV 播放器
──── 0人觉得很赞 ────

使用道具 举报

大佬加油
2025-10-22 12:20:57
Ai-Pi-Eyes-S1有es8388,可以参考其代码
2025-10-22 22:49:34
hdydy 发表于 2025-10-22 12:20
Ai-Pi-Eyes-S1有es8388,可以参考其代码

我没这个板子。测试不了
2025-10-23 20:28:54
哇,学习一下😁
2025-10-28 08:43:25
学习以下,感觉能用到。
2025-11-23 15:07:42
刚好需要,我也买一块测试下
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 30550 个
  • 话题数: 44705 篇