概念先知道
- 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 组件)。
操作步骤
I2S 需要外接音频编解码器(codec)与喇叭/麦克风。把 codec 模块的 I2S 数据/时钟引脚接到开发板对应引脚(见 board.h 与例程配置),并连接喇叭、麦克风与 3.3V/GND。
在终端进入 SDK 的 I2S 例程目录(前提:已按快速开始(Linux)或Windows搭建好环境):
cd examples/peripherals/i2s/i2s_codec执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:
make CHIP=bl616 BOARD=bl616dk用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):
make flash CHIP=bl616 COMX=/dev/ttyUSB0打开串口助手(波特率 2000000),例程启动后会初始化 I2S 编解码器并进入串口 shell。可输入 cpu_diag 查看 CPU 占用;录音相关命令以 rec_ 开头(如 rec_diag 查看录音诊断信息),播放命令见 wave_player 组件。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
注意
该例程依赖外部音频 codec 与文件系统(FATFS/SD),首次使用请先阅读例程内 defconfig 与 recorder.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 完整代码
#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

