37. SDIO 您所在的位置:网站首页 sd卡显示异常如何处理 37. SDIO

37. SDIO

2024-07-14 14:38| 来源: 网络整理| 查看: 265

37.9. SD卡读写测试实验¶

SD卡广泛用于便携式设备上,比如数码相机、手机、多媒体播放器等。对于嵌入式设备来说是一种重要的存储数据部件。类似于SPI Flash芯片数据操作, 可以直接进行读写,也可以写入文件系统,然后使用文件系统读写函数,使用文件系统操作。本实验是进行SD卡最底层的数据读写操作,直接使用SDIO对SD卡进行读写, 会损坏SD卡原本内容,导致数据丢失,实验前请注意备份SD卡的原内容。由于SD卡容量很大,我们平时使用的SD卡都是已经包含有文件系统的, 一般不会使用本章的操作方式编写SD卡的应用,但它是SD卡操作的基础,对于原理学习是非常有必要的,在它的基础上移植文件系统到SD卡的应用将在下一章讲解。

37.9.1. 硬件设计¶

STM32控制器的SDIO引脚是被设计固定不变的,开发板设计采用四根数据线模式。对于命令线和数据线须需要加一个上拉电阻。

37.9.2. 软件设计¶

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等没有全部罗列出来,完整的代码请参考本章配套的工程。有了之前相关SDIO知识基础, 我们就可以着手开始编写SD卡驱动程序了,根据之前内容,可了解操作的大概流程:

初始化相关GPIO及SDIO外设;

配置SDIO基本通信环境进入卡识别模式,通过几个命令处理后得到卡类型;

如果是可用卡就进入数据传输模式,接下来就可以进行读、写、擦除的操作。

虽然看起来只有三步,但它们有非常多的细节需要处理。实际上,SD卡是非常常用外设部件,ST公司在其测试板上也有板子SD卡卡槽, 并提供了完整的驱动程序,我们直接参考移植使用即可。类似SDIO、USB这些复杂的外设,它们的通信协议相当庞大,要自行编写完整、 严谨的驱动不是一件轻松的事情,这时我们就可以利用ST官方例程的驱动文件,根据自己硬件移植到自己开发平台即可。

在“初识STM32标准库”章节我们重点讲解了标准库的源代码及启动文件和库使用帮助文档这两部分内容,实际上“Utilities”文件夹内容是非常有参考价值的, 该文件夹包含了基于ST官方实验板的驱动文件,比如LCD、SRAM、SD卡、音频解码IC等等底层驱动程序, 另外还有第三方软件库,如emWin图像软件库和FatFs文件系统。虽然,我们的开发平台跟ST官方实验平台硬件设计略有差别,但移植程序方法是完全可行的。 学会移植程序可以减少很多工作量,加快项目进程,更何况ST官方的驱动代码是经过严格验证的。

在ST固件库“STM32F10x_StdPeriph_Lib_V3.5.0UtilitiesSTM32_EVALCommon”文件夹下可以找到SD卡驱动文件, 见图 ST官方实验板SD卡驱动文件。 我们需要stm32_eval_sdio_sd.c和stm32_eval_sdio_sd.h两个文件的完整内容。 另外还可以参考目录“STM32F10x_StdPeriph_Lib_V3.5.0ProjectSTM32F10x_StdPeriph_ExamplesSDIOuSDCard”下中的示例代码编写测试, 为简化工程,本章配置工程代码是将这些与SD卡相关的的内容都添加到stm32_eval_sdio_sd.c文件中,具体可以参考工程文件。

我们把stm32_eval_sdio_sd.c和stm32_eval_sdio_sd.h两个文件拷贝到我们的工程文件夹中, 并将其对应改名为bsp_sdio_sdcard.c和bsp_sdio_sdcard.h,见图 SD卡驱动文件。 另外,添加的sdio_test.c和sdio_test.h文件包含了SD卡读、写、擦除测试代码。

本实验中讲解的代码,极大部分是从ST提供的这个SDIO驱动示例整理而来。

37.9.2.1. GPIO初始化和DMA配置¶

