【GD32】从零开始学GD32单片机高级篇——SDIO外设详解(GD32F470ZGT6)

news2025/7/5 16:51:31

目录

  • 简介
  • 总线拓扑
  • 总线操作
    • “无响应” 和 “无数据” 操作
    • 多块读写操作
    • 数据流读写操作
  • 总线协议
    • 命令
    • 响应
      • R1/R1b (普通命令响应)
      • R2 (CID, CSD 寄存器)
      • R3 (OCR 寄存器)
      • R4 (Fast IO)
      • R4b(Fast IO)
      • R5 (中断请求)
      • R5b(中断请求)
      • R6 (发布的RCA响应)
      • R7 (卡接口条件)
  • SD卡操作模式
    • 卡识别
    • 数据操作模式
  • 例程

简介

SDIO(Secure Digital Input and Output)全称安全的数字输入输出接口;是从SD内存卡接口的基础上演化出来的一种外设接口。SDIO接口兼容以前的SD内存卡,并且可以连接支持SDIO接口的设备。它可以连接SD 卡、SD I/O卡、多媒体卡(MMC)和 CE-ATA卡主机接口;除了连接常见的存储介质外,它还可以用于连接WiFi、GPS、摄像头等设备。

本文基于GD32F470ZGT6芯片,其SDIO外设对存储卡的兼容性如下:

  1. MMC:与多媒体卡系统规格书 V4.2 及之前的版本全兼容。有三种不同的数据总线模式:
    1 位(默认)、4 位和 8 位;
  2. SD 卡:与 SD 存储卡规格版本 2.0 全兼容;
  3. SD I/O:与 SD I/O 卡规格版本 2.0 全兼容,有两种不同的数据总线模式:1 位(默认)和 4
    位;
  4. CE-ATA:与 CE-ATA 数字协议版本 1.1 全兼容;

总线拓扑

在这里插入图片描述
SDIO外设通过时钟线(CLK)、命令线(CMD)和数据线(DATA)进行通讯;其中,根据SDIO模式的不同数据线的数量也会有不同,分别有1bit模式、4bit模式和8bit模式

在SDIO总线上的每一个消息都是由以下3个部分组成的:

  1. 命令:命令是启动一个操作的令牌,从主机发送到卡;由CMD线进行传输。
  2. 响应:响应是从卡发送到主机,作为命令的回应;由CMD线进行传输。
  3. 数据:数据可以从卡传输到主机或者从主机传输到卡;通过DATA线传送。

其中SDIO的命令也分为2种——流命令和面向块的命令

  1. 流命令:这些命令发起连续的数据流,只有当 CMD 信号线上出现停止命令时,数据传输
    终止。该模式将命令的开销减少到最低(仅支持 MMC)。
  2. 面向块的命令:这些命令成功发送一个数据块后紧跟一个 CRC 校验。读和写操作允许单
    个或多个块传输。与连续读相同,当 CMD 信号线上出现停止命令时,多块传输终止。

总线操作

“无响应” 和 “无数据” 操作

总线上的基本操作是命令/响应操作,如下图;有“无响应”操作和“无数据”操作两种。
在这里插入图片描述
无响应操作即主机只发送命令,但设备不响应;无数据操作即主机发送命令,设备响应,但设备和主机都不发送数据。

多块读写操作

多块读操作即主机发送命令,设备响应后,数据会在DAT线上以块的形式,一块块发送到主机,每个块的后面都紧跟CRC的校检;需要停止读数据时,主机发送停止命令,设备响应后即可停止。
SD存储卡、SD I/O卡(包括仅IO卡和组合卡)和CE-ATA设备直接的数据传输是以数据块
的方式完成的。
在这里插入图片描述
在这里插入图片描述

数据流读写操作

MMC卡以数据块或数据流方式进行数据传输。数据流读操作也是主机发送命令,设备响应后,设备通过DAT线发送数据,在该模式下数据是连续不断的,只到主机发送停止命令,设备响应后停止。
在这里插入图片描述
在这里插入图片描述

