SD NAND 的 SDIO在STM32上的应用详解(下篇)

2024-03-25  

七.SDIO外设结构体

其实前面关于SDIO寄存器的讲解已经比较详细了,这里再借助于关于SDIO结构体再进行总结一遍。

标准库函数对 SDIO 外设建立了三个初始化结构体,分别为 SDIO 初始化结构体SDIO_InitTypeDef、SDIO 命令初始化结构体 SDIO_CmdInitTypeDef 和 SDIO 数据初始化结
构体 SDIO_DataInitTypeDef。这些结构体成员用于设置 SDIO 工作环境参数,并由 SDIO 相应初始化配置函数或功能函数调用,这些参数将会被写入到 SDIO 相应的寄存器,达到配置 SDIO 工作环境的目的。


至于为什么需要一个命令结构体与数据结构体,就是为了方便我们配置SDIO关于寄存器位,因为发送命令或者数据需要很多参数配置。

1.SDIO初始化结构体

SDIO 初始化结构体用于配置 SDIO 基本工作环境,比如时钟分频、时钟沿、数据宽度等等。它被 SDIO_Init 函数使用。

1.png


1) SDIO_ClockEdge:主时钟 SDIOCLK 产生 CLK 引脚时钟有效沿选择,可选上升沿或下降沿。

2.png


2) SDIO_ClockBypass:时钟分频旁路使用,可选使能或禁用,如果使能旁路,SDIOCLK (72MHZ )直接驱动 CLK 线输出时钟(不满足最高25HZ的要求),如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分频 SDIOCLK,然后输出到 CLK 线。一般选择禁用时钟分频旁路。

3.png

3) SDIO_ClockPowerSave:节能模式选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能节能模式,CLK 线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能 CLK 线输出时钟。

4.png

4) SDIO_BusWide:数据线宽度选择,可选 1 位数据总线、4 位数据总线或 8 为数据总线,系统默认使用 1 位数据总线,操作 SD 卡时在数据传输模式下一般选择 4 位数据总线。它设定 SDIO_CLKCR 寄存器的 WIDBUS 位的值。

5.png

5) SDIO_HardwareFlowControl:硬件流控制选择,可选使能或禁用,它设定SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 发送上溢和下溢错误。

6.png


6) SDIO_ClockDiv:时钟分频系数,它设定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,设置 SDIOCLK 与 CLK 线输出时钟分频系数:
CLK 线时钟频率=SDIOCLK/([CLKDIV+2])。

7.png

2.SDIO命令初始化结构体

8.png


1) SDIO_Argument:作为命令的一部分发送到卡的命令参数,它设定 SDIO 参数寄存器(SDIO_ARG)的值。

9.png


(2) SDIO_CmdIndex:命令号选择,它设定 SDIO 命令寄存器(SDIO_CMD)的 CMDINDEX位的值。

10.png

(3) SDIO_Response:响应类型,SDIO 定义两个响应类型:长响应和短响应。根据命令号选择对应的响应类型。SDIO 定义了四个 32 位的 SDIO 响应寄存器(SDIO_RESPx,x=1…4),短响应只用SDIO_RESP1,长响应使用4个(SDIO_RESPx,x=1…4)。

11.png

1)命令响应寄存器

12.png


2)SDIO响应寄存器1~4

13.png

4) SDIO_Wait:等待类型选择,有三种状态可选,一种是无等待状态,超时检测功能启动,一种是等待中断,另外一种是等待传输完成。

14.png

5) SDIO_CPSM:命令路径状态机控制,可选使能或禁用 CPSM。它设定 SDIO_CMD 寄存器的 CPSMEN 位的值

15.png

只要我们使能的了命令状态机,则下面发送命令和接收响应的过程中的状态转换就不用我们管了

16.png

当我们要发送命令,我们只需要配置这个命令初始化结构体的成员,然后调用下图这个函数,则我们配置的参数写入对应的寄存器位中。

17.png

3.SDIO数据初始化结构体

18.png


1) SDIO_DataTimeOut:设置数据传输以卡总线时钟周期表示的超时周期,它设定 SDIO数据定时器寄存器(SDIO_DTIMER)的值。在 DPSM 进入 Wait_R 或繁忙状态后开始递减,直到 0 还处于以上两种状态则将超时状态标志置 1(详情前面的数据通道小节)。

19.png

2) SDIO_DataLength:设置传输数据长度。

20.png

3) SDIO_DataBlockSize:设置数据块大小,有多种尺寸可选,不同命令要求的数据块可能不同。

21.png


4) SDIO_TransferDir:数据传输方向,可选从主机到卡的写操作,或从卡到主机的读操作。

