GD32F303 SDIO-DMA读写SD卡

简单介绍

原版示例代码位于\GD32F30x_Firmware_Library_V2.2.0\Examples\SDIO,对SD卡读写,卡信息获取,切换高速模式等基本功能已经实现了,但是默认实现的传输方式为CPU轮询读写FIFO,速度偏慢;卡上电初始化部分指令未考虑到一些大容量卡首次上电忙时间过长的问题,这里将在原版代码基础上进一步完善。
新增内容如下:

  • 写单块CMD24,写多块CMD25,读单块CMD17,读多块CMD18支持DMA传输
  • 使用CMSIS-RTOS2的信号量做DMA同步控制
  • 60MHz的时钟速度(超出控制器支持的48MHz,连续调用写单块(CMD24)时不太稳定)
  • 在一些操作指令之间增加延时,适配一些山寨TF卡

代码下载:
https://files.cnblogs.com/files/blogs/575121/gd32f30x_sdio.zip

SDIO硬件电路

GD32F303的SDIO功能只有1组管脚可以使用,具体分配如下:

SDIO定义 GPIO编号 PIN位置 备注
SDIO_DAT0 PC8 39 数据0, 上拉10K
SDIO_DAT1 PC9 40 数据1, 上拉10K
SDIO_DAT2 PC10 51 数据2, 上拉10K
SDIO_DAT3 PC11 52 数据3, 上拉10K
SDIO_CLK PC12 53 时钟, 上拉10K
SDIO_CMD PD2 54 命令/响应, 上拉10K

SDIO的DMA通道为DMA1 Channel3。

函数说明

  • 如果要测试60MHz时钟,在sd_init()中建议开启IO补偿单元。
volatile uint32_t reg = 0;
volatile uint32_t timeout = 0xffffu;
// enable the I/O compensation cell for 60Mbps IO speed
gpio_compensation_config(GPIO_COMPENSATION_ENABLE);
do {
	reg = AFIO_CPSCTL;
	timeout--;
}while(!(reg & AFIO_CPSCTL_CPS_RDY) && timeout);
if(timeout == 0) {
	init_status = SD_ERROR;
	return SD_ERROR;
}
  • 启用DMA传输时,需要在NVIC控制器中打开DMA的中断。
#define IRQ_SDIO_PRIORITY             10
#define IRQ_SDIO_DMA_PRIORITY         9

nvic_irq_enable(SDIO_IRQn, IRQ_SDIO_PRIORITY, 0);
nvic_irq_enable(DMA1_Channel3_Channel4_IRQn, IRQ_SDIO_DMA_PRIORITY, 0);
  • ACMD指令添加重试
    sd_power_on()阶段,发送完CMD0(GO_IDLE_STATE),CMD8(SEND_IF_COND)后切换到CMD55(APP_CMD)时,部分大容量SD卡还处于忙碌状态,导致CMD55的请求未及时回复,SDIO控制器认为返回响应超时(SD_CMD_RESP_TIMEOUT)。这里需要添加重试机制,经过测试大部分卡在1ms的重试间隔下,只需要重试一次即可继续发送ACMD41(SD_SEND_OP_COND)。
