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

描述

七.SDIO外设结构体

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

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

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

1.SDIO初始化结构体

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

FlaSh


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

FlaSh


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

FlaSh

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

FlaSh

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

FlaSh

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

FlaSh


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

FlaSh

2.SDIO命令初始化结构体

FlaSh


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

FlaSh


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

FlaSh

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

FlaSh

1)命令响应寄存器
 

FlaSh


2)SDIO响应寄存器1~4
 

FlaSh

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

FlaSh

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

FlaSh

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

FlaSh

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

FlaSh

3.SDIO数据初始化结构体

FlaSh


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

FlaSh

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

FlaSh

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

FlaSh


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

FlaSh

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

FlaSh

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

FlaSh


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

八.SD卡读写测试实验

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

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

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

卡识别模式流程图

FlaSh


数据传输流程图
 

FlaSh

1.硬件设计

原理图:

FlaSh


实物图:
 

FlaSh

 

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

 

FlaSh

详情请参考:雷龙官网
 

FlaSh

2.代码讲解

先看主函数:
 

FlaShFlaSh

SD_Terst函数:
 

FlaSh


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

FlaSh


SD_Init()函数:

/**

 * 函数名:SD_Init

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

 * 输入:无

 * 输出:-SD_Error SD卡错误代码

 * 成功时则为 SD_OK

 * 调用:外部调用

 */SD_Error SD_Init(void){

/*重置SD_Error状态*/

SD_Error errorstatus = 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);}

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

FlaSh


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

FlaSh

配置SDIO中断:
 

FlaSh


 

FlaSh


SDIO 外设底层引脚初始化
 

FlaSh


 

FlaSh

复位所有SDIO寄存器
 

FlaShFlaSh

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

FlaSh