22.png

5) SDIO_TransferMode:数据传输模式,可选数据块或数据流模式。对于 SD 卡操作使用数据块类型。

23.png

6) SDIO_DPSM:数据路径状态机控制,可选使能或禁用 DPSM。它设定 SDIO_DCTRL寄存器的 DTEN 位的值。要实现数据传输都必须使能 SDIO_DPSM。

24.png


与命令一样使能了数据路径状态机,就不用高那么多麻烦的状态转换了

八.SD卡读写测试实验

我们平时使用的SD 卡都是已经包含有文件系统的,一般不会使用本实验的操作方式读写 SD 卡,但是对学习SD卡的驱动原理非常重要!!!

本实验是进行 SD卡最底层的数据读写操作,直接使用 SDIO 对 SD 卡进行读写,会损坏 SD 卡的文件系统,导致数据丢失,所以做这个实验之前需要备份SD卡数据。

主要是学习SD卡的卡识别过程,以及数据传输工过程,其实就是完全依照前面的两个流程图来实现代码的。

卡识别模式流程图

25.png


数据传输流程图

26.png

1.硬件设计

原理图:

27.png


实物图:

28.png


我这里用的是CS创世的贴片式SD卡,也称之为SD NAND , 内部存储单元架构为SLC,适合存代码。直接上板时相比于拔插式SD卡在抗震和抗PIN氧化方面更有优势,对于缩小整板体积也有一定帮助。


在这里插入图片描述

详情请参考:雷龙官网

30.png

2.代码讲解

先看主函数:

31.png32.png

SD_Terst函数:

33.png


我们主要讲解的就是SD卡的初始化

34.png


SD_Init()函数:

/**

*函数名:SD_Init

*描述:初始化SD卡,使卡处于就绪状态(准备传输数据)

*输入:无

*输出:-SD_ErrorSD卡错误代码

*成功时则为SD_OK

*调用:外部调用

*/SD_ErrorSD_Init(void){

/*重置SD_Error状态*/

SD_Errorerrorstatus=SD_OK;

NVIC_Configuration();


/*SDIO外设底层引脚初始化*/

GPIO_Configuration();

/*对SDIO的所有寄存器进行复位*/

SDIO_DeInit();

/*上电并进行卡识别流程,确认卡的操作电压*/

errorstatus=SD_PowerON();

/*如果上电,识别不成功,返回“响应超时”错误*/

if(errorstatus!=SD_OK)

{

/*!< CMD Response TimeOut (wait for CMDSENT flag) */

return(errorstatus);

}

/*卡识别成功,进行卡初始化*/

errorstatus=SD_InitializeCards();

if(errorstatus!=SD_OK) //失败返回

{

/*!< CMD Response TimeOut (wait for CMDSENT flag) */

return(errorstatus);

}

/*配置SDIO外设

*上电识别,卡初始化都完成后,进入数据传输模式,提高读写速度

*/

/*SDIOCLK=HCLK,SDIO_CK=HCLK/(2+SDIO_TRANSFER_CLK_DIV)*/

SDIO_InitStructure.SDIO_ClockDiv=SDIO_TRANSFER_CLK_DIV;

/*上升沿采集数据*/

SDIO_InitStructure.SDIO_ClockEdge=SDIO_ClockEdge_Rising;

/*Bypass模式使能的话,SDIO_CK不经过SDIO_ClockDiv分频*/

SDIO_InitStructure.SDIO_ClockBypass=SDIO_ClockBypass_Disable;


/*若开启此功能,在总线空闲时关闭sd_clk时钟*/

SDIO_InitStructure.SDIO_ClockPowerSave=SDIO_ClockPowerSave_Disable;


/*暂时配置成1bit模式*/

SDIO_InitStructure.SDIO_BusWide=SDIO_BusWide_1b;

/*硬件流,若开启,在FIFO不能进行发送和接收数据时,数据传输暂停*/

SDIO_InitStructure.SDIO_HardwareFlowControl=SDIO_HardwareFlowControl_Disable;


SDIO_Init(&SDIO_InitStructure);

if(errorstatus==SD_OK)

{

/*用来读取csd/cid寄存器*/

errorstatus=SD_GetCardInfo(&SDCardInfo);

}

if(errorstatus==SD_OK)

{

/*通过cmd7,rca选择要操作的卡*/

errorstatus=SD_SelectDeselect((uint32_t)(SDCardInfo.RCA<< 16));

}

if(errorstatus==SD_OK)

{

/*最后为了提高读写,开启4bits模式*/

errorstatus=SD_EnableWideBusOperation(SDIO_BusWide_4b);

}

return(errorstatus);}

