Skip to content

Concepts First ​

  • SDIO: a high-speed IO interface similar to SD cards; the module can act as a "slave" exchanging data with a Linux host.
  • T-L-V message: a framing format with Tag/Length/Value fields, used by the example when echoing data back.
  • Typical use: module↔host high-speed interconnects (camera, Wi-Fi bridging, high-throughput peripherals); requires an SDH host controller and driver on the host.

Example Overview ​

This page is based on the sdio2_test example in the official Bouffalo SDK (examples/peripherals/sdio/sdio2_test), which demonstrates SDIO2 high-speed interface communication:

  • The board acts as an SDIO slave and echoes received data back (T-L-V message format);
  • A Linux host (SDH Host) is required; the kernel driver source is at bsp/common/msg_router/linux_host/kernel and supports hot-plugging;
  • Ideal for validating SDIO driver stability and throughput — the typical scenario for module↔host high-speed interconnects (camera, Wi-Fi bridging, etc.).
  • Sibling examples (examples/peripherals/sdio/): sdio_msg_router (message router).

Operation Steps ​

1
Prepare the Hardware

The SDIO2 test needs a Linux device with an SDH host as the host (e.g. a Raspberry Pi or industrial board); the development board acts as the SDIO slave. Wire CLK/CMD/D0-D3 per the SDIO2 pins in board.h and share ground.

2
Enter the Example Directory

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

cd examples/peripherals/sdio/sdio2_test
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

After loading the kernel driver on the Linux host (source in bsp/common/msg_router/linux_host/kernel, hot-swap supported), the host continuously sends data to the board and the board echoes it back, verifying SDIO2 link stability and throughput.

Code Execution Flow ​

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

APIs Used by the Example ​

bflb_device_get_by_name("sdio2") ​

Gets the SDIO2 device handle.

Parameters:

  • name: device name

Return: struct bflb_device_s * device handle

bflb_sdio2_upld_port_push(sdio2, trans_desc) ​

Pushes a transfer descriptor into the upload queue so the SDIO controller sends it to the host.

Parameters:

  • sdio2: SDIO2 device handle
  • trans_desc: bflb_sdio2_trans_desc_t * transfer descriptor (buffer, data length)

Return: 0 on success; negative error code on failure

bflb_l1c_dcache_invalidate_range(buff, len) ​

Invalidates the cache in the receive callback so reads see the latest data written by the SDIO controller.

Parameters:

  • buff: buffer start address
  • len: length in bytes

Return: none

Complete Code ​

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

📜 Click to expand sdio2_test/main.c full code
c
#include "bflb_mtimer.h"
#include "bflb_gpio.h"
#include "bflb_sdio2.h"

#if defined(BL616)
#include "bl616_glb.h"
#elif defined(BL616CL)
#include "bl616cl_glb.h"
#endif

#include "board.h"

#define DBG_TAG "MAIN"
#include "log.h"

// #define SDIO2_DBG(a, ...) LOG_I(a, ##__VA_ARGS__)
#define SDIO2_DBG(a, ...)

#define SDIO2_TEST_SIZE (2 * 1024)

/* T-L-V struct */
typedef struct {
    uint16_t tag;
    uint16_t length;
    uint8_t value[0];
} sdio_tlv_t;

volatile uint32_t dnld_cnt, upld_cnt;
struct bflb_device_s *sdio2_hd;

/* dnld irq callback */
void sdio2_dnld_irq_callback(void *arg, bflb_sdio2_trans_desc_t *trans_desc)
{
    /* dnld done */
    dnld_cnt++;
    /* invalid cache */
    bflb_l1c_dcache_invalidate_range(trans_desc->buff, trans_desc->data_len);

    sdio_tlv_t *tlv = (sdio_tlv_t *)trans_desc->buff;

    /* update dnld data_len */
    trans_desc->data_len = tlv->length + sizeof(sdio_tlv_t);

    SDIO2_DBG("dnld done, tag 0x%04X, len %d, upld it!\r\n", tlv->tag, (tlv->length + sizeof(sdio_tlv_t)));

    /* push to upld queue*/
    bflb_sdio2_upld_port_push(sdio2_hd, trans_desc);
}