总线协议

命令

SDIO有4种控制卡的命令:

  1. 广播命令(bc),发送到所有卡,没有响应;
  2. 带响应的广播命令(bcr),发送到所有卡,同时从所有卡收到响应;
  3. 寻址(点对点)命令(ac),发送到寻址的卡上,DAT 信号线没有数据传输;
  4. 寻址(点对点)的数据传输的命令(adtc),发送到寻址的卡上,DAT 信号线进行数据传输。

所有命令都是48位的固定码长,定义如下图所示。
在这里插入图片描述
一个命令总是从一个起始位(始终为0)开始,随后的位表示传输的方向(主机=1)。接下来的6位表示命令的索引,该值被解释为一个二进制编码的数字(0到63之间)。一些命令需要一
个参数(例如,一个地址),由32位编码。上表中的表示为“x”的值表示这个变量依赖于该命
令。所有的命令有一个CRC 7位校验,由结束位(总是 1)终止。

响应

响应的类型有7种:

  1. R1/R1b: 普通命令响应
  2. R2: CID, CSD 寄存器
  3. R3: OCR 寄存器
  4. R4: Fast I/O
  5. R5: 中断请求
  6. R6: 发布的 RCA 响应
  7. R7: 卡接口条件

除了R2的响应长度是136位,其他响应长度均为48位

R1/R1b (普通命令响应)

在这里插入图片描述
长度为48位,位[45:40]指示要响应的命令索引。

R2 (CID, CSD 寄存器)

在这里插入图片描述
长度为136位,CID寄存器的内容作为对命令CMD2和CMD10的响应被发送,CSD寄
存器的内容将作为以CMD9响应被发送。

R3 (OCR 寄存器)

在这里插入图片描述
长度为48位,OCR寄存器的内容作为SD存储卡或MMC的响应被发送。

R4 (Fast IO)

R4响应仅适用于MMC。长度为48位。参数域包括选定卡的RCA,被读取或写入寄存器的地址及其内容。
在这里插入图片描述

R4b(Fast IO)

R4b响应仅适用于SD I/O卡。长度为48位。SD I/O卡接收到CMD5命令后会返回一个唯一的 SD I/O卡响应R4。
在这里插入图片描述
在这里插入图片描述

R5 (中断请求)

在这里插入图片描述
R5响应仅适用于MMC。长度为48位。若这个响应由主机产生,参数中RCA域为0x0。

R5b(中断请求)

在这里插入图片描述
R5b响应仅适用于SD I/O卡。SD I/O卡对于CMD52和CMD53命令的响应是R5。

R6 (发布的RCA响应)

在这里插入图片描述
代码长度为48位。位[45:40]表示对CMD3响应的命令索引。参数字段的16个最高位比特用于已发布的RCA号。

R7 (卡接口条件)

在这里插入图片描述
在这里插入图片描述
仅适用于SD存储卡。代码长度为48位。位[19:16]表明该卡支持的电压范围。

SD卡操作模式

卡识别

卡识别过程如下:
在这里插入图片描述
上电后,主机进入卡识别模式,寻找总线上的新卡;而卡则处于空闲状态。在与卡进行通信前主机必须先知道卡的电压范围,对于SD卡,需调用ACMD41命令;对于MMC卡需调用CMD1命令;对于SD I/O卡,需调用CMD5命令。另外,对于支持SDIO V2.0的SD卡,在调用ACMD41命令前还需调用CMD8命令,让卡知道主机支持物理层2.00协议及高版本功能。
接着卡会进入准备状态,然后发送CMD2命令使卡进入识别状态;最后再发送CMD3,使卡进入待机状态。

数据操作模式

