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