/* upld irq callback */
void sdio2_upld_irq_callback(void *arg, bflb_sdio2_trans_desc_t *trans_desc)
{
    /* upld done */
    upld_cnt++;

    /* max upld buff len */
    trans_desc->buff_len = SDIO2_TEST_SIZE;

    SDIO2_DBG("upld done, push buff to dnld queue\r\n");

    /* push to dnld queue */
    bflb_sdio2_dnld_port_push(sdio2_hd, trans_desc);
}

#ifdef SDIO2_SOFT_RST_INT_SUP
/* reset irq callback */
void sdio2_reset_irq_callback(void *arg)
{
    LOG_I("\r\n***** sdio2 reset! *****\r\n");

    /* clean cnt */
    dnld_cnt = 0;
    upld_cnt = 0;

    /* free test buff */
    {
        bflb_sdio2_trans_desc_t trans_desc;
        int free_n = 0;
        LOG_I("free dnld queue buff\r\n");
        while (bflb_sdio2_dnld_port_pop(sdio2_hd, &trans_desc) == 0) {
            free(trans_desc.buff);
            free_n++;
        }

        LOG_I("free upld queue buff\r\n");
        while (bflb_sdio2_upld_port_pop(sdio2_hd, &trans_desc) == 0) {
            free(trans_desc.buff);
            free_n++;
        }

        LOG_I("buff free num:%d\r\n", free_n);
    }

    /* sdio2 queue ctrl reset */
    bflb_sdio2_deinit(sdio2_hd);

    /* sdio2 init */
    bflb_sdio2_init(sdio2_hd, SDIO2_TEST_SIZE);

    /* link all dnld buff */
    LOG_I("malloc buff and push to dnld_queue\r\n");
    for (uint32_t i = 0; i < SDIO2_MAX_PORT_NUM; i++) {
        void *buff = malloc(SDIO2_TEST_SIZE);
        if (buff == NULL) {
            LOG_E("malloc NULL\r\n");
            while (1) {
            }
        }

        bflb_sdio2_trans_desc_t dnld_desc = {
            .buff = buff,
            .buff_len = SDIO2_TEST_SIZE,
            .user_arg = NULL,
        };
        bflb_l1c_dcache_invalidate_range(buff, SDIO2_TEST_SIZE);
        bflb_sdio2_dnld_port_push(sdio2_hd, &dnld_desc);
    }
}
#endif

void sdio2_irq_cb(void *arg, uint32_t irq_event, bflb_sdio2_trans_desc_t *trans_desc)
{
    switch (irq_event) {
        case SDIO2_IRQ_EVENT_DNLD_CPL:
            sdio2_dnld_irq_callback(arg, trans_desc);
            break;

        case SDIO2_IRQ_EVENT_UPLD_CPL:
            sdio2_upld_irq_callback(arg, trans_desc);
            break;

#ifdef SDIO2_SOFT_RST_INT_SUP
        case SDIO2_IRQ_EVENT_SOFT_RESET:
            sdio2_reset_irq_callback(arg);
            break;
#endif

        case SDIO2_IRQ_EVENT_ERR_CRC:
            LOG_E("sdio2 error: CRC ERROR!\r\n");
            break;
        case SDIO2_IRQ_EVENT_ERR_ABORT:
            LOG_E("sdio2 error: ABORT!\r\n");
            break;
        case SDIO2_IRQ_EVENT_ERR_UNKNOW:
            LOG_E("sdio2 error: UNKNOW!\r\n");
            break;
        default:
            LOG_E("irq_event %d\r\n", irq_event);
            break;
    }
}