retry = 0;
// 2025-04-01: add ACMD retry for SANDISK 32G Card
do {
	/* send CMD55(APP_CMD) to indicate next command is application specific command */
	sdio_command_response_config(SD_CMD_APP_CMD, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
	sdio_wait_type_set(SDIO_WAITTYPE_NO);
	sdio_csm_enable();

	/* check if some error occurs */
	status = r1_error_check(SD_CMD_APP_CMD);
	if(SD_OK == status) {
		break;
	}
	osDelay(1); retry++;
}while(retry < 10); // 10ms is enough, maybe...
  • 新增sd_switch_mode()切换高速模式
    按以下序列发送命令参数,先查询是否支持高速模式,不支持则返回SD_FUNCTION_UNSUPPORTED
const uint32_t cmd_array[] = {0x00FFFFF0u, 0x80FFFFF0u};
  • 修改sd_bus_mode_config()添加参数配置时钟速度
  • 修改sd_block_read()函数中,readaddr由调用者控制类型,标准容量 SD 存储卡数据地址以字节为单位,高容量 SD 存储卡数据地址以块(512 字节)为单位。
sd_error_enum sd_block_read(uint32_t *preadbuffer, uint32_t readaddr, uint16_t blocksize) {
....
    /* blocksize is fixed in 512B for SDHC card */
    if(SDIO_HIGH_CAPACITY_SD_CARD == cardtype){
        blocksize = 512;
		// readaddr /= 512;
    }
...
}
  • 写多数据块(CMD25)前添加SD_R1_READY_FOR_DATA的状态位查询
sd_error_enum sd_multiblocks_write(uint32_t *pwritebuffer, uint32_t writeaddr, uint16_t blocksize, uint32_t blocksnumber)
{
...
	response = sdio_response_get(SDIO_RESPONSE0);
	timeout = SDIO_PULL_TIMEOUT;
	while((0 == (response & SD_R1_READY_FOR_DATA)) && (timeout > 0)){
		/* continue to send CMD13 to polling the state of card until buffer empty or timeout */
		--timeout;
		/* send CMD13(SEND_STATUS), addressed card sends its status registers */
		sdio_command_response_config(SD_CMD_SEND_STATUS, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
		sdio_wait_type_set(SDIO_WAITTYPE_NO);
		sdio_csm_enable();
		/* check if some error occurs */
		status = r1_error_check(SD_CMD_SEND_STATUS);
		if(SD_OK != status){
			return status;
		}
		response = sdio_response_get(SDIO_RESPONSE0);
	}
	if(0 == timeout){
		return SD_ERROR;
	}
...
}
  • 新增DMA中断处理
    在调用sd_block_read,sd_multiblocks_read,sd_block_write,sd_multiblocks_write时,调用者线程会因为等待信号量而进入阻塞状态,在DMA完成中断中释放信号量,使调用者线程获得继续向下执行的机会。
void DMA1_Channel3_4_IRQHandler(void) {
    uint32_t flag = DMA_INTF(DMA1);
    if(flag & DMA_FLAG_ADD(DMA_INT_FLAG_FTF, DMA_CH3)) {
        dma_interrupt_flag_clear(DMA1, DMA_CH3, DMA_INT_FLAG_FTF);
        if(transerror == SD_OK) {
            osSemaphoreRelease(sem_sdio);
        }
    }
}
在SDIO中断中,当传输出错时也需要释放信号量。
void SDIO_IRQHandler(void) {
    // update the 'transerror' variable
    sd_interrupts_process();
    if(transerror != SD_OK) {
        osSemaphoreRelease(sem_sdio);
    }
}

使用示例

挂载SD卡:实例中使用了FileX文件系统。

static FX_MEDIA sdio_media;
static rt_align(4) uint8_t filex_cache[FX_MAX_SECTOR_CACHE * 512];

static sd_card_info_struct sd_cardinfo;
static uint32_t cardstate = 0;
static uint16_t buswidth = 0;
static uint16_t busspeed = 0;

/**
 * @return -1: card not exist
 *         -2: select card or get status fail
 *         -3: card locked
 *         -4: formated fail
 *         -5: open fail
 * */
static int mount_filesystem(void) {
    sd_error_enum status = SD_OK;
    uint32_t fxstatus;
    uint32_t block_count;

    status = sd_init();
    if(SD_OK == status){
        sd_card_information_get(&sd_cardinfo);
    }else {
        sd_deinit();
        return -1;
    }

    status = sd_card_select_deselect(sd_cardinfo.card_rca);
    if(SD_OK == status){
        status = sd_cardstatus_get(&cardstate);
        if(cardstate & SD_CARDSTATE_LOCKED){
            return -3;
        }
    }

    if(SD_OK == status) {
        buswidth = 1;
        busspeed = 24;
        status = sd_switch_mode(SD_SPEED_MODE_HS);
        if(status == SD_OK) {
            status = sd_bus_mode_config(SD_BUSWIDTH_4BIT, SD_CLK_DIV_40MHZ);
            if(status == SD_OK) {
                buswidth = 4;
                busspeed = 40;
            }
        }else {
            status = sd_bus_mode_config(SD_BUSWIDTH_4BIT, SD_CLK_DIV_24MHZ);
            if(status == SD_OK) {
                buswidth = 4;
            }
        }

        fx_system_initialize();
        block_count = (sd_cardinfo.card_csd.c_size + 1) * 1024;

        fxstatus = fx_media_open(&sdio_media, NULL, fx_sdcard_driver, NULL, filex_cache, sizeof(filex_cache));
        if(fxstatus != FX_SUCCESS) {
            rt_kprintf("error1:0x%02x\n", fxstatus);
            fxstatus = fx_media_format(&sdio_media, fx_sdcard_driver, NULL, filex_cache, sizeof(filex_cache),
                    "FILEX", 1, 64, 0, block_count, 512, 32, 1, 1); // 16KB/cluster
            if(fxstatus != FX_SUCCESS) {
                rt_kprintf("error2:0x%02x\n", fxstatus);
                return -4; // formated fail
            }
            fxstatus = fx_media_open(&sdio_media, NULL, fx_sdcard_driver, NULL, filex_cache, sizeof(filex_cache));
            if(fxstatus != FX_SUCCESS) {
                rt_kprintf("error3:0x%02x\n", fxstatus);
                return -5; // open fail
            }
        }
        return 0; // success
    }else {
        return -2;
    }
}

格式化卡:

void cardformat(void) {
    if(sd_isinit() != SD_OK) {
        rt_kprintf("Card NOT INIT\n");
        return;
    }
    rt_kprintf("waiting...\n");
    uint32_t block_count = (sd_cardinfo.card_csd.c_size + 1) * 1024;
    uint32_t fxstatus = fx_media_format(&sdio_media, fx_sdcard_driver, NULL, filex_cache, sizeof(filex_cache),
                    "FILEX", 1, 64, 0, block_count, 512, 32, 1, 1); // 16KB/cluster
    rt_kprintf("format result:0x%02x\n", fxstatus);
}
MSH_CMD_EXPORT(cardformat, format tfcard);

输出SD卡信息:

void cardinfo(void) {
    uint8_t sd_spec, sd_spec3, sd_spec4, sd_security;
    uint32_t block_count, block_size;
    uint16_t temp_ccc;
    uint8_t name[8];
    sd_cid_struct *cid;

    if(sd_isinit() != SD_OK) {
        rt_kprintf("Card NOT INIT\n");
        return;
    }
    rt_kprintf("Card Information\n");

    cid = &(sd_cardinfo.card_cid);
    rt_kprintf("Manufacturer ID: 0x%02x\n", cid->mid);
    rt_kprintf("OEM/Application ID: 0x%02x\n", cid->oid);
    rt_kprintf("Name: ");
    rt_memcpy(name, (void *)&(cid->pnm0), 4);
    rt_memcpy(name + 4, (void *)&(cid->pnm1), 4);
    for(int i = 0; i < 6; i++) {
        if((name[i]>=0x20) && (name[i]<0x7F)) {
            rt_kprintf("%c", name[i]);
        }
    }
    rt_kprintf("\n");
    rt_kprintf("Revision: 0x%02x\n", cid->prv);
    rt_memcpy(name, (void *)&(cid->psn), 4);
    rt_kprintf("Serial number: %02x%02x%02x%02x\n", name[3], name[2],name[1],name[0]);
    rt_kprintf("Manufacturing date: 0x%x\n", cid->mdt);

    rt_kprintf("Version: ");
    sd_spec = (sd_scr[1] & 0x0F000000) >> 24;
    sd_spec3 = (sd_scr[1] & 0x00008000) >> 15;
    sd_spec4 = (sd_scr[1] & 0x00000400) >> 10;
    if(2 == sd_spec) {
        if(1 == sd_spec3) {
            if(1 == sd_spec4) {
                rt_kprintf("4.xx\n");
            }else {
                rt_kprintf("3.0x\n");
            }
        }else {
            rt_kprintf("2.00\n");
        }
    }else if(1 == sd_spec) {
        rt_kprintf("1.10\n");
    }else if(0 == sd_spec) {
        rt_kprintf("1.0x\n");
    }

    rt_kprintf("Type: ");
    sd_security = (sd_scr[1] & 0x00700000) >> 20;
    if(2 == sd_security) {
        rt_kprintf("SDSC card\n");
    }else if(3 == sd_security) {
        rt_kprintf("SDHC card\n");
    }else if(4 == sd_security) {
        rt_kprintf("SDXC card\n");
    }

    block_count = (sd_cardinfo.card_csd.c_size + 1)*1024;
    block_size = 512;
    rt_kprintf("Device Size: %dKB\n", sd_card_capacity_get());
    rt_kprintf("Block Size: %dB\n", block_size);
    rt_kprintf("Block Count: %d\n", block_count);

    rt_kprintf("Bus Width: %dBit\n", buswidth);
    rt_kprintf("Bus Clock: %dMHz\n", busspeed);

    temp_ccc = sd_cardinfo.card_csd.ccc;
    rt_kprintf("Card Command Classes: %x\n", temp_ccc);

    rt_kprintf("Features:\n");
    if(sd_cardinfo.card_csd.read_bl_partial){
        rt_kprintf("Partial blocks for read allowed\n" );
    }
    if(sd_cardinfo.card_csd.write_bl_partial){
        rt_kprintf("Partial blocks for write allowed\n" );
    }
    if((SD_CCC_BLOCK_READ & temp_ccc) && (SD_CCC_BLOCK_WRITE & temp_ccc)){
        rt_kprintf("Block operation supported\n");
    }
    if(SD_CCC_ERASE & temp_ccc){
        rt_kprintf("Erase supported\n");
    }
    if(SD_CCC_WRITE_PROTECTION & temp_ccc){
        rt_kprintf("Write protection supported\n");
    }
    if(SD_CCC_LOCK_CARD & temp_ccc){
        rt_kprintf("Lock unlock supported\n");
    }
    if(SD_CCC_APPLICATION_SPECIFIC & temp_ccc){
        rt_kprintf("Application specific supported\n");
    }
    if(SD_CCC_IO_MODE & temp_ccc){
        rt_kprintf("I/O mode supported\n");
    }
    if(SD_CCC_SWITCH & temp_ccc){
        rt_kprintf("Switch function supported\n");
    }
}
MSH_CMD_EXPORT(cardinfo, show tfcard info);

卡片信息

Card Information
Manufacturer ID: 0x03
OEM/Application ID: 0x5344
Name: 23DSG
Revision: 0x85
Serial number: 54bb003e
Manufacturing date: 0x161
Version: 3.0x
Type: SDHC card
Device Size: 31166976KB
Block Size: 512B
Block Count: 62333952
Bus Width: 4Bit
Bus Clock: 40MHz
Card Command Classes: 5b5
Features:
Block operation supported
Erase supported
Lock unlock supported
Application specific supported
Switch function supported

速度测试

CMD25连续写20KB

时钟 Ticks 速度 卡类型 备注
40M 525543 4.4596MB/s SANDISK-32G
60M 416303 5.6299MB/s SANDISK-32G
60M 411787 5.7145MB/s SANDISK-32G
60M 408590 5.7592MB/s SANDISK-32G no print log
60M 409943 5.7172MB/s SANDISK-32G no print log

CMD18连续读20KB

时钟 Ticks 速度 卡类型
40M 188076 12.4617MB/s SDTRUVAL-16G
60M 127804 18.3387MB/s SANDISK-32G
60M 147390 15.9017MB/s SDTRUVAL-16G
60M 146691 15.9775MB/s SDTRUVAL-16G
60M 133362 17.5744MB/s SDTRUVAL-16G

不同时钟的CMD18连续读取

时钟 大小 Ticks 速度 卡类型
24MHZ 4K 95183 4.9247MB/s SANDISK-32G
40MHZ 4K 76923 6.0939MB/s SANDISK-32G
60MHZ 4K 69888 6.7071MB/s SANDISK-32G
60MHZ 8K 86264 10.8678MB/s SANDISK-32G

卡时钟40MHz

posted @ 2025-04-04 21:04  Yanye  阅读(399)  评论(0)    收藏  举报