数据操作模式过程如下:
在这里插入图片描述
数据操作模式比较复杂,这里只介绍SD卡的流程。
对于写操作,主机先发CMD7,选中对应的卡,使其进入传输状态。接着发送CMD23(单块写命令)或CMD24(多块写命令)使卡进入数据接收状态,然后主机就可以开始传输数据。主机传输完指定的数据后必须发送CMD12命令终止数据传输,此时卡会进入编程状态。当卡完成数据的存储后,会自动返回传输状态。上面的过程中我们可以通过CMD13命令轮询检查卡的状态,判断编程状态是否已退出。
对于读操作,同样,主机先发CMD7命令,选中对应的卡,卡进入传输状态。接着发送CMD17(单块读操作)或CMD18(多块读操作)使卡进入数据发送状态,然后主机开始接收数据,数据传输完成卡会自动返回传输状态。

例程

例程中实现了SD卡的初始化、信息读取、单块读写操作、锁卡操作和多块读写操作。

int main(void)
{
	nvic_priority_group_set(NVIC_PRIGROUP_PRE1_SUB3);
	nvic_irq_enable(SDIO_IRQn, 0, 0);

	debug_init();
	
	printf("sdcard demo\r\n");
	
	// 初始化SD卡
	int i = 5;
	while ((SD_OK != sdcard_init()) && (i--));
	if (i == 0)
	{
		printf("sdcard init failed\r\n");
		while (1);
	}

	// 打印卡信息
	sdcard_info_print();
	
	// 测试
	sdcard_read_write_test();
	sdcard_lock_test();
	sdcard_multi_read_write_test();

    while(1)
	{
    }
}

兆易创新官方固件库很贴心地自带一个SDIO外设的驱动库,在sdio的例程里面;SD卡初始化的代码如下。

static sd_error_enum sdcard_init(void)
{
    sd_error_enum status = SD_OK;
    uint32_t cardstate = 0;
	
	// 初始化SD卡
    if ((status = sd_init()) != SD_OK)
	{
		printf("sdcard init failed\r\n");
		return status;
	}
	
	// 获取SD卡信息
    if(SD_OK != (status = sd_card_information_get(&sd_cardinfo)))
	{
        printf("get sdcard info failed\r\n");
		return status;
    }
	
	// 片选SD卡
    if(SD_OK != (status = sd_card_select_deselect(sd_cardinfo.card_rca)))
	{
        printf("select card failed\r\n");
		return status;
    }
	
	// 获取SD卡状态
    if (SD_OK != (status = sd_cardstatus_get(&cardstate)))
	{
		printf("get card status failed\r\n");
		return status;
	}
	else if(cardstate & 0x02000000)
	{
		printf("the card is locked!\r\n");
		while(1);
	}

	// 设置4bit总线模式
    if(SD_OK != (status = sd_bus_mode_config(SDIO_BUSMODE_4BIT)))
	{
		printf("set bus mode failed\r\n");
		return status;
    }

	// 设置DMA传输模式
    if(SD_OK != (status = sd_transfer_mode_config(SD_DMA_MODE)))
	{
		printf("set dma mode failed\r\n");
		return status;
    }
	
	return status;
}

先调用sd_init函数初始化SDIO外设,驱动库内的代码如下。

sd_error_enum sd_init(void)
{
    sd_error_enum status = SD_OK;
    /* configure the RCU and GPIO, deinitialize the SDIO */
    rcu_config();
    gpio_config();
    sdio_deinit();

    /* configure the clock and work voltage */
    status = sd_power_on();
    if(SD_OK != status) {
        return status;
    }

    /* initialize the card and get CID and CSD of the card */
    status = sd_card_init();
    if(SD_OK != status) {
        return status;
    }

    /* configure the SDIO peripheral */
    sdio_clock_config(SDIO_SDIOCLKEDGE_RISING, SDIO_CLOCKBYPASS_DISABLE, SDIO_CLOCKPWRSAVE_DISABLE, SD_CLK_DIV_TRANS);
    sdio_bus_mode_set(SDIO_BUSMODE_1BIT);
    sdio_hardware_clock_disable();

    return status;
}

先使能外设时钟并配置管脚,所有使用到的管脚配置成复用推挽模式即可。
接着调用sd_power_on函数进行SD卡的配置;函数内部主要是先发送CMD0命令复位卡,然后发送CMD8命令判断卡版本,最后发送ACMD41命令获取卡电压值和容量支持信息。
然后调用sd_card_init函数初始化SD卡;函数内部主要是先发送CMD2获取卡的CID值,接着发送CMD3命令使卡发布新的RCA地址,最后发送CMD9命令获取卡的CSD值。