SDIO用到CLK线、CMD线和4根DAT线,使用之前必须初始化相关的GPIO,并设置复用模式为SDIO的类型。而SDIO外设又支持生成DMA请求, 使用DMA传输可以提高数据传输效率,因此在SDIO的控制代码中,可以把它设置为DMA传输模式或轮询模式, ST标准库提供SDIO示例中针对这两个模式做了区分处理。由于应用中一般都使用DMA传输模式,所以接下来代码分析都以DMA传输模式介绍。

SDIO模式、地址及时钟分频配置相关的宏定义

代码清单:SDIO-4 SDIO模式、地址及时钟分频配置相关的宏定义(bsp_sdio_sdcard.h文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17/*宏定义*/ //SDIO_FIOF地址=SDIO地址+0x80至 sdio地址+0xfc #define SDIO_FIFO_ADDRESS ((uint32_t)0x40018080) /** * @brief SDIO 初始化时钟频率 (最大400KHz ) */ #define SDIO_INIT_CLK_DIV ((uint8_t)0xB2) /** * @brief SDIO 数据传输时钟频率 (最大25MHz ) */ /*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */ #define SDIO_TRANSFER_CLK_DIV ((uint8_t)0x01) /* 通过注释,选择SDIO传输时使用的模式,可选DMA模式及普通模式, */ #define SD_DMA_MODE ((uint32_t)0x00000000) /*#define SD_POLLING_MODE ((uint32_t)0x00000002)*/

代码中主要定义了如下内容:

(1) 定义了SDIO外设的FIFO地址,SDIO进行传输时,数据会被存储在FIFO,该FIFO的大小为32字节, 即从0x40018080至0x400180fc(可从《STM32参考手册》的SDIO寄存器说明中查询到),把FIFO的起始地址定义成宏后方便后面配置DMA传输时使用;

(2) 定义卡识别模式和数据传输模式下的时钟分频因子。SDIO_CK引脚的时钟信号在卡识别模式时要求不超过400KHz, 而在识别后的数据传输模式时则希望有更高的速度(最大不超过25MHz),所以会针对这两种模式配置SDIOCLK的时钟。 代码中的SDIO_INIT_CLK_DIV分频因子用于卡识别模式,SDIO_TRANSFER_CLK_DIV用于数据传输模式。把两种模式的分频因子代入公式计算:

SDIOCLK = HCLK=72MHz, SDIO_CK = HCLK/(2 +CLK_DIV)

可得卡识别模式SDIO_CK时钟为400KHz,数据传输模式SDIO_CK时钟为24MHz。

(3) 定义SDIO传输使用DMA还是普通的轮询模式。宏SD_DMA_MODE和SD_POLLING_MODE可用于选择是否使用DMA, 只要定义了其中一个宏并把另一个注释掉即可选择宏对应的模式,上述代码中选择了DMA模式。

GPIO初始化

代码清单:SDIO-5 GPIO初始化(bsp_sdio_sdcard.c文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31/* * 函数名:GPIO_Configuration * 描述 :初始化SDIO用到的引脚,开启时钟。 * 输入 :无 * 输出 :无 * 调用 :内部调用 */ static void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; /*!< 使能端口时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD , ENABLE); /*!< 配置 PC.08, PC.09, PC.10, PC.11,PC.12 引脚: D0, D1, D2, D3,CLK 引脚 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOC, &GPIO_InitStructure); /*!< 配置 PD.02 CMD 引脚 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_Init(GPIOD, &GPIO_InitStructure); /*!< 使能 SDIO AHB 时钟 */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO, ENABLE); /*!< 使能 DMA2 时钟 */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE); }

由于SDIO对应的IO引脚都是固定的,所以这里没有使用宏定义方式给出,直接使用GPIO引脚,该函数初始化引脚之后还使能了SDIO和DMA2时钟。

DMA传输配置