接下来逐段代码来分析一下:

35.png


errorstatus其实是一个SD_Error类型的枚举变量,SD_Error 是
列举了控制器可能出现的错误、比如 CRC 校验错误、CRC 校验错误、通信等待超时、FIFO 上溢或下溢、擦除命令错误等等。这些错误类型部分是控制器系统寄存器的标志位,部分是通过命令的响应内容得到的,如果是SD_OK则代表没有发送错误,

36.png

配置SDIO中断:

37.png


38.png


SDIO 外设底层引脚初始化

39.png


40.png

复位所有SDIO寄存器

41.png42.png

重点来了:调用SD_PowerON()进入卡识别模式

43.png

/*

*函数名:SD_PowerON

*描述:确保SD卡的工作电压和配置控制时钟

*输入:无

*输出:-SD_ErrorSD卡错误代码

*成功时则为SD_OK

*调用:在SD_Init()调用

*/SD_ErrorSD_PowerON(void){

SD_Errorerrorstatus=SD_OK;

uint32_tresponse=0,count=0,validvoltage=0;

uint32_tSDType=SD_STD_CAPACITY;

/********************************************************************************************************/

/*上电初始化

*配置SDIO的外设

*SDIOCLK=HCLK,SDIO_CK=HCLK/(2+SDIO_INIT_CLK_DIV)

*初始化时的时钟不能大于400KHz

*/

/*HCLK=72MHz,SDIOCLK=72MHz,SDIO_CK=HCLK/(178+2)=400KHz*/

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)

{

/*CMDResponseTimeOut(waitforCMDSENTflag)*/

return(errorstatus);

}/********************************************************************************************************/

/*CMD8:SEND_IF_COND

*发送CMD8检查SD卡的电压操作条件

*

*参数:-[31:12]:保留(要被设置为'0')

*-[11:8]:支持的电压(VHS)0x1(范围:2.7-3.6V)

*-[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)

{

/*SDCard2.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为CommandTimeOut,说明是MMC卡

*若errorstatus为SD_OK,说明是SDcard:SD卡2.0(电压范围不匹配)

*或SD卡1.x

*/

if(errorstatus==SD_OK) //响应了cmd55,是sd卡,可能为1.x,可能为2.0

{

/*下面开始循环地发送sdio支持的电压范围,循环一定次数*/

/*SDCARD

*发送ACMD41SD_APP_OP_COND,带参数0x80100000

*/

while((!validvoltage)&&(count< SD_MAX_VOLT_TRIAL))

{

/*在发送ACMD命令前都要先向卡发送CMD55

*发送CMD55APP_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型*/

}

}/*elseMMCCard*/

return(errorstatus); }

1.配置SDIO初始化结构体**

44.png


配置 SDIO_InitStructure 结构体变量成员并调用 SDIO_Init 库函数完成 SDIO 外设的基本配置,注意此处的 SDIO 时钟分频,由于处于卡识别阶段,其时钟不能超过 400KHz。

45.png

2.发送CMD0命令:要SD卡回到空闲状态

46.png


47.png


那些检测标志全是来源与下图:

48.png


3.发送CMD8: 用来识别不同版本的卡和检测卡是否能在主机提供的电压下工作。

如果发送CMD8无响应:

1.电压不匹配的 2.0 以上 SD 卡
2.1.0 的 SD 卡
3.不是 SD 卡

如果发送CMD8有响应:
电压匹配的 2.0 以上 SD 卡(就是我们即将要使用的SD卡)

49.png

4.使用 ACMD41 命令判断卡的具体类型。因为是 A 类命令,所以在发送 ACMD41之前必须先发送 CMD55,CMD55 命令的响应类型的 R1。如果 CMD55 命令都没有响应说明是 MMC 卡或不可用卡。在正确发送 CMD55 之后就可以送ACMD41,并根据响应判断卡类型,ACMD41 的响应号为 R3,CmdResp3Error 函数用于检测命令正确发送并带有超时检测功能,但并不具备响应内容接收功能,需要在判定命令正确发送之后调用 SDIO_GetResponse 函数才能获取响应的内容。

实际上,在有响应时,SDIO 外设会自动把响应存放在 SDIO_RESPx 寄存器中,SDIO_GetResponse 函数只是根据形参返回对应响应寄存器的值。通过判定响应内容值即可确定 SD 卡类型。

50.png


51.png

总结:执行 SD_PowerON 函数无错误后就已经确定了 SD 卡类型,并说明卡和主机电压是匹配的,SD 卡处于卡识别模式下的准备状态。退出 SD_PowerON 函数返回SD_Init 函数,执行接下来代码。

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。