void sdio2_test(void)
{
    uint32_t dnld_cnt_last = 0, upld_cnt_last = 0;

#if defined(BL616)
    GLB_PER_Clock_UnGate(GLB_AHB_CLOCK_USB20_SDU);
#elif defined(BL616CL)
    GLB_PER_Clock_UnGate(GLB_AHB_CLOCK_SDU);
#elif defined(BL602)
#endif

    sdio2_hd = bflb_device_get_by_name("sdio2");
    if (sdio2_hd == NULL) {
        LOG_I("get device failed\r\n");
        while (1) {
        }
    }

    /* sdio2 init */
    bflb_sdio2_init(sdio2_hd, SDIO2_TEST_SIZE);

    /* attach int callback */
    bflb_sdio2_irq_attach(sdio2_hd, sdio2_irq_cb, NULL);

    /* malloc buff and link to dnld queue */
    LOG_I("malloc buff and push to dnld_queue\r\n");
    for (uint32_t i = 0; i < SDIO2_MAX_PORT_NUM; i++) {
        void *buff = malloc(SDIO2_TEST_SIZE);
        if (buff == NULL) {
            LOG_E("malloc NULL\r\n");
            while (1) {
            }
        }

        bflb_sdio2_trans_desc_t dnld_desc = {
            .buff = buff,
            .buff_len = SDIO2_TEST_SIZE,
            .user_arg = NULL,
        };
        bflb_l1c_dcache_invalidate_range(buff, SDIO2_TEST_SIZE);
        bflb_sdio2_dnld_port_push(sdio2_hd, &dnld_desc);
    }

    /* wait host ready */
    LOG_I("sdio2 wait host ready...\r\n");
    while (bflb_sdio2_feature_control(sdio2_hd, SDIO2_CMD_GET_FUNC_HOST_READY, 0) == 0) {
        bflb_mtimer_delay_ms(1);
    }
    LOG_I("sdio2 host ready! \r\n");
    /* bus width */
    LOG_I("sdio2 bus width: %d\r\n", bflb_sdio2_feature_control(sdio2_hd, SDIO2_CMD_GET_BUS_WIDTH, 0));
    /* get block size */
    LOG_I("sdio2 block size: %d\r\n", bflb_sdio2_feature_control(sdio2_hd, SDIO2_CMD_GET_FUNC_BLK_SIZE, 0));
    /* get max size */
    LOG_I("sdio dnld max size %d\r\n", bflb_sdio2_feature_control(sdio2_hd, SDIO2_CMD_GET_DNLD_MAX_SIZE, 0));
    LOG_I("sdio upld max size %d\r\n", bflb_sdio2_feature_control(sdio2_hd, SDIO2_CMD_GET_UPLD_MAX_SIZE, 0));

    /* print info */
    while (1) {
        if (dnld_cnt_last != dnld_cnt || upld_cnt_last != upld_cnt) {
            dnld_cnt_last = dnld_cnt;
            upld_cnt_last = upld_cnt;
            LOG_I("sdio2 test, dnld_cnt: %d, upld_cnt: %d\r\n", dnld_cnt_last, upld_cnt_last);
        }

#if 0
        /* Real-time stress testing */
        for (uint32_t i = 0; i < 200; i++) {
            intptr_t flag = bflb_irq_save();
            bflb_mtimer_delay_ms(1);
            bflb_irq_restore(flag);
            bflb_mtimer_delay_ms(1);
        }
#else
        bflb_mtimer_delay_ms(500);
#endif
    }
}

int main(void)
{
    board_init();

    LOG_I("sdio2_test\r\n");

    board_sdio_gpio_init();

    /* sdio2 test */
    sdio2_test();

    while (1) {
        bflb_mtimer_delay_ms(5000);
        LOG_I("sdio2 test...\r\n");
    }
}

FAQ ​

The Linux host cannot see the device

Check the SDIO pin wiring (CLK/CMD/D0-D3/shared ground) and that the kernel driver loaded; different hosts have different 3.3V/1.8V level requirements — adjust per the board config.

Echoed data fails verification

Check that the T-L-V length field (tlv->length) matches the buffer length, and that both ends use the same data width/byte order.

Is this a normal TF card interface?

No. SDIO2 is a high-speed IO interface of the chip; this example interconnects the module with a Linux host. Reading TF cards requires the SDH storage driver, not this example.

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