七.SDIO外设结构体
其实前面关于SDIO寄存器的讲解已经比较详细了,这里再借助于关于SDIO结构体再进行总结一遍。
标准库函数对 SDIO 外设建立了三个初始化结构体,分别为 SDIO 初始化结构体SDIO_InitTypeDef、SDIO 命令初始化结构体 SDIO_CmdInitTypeDef 和 SDIO 数据初始化结
构体 SDIO_DataInitTypeDef。这些结构体成员用于设置 SDIO 工作环境参数,并由 SDIO 相应初始化配置函数或功能函数调用,这些参数将会被写入到 SDIO 相应的寄存器,达到配置 SDIO 工作环境的目的。
至于为什么需要一个命令结构体与数据结构体,就是为了方便我们配置SDIO关于寄存器位,因为发送命令或者数据需要很多参数配置。
1.SDIO初始化结构体
SDIO 初始化结构体用于配置 SDIO 基本工作环境,比如时钟分频、时钟沿、数据宽度等等。它被 SDIO_Init 函数使用。
![1668757870596558.png 1.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAHGDPAAEcdwWBdQo109.png)
-
SDIO_ClockEdge:主时钟 SDIOCLK 产生 CLK 引脚时钟有效沿选择,可选上升沿或下降沿。
![1668757886824494.png 2.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAZ9EXAAD05AlmZcU735.png)
-
SDIO_ClockBypass:时钟分频旁路使用,可选使能或禁用,如果使能旁路,SDIOCLK (72MHZ )直接驱动 CLK 线输出时钟(不满足最高25HZ的要求),如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分频 SDIOCLK,然后输出到 CLK 线。一般选择禁用时钟分频旁路。
![1668757903524201.png 3.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAOLIaAAEsbxtyMhw562.png)
-
SDIO_ClockPowerSave:节能模式选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能节能模式,CLK 线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能 CLK 线输出时钟。
![1668757914619517.png 4.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZSASCQpAAE6S1oEemg698.png)
-
SDIO_BusWide:数据线宽度选择,可选 1 位数据总线、4 位数据总线或 8 为数据总线,系统默认使用 1 位数据总线,操作 SD 卡时在数据传输模式下一般选择 4 位数据总线。它设定 SDIO_CLKCR 寄存器的 WIDBUS 位的值。
![1668757924192566.png 5.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZKAF5tYAAEnpQ7xUQs238.png)
-
SDIO_HardwareFlowControl:硬件流控制选择,可选使能或禁用,它设定SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 发送上溢和下溢错误。
![1668757936849762.png 6.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAILHKAAEZ0LXP2Iw135.png)
-
SDIO_ClockDiv:时钟分频系数,它设定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,设置 SDIOCLK 与 CLK 线输出时钟分频系数:
CLK 线时钟频率=SDIOCLK/([CLKDIV+2])。
![1668757947756897.png 7.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZSAJfNvAAEmbGoy5m4116.png)
2.SDIO命令初始化结构体
![1668757961808228.png 8.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAJQveAAD9jf08jXM323.png)
- SDIO_Argument:作为命令的一部分发送到卡的命令参数,它设定 SDIO 参数寄存器(SDIO_ARG)的值。
![1668757980473816.png 9.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSARQ0rAAHz274LRcY013.png)
(2) SDIO_CmdIndex:命令号选择,它设定 SDIO 命令寄存器(SDIO_CMD)的 CMDINDEX位的值。
![1668757994563172.png 10.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAeuhMAADBd8Wgy5k106.png)
(3) SDIO_Response:响应类型,SDIO 定义两个响应类型:长响应和短响应。根据命令号选择对应的响应类型。SDIO 定义了四个 32 位的 SDIO 响应寄存器(SDIO_RESPx,x=1…4),短响应只用SDIO_RESP1,长响应使用4个(SDIO_RESPx,x=1…4)。
![1668758008924897.png 11.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAAhlYAAFdkzIAKtU689.png)
1)命令响应寄存器
![1668758024102243.png 12.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAUUvHAAK0tA0lNfo183.png)
2)SDIO响应寄存器1~4
![1668758040976960.png 13.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSAAeLoAAOJ480mxi8295.png)
-
SDIO_Wait:等待类型选择,有三种状态可选,一种是无等待状态,超时检测功能启动,一种是等待中断,另外一种是等待传输完成。
![1668758055551153.png 14.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAF4I9AAF8fZya4_0207.png)
-
SDIO_CPSM:命令路径状态机控制,可选使能或禁用 CPSM。它设定 SDIO_CMD 寄存器的 CPSMEN 位的值![1668758071338257.png 15.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAO0gBAADJLUw91NQ783.png)
只要我们使能的了命令状态机,则下面发送命令和接收响应的过程中的状态转换就不用我们管了
![1668758082861542.png 16.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSAC3YdAAQtrr_wmEs782.png)
当我们要发送命令,我们只需要配置这个命令初始化结构体的成员,然后调用下图这个函数,则我们配置的参数写入对应的寄存器位中。
![1668758094371491.png 17.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAKwc2AAN9oVjF8R4542.png)
3.SDIO数据初始化结构体
![1668758105939698.png 18.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOATpw_AAEv74GAj5E043.png)
-
SDIO_DataTimeOut:设置数据传输以卡总线时钟周期表示的超时周期,它设定 SDIO数据定时器寄存器(SDIO_DTIMER)的值。在 DPSM 进入 Wait_R 或繁忙状态后开始递减,直到 0 还处于以上两种状态则将超时状态标志置 1(详情前面的数据通道小节)。
![1668758116287293.png 19.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZSAUPMcAAK_tnY3aeA631.png)
-
SDIO_DataLength:设置传输数据长度。
![1668758136939423.png 20.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAcyWsAADMR3lMyNA270.png)
-
SDIO_DataBlockSize:设置数据块大小,有多种尺寸可选,不同命令要求的数据块可能不同。
![1668758149102692.png 21.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZKAf2aIAAISua3rN1E236.png)
-
SDIO_TransferDir:数据传输方向,可选从主机到卡的写操作,或从卡到主机的读操作。
![1668758162643067.png 22.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZKAQ5YXAADT4rYqEPc429.png)
-
SDIO_TransferMode:数据传输模式,可选数据块或数据流模式。对于 SD 卡操作使用数据块类型。
![1668758177353516.png 23.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAUnuAAADe2cC4xJs532.png)
-
SDIO_DPSM:数据路径状态机控制,可选使能或禁用 DPSM。它设定 SDIO_DCTRL寄存器的 DTEN 位的值。要实现数据传输都必须使能 SDIO_DPSM。![1668758193702240.png 24.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOALtUPAAGE_xXxxEM429.png)
与命令一样使能了数据路径状态机,就不用高那么多麻烦的状态转换了
八.SD卡读写测试实验
我们平时使用的SD 卡都是已经包含有文件系统的,一般不会使用本实验的操作方式读写 SD 卡,但是对学习SD卡的驱动原理非常重要!!!
本实验是进行 SD卡最底层的数据读写操作,直接使用 SDIO 对 SD 卡进行读写,会损坏 SD 卡的文件系统,导致数据丢失,所以做这个实验之前需要备份SD卡数据。
主要是学习SD卡的卡识别过程,以及数据传输工过程,其实就是完全依照前面的两个流程图来实现代码的。
=卡识别模式流程图=![1668758209942780.png 25.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAQzlIAAJBTD77TXo986.png)
=数据传输流程图=
![1668758228795624.png 26.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAWjjOAAKLK5d1f9A897.png)
1.硬件设计
原理图:
![1668758242830393.png 27.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZKAfCulAAJ6YoNnP_c884.png)
实物图:
![1668758263598755.png 28.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZuAHfK-AA_FCVMcu7g965.png)
我这里用的是CS创世的贴片式SD卡,也称之为SD NAND , 内部存储单元架构为SLC,适合存代码。直接上板时相比于拔插式SD卡在抗震和抗PIN氧化方面更有优势,对于缩小整板体积也有一定帮助。
![在这里插入图片描述](//file.elecfans.com/web2/M00/81/64/poYBAGORkZGAGo5TAB0xVEJHvkQ429.png)
详情请参考:雷龙官网
![1668758281420841.png 30.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZeAFiwoAAh91lqbEAU922.png)
2.代码讲解
先看主函数:
![1668758302652802.png 31.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAag54AAS7Iqekeh0467.png)
![1668758317575654.png 32.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAQXclAAFpHnsX6No778.png)
SD_Terst函数:
![1668758372651673.png 33.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAahZ2AAO86qre_TU426.png)
我们主要讲解的就是SD卡的初始化
![1668758393211670.png 34.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAF1nhAAG6ri_kCbA864.png)
SD_Init()函数:
SD_Error SD_Init(void){
SD_Error errorstatus = SD_OK;
NVIC_Configuration();
GPIO_Configuration();
SDIO_DeInit();
errorstatus = SD_PowerON();
if (errorstatus != SD_OK)
{
return(errorstatus);
}
errorstatus = SD_InitializeCards();
if (errorstatus != SD_OK)
{
return(errorstatus);
}
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);
if (errorstatus == SD_OK)
{
errorstatus = SD_GetCardInfo(&SDCardInfo);
}
if (errorstatus == SD_OK)
{
errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
}
if (errorstatus == SD_OK)
{
errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
}
return(errorstatus);}
接下来逐段代码来分析一下:
![1668758413462220.png 35.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAcucyAACHOo2Ho-Y822.png)
errorstatus其实是一个SD_Error类型的枚举变量,SD_Error 是
列举了控制器可能出现的错误、比如 CRC 校验错误、CRC 校验错误、通信等待超时、FIFO 上溢或下溢、擦除命令错误等等。这些错误类型部分是控制器系统寄存器的标志位,部分是通过命令的响应内容得到的,如果是SD_OK则代表没有发送错误,
![1668758432665040.png 36.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZmAOeSSAAvrTrw4prY836.png)
配置SDIO中断:
![1668758457362519.png 37.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOASk2MAAGF9M1YqWg203.png)
![1668758470926545.png 38.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAX12MAAH_2IcKktk150.png)
SDIO 外设底层引脚初始化
![1668758483343720.png 39.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAFeIEAAJdShYUusQ361.png)
![1668758496953492.png 40.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZSAKWNvAAPHcl9PeZA267.png)
复位所有SDIO寄存器
![1668758511999512.png 41.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAA6wAAAG2m41nZXE799.png)
![1668759101122803.png 42.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOATcMrAAG2b-vXUEU488.png)
重点来了:调用SD_PowerON()进入卡识别模式
![1668759125518860.png 43.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZKAP9tvAACGTig2V94086.png)
/*
* 函数名:SD_PowerON
* 描述:确保SD卡的工作电压和配置控制时钟
* 输入:无
* 输出:-SD_Error SD卡错误代码
* 成功时则为 SD_OK
* 调用:在 SD_Init() 调用
*/SD_Error SD_PowerON(void){
SD_Error errorstatus = SD_OK;
uint32_t response = 0, count = 0, validvoltage = 0;
uint32_t SDType = SD_STD_CAPACITY;
/********************************************************************************************************/
/* 上电初始化
* 配置SDIO的外设
* SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_INIT_CLK_DIV)
* 初始化时的时钟不能大于400KHz
*/
/* HCLK = 72MHz, SDIOCLK = 72MHz, SDIO_CK = HCLK/(178 + 2) = 400 KHz */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
/* 不使用bypass模式,直接用HCLK进行分频得到SDIO_CK */
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
/* 空闲时不关闭时钟电源 */
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
/* 初始化的时候暂时先把数据线配置成1根 */
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
/* 失能硬件流控制 */
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);
/* 开启SDIO外设的电源 */
SDIO_SetPowerState(SDIO_PowerState_ON);
/* 使能 SDIO 时钟 */
SDIO_ClockCmd(ENABLE);/********************************************************************************************************/
/* 下面发送一系列命令,开始卡识别流程
* CMD0: GO_IDLE_STATE(复位所以SD卡进入空闲状态)
* 没有响应
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE;
/* 没有响应 */
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No;
/* 关闭等待中断 */
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
/* CPSM在开始发送命令之前等待数据传输结束 */
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/* 检测是否正确接收到cmd0 */
errorstatus = CmdError();
/* 命令发送出错,返回 */
if (errorstatus != SD_OK)
{
/* CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}/********************************************************************************************************/
/* CMD8: SEND_IF_COND
* 发送 CMD8 检查SD卡的电压操作条件
*
* 参数: - [31:12]: 保留 (要被设置为 '0')
* - [11:8] : 支持的电压 (VHS) 0x1 (范围: 2.7-3.6 V)
* - [7:0]: 校验模式 (推荐 0xAA)
* 响应类型: R7
*/
/* 接收到命令sd会返回这个参数 */
SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN;
SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/*检查是否接收到命令*/
errorstatus = CmdResp7Error();
/* 有响应则card遵循sd协议2.0版本 */
if (errorstatus == SD_OK)
{
/* SD Card 2.0 ,先把它定义会sdsc类型的卡 */
CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0;
/* 这个变量用作ACMD41的参数,用来询问是sdsc卡还是sdhc卡 */
SDType = SD_HIGH_CAPACITY;
}
else /* 无响应,说明是1.x的或mmc的卡 */
{
/* 发命令 CMD55 */
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
}
/* CMD55
* 发送cmd55,用于检测是sd卡还是mmc卡,或是不支持的卡
* CMD 响应: R1
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/* 是否响应,没响应的是mmc或不支持的卡 */
errorstatus = CmdResp1Error(SD_CMD_APP_CMD); /********************************************************************************************************/
/* 若 errorstatus 为 Command TimeOut, 说明是MMC 卡
* 若 errorstatus 为 SD_OK ,说明是SD card: SD 卡 2.0 (电压范围不匹配)
* 或 SD 卡 1.x
*/
if (errorstatus == SD_OK) //响应了cmd55,是sd卡,可能为1.x,可能为2.0
{
/*下面开始循环地发送sdio支持的电压范围,循环一定次数*/
/* SD CARD
* 发送 ACMD41 SD_APP_OP_COND ,带参数 0x80100000
*/
while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL))
{
/* 在发送ACMD命令前都要先向卡发送CMD55
* 发送 CMD55 APP_CMD , RCA 为 0
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/* ACMD41
* 命令参数由支持的电压范围及HCS位组成,HCS位置一来区分卡是SDSC还是SDHC
* 0:SDSC
* 1:SDHC
* 响应:R3,对应的是OCR寄存器
*/
SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD | SDType;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp3Error();
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/* 若卡需求电压在SDIO的供电电压范围内,会自动上电并标志pwr_up位
* 读取卡寄存器,卡状态
*/
response = SDIO_GetResponse(SDIO_RESP1);
/* 读取卡的ocr寄存器的pwr_up位,看是否已工作在正常电压 */
validvoltage = (((response >> 31) == 1) ? 1 : 0);
count++; /* 计算循环次数 */
}
if (count >= SD_MAX_VOLT_TRIAL) /* 循环检测超过一定次数还没上电 */
{
errorstatus = SD_INVALID_VOLTRANGE; /* SDIO不支持card的供电电压 */
return(errorstatus);
}
/*检查卡返回信息中的HCS位*/
/* 判断ocr中的ccs位 ,如果是sdsc卡则不执行下面的语句 */
if (response &= SD_HIGH_CAPACITY)
{
CardType = SDIO_HIGH_CAPACITY_SD_CARD; /* 把卡类型从初始化的sdsc型改为sdhc型 */
}
}/* else MMC Card */
return(errorstatus); }
1.配置SDIO初始化结构体**
![1668759343632203.png 44.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSAfQf7AATyNgcoEDM781.png)
配置 SDIO_InitStructure 结构体变量成员并调用 SDIO_Init 库函数完成 SDIO 外设的基本配置,注意此处的 SDIO 时钟分频,由于处于卡识别阶段,其时钟不能超过 400KHz。![1668759365361329.png 45.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOASSk3AADHn7zD2No730.png)
2.发送CMD0命令:要SD卡回到空闲状态
![1668759385645997.png 46.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAN3sjAAPb1mnoeJI618.png)
![1668759408467865.png 47.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSAcO2zAANBdmIrpPM419.png)
那些检测标志全是来源与下图:
![1668759841519410.png 48.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZSAXpvAAAZ1dxeraYw559.png)
3.发送CMD8: 用来识别不同版本的卡和检测卡是否能在主机提供的电压下工作。
1.电压不匹配的 2.0 以上 SD 卡
2.1.0 的 SD 卡
3.不是 SD 卡
4.使用 ACMD41 命令判断卡的具体类型。因为是 A 类命令,所以在发送 ACMD41之前必须先发送 CMD55,CMD55 命令的响应类型的 R1。如果 CMD55 命令都没有响应说明是 MMC 卡或不可用卡。在正确发送 CMD55 之后就可以送ACMD41,并根据响应判断卡类型,ACMD41 的响应号为 R3,CmdResp3Error 函数用于检测命令正确发送并带有超时检测功能,但并不具备响应内容接收功能,需要在判定命令正确发送之后调用 SDIO_GetResponse 函数才能获取响应的内容。
实际上,在有响应时,SDIO 外设会自动把响应存放在 SDIO_RESPx 寄存器中,SDIO_GetResponse 函数只是根据形参返回对应响应寄存器的值。通过判定响应内容值即可确定 SD 卡类型。
![1668759796511017.png 50.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZiAAuIjAA6zL_4o1U8029.png)
![1668760368419068.png 51.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAC-3yAALu5Rcy8Fo446.png)
总结:执行 SD_PowerON 函数无错误后就已经确定了 SD 卡类型,并说明卡和主机电压是匹配的,SD 卡处于卡识别模式下的准备状态。退出 SD_PowerON 函数返回SD_Init 函数,执行接下来代码。
执行 SD_PowerON 函数没有错误后:SD 卡处于卡识别模式下的准备状态
![1668760387254059.png 52.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOACw3iAALgV7f9XcY137.png)
![1668760404238963.png 53.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSAT79fAAJpWkrZBhk711.png)
SD_InitializeCards()函数:
/*
* 函数名:SD_InitializeCards
* 描述:初始化所有的卡或者单个卡进入就绪状态
* 输入:无
* 输出:-SD_Error SD卡错误代码
* 成功时则为 SD_OK
* 调用:在 SD_Init() 调用,在调用power_on()上电卡识别完毕后,调用此函数进行卡初始化
*/SD_Error SD_InitializeCards(void){
SD_Error errorstatus = SD_OK;
uint16_t rca = 0x01;
if (SDIO_GetPowerState() == SDIO_PowerState_OFF)
{
errorstatus = SD_REQUEST_NOT_APPLICABLE;
return(errorstatus);
}
/* 判断卡的类型 */
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
{
/* Send CMD2 ALL_SEND_CID
* 响应:R2,对应CID寄存器
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_SEND_CID;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp2Error();
if (SD_OK != errorstatus)
{
return(errorstatus);
}
/* 将返回的CID信息存储起来 */
CID_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}/********************************************************************************************************/
if ( (SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType)
||(SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType)
||(SDIO_SECURE_DIGITAL_IO_COMBO_CARD == CardType)
||(SDIO_HIGH_CAPACITY_SD_CARD == CardType) ) /* 使用的是2.0的卡 */
{
/* Send CMD3 SET_REL_ADDR with argument 0
* SD Card publishes its RCA.
* 响应:R6,对应RCA寄存器
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/* 把接收到的卡相对地址存起来 */
errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca);
if (SD_OK != errorstatus)
{
return(errorstatus);
}
}/********************************************************************************************************/
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
{
RCA = rca;
/* Send CMD9 SEND_CSD with argument as card's RCA
* 响应:R2对应寄存器CSD(Card-Specific Data)
*/
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)(rca << 16);
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp2Error();
if (SD_OK != errorstatus)
{
return(errorstatus);
}
CSD_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
CSD_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CSD_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CSD_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}/********************************************************************************************************/
/*全部卡初始化成功 */
errorstatus = SD_OK;
return(errorstatus);}
1.判断 SDIO 电源是否启动,如果没有启动电源返回错误。
![1668760424142334.png 54.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZKAEqH6AAFdWZ1TqPs413.png)
2.发送CMD2命令 :是用于通知所有卡通过 CMD 线返回 CID 值,执行 CMD2 发送之后就可以使用 CmdResp2Error 函数获取 CMD2 命令发送情况,发送无错误后=即可以使用 SDIO_GetResponse 函数获取响应内容,它是个长响应,我们把 CMD2 响应内容存放在 CID_Tab 数组内。=![1668760435472504.png 55.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAGXDiAAQaKfUVNNY789.png)
3.发送CMD3命令,用于指示 SD 卡自行推荐 RCA 地址,CMD3 的响应为 R6 类型,CmdResp6Error 函数用于检查 R6 响应错误,它有两个形参,一个是命令号,这里为 CMD3,另外一个是 RCA 数据指针,这里使用 rca变量的地址赋值给它,使得在 CMD3 正确响应之后 rca 变量即存放 SD 卡的 RCA。![1668760451416626.png 56.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZWAPKSzAATojZ5Hl3k948.png)
CmdResp6Error 函数通用会对每个错误位进行必要的检测,如果发现有错误存在则直接返回对应错误类型。
![1668760484734467.png 57.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZaAY9UOAAgoOBaNMFk403.png)
执行完CmdResp6Error 函数之后返回到 SD_InitializeCards 函数中,如果判断无错误说明此刻 SD 卡已经处于数据传输模式。
![1668760508656739.png 58.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSARJ1hAAMY7uTpeFw356.png)
4.发送 CMD9 给指定 RCA 的 SD 卡使其发送返回其 CSD 寄存器内容,这里的 RCA就是在 CmdResp6Error 函数获取得到的 rca。最后把响应内容存放在 CSD_Tab 数组中。![1668760724330445.png 59.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAdeYVAAOfoL9CgoE893.png)
![在这里插入图片描述](//file.elecfans.com/web2/M00/81/ED/pYYBAGORkZGANOq_AABoq0kDZbQ861.png)
执行 SD_InitializeCards 函数无错误后 SD 卡就已经处于数据传输模式下的待机状态,退出 SD_InitializeCards 后会返回前面的 SD_Init 函数,执行接下来代码,以下是 SD_Init 函数的后续执行过程:
![1668760746837108.png 60.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZSAXeqMAAPiy5RBT-g793.png)
-
重新配置 SDIO 外设,提高时钟频率,之前的卡识别模式都设定 CMD 线时钟为小于 400KHz,进入数据传输模式可以把时钟设置为小于 25MHz,以便提高数据传输速率。
![1668760761846070.png 61.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSAJfY5AAQW5R-yVNM008.png)
(2) 调用 SD_GetCardInfo 函数获取 SD 卡信息,它需要一个指向 SD_CardInfo 类型变量地址的指针形参,这里赋值为 SDCardInfo 变量的地址。SD 卡信息主要是 CID和 CSD 寄存器内容,这两个寄存器内容在 SD_InitializeCards 函数中都完成读取过程并将其分别存放在 CID_Tab 数组
和 CSD_Tab 数组
中,=SD_GetCardInfo 函数只是简单的把这两个数组内容整合复制到 SDCardInfo 变量对应成员内。正确执行 SD_GetCardInfo 函数后,SDCardInfo 变量就存放了 SD 卡的很多状态信息,这在之后应用中使用频率是很高的。=
![1668760777915231.png 62.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAH5OAAACr-Zyv4mE829.png)
结构体类型定义:有 SD_CSD、SD_CID、SD_CardStatus 以及 SD_CardInfo。SD_CSD 定义了 SD 卡的特定数据(CSD)寄存器位,一般提供 R2 类型的响应可以获取得到 CSD 寄存器内容。SD_CID 结构体类似 SD_CSD 结构体,它定义 SD 卡CID 寄存器内容,也是通过 R2 响应类型获取得到。SD_CardStatus 结构体定义了SD 卡状态,有数据宽度、卡类型、速度等级、擦除宽度、传输偏移地址等等 SD卡状态。SD_CardInfo 结构体定义了 SD 卡信息,包括了 SD_CSD 类型和 SD_CID类型成员,还有定义了卡容量、卡块大小、卡相对地址 RCA 和卡类型成员。
![1668760791744935.png 63.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAIEJpAAEwqHog0Tw082.png)
主要是存储卡的容量,卡的大小,RCA地址,卡的类型(这些是关键信息,由命令响应返回然后存入这个结构体中)
![1668760807980500.png 64.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZaAPK_nAAo-CncPiQs124.png)
![1668760826225098.png 65.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAcKp4AAKxh22w8cc287.png)
![1668760839558594.png 66.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOActKtAAFq5RBM294812.png)
- 调用 SD_SelectDeselect 函数用于选择特定 RCA 的 SD 卡,它实际是向 SD 卡发送CMD7。执行之后,卡就从待机状态转变为传输模式,可以说数据传输已经是万事俱备了
![1668760875243042.png 67.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZKAc7QfAADXrg5eG1w171.png)
![1668760861707825.png 68.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZSAE_EcAAP7mYtddfY782.png)
![1668760892487671.png 70.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZKAXk3CAALLCSAKvE8999.png)
- 扩展数据线宽度,之前的所有操作都是使用一根数据线传输完成的,使用 4 根数据线可以提高传输性能,调用可以设置数据线宽度,函数只有一个形参,用于指定数据线宽度。
![1668760911529725.png 71.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZKARhgPAADdGCP3qrI383.png)
至此,SD_Init 函数已经全部执行完成。如果程序可以正确执行,接下来就可以进行SD 卡读写以及擦除等操作。
![1668760932712769.png 72.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAP4OUAANs0CHikLU492.png)
SD_EraseTest()函数
![1668760954181580.png 73.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSAHYJyAAUA3XOy2ks706.png)
SD_Erase()函数:
![1668760976183728.png 74.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZqASJp9AA23mHte4hk874.png)
![1668760989315125.png 75.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZKAXj-hAAEbNdP1zM8972.png)
-
检查 SD 卡是否支持擦除功能,如果不支持则直接返回错误。为保证擦除指令正常进行,要求主机一个遵循下面的命令序列发送指令:CMD32->CMD33->CMD38。如果发送顺序不对,SD 卡会设置 ERASE_SEQ_ERROR 位到状态寄存器
![1668761000176941.png 76.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAHba0AAGKuAyMWaE540.png)
![1668761014639679.png 77.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAT6_7AAFpU1nlm98456.png)
-
SD_Erase 函数发送 CMD32 指令用于设定擦除块开始地址,在执行无错误后发送CMD33 设置擦除块的结束地址。
![1668761053736207.png 78.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZWAKgx-AAYR2R4I1IQ059.png)
-
发送擦除命令 CMD38,使得 SD 卡进行擦除操作。SD 卡擦除操作由 SD 卡内部控制完成,不同卡擦除后是 0xff 还是 0x00 由厂家决定。擦除操作需要花费一定时间,这段时间不能对 SD 卡进行其他操作。
![1668761069765839.png 79.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOALvR_AAJwZo2kd80273.png)
-
通过 IsCardProgramming 函数可以检测 SD 卡是否处于编程状态(即卡内部的擦写状态),需要确保 SD 卡擦除完成才退出 SD_Erase 函数。IsCardProgramming 函数先通过发送CMD13 命令 SD 卡发送它的状态寄存器内容,并对响应内容进行分析得出当前 SD 卡的状态以及可能发送的错误。![1668761087940491.png 80.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAIBUJAAGkSZ2bWIo377.png)
![1668761103796876.png 81.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZaAH_ApAAsuv0OruRM269.png)
![1668761133348816.png 82.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAVPjvAAFjhKJ3lnI515.png)
![1668761147196717.png 83.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAHtm1AAFHT1YZDKU782.png)
SD_WriteBlock 函数用于向指定的目标地址写入一个块的数据,它有三个形参,分别为指向待写入数据的首地址的指针变量、目标写入地址和块大小。块大小一般都设置为512 字节。SD_WriteBlock 写入函数的执行流程如下:
-
SD_WriteBlock 函数开始时将 SDIO 数据控制寄存器 (SDIO_DCTRL)清零,复位之前的传输设置。
![在这里插入图片描述](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZGALPKPAAAmjXJ89nU298.png)
-
对 SD 卡进行数据读写之前,都必须发送 CMD16 指定块的大小,对于标准卡,要写入BlockSize 长度字节的块;对于 SDHC 卡,写入固定为 512 字节的块。接下来就可以发送块写入命令 CMD24 通知 SD 卡要进行数据写入操作,并指定待写入数据的目标地址。
![在这里插入图片描述](//file.elecfans.com/web2/M00/81/64/poYBAGORkZGAFZQGAACh0dY1QCs338.png)
![1668761161951857.png 84.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZWAcJekAAMPS628TAY989.png)
-
利用 SDIO_DataInitTypeDef 结构体类型变量配置数据传输的超时、块数量、数据块大小、数据传输方向等参数并使用 SDIO_DataConfig 函数完成数据传输环境配置。
![1668761175388199.png 85.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZSAIgDJAAUKugSl1so378.png)
-
调用 SDIO_ITConfig 函数使能 SDIO 数据结束传输结束中断,传输结束时,会跳转到SDIO 的中断服务函数运行。
5)SD_DMA_TxConfig 函数,配置使能 SDIO 数据向 SD 卡的数据传输的DMA 请求,为使 SDIO 发送 DMA 请求,需要调用
SDIO_DMACmd 函数使能。对于高容量的 SD 卡要求块大小必须为 512 字节,SDIO 外设会自动生成 DMA 发送请求,将指定数据使用 DMA 传输写入到 SD 卡内。
普通模式需要自己去处理那些溢出什么的太麻烦了,用DMA传输数据就好了
![1668761193133679.png 86.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAMvXJAAFmWRDbowE469.png)
DMA外设配置(不清楚的参考:DMA外设详解):
![1668761209639313.png 87.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZSAR2VPAAVguPJk5es151.png)
![1668761221357148.png 88.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAHACMAAHYGkPfUB8819.png)
写入操作等待函数
SD_WaitWriteOperation 函数用于检测和等待数据写入完成,在调用数据写入函数之后一般都需要调用,SD_WaitWriteOperation 函数适用于单块及多块写入函数。
![1668761235515748.png 89.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAW-mLAAFhbF7ZmZ8257.png)
SDIO 中断服务函数
在进行数据传输操作时都会使能相关标志中断,用于跟踪传输进程和错误检测。
SD_ProcessIRQSrc 函数首先判断全局变量 StopCondition 变量是否为 1,该全局变量在SDIO 的多块读写函数中被置 1(前面分析的单块读写函数中 StopCondition 均为 0),因为根据 SD 卡的要求,多块读写命令由 CMD12 结束,SD 卡在接收到该命令时才停止多块的传输,此处正是根据 StopCondition 的情况控制是否发送 CMD12 命令,它发送命令时直接采用往寄存器写入命令和参数的方式。
![1668761249954133.png 90.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZSAWA6wAAQTTtMPK1s313.png)
![1668761263194672.png 91.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZOAAoqrAAJFjeSJHD8473.png)
![1668761277962830.png 92.png](//file.elecfans.com/web2/M00/81/65/poYBAGORkZWATKCMAAWvjeCZDTs191.png)
调用库函数 SD_DMAEndOfTransferStatus 一直检测 DMA 的传输完成标志,当 DMA 传输结束时,该函数会返回 SET 值。另外,while 循环中的判断条件使用的TransferEnd 和 TransferError 是全局变量,它们会在 SDIO 的中断服务函数根据传输情况被设置,传输结束后,根据 TransferError 的值来确认是否正确传输,若不正确则直接返回错
误代码。SD_WaitWriteOperation 函数最后是清除相关标志位并返回错误。
数据读取操作
同向 SD 卡写入数据类似,从 SD 卡读取数据可分为单块读取和多块读取。这里仅介绍单块读操作函数,多块读操作类似。
这一部分自己看代码吧,操作差不多,已经人麻了太多了。
![1668761293803231.png 93.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZOAL_9WAAHLV8RFb10528.png)
![1668761307804749.png 94.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZeAAP22AAobocouihA119.png)
还有多块读取与多块写入,其实是一样的,只不过传输结束需要发送CMD12来结束传输。
总结:代码太多了,但是核心的东西已经讲完了,自己去看代码悟一下,其实前面的理论部分懂了,代码部分是完全按照理论来走的,只不过多了一点点细节,就这样咯,那些边边角角留给你们。
3.实验结果
![在这里插入图片描述](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZGADPErABgQUt_ZgHU035.png)
![1668761386691193.png 95.png](//file.elecfans.com/web2/M00/81/EE/pYYBAGORkZqAHkYNAA60SUa6UwU284.png)
![1668761398907593.png 96.png](//file.elecfans.com/web2/M00/81/64/poYBAGORkZSACFFsAAYJMKLZKJA444.png)
【本文转载自CSDN,作者:rivencode】
全文目录内容分为三篇【上/中/下】原文链接跳转如下:
SD NAND 的 SDIO在STM32上的应用详解(上篇):
SD NAND 的 SDIO在STM32上的应用详解(中篇):
SD NAND 的 SDIO在STM32上的应用详解(下篇):