Skip to content

概念先知道

  • I2S:音频专用的串行总线,按左右声道把数字音频(采样点)传给编解码器(codec)
  • 编解码器:负责“数字 ↔ 模拟”转换的芯片——麦克风采集要它把模拟声波变数字,播放要它把数字变声音。
  • 采样率/位深:每秒采多少个点、每个点用多少位表示(例程 32kHz、16bit),决定音质与数据量。

例程功能简介

本页对应博流官方 SDK 的 i2s_codec 例程(examples/peripherals/i2s/i2s_codec),演示 I2S 音频采集与播放:

  • 基于 FreeRTOS,初始化 I2S 音频编解码器与串口 shell;
  • wave_player 组件负责 WAV 播放,recorder 组件负责录音(支持 SD 卡存储,提供 rec_* 系列 shell 命令);
  • 通过串口命令即可完成播放、录音、SD 卡管理等操作,适合做语音类项目的基础验证。
  • 同族例程examples/peripherals/i2s/):i2s_dma(I2S DMA 直传音频,不经过 codec 组件)。

操作步骤

1
硬件准备

I2S 需要外接音频编解码器(codec)与喇叭/麦克风。把 codec 模块的 I2S 数据/时钟引脚接到开发板对应引脚(见 board.h 与例程配置),并连接喇叭、麦克风与 3.3V/GND。

2
进入例程目录

在终端进入 SDK 的 I2S 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/i2s/i2s_codec
3
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
运行验证

打开串口助手(波特率 2000000),例程启动后会初始化 I2S 编解码器并进入串口 shell。可输入 cpu_diag 查看 CPU 占用;录音相关命令以 rec_ 开头(如 rec_diag 查看录音诊断信息),播放命令见 wave_player 组件。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):

注意

该例程依赖外部音频 codec 与文件系统(FATFS/SD),首次使用请先阅读例程内 defconfigrecorder.c 中的配置说明。

例程提供的串口命令

rec_sd_mount

重新挂载 SD 卡(FATFS),录音前需要先挂载成功。

rec_sd_store <0|1>

开启/关闭录音的 SD 卡存储;不开启时仅做内存诊断。

rec_file_play <wav_path>

播放指定路径的 WAV 文件,路径形如 /sd/records/xxx.wav

rec_diag [clr] / rec_vad_diag [clr] / rec_ls / rec_sd_format confirm / cpu_diag [ms]

查看录音/VAD 诊断信息、列出录音文件、格式化 SD 卡(FAT32)、查看 CPU 占用率。

完整代码

以下为 i2s_codec/main.c 完整源码,与官方示例(examples/peripherals/i2s/i2s_codec)一致;点灯引脚已适配 Ai-M61/62-32S-Kit 板载 RGB 灯,默认折叠,点击展开:

📜 点击展开 i2s_codec/main.c 完整代码
c
#include <stdio.h>
#include <stdlib.h>

#include <FreeRTOS.h>
#include <task.h>

#include "board.h"
#include "bflb_core.h"
#include "shell.h"
#include "wave_player.h"
#include "recorder.h"
#include "mm.h"