卡初始化完后,调用sd_card_select_deselect函数,传入卡的RCA地址来选中卡;因为卡在复位后默认是工作在1bit模式下,因此根据需要调用sd_bus_mode_config函数使卡工作在4bit模式;最后根据需要调用sd_transfer_mode_config函数,使用DMA传输卡数据。

例程第一项测试单块数据的读写,代码如下。

static void sdcard_read_write_test(void)
{
	sd_error_enum sd_error;
	
	for (uint32_t i = 0; i < 512; i++)
		buf_write[i] = i;
	memset(buf_read, 0, sizeof(buf_read));
	
    /* single block operation test */
    sd_error = sd_block_write(buf_write, 100 * 512, 512);
    if(SD_OK != sd_error)
	{
        printf("\r\n Block write fail!");
		return;
    }
	else
	{
        printf("\r\n Block write success!");
    }
	
    sd_error = sd_block_read(buf_read, 100 * 512, 512);
    if(SD_OK != sd_error)
	{
        printf("\r\n Block read fail!");
		return;
    }
	else
	{
        printf("\r\n Block read success!");
	}
}

单块写调用sd_block_write函数;函数内部先发送CMD16设置块大小,然后发送CMD24命令写入一个块的数据。
单块读调用sd_block_read函数;函数内部操作与单块写同理,唯一的不同是最后发送CMD17命令读取一个块的数据。

例程第二项测试卡的上锁和解锁,代码如下。

static void sdcard_lock_test(void)
{
	sd_error_enum sd_error;
	
    if(SD_CCC_LOCK_CARD & sd_cardinfo.card_csd.ccc)
	{
        /* lock the card */
        sd_error = sd_lock_unlock(SD_LOCK);
        if(SD_OK != sd_error)
		{
            printf("\r\n Lock failed!");
			return;
        }
		else
		{
            printf("\r\n The card is locked!");
        }
		
        sd_error = sd_erase(100 * 512, 101 * 512);
        if(SD_OK != sd_error)
            printf("\r\n Erase failed!");
        else
            printf("\r\n Erase success!");

        /* unlock the card */
        sd_error = sd_lock_unlock(SD_UNLOCK);
        if(SD_OK != sd_error)
		{
            printf("\r\n Unlock failed!");
			return;
        }
		else
		{
            printf("\r\n The card is unlocked!");
        }
		
        sd_error = sd_erase(100 * 512, 101 * 512);
        if(SD_OK != sd_error)
            printf("\r\n Erase failed!");
        else
            printf("\r\n Erase success!");
    }
}

使用sd_lock_unlock函数可实现上锁和解锁;上锁和解锁都是通过发送CMD41命令实现,若我们想知道卡是否上锁可以随时发送CMD13命令来判断。
例程中在上锁和解锁后都调用了sd_erase函数擦除指定块,验证是否上锁或解锁成功,函数内部先发送CMD32CMD33命令分别设置擦除的块起始地址和结束地址,最后发送CMD38命令执行擦除。

例程最后一项测试多块读写,代码如下。

static void sdcard_multi_read_write_test(void)
{
	sd_error_enum sd_error;
	
	for (uint32_t i = 0; i < 512; i++)
		buf_write[i] = i;
	memset(buf_read, 0, sizeof(buf_read));

    sd_error = sd_multiblocks_write(buf_write, 200 * 512, 512, 3);
    if(SD_OK != sd_error)
	{
        printf("\r\n Multiple block write fail!");
		return;
    }
	else
	{
        printf("\r\n Multiple block write success!");
    }
	
    sd_error = sd_multiblocks_read(buf_read, 200 * 512, 512, 3);
    if(SD_OK != sd_error)
	{
        printf("\r\n Multiple block read fail!");
		return;
    }
	else
	{
        printf("\r\n Multiple block read success!");
	}
}

