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-30 17:31:25