代码清单:SDIO-6 DMA传输配置(bsp_sdio_sdcard.c文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84//SDIO_FIOF地址=SDIO地址+0x80至 sdio地址+0xfc #define SDIO_FIFO_ADDRESS ((uint32_t)0x40018080) /* * 函数名:SD_DMA_RxConfig * 描述 :为SDIO接收数据配置DMA2的通道4的请求 * 输入 :BufferDST:用于装载数据的变量指针 * : BufferSize: 缓冲区大小 * 输出 :无 */ void SD_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize) { DMA_InitTypeDef DMA_InitStructure; DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4);//清除DMA标志位 /*!< 配置前先禁止DMA */ DMA_Cmd(DMA2_Channel4, DISABLE); /*!< DMA2 传输配置 */ //外设地址,fifo DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS; //目标地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferDST; //外设为源地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //除以4,把字转成字节单位 DMA_InitStructure.DMA_BufferSize = BufferSize / 4; //外设地址不自增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //存储目标地址自增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //外设数据大小为字,32位 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //外设数据大小为字,32位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //不循环 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //通道优先级高 DMA_InitStructure.DMA_Priority = DMA_Priority_High; //非 存储器至存储器模式 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA2_Channel4, &DMA_InitStructure); /*!< 使能DMA通道 */ DMA_Cmd(DMA2_Channel4, ENABLE); } /* * 函数名:SD_DMA_RxConfig * 描述 :为SDIO发送数据配置DMA2的通道4的请求 * 输入 :BufferDST:装载了数据的变量指针 BufferSize: 缓冲区大小 * 输出 :无 */ void SD_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize) { DMA_InitTypeDef DMA_InitStructure; DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4); /*!< 配置前先禁止DMA */ DMA_Cmd(DMA2_Channel4, DISABLE); /*!< DMA2 传输配置 */ DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferSRC; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//外设为写入目标 DMA_InitStructure.DMA_BufferSize = BufferSize / 4; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA2_Channel4, &DMA_InitStructure); /*!< 使能DMA通道 */ DMA_Cmd(DMA2_Channel4, ENABLE); }

SD_DMA_RxConfig函数用于配置DMA的SDIO接收请求参数,并指定接收存储器地址和大小。SD_DMA_TxConfig函数用于配置DMA的SDIO发送请求参数, 并指定发送存储器地址和大小。这两个函数在SDIO数据传输时会被调用来对DMA进行配置,使得传输过程采用DMA搬运数据。函数有两个输入参数, BufferSRC在接收时用于指定接收到的数据存储的内存地址,发送时用于指定要对外发送的数据的内存地址;而BufferSize用于指定数据的大小, 它是32个字节的,所以在配置DMA传输的数据大小时,要把它由字的单位转换成字节,也就是1/4大小;另外,DMA配置中的外设地址在接收和发送时都是SDIO的FIFO, 在代码中使用了宏SDIO_FIFO_ADDRESS来定义。接收和发送函数非常类似,只是数据的方向和来源不一样而已,对于DMA相关配置的详细解释可以参考DMA章节内容。

37.9.2.2. 相关类型定义¶

打开bsp_sdio_sdcard.h文件可以发现有非常多的枚举类型定义、结构体类型定义以及宏定义,把所有的定义在这里罗列出来不太现实, 这部分代码内容请直接打开工程查看,针对这些内容在此处简要介绍如下:

枚举类型定义:有SD_Error、SDTransferState和SDCardState三个。SD_Error是列举了控制器可能出现的错误、 比如CRC校验错误、通信等待超时、FIFO上溢或下溢、擦除命令错误等等。这些错误类型部分是控制器系统寄存器的标志位, 部分是通过命令的响应内容得 到的。SDTransferState定义了SDIO传输状态,有传输正常状态、传输忙状态和传输错误状态。 SDCardState定义卡的当前状态,比如准备状态、识别状态、待机状态、传输状态等等, 具体状态转换过程参考图 卡识别模式状态转换图 和图 数据传输模式卡状态转换。

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

宏定义内容:包含有命令号定义、SDIO传输方式、SD卡插入状态以及SD卡类型定义。 参考表 SD部分命令描述 列举了描述了部分命令,文件中为每个命令号定义一个宏, 比如将复位CMD0定义为SD_CMD_GO_IDLE_STATE,这与表 SD部分命令描述 中缩写部分是类似的, 所以熟悉命名用途可以更好理解SD卡操作过程。 SDIO数据传输可以选择是否使用DMA传输,前面提到的SD_DMA_MODE和SD_POLLING_MODE就定义在这里, 两种方式只能二选一使用,为提高系统性能,一般使用DMA传输模式。接下来还定义了检测SD卡是否正确插入的宏SD_PRESENT和SD_NOT_PRESENT, ST官方的原SD卡驱动是以一个输入引脚电平判断SD卡是否正确插入,由于我们的硬件没有使用该引脚, 所以我们的程序里把ST驱动中原来的引脚检测部分代码删除掉了,但保留了SD_PRESENT和SD_NOT_PRESENT两个宏定义。 最后定义SD卡具体的类型,有V1.1版本标准卡、V2.0版本标准卡、高容量SD卡以及其他类型卡,前面三个是常用的类型。

在bsp_sdio_sdcard.c文件也有部分宏定义,这部分宏定义只能在该文件中使用。这部分宏定义包括命令超时时间定义、OCR寄存器位掩码、R6响应位掩码等等, 这些定义更多是为提取特定响应位内容而设计的掩码。

因为类型定义和宏定义内容没有在本文中列举出来,读者有必要使用KEIL工具打开本章配套例程理解清楚。同时了解bsp_sdio_sdcard.c文件中定义的多个不同类型变量。

接下来我们就开始根据SD卡识别过程和数据传输过程理解SD卡驱动函数代码。这部分代码内容也是非常庞大,不可能全部在文档中全部列出,对于部分函数只介绍其功能。

37.9.2.3. SD卡初始化¶

SD卡初始化过程主要是卡识别和相关SD卡状态获取。整个初始化函数可以实现图 SD卡初始化和识别流程 中的功能。

SD卡初始化函数

代码清单:SDIO-7 SD_Init函数¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99/* * 函数名:NVIC_Configuration * 描述 :SDIO 优先级配置为最高优先级。 * 输入 :无 * 输出 :无 */ static void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; /* Configure the NVIC Preemption Priority Bits */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } /** * 函数名: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 = 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); }

SD_POWERON函数执行流程如下:

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

(2) 调用SDIO_SetPowerState库函数控制SDIO的电源状态, 给SDIO提供电源,并调用SDIO_ClockCmd库函数使能SDIO时钟。

(3) 发送命令给SD卡,首先发送CMD0,复位所有SD卡, CMD0命令无需响应,所以调用CmdError函数检测错误即可。CmdError函数用于无需响应的命令发送检测,带有等待超时检测功能, 它通过不断检测SDIO_STA寄存器的CMDSENT位即可知道命令发送成功与否。如果遇到超时错误则直接退出SD_PowerON函数。如果无错误则执行下面程序。

(4) 发送CMD8命令,检测SD卡支持的操作条件,主要就是电压匹配,CMD8的响应类型是R7,使用CmdResp7Error函数可获取得到R7响应结果, 它是通过检测SDIO_STA寄存器相关位完成的,并具有等待超时检测功能。如果CmdResp7Error函数返回值为SD_OK,即CMD8有响应, 可以判定SD卡为V2.0及以上的高容量SD卡,如果没有响应可能是V1.1版本卡或者是不可用卡。

(5) 使用ACMD41命令判断卡的具体类型。因为是A类命令,所以在发送ACMD41之前必须先发送CMD55, CMD55命令的响应类型的R1。如果CMD55命令都没有响应说明是MMC卡或不可用卡。 在正确发送CMD55之后就可以发送ACMD41,并根据响应判断卡类型,ACMD41的响应号为R3,CmdResp3Error函数用于检测命令正确发送并带有超时检测功能, 但并不具备响应内容接收功能,需要在判定命令正确发送之后调用SDIO_GetResponse函数才能获取响应的内容。实际上,在有响应时,SDIO外设会自动把响应存放在SDIO_RESPx寄存器中, SDIO_GetResponse函数只是根据形参返回对应响应寄存器的值。通过判定响应内容值即可确定SD卡类型。

(6) 执行SD_PowerON函数无错误后就已经确定了SD卡类型,并说明卡和主机电压是匹配的,SD卡处于卡识别模式下的准备状态。 退出SD_PowerON函数返回SD_Init函数,执行接下来代码。判断执行SD_PowerON函数无错误后,执行下面的SD_InitializeCards函数进行与SD卡相关的初始化,使得卡进入数据传输模式下的待机模式。

SD_InitializeCards函数

代码清单:SDIO-9 SD_InitializeCards函数¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96/* * 函数名: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的卡 */ /* 发送 CMD3 SET_REL_ADDR ,带参数 0 * 要求各个SD卡返回自身的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 20) & SD_CCCC_ERASE) == 0) { errorstatus = SD_REQUEST_NOT_APPLICABLE; return (errorstatus); } //延时,根据时钟分频设置来计算 maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2); if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED) { //卡已上锁 errorstatus = SD_LOCK_UNLOCK_FAILED; return (errorstatus); } if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) { //sdhc卡 //在sdhc卡中,地址参数为块地址,每块512字节,而sdsc卡地址为字节地址 //所以若是sdhc卡要对地址/512进行转换 startaddr /= 512; endaddr /= 512; } /*!< ERASE_GROUP_START (CMD32)设置擦除的起始地址, erase_group_end(CMD33)设置擦除的结束地址, */ if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_HIGH_CAPACITY_SD_CARD == CardType)) { /*!< 发送命令 CMD32 SD_ERASE_GRP_START ,带参数startaddr */ SDIO_CmdInitStructure.SDIO_Argument = startaddr; SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START; SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //R1 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_START); if (errorstatus != SD_OK) { return (errorstatus); } /*!< 发送命令 CMD33 SD_ERASE_GRP_END ,带参数endaddr */ SDIO_CmdInitStructure.SDIO_Argument = endaddr; SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END; 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_SD_ERASE_GRP_END); if (errorstatus != SD_OK) { return (errorstatus); } } /*!< 发送 CMD38 ERASE 命令,开始擦除*/ SDIO_CmdInitStructure.SDIO_Argument = 0; SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE; 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_ERASE); if (errorstatus != SD_OK) { return (errorstatus); } for (delay = 0; delay CMD33->CMD38。如果发送顺序不对,SD卡会设置ERASE_SEQ_ERROR位到状态寄存器。

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

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

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

数据写入操作

数据写入可分为单块数据写入和多块数据写入,这里只分析单块数据写入,多块的与之类似。SD卡数据写入之前并没有硬性要求擦除写入块, 这与SPI Flash芯片写入是不同的。ST官方的SD卡写入函数包括扫描查询方式和DMA传输方式,我们这里只介绍DMA传输模式。

代码清单:SDIO-11 SD_WriteBlock函数¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73/** * @brief 向sd卡写入一个BLOCK的数据(512字节) * @note 本函数使用后需要调用如下两个函数来等待数据传输完成 * - SD_WaitWriteOperation(): 确认DMA已把数据传输到SDIO接口 * - SD_GetStatus(): 确认SD卡内部已经把数据写入完毕 * @param writebuff: 指向要写入的数据 * @param WriteAddr: 要把数据写入到sd卡的地址 * @param BlockSize: 块大小,sdhc卡为512字节 * @retval SD_Error: 返回的sd错误代码 */ SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize) { SD_Error errorstatus = SD_OK; TransferError = SD_OK; TransferEnd = 0; StopCondition = 0; SDIO->DCTRL = 0x0; if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) { BlockSize = 512; WriteAddr /= 512; } /*----以下这段是在st驱动库上添加的 , 没有这一段容易卡死在DMA检测中 ---*/ /* 设置块BLOCK的大小,cmd16, * 若是sdsc卡,可以用来设置块大小, * 若是sdhc卡,块大小为512字节,不受cmd16影响 */ SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; 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_SET_BLOCKLEN); if (SD_OK != errorstatus) { return (errorstatus); } /********************************************************/ /*!< 发送 CMD24 WRITE_SINGLE_BLOCK 写入 */ SDIO_CmdInitStructure.SDIO_Argument = WriteAddr; //写入地址 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK; SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r1 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_WRITE_SINGLE_BLOCK); if (errorstatus != SD_OK) { return (errorstatus); } //配置sdio的写数据寄存器 SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; SDIO_DataInitStructure.SDIO_DataLength = BlockSize; SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有