多块写操作调用sd_multiblocks_write函数,内部主要是先发送CMD16命令设置块大小,然后发送ACMD23命令预擦除要写入的区域,最后发送CMD25命令写入所有的数据。
多块读操作调用sd_multiblocks_read函数,流程和多块写类似,也是先发送CMD16命令设置块大小,然后发送CMD18命令读取指定数量的块数据。

例程的完整打印如下。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1790963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

揭秘数字工厂:如何运用AGV、LMS和WMS成为制造业的隐藏神器

揭秘数字工厂&#xff1a;如何运用AGV、LMS和WMS成为制造业的隐藏神器 &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f3c6; 博客首页 怒放吧德德 To记录领地 &a…

当企业越来越难做,精益变革能带来什么改变?

随着技术的不断进步和消费者需求的日益多样化&#xff0c;传统的管理模式和生产方式已经难以适应时代的发展。越来越多的企业开始陷入困境&#xff0c;难以在激烈的市场竞争中立足。然而&#xff0c;正是在这样的背景下&#xff0c;精益变革应运而生&#xff0c;为企业带来了前…

【AI大模型】Transformers大模型库(四):AutoTokenizer

目录​​​​​​​ 一、引言 二、自动分词器&#xff08;AutoTokenizer&#xff09; 2.1 概述 2.2 主要特点 2.3 代码示例 三、总结 一、引言 这里的Transformers指的是huggingface开发的大模型库&#xff0c;为huggingface上数以万计的预训练大模型提供预测、训练等服…

Spark SQL - 操作数据帧

本教程将通过一个具体的案例来演示如何在Spark SQL中操作数据帧。我们将从获取学生数据帧开始&#xff0c;包括两种方法&#xff1a;一是由数据集转换而来&#xff0c;二是直接读取文件生成数据帧。然后&#xff0c;我们将对数据帧进行各种操作&#xff0c;如投影、过滤、统计和…

数电实验中设计数字钟所用到的代码详细版(Verilog语言来实现)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、模6计数器的代码&#xff1a;二、模10计数器的代码三、模60计数器的代码四、模24计数器的代码五、显示控制电路模块代码六、1khz和250khz时钟输出实现的代码…

如何充分利用代理IP扩大网络接触面

目录 前言 第一部分&#xff1a;什么是代理IP&#xff1f; 第二部分&#xff1a;如何获取代理IP&#xff1f; 1. IP质量 2. 匿名性 3. 限制 第三部分&#xff1a;如何使用代理IP&#xff1f; 第四部分&#xff1a;如何充分利用代理IP&#xff1f; 总结&#xff1a; 前…

CentOS 9安装Kubernetes(k8s)集群

前言 1、版本说明 系统版本&#xff1a;CentOS 9 k8s版本&#xff1a;v1.29.5 docker版本&#xff1a;26.1.3 harbor&#xff1a;v2.9.4 2、提前准备好1台虚拟机&#xff0c;可以参考博客&#xff1a;Vmware 17安装 CentOS9 3、虚拟机提前安装好docker&#xff0c;参考博客&a…

Python 实现乘数加密法

乘数加密是简单代替密码的一种。乘数加密法脱胎于凯撒加密法,加密和解密符号设计把他们转换成数字,加上或者减去密钥,然后把新的数字转换回符号,当我们把加减密钥变成乘以密钥,就是乘法加密法。有关凯撒加密法可以看之前的文章《Python实现凯撒加解密》。 加密过程 乘数加…

二叉树练习题(2024/6/5)

1翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xff1a;[2,3,1]…

电子电气架构 —— 刷写模式:并行刷写

电子电气架构 —— 刷写模式:并行刷写 我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 人们会在生活中不断攻击你。他们的主要武器是向你灌输对自己的怀疑:你的价值、你的能力、你的潜力。他们往往会将此…

C++ : 模板初阶

