Skip to content

Concepts First

  • I2S: an audio-specific serial bus that carries digital samples (left/right channels) to the codec.
  • Codec: the chip converting digital ↔ analog — capture needs it to turn sound waves into digital, playback turns digital back into sound.
  • Sample rate / bit depth: how many samples per second and how many bits per sample (the example uses 32 kHz, 16-bit); they determine quality and data size.

Example Overview

This page is based on the i2s_codec example in the official Bouffalo SDK (examples/peripherals/i2s/i2s_codec), which demonstrates I2S audio capture and playback:

  • Based on FreeRTOS, it initializes the I2S audio codec and a serial shell;
  • The wave_player component plays WAV files and the recorder component records audio (with SD card storage and rec_* shell commands);
  • Playback, recording, and SD management are all driven from the serial shell, making it a good base for voice projects.
  • Sibling examples (examples/peripherals/i2s/): i2s_dma (raw I2S DMA audio without the codec components).

Operation Steps

1
Prepare the Hardware

I2S needs an external audio codec plus a speaker/microphone. Connect the codec’s I2S data/clock pins to the board pins (see board.h and the example config), and wire the speaker, microphone, 3.3V, and GND.

2
Enter the Example Directory

Open a terminal and enter the I2S example directory (prerequisite: set up the environment as in Quick Start (Linux) or Windows):

cd examples/peripherals/i2s/i2s_codec
3
Build the Project

Run the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:

make CHIP=bl616 BOARD=bl616dk
4
Flash the Firmware

Connect the board with a USB cable, hold the BOOT button (IO2 on the Ai-M61-32S-Kit), briefly press EN/RST to enter download mode, then flash (replace the serial port with the one on your computer):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
Run and Verify

Open a serial tool (baud rate 2000000). After startup the example initializes the I2S codec and enters a serial shell. Type cpu_diag to check CPU usage; recording commands start with rec_ (e.g. rec_diag for recording diagnostics); playback commands are provided by the wave_player component.

Code Execution Flow

The complete execution flow from startup to running is shown below (loop arrows mean repeated execution):

Note

This example depends on an external audio codec and a file system (FATFS/SD). Read the defconfig and recorder.c configuration notes before first use.

Serial Commands Provided by the Example

rec_sd_mount

Remounts the SD card (FATFS); mounting must succeed before recording.

rec_sd_store <0|1>

Enables/disables SD storage for recordings; when disabled, only in-memory diagnostics run.

rec_file_play <wav_path>

Plays a WAV file at the given path, e.g. /sd/records/xxx.wav.

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

Shows recording/VAD diagnostics, lists recording files, formats the SD card (FAT32), and samples CPU usage.

Complete Code

The complete source below matches the effect described on this page. It is based on the official example (examples/peripherals/i2s/i2s_codec); the LED pins are adapted to the Ai-M61/62-32S-Kit onboard RGB LED. Collapsed by default, click to expand:

📜 Click to expand i2s_codec/main.c full code
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

Recording init fails with storage init failed

The SD card mount failed: check that the card is inserted and FAT32-formatted, run rec_sd_mount to retry, or rec_sd_format confirm to format it.

No sound when playing

Check the codec wiring (I2S clocks/data), speaker connection, and that wave_player_init succeeded; try a 16-bit WAV at 32 kHz or 44.1 kHz.

No serial shell output

Set the baud rate to 2000000; the example uses shell_init_with_task(uart0), so connect to the uart0 USB port and press reset.

Have questions?

For any other questions, visit the unified Q&A and discussion board: Ai-Thinker Discussions

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