static int cmd_cpu_diag(int argc, char **argv)
{
#if (configGENERATE_RUN_TIME_STATS == 1)
    uint32_t window_ms = 1000U;
    if (argc >= 2) {
        unsigned long v = strtoul(argv[1], NULL, 0);
        if (v < 50UL || v > 60000UL) {
            printf("usage: cpu_diag [window_ms:50..60000]\r\n");
            return -1;
        }
        window_ms = (uint32_t)v;
    }

    configRUN_TIME_COUNTER_TYPE total0 = (configRUN_TIME_COUNTER_TYPE)portGET_RUN_TIME_COUNTER_VALUE();
    configRUN_TIME_COUNTER_TYPE idle0 = ulTaskGetIdleRunTimeCounter();

    vTaskDelay(pdMS_TO_TICKS(window_ms));

    configRUN_TIME_COUNTER_TYPE total1 = (configRUN_TIME_COUNTER_TYPE)portGET_RUN_TIME_COUNTER_VALUE();
    configRUN_TIME_COUNTER_TYPE idle1 = ulTaskGetIdleRunTimeCounter();

    if (total1 <= total0 || idle1 < idle0) {
        printf("cpu_diag: invalid sample (counter wrap or too short window)\r\n");
        return -1;
    }

    uint64_t delta_total = (uint64_t)(total1 - total0);
    uint64_t delta_idle = (uint64_t)(idle1 - idle0);
    if (delta_idle > delta_total) {
        delta_idle = delta_total;
    }

    uint64_t cpu_pm = ((delta_total - delta_idle) * 1000ULL) / delta_total;
    uint64_t idle_pm = 1000ULL - cpu_pm;

    printf("cpu_diag: window=%lu ms cpu=%lu.%lu%% idle=%lu.%lu%% total=%llu idle_ticks=%llu\r\n",
           (unsigned long)window_ms, (unsigned long)(cpu_pm / 10ULL), (unsigned long)(cpu_pm % 10ULL),
           (unsigned long)(idle_pm / 10ULL), (unsigned long)(idle_pm % 10ULL), (unsigned long long)delta_total,
           (unsigned long long)delta_idle);
    return 0;
#else
    (void)argc;
    (void)argv;
    printf("cpu_diag: runtime stats disabled\r\n");
    return -1;
#endif
}

SHELL_CMD_EXPORT_ALIAS(cmd_cpu_diag, cpu_diag, "sample CPU usage: cpu_diag [window_ms]");

static void recorder_init_task(void *arg)
{
    (void)arg;

    if (recorder_storage_init() != 0) {
        printf("[WARN] recorder storage init failed, recording to SD disabled\r\n");
    }

    vTaskDelete(NULL);
}

void vApplicationMallocFailedHook(void)
{
    const char *task_name = pcTaskGetName(NULL);
    printf("[FATAL] malloc failed task=%s heap_free=%u\r\n", task_name != NULL ? task_name : "unknown",
           (unsigned)kfree_size(0));
    taskDISABLE_INTERRUPTS();
    while (1) {}
}

void vAssertCalled(void)
{
    const char *task_name = pcTaskGetName(NULL);
    printf("[FATAL] assert task=%s\r\n", task_name != NULL ? task_name : "unknown");
    taskDISABLE_INTERRUPTS();
    while (1) {}
}

int main(void)
{
    board_init();

    struct bflb_device_s *uart0 = bflb_device_get_by_name("uart0");
    if (uart0 != NULL) {
        shell_init_with_task(uart0);
    } else {
        printf("[WARN] uart0 not found, shell disabled\r\n");
    }

    wave_player_hw_cfg_t audio_hw_cfg = {
        .i2s_dev_name = "i2s0",
        .i2c_dev_name = "i2c0",
        .dma_tx_dev_name = "dma0_ch0",
        .dma_rx_dev_name = "dma0_ch1",
    };
    if (wave_player_init(&audio_hw_cfg) != 0) {
        printf("[ERROR] wave_player_init failed\r\n");
        while (1) {}
    }

    if (xTaskCreate(recorder_init_task, (char *)"rec_init", 2048, NULL, 6, NULL) != pdPASS) {
        printf("[WARN] create rec_init task failed, recorder init skipped\r\n");
    }

    printf("i2s_codec ready. see audio command using help\r\n");
    vTaskStartScheduler();

    while (1) {}
}

FAQ

录音初始化失败提示 storage init failed

说明 SD 卡挂载失败:检查 SD 卡是否插入且为 FAT32 格式,先执行 rec_sd_mount 重试,必要时 rec_sd_format confirm 格式化。

播放没有声音

确认 codec 接线正确(I2S 时钟/数据/位时钟)、喇叭已连接;确认 wave_player_init 返回成功;更换 WAV 文件(建议 16bit/32kHz 或 44.1kHz)再试。

串口 shell 没有输出

波特率设为 2000000;例程使用 shell_init_with_task(uart0),确认连接的是 uart0 对应 USB 串口,并按复位键重启。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23