标题&#xff1a;C : 模板初阶 水墨不写bug 正文开始&#xff1a; C语言的问题 &#xff1a; 写不完的swap函数 在学习C语言时&#xff0c;我们有一个经常使用的函数swap函数&#xff0c;它可以将两个对象的值交换。 我们通常这样实现它&#xff1a; void swap(int t1,int t2)…

【网络协议 | HTTP】HTTP总结与全梳理(一) —— HTTP协议超详细教程

&#x1f525;博客简介&#xff1a;开了几个专栏&#xff0c;针对 Linux 和 rtos 系统&#xff0c;嵌入式开发和音视频开发&#xff0c;结合多年工作经验&#xff0c;跟大家分享交流嵌入式软硬件技术、音视频技术的干货。   ✍️系列专栏&#xff1a;C/C、Linux、rtos、嵌入式…

【微信小程序】模板语法

数据绑定 对应页面的 js 文件中 定义数据到 data 中&#xff1a; 在页面中使用 {{}} 语法直接使用&#xff1a; 事件绑定 事件触发 常用事件&#xff1a; 事件对象的属性列表&#xff08;事件回调触发&#xff0c;会收到一个事件对象 event&#xff0c;它的详细属性如下&…

28 hive安装-本地模式

1.安装mysql&#xff08;参考文章&#xff1a;centos7.8安装Mysql8.4-CSDN博客&#xff09; 2.将mysql驱动拷贝到/opt/module/hive/lib目录下 &#xff08;直接windows通过finalShell上传&#xff09; 3./opt/module/hive/conf目录下新建hive-site.xml文件&#xff0c;进行配置…

InvokeAI 最新版安装指南

由于stable diffusion webui不太好安装&#xff0c;或者你使用一些SD整合包&#xff0c;可免除复杂安装&#xff0c;但你认为SD的操作界面太复杂&#xff0c;所以今天介绍一款支持新手一键安装的Stable Diffusion工具包InvokeAI。 1.什么是InvokeAI InvokeAI 是一个创新的开源…

点量3D实时云渲染平台:三步轻松实现云流化

3D实时云渲染技术通过在云端执行3D渲染任务&#xff0c;并将渲染结果实时传送到用户端&#xff0c;它不仅降低了用户的硬件要求&#xff0c;还提高了数据处理的安全性和效率&#xff0c;为用户提供了高效的体验。 可以通过下载“点量云流服务单机版”进行低延时、高画质、沉浸…

vivado BEL

描述 通常&#xff0c;BEL或基本元素对应于设计的网表视图中的叶单元。 BEL是目标Xilinx FPGA上的设备对象&#xff0c;用于放置或映射基本网表 触发器、LUT和进位逻辑等对象。 BEL在SITE对象&#xff08;如SLICE和IO块&#xff09;中的设备上分组在一起 &#xff08;IOB&#…

kafka-消费者服务搭建配置简单消费(SpringBoot整合Kafka)

文章目录 1、使用efak 创建 主题 my_topic1 并建立6个分区并给每个分区建立3个副本2、创建生产者发送消息3、application.yml配置4、创建消费者监听器5、创建SpringBoot启动类6、屏蔽 kafka debug 日志 logback.xml7、引入spring-kafka依赖 1、使用efak 创建 主题 my_topic1 并…

VS2022,lib调用dll工程的一个函数

lib工程本身是一个静态库工程&#xff0c;没有链接器设置。然而&#xff0c;我们依然可以在lib工程中调用DLL工程中的函数&#xff0c;只需要确保头文件正确导入&#xff0c;并在最终使用lib的可执行文件项目中正确链接DLL的.lib文件。下面是一个详细的步骤说明&#xff1a; 假…

做自媒体素材哪里找?做自媒体必备的几个高质量素材网站分享

在自媒体的世界里&#xff0c;内容是王道。无论是视频还是文章&#xff0c;优秀的自媒体作品都需要有力的内容和高质量的素材作支撑。今天&#xff0c;我为大家整理了一些优质的素材网站&#xff0c;帮助每一位自媒体创作者&#xff0c;无论新手还是老手&#xff0c;都能找到适…