Skip to content

概念先知道

  • SDIO:类似 SD 卡的高速 IO 接口,模组可以作为“从设备”与 Linux 主机高速互通数据。
  • T-L-V 报文:一种带标签(Tag)、长度(Length)、数据(Value)的封装格式,例程回传数据时使用。
  • 适用场景:模组与主机高速互联(摄像头、Wi-Fi 桥接、大吞吐外设),需要主机端有 SDH 控制器与驱动配合。

例程功能简介

本页对应博流官方 SDK 的 sdio2_test 例程(examples/peripherals/sdio/sdio2_test),演示 SDIO2 高速接口的通信:

  • 开发板作为 SDIO 从设备,把接收到的数据原样回传(T-L-V 报文格式);
  • 需要 Linux 主机(SDH Host)配合,内核驱动源码位于 bsp/common/msg_router/linux_host/kernel,支持热插拔;
  • 适合验证 SDIO 驱动稳定性与吞吐,是模组与主机高速互联(如摄像头、Wi-Fi 桥接)的典型场景。
  • 同族例程examples/peripherals/sdio/):sdio_msg_router(消息路由)。

操作步骤

1
硬件准备

SDIO2 测试需要一台运行 Linux 且支持 SDH 主机的设备作为主机(例如树莓派/工控板),开发板作为 SDIO 从设备。按 board.h 中 SDIO2 引脚定义接线(CLK/CMD/D0-D3/共地)。

2
进入例程目录

在终端进入 SDK 的 SDIO2 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/sdio/sdio2_test
3
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
运行验证

在 Linux 主机加载内核驱动(源码在 bsp/common/msg_router/linux_host/kernel,支持热插拔)后,主机持续向开发板下发数据,开发板把收到的数据原样回传,可验证 SDIO2 链路稳定性与速度。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):

例程调用的 API 介绍

bflb_device_get_by_name("sdio2")

获取 SDIO2 外设的设备句柄。

参数

  • name:外设名称

返回值struct bflb_device_s * 设备句柄

bflb_sdio2_upld_port_push(sdio2, trans_desc)

把待上传的传输描述符压入上传队列,由 SDIO 控制器发送给主机。

参数

  • sdio2:SDIO2 设备句柄
  • trans_descbflb_sdio2_trans_desc_t * 传输描述符(含缓冲区、数据长度)

返回值:成功返回 0;失败返回负值错误码

bflb_l1c_dcache_invalidate_range(buff, len)

接收回调中失效缓存,保证读到的是 SDIO 控制器写入的最新数据。

参数

  • buff:缓冲区起始地址
  • len:长度(字节)

返回值:无

完整代码

以下为 sdio2_test/main.c 完整源码,与官方示例(examples/peripherals/sdio/sdio2_test)一致;点灯引脚已适配 Ai-M61/62-32S-Kit 板载 RGB 灯,默认折叠,点击展开:

📜 点击展开 sdio2_test/main.c 完整代码
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

Linux 主机识别不到设备

确认 SDIO 引脚接线(CLK/CMD/D0-D3/共地)与内核驱动加载成功;不同主机 SDH 控制器对 3.3V/1.8V 电平要求不同,按板级配置调整。

回传数据校验失败

检查 T-L-V 报文长度字段(tlv->length)与缓冲区长度是否一致;确认收发两端数据宽度/字节序一致。

这是普通 TF 卡接口吗

不是。SDIO2 是芯片的高速 IO 接口,此处做的是模组与 Linux 主机的互联测试;读 TF 卡需使用 SDH 存储驱动而非本例程。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

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