/*

 * 函数名: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初始化结构体**
 

FlaSh


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

FlaSh

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

FlaSh


 

FlaSh


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

FlaSh


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

如果发送CMD8无响应:

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

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

FlaSh

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

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

FlaSh


 

FlaSh

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

执行 SD_PowerON 函数没有错误后:SD 卡处于卡识别模式下的准备状态
 

FlaShFlaSh


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 电源是否启动,如果没有启动电源返回错误。
 

FlaSh


2.发送CMD2命令 :是用于通知所有卡通过 CMD 线返回 CID 值,执行 CMD2 发送之后就可以使用 CmdResp2Error 函数获取 CMD2 命令发送情况,发送无错误后即可以使用 SDIO_GetResponse 函数获取响应内容,它是个长响应,我们把 CMD2 响应内容存放在 CID_Tab 数组内。

FlaSh


3.发送CMD3命令,用于指示 SD 卡自行推荐 RCA 地址,CMD3 的响应为 R6 类型,CmdResp6Error 函数用于检查 R6 响应错误,它有两个形参,一个是命令号,这里为 CMD3,另外一个是 RCA 数据指针,这里使用 rca变量的地址赋值给它,使得在 CMD3 正确响应之后 rca 变量即存放 SD 卡的 RCA。

FlaSh

CmdResp6Error 函数通用会对每个错误位进行必要的检测,如果发现有错误存在则直接返回对应错误类型。
 

FlaSh

执行完CmdResp6Error 函数之后返回到 SD_InitializeCards 函数中,如果判断无错误说明此刻 SD 卡已经处于数据传输模式。
 

FlaSh


4.发送 CMD9 给指定 RCA 的 SD 卡使其发送返回其 CSD 寄存器内容,这里的 RCA就是在 CmdResp6Error 函数获取得到的 rca。最后把响应内容存放在 CSD_Tab 数组中。

FlaSh


 

FlaSh


执行 SD_InitializeCards 函数无错误后 SD 卡就已经处于数据传输模式下的待机状态,退出 SD_InitializeCards 后会返回前面的 SD_Init 函数,执行接下来代码,以下是 SD_Init 函数的后续执行过程:

FlaSh


1) 重新配置 SDIO 外设,提高时钟频率,之前的卡识别模式都设定 CMD 线时钟为小于 400KHz,进入数据传输模式可以把时钟设置为小于 25MHz,以便提高数据传输速率。
 

FlaSh


(2) 调用 SD_GetCardInfo 函数获取 SD 卡信息,它需要一个指向 SD_CardInfo 类型变量地址的指针形参,这里赋值为 SDCardInfo 变量的地址。SD 卡信息主要是 CID和 CSD 寄存器内容,这两个寄存器内容在 SD_InitializeCards 函数中都完成读取过程并将其分别存放在 CID_Tab 数组和 CSD_Tab 数组中,SD_GetCardInfo 函数只是简单的把这两个数组内容整合复制到 SDCardInfo 变量对应成员内。正确执行 SD_GetCardInfo 函数后,SDCardInfo 变量就存放了 SD 卡的很多状态信息,这在之后应用中使用频率是很高的。
 

FlaSh

结构体类型定义:有 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 和卡类型成员。
 

FlaSh


主要是存储卡的容量,卡的大小,RCA地址,卡的类型(这些是关键信息,由命令响应返回然后存入这个结构体中)

FlaSh


 

FlaSh


 

FlaSh


3) 调用 SD_SelectDeselect 函数用于选择特定 RCA 的 SD 卡,它实际是向 SD 卡发送CMD7。执行之后,卡就从待机状态转变为传输模式,可以说数据传输已经是万事俱备了

FlaSh


 

FlaSh


 

FlaSh


4) 扩展数据线宽度,之前的所有操作都是使用一根数据线传输完成的,使用 4 根数据线可以提高传输性能,调用可以设置数据线宽度,函数只有一个形参,用于指定数据线宽度。
 

FlaSh


至此,SD_Init 函数已经全部执行完成。如果程序可以正确执行,接下来就可以进行SD 卡读写以及擦除等操作。

FlaSh


SD_EraseTest()函数
 

FlaSh


SD_Erase()函数:
 

FlaSh


 

FlaSh


1) 检查 SD 卡是否支持擦除功能,如果不支持则直接返回错误。为保证擦除指令正常进行,要求主机一个遵循下面的命令序列发送指令:CMD32->CMD33->CMD38。如果发送顺序不对,SD 卡会设置 ERASE_SEQ_ERROR 位到状态寄存器
 

FlaSh


 

FlaSh


2) SD_Erase 函数发送 CMD32 指令用于设定擦除块开始地址,在执行无错误后发送CMD33 设置擦除块的结束地址。
 

FlaSh

3) 发送擦除命令 CMD38,使得 SD 卡进行擦除操作。SD 卡擦除操作由 SD 卡内部控制完成,不同卡擦除后是 0xff 还是 0x00 由厂家决定。擦除操作需要花费一定时间,这段时间不能对 SD 卡进行其他操作。
 

FlaSh

4) 通过 IsCardProgramming 函数可以检测 SD 卡是否处于编程状态(即卡内部的擦写状态),需要确保 SD 卡擦除完成才退出 SD_Erase 函数。IsCardProgramming 函数先通过发送CMD13 命令 SD 卡发送它的状态寄存器内容,并对响应内容进行分析得出当前 SD 卡的状态以及可能发送的错误。

FlaSh

数据写入操作

FlaSh


 

FlaShFlaSh


SD_WriteBlock 函数用于向指定的目标地址写入一个块的数据,它有三个形参,分别为指向待写入数据的首地址的指针变量、目标写入地址和块大小。块大小一般都设置为512 字节。SD_WriteBlock 写入函数的执行流程如下:

1) SD_WriteBlock 函数开始时将 SDIO 数据控制寄存器 (SDIO_DCTRL)清零,复位之前的传输设置。
 

FlaSh

2) 对 SD 卡进行数据读写之前,都必须发送 CMD16 指定块的大小,对于标准卡,要写入BlockSize 长度字节的块;对于 SDHC 卡,写入固定为 512 字节的块。接下来就可以发送块写入命令 CMD24 通知 SD 卡要进行数据写入操作,并指定待写入数据的目标地址。
 

FlaShFlaSh

3) 利用 SDIO_DataInitTypeDef 结构体类型变量配置数据传输的超时、块数量、数据块大小、数据传输方向等参数并使用 SDIO_DataConfig 函数完成数据传输环境配置。
 

FlaSh

4) 调用 SDIO_ITConfig 函数使能 SDIO 数据结束传输结束中断,传输结束时,会跳转到SDIO 的中断服务函数运行。
5)SD_DMA_TxConfig 函数,配置使能 SDIO 数据向 SD 卡的数据传输的DMA 请求,为使 SDIO 发送 DMA 请求,需要调用
SDIO_DMACmd 函数使能。对于高容量的 SD 卡要求块大小必须为 512 字节,SDIO 外设会自动生成 DMA 发送请求,将指定数据使用 DMA 传输写入到 SD 卡内。

普通模式需要自己去处理那些溢出什么的太麻烦了,用DMA传输数据就好了
 

FlaSh

DMA外设配置(不清楚的参考:DMA外设详解):
 

FlaSh


 

FlaSh

写入操作等待函数
SD_WaitWriteOperation 函数用于检测和等待数据写入完成,在调用数据写入函数之后一般都需要调用,SD_WaitWriteOperation 函数适用于单块及多块写入函数。
 

FlaSh


SDIO 中断服务函数
在进行数据传输操作时都会使能相关标志中断,用于跟踪传输进程和错误检测。
SD_ProcessIRQSrc 函数首先判断全局变量 StopCondition 变量是否为 1,该全局变量在SDIO 的多块读写函数中被置 1(前面分析的单块读写函数中 StopCondition 均为 0),因为根据 SD 卡的要求,多块读写命令由 CMD12 结束,SD 卡在接收到该命令时才停止多块的传输,此处正是根据 StopCondition 的情况控制是否发送 CMD12 命令,它发送命令时直接采用往寄存器写入命令和参数的方式。
 

FlaSh


 

FlaSh


 

FlaSh

调用库函数 SD_DMAEndOfTransferStatus 一直检测 DMA 的传输完成标志,当 DMA 传输结束时,该函数会返回 SET 值。另外,while 循环中的判断条件使用的TransferEnd 和 TransferError 是全局变量,它们会在 SDIO 的中断服务函数根据传输情况被设置,传输结束后,根据 TransferError 的值来确认是否正确传输,若不正确则直接返回错
误代码。SD_WaitWriteOperation 函数最后是清除相关标志位并返回错误。

数据读取操作
同向 SD 卡写入数据类似,从 SD 卡读取数据可分为单块读取和多块读取。这里仅介绍单块读操作函数,多块读操作类似。

这一部分自己看代码吧,操作差不多,已经人麻了太多了。
 

FlaShFlaSh

还有多块读取与多块写入,其实是一样的,只不过传输结束需要发送CMD12来结束传输。

总结:代码太多了,但是核心的东西已经讲完了,自己去看代码悟一下,其实前面的理论部分懂了,代码部分是完全按照理论来走的,只不过多了一点点细节,就这样咯,那些边边角角留给你们。

3.实验结果

FlaShFlaShFlaSh

【本文转载自CSDN,作者:rivencode】

 


 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分