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/kerneland 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
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.
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_testRun the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:
make CHIP=bl616 BOARD=bl616dkConnect 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/ttyUSB0After 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 handletrans_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 addresslen: 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
#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

