目录
(2)读取CID的函数HAL_SD_GetCardCID()
(3)读取CSD的函数HAL_SD_GetCardCSD()
(4)读取SSR的函数HAL_SD_GetCardStatus()
(5)获取SD卡信息的函数HAL_SD_GetCardInfo()
SD卡是嵌入式设备上常用的存储介质,某些STM32处理器上有SDIO接口,可以连接SD卡。本文介绍直接用HAL驱动程序访问SD卡的编程原理。
一、SD卡简介
1、 SD卡的分类
SD存储卡(Secure Digital Memory Card,简称SD卡)是一种基于半导体Flash存储器的存储设备,是由SD协会(SD Association)管理的一种完全开放的标准。SD卡具有高容量、高数据传输率、极大的移动灵活性以及很好的安全性,广泛应用于电子产品和嵌入式设备上,如数码相机、手机、行车记录仪等。
(1)外形尺寸
SD卡主要有两种外形与尺寸:一种是标准SD卡,其大小是24mm×32mm×2.1mm;另一种是microSD卡,通常也被称作TF(Trans-flash)卡,其大小是11mm×15mm×1.0mm。
常规SD卡有9个针脚(两个VSS),而常规microSD卡有8个针脚(一个VSS)。SD卡和microSD卡的功能相同,只有一个区别,即SD卡有一个写保护开关,而microSD卡没有。
(2)存储容量
按照存储容量划分,SD卡分为以下4种类别,不同的容量类别要使用不同的文件系统。
- SD,容量上限为2GB,使用FAT12和FAT16文件系统。
- SDHC,容量为2GB至32GB,使用FAT32文件系统。
- SDXC,容量为32GB至2TB,使用exFAT文件系统。
- SDUC,容量为2TB至128TB,使用exFAT文件系统。
任何SD卡数据读写的最小单位都是块(Block),一个块的大小是512字节。
(3)总线速度
STM32F4系列MCU的SDIO接口只支持到SD 2.0规范,也就是只支持到25MB/s的高速模式。
2、常规SD卡的接口
常规SD卡有9个引脚。SD卡的接口可以是SDIO接口或SPI接口。STM32F407的SDIO接口不提供SPI兼容模式。
引脚编号 | 名称 | 功能 |
1 | CD/DATA3 | SD卡检测/数据线3 |
2 | CMD | 命令 |
3 | VSS1 | 电源地 |
4 | VDD | 电源 |
5 | CLK | 时钟信号 |
6 | VSS2 | 电源地 |
7 | DATA0 | 数据线0 |
8 | DATA1 | 数据线1 |
9 | DATA2 | 数据线2 |
microSD卡有8个引脚,相比于SD卡只是少了一个VSS引脚,microSD卡8个引脚的定义见表。
引脚编号 | 名称 | 功能 |
1 | DATA2 | 数据线2 |
2 | CD/DATA3 | SD卡检测/数据线3 |
3 | CMD | 命令 |
4 | VDD | 电源 |
5 | CLK | 时钟信号 |
6 | VSS | 电源地 |
7 | DATA0 | 数据线0 |
8 | DATA1 | 数据线1 |
SD卡和microSD卡的接口功能是相同的,只是引脚分布顺序不一样。这些引脚主要有一个指令线CMD和一个时钟线CLK,还有4根数据线DATA0至DATA3,其中,DATA3还可作为SD卡检测线CD(Card Detection),也就是在SD卡插入时产生一个信号,让主机知道SD卡插入了。
二、SDIO接口硬件电路
1、 STM32F407的SDIO接口
STM32F407上有一个SDIO接口,可以连接多媒体卡(Multi-media Card,MMC)或SD卡。SDIO接口连接SD卡时,具有如下特性。
- 完全兼容SD卡规范版本2.0。
- 支持2种数据总线模式:1位(默认)或4位。
- 只支持高速SD卡,速度最高为25MB/s。
- STM32F407的SDIO接口没有SPI兼容模式。
STM32F407的SDIO接口的模块功能结构如图所示。SDIO接口由两部分组成:SDIO适配器和APB2接口。SDIO适配器提供SD卡访问的功能,如生成时钟、命令和数据传输。APB2接口访问SDIO适配器的寄存器,并生成中断和DMA请求。
SDIO使用两个时钟信号:
- SDIO适配器时钟SDIOCLK,来自时钟树上的48MHz时钟源。
- APB2总线时钟PCLK2。
PCLK2和SDIO_CK的时钟频率必须满足。
SDIO_CK是与SD卡连接的时钟信号,由SDIO适配器产生,其频率由SDIO适配器的一个分频器和SDIOCLK产生:。
其中,N是设置的SDIOCLK分频系数。一般情况下,SDIOCLK为48MHz,所以,当设置分频系数为0时,SDIO_CK最高频率为24MHz。
MCU与SD卡之间通过SDIO接口通信协议进行通信。通信协议由一系列指令组成,通过这些指令可以完成块擦除、块数据读取、块数据写入等各种操作,HAL库提供了SDIO接口访问SD卡的各种操作函数。
2、开发板上的microSD卡连接电路
旺宝红龙STM32F407ZGT6 KIT V1.0开发板上有一个microSD卡座,MCU道过SDIO接口连接microSD卡。这个SDIO接口使用了4根数据线,这4根数据线和SDIO_CMD都使用了外接上拉电阻,SDIO_SCK无外接上拉电阻。
三、SDIO接口和SD卡的HAL驱动程序
HAL库提供了SDIO接口和SD卡访问的驱动程序,驱动程序的功能主要包括SDIO接口初始化、SD卡信息读取、SD卡数据块读写等。我们将SDIO接口和SD卡访问的HAL驱动程序总称为SD的HAL驱动程序,因为它既包括SDIO外设的驱动程序,还包括SD卡器件的驱动程序。
SD卡的HAL驱动程序是访问SD卡的软件基础,在针对SD卡进行FatFS移植时,FatFS的硬件层访问函数也要用到SD的HAL驱动程序。
1、 SD驱动程序概述
STM32F4的SD驱动程序头文件是stm32f4xx_hal_sd.h,在这个文件中,有一个常用的宏定义,如下所示:
#define BLOCKSIZE 512U /*!< Block size is 512 bytes */
SD卡读写数据的最小单位是块(Block),不管什么容量的SD卡,其块大小都是512字节。
文件stm32f4xx_hal_sd.h还定义了一些其他的宏、枚举类型和结构体。SD的主要驱动函数见表:
分组 | 函数名 | 函数功能 |
初始化和 设置 | HAL_SD_Init() | SDIO接口和SD卡初始化,内部会调HAL_SD_InitCard() |
HAL_SD_InitCard() | SD卡初始化,如果需要重新初始化SD卡,可以单独调用这个函数 | |
HAL_SD_ConfigWideBusOperation() | 设置SDIO接口数据线位数,即设置为1位或4位数据线 | |
HAL_SD_Erase() | 擦除指定编号范围的数据块 | |
读取 SD卡 信息 | HAL_SD_GetCardInfo() | 读取SD卡的信息,包括SD卡类型、数据块个数、数据块大小等 |
HAL_SD_GetCardCID() | 返回SD卡上CID里存储的信息,包括生产厂家ID、产品序列号等 | |
HAL_SD_GetCardCSD() | 返回SD卡上CSD里存储的信息,包括系统版本号、总线最高频率、读取数据块最大长度等 | |
HAL_SD_GetCardStatus() | 返回SD卡上SSR的内容,包括当前总线位宽、卡的类型等 | |
获取 | HAL_SD_GetCardState() | 获取SD卡当前数据状态,返回状态类型是HAL_SD_CardStateTypeDef |
HAL_SD_GetState() | 获取SDIO接口的状态,返回状态类型是HAL_SD_StateTypeDef | |
HAL_SD_GetError() | 可以在回调函数HAL_SD_ErrorCallback()里调用这个函数获取错误编号 | |
轮询方 | HAL_SD_ReadBlocks() | 以轮询方式读取1个或多个数据块的数据 |
HAL_SD_WriteBlocks() | 以轮询方式写入1个或多个数据块的数据 | |
中断方 | HAL_SD_ReadBlocks_IT() | 以中断方式读取1个或多个数据块的数据 |
HAL_SD_WriteBlocks_IT() | 以中断方式写入1个或多个数据块的数据 | |
HAL_SD_Abort_IT() | 取消中断方式传输过程 | |
HAL_SD_IRQHandler() | SDIO中断ISR里调用的通用处理函数 | |
D M A 方 式读写 | HAL_SD_ReadBlocks_DMA() | 以DMA方式读取1个或多个数据块的数据 |
HAL_SD_WriteBlocks_DMA() | 以DMA方式写入1个或多个数据块的数据 | |
HAL_SD_Abort() | 取消DMA方式传输过程 | |
回调 | HAL_SD_RxCpltCallback() | 中断方式或DMA方式接收数据完成时的回调函数 |
HAL_SD_TxCpltCallback() | 中断方式或DMA方式发送数据完成时的回调函数 | |
HAL_SD_AbortCallback() | 取消中断或DMA方式数据传输过程时的回调函数 | |
HAL_SD_ErrorCallback() | 发生错误时的回调函数 |
SDIO接口可以在CubeMX中可视化配置,生成的代码会自动调用HAL_SD_Init()对SDIO接口和SD卡进行初始化。SD卡初始化完成后,程序可以通过HAL_SD_GetCardInfo()等函数读取SD卡的一些信息和参数。SD卡的数据读写有轮询、中断和DMA方式,常用的是轮询和DMA方式。SD的驱动程序中只有4个回调函数,最主要的是HAL_SD_RxCpltCallback()和HAL_SD_TxCpltCallback()。
2、初始化和配置函数
函数HAL_SD_Init()用于对SDIO接口和SD卡进行初始化。SDIO接口的设置主要包括数据线条数、SDIOCLK时钟分频系数等。函数HAL_SD_Init()内部会调用函数HAL_SD_InitCard()对SD卡进行初始化。
CubeMX生成的SDIO外设程序文件sdio.c包含初始化函数MX_SDIO_SD_Init(),用于对SDIO接口和SD卡初始化。文件sdio.c还定义了一个SD_HandleTypeDef结构体类型变量hsd:
SD_HandleTypeDef hsd;
hsd是SD对象变量,它表示了SDIO接口和SD卡。&hsd称为SD对象指针,SD的HAL驱动函数都需要一个SD对象指针作为输入参数。
函数HAL_SD_Erase()用于擦除1个或多个数据块。SD卡是Flash类型存储器,在向一个块写入数据时,这个块必须是被擦除过的,否则数据是无法写入的。函数HAL_SD_Erase()的原型定义如下:
/**
* @brief Erases the specified memory area of the given SD card.
* @note This API should be followed by a check on the card state through
* HAL_SD_GetCardState().
* @param hsd: Pointer to SD handle
* @param BlockStartAdd: Start Block address
* @param BlockEndAdd: End Block address
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_Erase(SD_HandleTypeDef *hsd, uint32_t BlockStartAdd, uint32_t BlockEndAdd)
其中,hsd是SD对象指针,BlockStartAdd是起始的块编号,BlockEndAdd是截止的块编号,注意,在SD的HAL驱动函数中,块地址参数都是指块编号。
3、读取SD卡的参数信息
(1)SD卡的寄存器
SD卡上有一些内置的寄存器这些寄存器存储了SD卡的一些信息和参数。
- CID,128位长度,卡的识别码(Card Identification)寄存器。这个寄存器存储了生产商ID、卡的序列号(32位无符号整数)、生产日期等信息。通过函数HAL_SD_GetCardCID()可以读取CID寄存器的内容。
- CSD,128位长度,卡的特性数据(Card Specifie Data)寄存器。这个寄存器包含访问该卡数据时的必要配置信息,如读取数据时间、SDIO_SCK最大时钟频率、读取/写入操作最大耗电流、是否允许擦除单个数据块等。通过函数HAL_SD_GetCardCSD()可以读取CSD寄存器的内容。
- OCR,32位长度,工作条件寄存器(Operation Condition Register)。这个寄存器存储了卡的VDD电压轮廓图。访问存储器的阵列需要2.7V至3.6V的工作电压,OCR显示了在访问卡的数据时所需要的电压范围。
- SCR,64位长度,SD配置寄存器(SD Configuration Register)。这个寄存器存储了SD卡的特殊功能特性信息,例如,支持的SD规范版本、数据被擦除后状态是0还是1、支持的安全算法类型等。MMC卡没有SCR。
- RCA,16位长度,卡的相对地址(Relative Card Address)寄存器。这个寄存器保存着在卡识别过程中卡发布的器件地址,在卡识别后,主机利用该地址与卡进行通信。这个寄存器在SPI接口模式下无效。
- SSR,512位长度,SD状态寄存器(SD Status Register)。这个寄存器保存着卡的特性参数,如当前总线位宽、卡的类型、卡的速度等级、卡的分区单元大小等,通过函数HAL_SD_GetCardStatus()可以读取SSR的内容。
- CSR,32位长度,卡状态寄存器(Card Status Register)。这个寄存器包含SD卡操作时的一些状态信息,例如,指令的参数大小是否超过范围、CRC校验是否成功等。
- DSR,16位长度,驱动级别寄存器(Driver Stage Register),用于配置卡的输出驱动。DSR不是必须有的,有的SD卡里可能没有这个寄存器。
这几个寄存器中,CID、CSD、OCR和SCR用于保存卡的配置信息;RCA用于保存卡识别过程中暂时分配的相对地址,在主机与卡之间通信时使用;SSR用于保存卡的特性参数,CSR用于保存上一次SD卡操作的状态信息。
某些寄存器可以由函数直接读取,例如,函数HAL_SD_GetCardCID()可读取CD的内容,函数HAL_SD_GetCardCSD()可读取CSD的内容,函数HAL_SD_GetCardStatus()可读取SSR的内容。另外,函数HAL_SD_GetCardInfo()可以返回SD卡的一些主要信息,例如卡的类型、数据块个数、块的大小等。
(2)读取CID的函数HAL_SD_GetCardCID()
函数HAL_SD_GetCardCID()用于读取CID的内容,其原型定义如下:
/**
* @brief Returns information the information of the card which are stored on
* the CID register.
* @param hsd: Pointer to SD handle
* @param pCID: Pointer to a HAL_SD_CardCIDTypeDef structure that
* contains all CID register parameters
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_GetCardCID(SD_HandleTypeDef *hsd, HAL_SD_CardCIDTypeDef *pCID)
其中,参数hsd是SD对象指针,参数pCID是HAL_SD_CardCIDTypeDef结构体指针,是一个返回参数,它指向的变量保存了SD卡CID的内容。结构体HAL_SD_CardCIDTypeDef的定义如下,各成员变量的意义见注释:
/** @defgroup SD_Exported_Types_Group5 Card Identification Data: CID Register
* @{
*/
typedef struct
{
__IO uint8_t ManufacturerID; /*!< Manufacturer ID */
__IO uint16_t OEM_AppliID; /*!< OEM/Application ID */
__IO uint32_t ProdName1; /*!< Product Name part1 */
__IO uint8_t ProdName2; /*!< Product Name part2 */
__IO uint8_t ProdRev; /*!< Product Revision */
__IO uint32_t ProdSN; /*!< Product Serial Number */
__IO uint8_t Reserved1; /*!< Reserved1 */
__IO uint16_t ManufactDate; /*!< Manufacturing Date */
__IO uint8_t CID_CRC; /*!< CID CRC */
__IO uint8_t Reserved2; /*!< Always 1 */
}HAL_SD_CardCIDTypeDef;
(3)读取CSD的函数HAL_SD_GetCardCSD()
函数HAL_SD_GetCardCSD()用于读取CSD的内容,其原型定义如下:
/**
* @brief Returns information the information of the card which are stored on
* the CSD register.
* @param hsd: Pointer to SD handle
* @param pCSD: Pointer to a HAL_SD_CardCSDTypeDef structure that
* contains all CSD register parameters
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_GetCardCSD(SD_HandleTypeDef *hsd, HAL_SD_CardCSDTypeDef *pCSD)
其中,参数hsd是SD对象指针,参数pCSD是HAL_SD_CardCSDTypeDef结构体指针,是一个返回参数,它指向的变量保存了SD卡的CSD的内容。结构体HAL_SD_CardCSDTypeDef的定义如下,各成员变量的意义见注释:
/** @defgroup SD_Exported_Types_Group4 Card Specific Data: CSD Register
* @{
*/
typedef struct
{
__IO uint8_t CSDStruct; /*!< CSD structure */
__IO uint8_t SysSpecVersion; /*!< System specification version */
__IO uint8_t Reserved1; /*!< Reserved */
__IO uint8_t TAAC; /*!< Data read access time 1 */
__IO uint8_t NSAC; /*!< Data read access time 2 in CLK cycles */
__IO uint8_t MaxBusClkFrec; /*!< Max. bus clock frequency */
__IO uint16_t CardComdClasses; /*!< Card command classes */
__IO uint8_t RdBlockLen; /*!< Max. read data block length */
__IO uint8_t PartBlockRead; /*!< Partial blocks for read allowed */
__IO uint8_t WrBlockMisalign; /*!< Write block misalignment */
__IO uint8_t RdBlockMisalign; /*!< Read block misalignment */
__IO uint8_t DSRImpl; /*!< DSR implemented */
__IO uint8_t Reserved2; /*!< Reserved */
__IO uint32_t DeviceSize; /*!< Device Size */
__IO uint8_t MaxRdCurrentVDDMin; /*!< Max. read current @ VDD min */
__IO uint8_t MaxRdCurrentVDDMax; /*!< Max. read current @ VDD max */
__IO uint8_t MaxWrCurrentVDDMin; /*!< Max. write current @ VDD min */
__IO uint8_t MaxWrCurrentVDDMax; /*!< Max. write current @ VDD max */
__IO uint8_t DeviceSizeMul; /*!< Device size multiplier */
__IO uint8_t EraseGrSize; /*!< Erase group size */
__IO uint8_t EraseGrMul; /*!< Erase group size multiplier */
__IO uint8_t WrProtectGrSize; /*!< Write protect group size */
__IO uint8_t WrProtectGrEnable; /*!< Write protect group enable */
__IO uint8_t ManDeflECC; /*!< Manufacturer default ECC */
__IO uint8_t WrSpeedFact; /*!< Write speed factor */
__IO uint8_t MaxWrBlockLen; /*!< Max. write data block length */
__IO uint8_t WriteBlockPaPartial; /*!< Partial blocks for write allowed */
__IO uint8_t Reserved3; /*!< Reserved */
__IO uint8_t ContentProtectAppli; /*!< Content protection application */
__IO uint8_t FileFormatGroup; /*!< File format group */
__IO uint8_t CopyFlag; /*!< Copy flag (OTP) */
__IO uint8_t PermWrProtect; /*!< Permanent write protection */
__IO uint8_t TempWrProtect; /*!< Temporary write protection */
__IO uint8_t FileFormat; /*!< File format */
__IO uint8_t ECC; /*!< ECC code */
__IO uint8_t CSD_CRC; /*!< CSD CRC */
__IO uint8_t Reserved4; /*!< Always 1 */
} HAL_SD_CardCSDTypeDef;
结构体HAL_SD_CardCSDTypeDef的成员变量很多,若要搞清楚变量的详细意义,请参考SD标准手册。
(4)读取SSR的函数HAL_SD_GetCardStatus()
函数HAL_SD_GetCardStatus()用于读取SSR的内容,其原型定义如下:
/**
* @brief Gets the SD status info.
* @param hsd: Pointer to SD handle
* @param pStatus: Pointer to the HAL_SD_CardStatusTypeDef structure that
* will contain the SD card status information
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_GetCardStatus(SD_HandleTypeDef *hsd, HAL_SD_CardStatusTypeDef *pStatus)
其中,参数hsd是SD对象指针,参数pStatus是HAL_SD_CardStatusTypeDef结构体指针,是一个返回参数。结构体HAL_SD_CardStatusTypeDef的定义如下,各成员变量的意义见注释:
/** @defgroup SD_Exported_Types_Group6 SD Card Status returned by ACMD13
* @{
*/
typedef struct
{
__IO uint8_t DataBusWidth; /*!< Shows the currently defined data bus width */
__IO uint8_t SecuredMode; /*!< Card is in secured mode of operation */
__IO uint16_t CardType; /*!< Carries information about card type */
__IO uint32_t ProtectedAreaSize; /*!< Carries information about the capacity of protected area */
__IO uint8_t SpeedClass; /*!< Carries information about the speed class of the card */
__IO uint8_t PerformanceMove; /*!< Carries information about the card's performance move */
__IO uint8_t AllocationUnitSize; /*!< Carries information about the card's allocation unit size */
__IO uint16_t EraseSize; /*!< Determines the number of AUs to be erased in one operation */
__IO uint8_t EraseTimeout; /*!< Determines the timeout for any number of AU erase */
__IO uint8_t EraseOffset; /*!< Carries information about the erase offset */
}HAL_SD_CardStatusTypeDef;
结构体HAL_SD_CardStatusTypeDef的成员变量大多是用代码表示的,几个主要变量的意义如下。各成员变量的代码值的意义详见STM32F4xx手册。
- DataBusWidth表示数据总线位宽,0x00=1位宽度,0x02=4位宽度。
- SecuredMode表示卡当前所处的安全操作模式,0=未处于安全模式,1=处于安全模式。
- CardType表示卡的类型,目前只定义了2种,0=常规SD RD/WR卡,1=SD ROM卡。
- AllocationUnitSize表示分配单元(AU)大小,也就是格式化SD卡时一个簇的大小,簇的大小是块的整数倍。AllocationUnitSize的取值与实际分配单元的大小见表。
AllocationUnitSize的取值 | 实际分配单元的大小 |
0x00 | 未定义 |
0x01 | 16KB |
0x02 | 32KB |
0x03 | 64KB |
0x04 | 128KB |
0x05 | 256KB |
0x06 | 512KB |
0x07 | 1MB |
0x08 | 2MB |
0x09 | 4MB |
0x0A~0x0F | 保留 |
AU的最大值取决于SD卡的容量大小,例如,容量为512MB的卡的AU最大值为2MB,容量为1GB至32GB的卡的AU最大值是4MB。在使用FatFS的函数fmkfs0格式化SD卡时,我们可以选择自动设置AU的大小。
(5)获取SD卡信息的函数HAL_SD_GetCardInfo()
函数HAL_SD_GetCardInfo()用于返回SD卡的一些主要信息,它并不是读取SD卡的某个寄存器,而是读取这些寄存器中的一些主要参数。函数HAL_SD_GetCardinfo()的原型定义如下:
/**
* @brief Gets the SD card info.
* @param hsd: Pointer to SD handle
* @param pCardInfo: Pointer to the HAL_SD_CardInfoTypeDef structure that
* will contain the SD card status information
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_GetCardInfo(SD_HandleTypeDef *hsd, HAL_SD_CardInfoTypeDef *pCardInfo)
返回的数据保存在指针pCardInfo指向的HAL_SD_CardInfoTypeDef类型的结构体变量中,这个结构体的定义如下,各成员变量的意义见注释:
/**
* @brief SD Card Information Structure definition
*/
typedef struct
{
uint32_t CardType; /*!< Specifies the card Type */
uint32_t CardVersion; /*!< Specifies the card version */
uint32_t Class; /*!< Specifies the class of the card class */
uint32_t RelCardAdd; /*!< Specifies the Relative Card Address */
uint32_t BlockNbr; /*!< Specifies the Card Capacity in blocks */
uint32_t BlockSize; /*!< Specifies one block size in bytes */
uint32_t LogBlockNbr; /*!< Specifies the Card logical Capacity in blocks */
uint32_t LogBlockSize; /*!< Specifies logical block size in bytes */
}HAL_SD_CardInfoTypeDef;
SD卡的块大小是512字节,一般情况下,卡的逻辑块大小就等于块的大小,逻辑块的个数也等于块的个数。
4、获取SD卡的当前状态
SD卡的操作时序是比较复杂的,一个操作有时涉及多个状态之间的转换,例如,上电时SD卡的识别过程,或将数据写入SD卡的过程。SD的HAL驱动函数封装了这些复杂的操作过程,但是有些函数在执行后,要求用函数HAL_SD_GetCardState()查询SD卡的状态,以确定操作是否完成。
函数HAL_SD_GetCardState()用于查询SD卡当前的状态,其原型定义如下:
/**
* @brief Gets the current sd card data state.
* @param hsd: pointer to SD handle
* @retval Card state
*/
HAL_SD_CardStateTypeDef HAL_SD_GetCardState(SD_HandleTypeDef *hsd)
函数的返回值类型是HAL_SD_CardStateTypeDef,它就是uint32_t类型,stm32f4xx_hal_sd.h中定义了各种状态的宏定义常数。
/** @defgroup SD_Exported_Types_Group2 SD Card State enumeration structure
* @{
*/
typedef uint32_t HAL_SD_CardStateTypeDef;
#define HAL_SD_CARD_READY 0x00000001U /*!< Card state is ready */
#define HAL_SD_CARD_IDENTIFICATION 0x00000002U /*!< Card is in identification state */
#define HAL_SD_CARD_STANDBY 0x00000003U /*!< Card is in standby state */
#define HAL_SD_CARD_TRANSFER 0x00000004U /*!< Card is in transfer state */
#define HAL_SD_CARD_SENDING 0x00000005U /*!< Card is sending an operation */
#define HAL_SD_CARD_RECEIVING 0x00000006U /*!< Card is receiving operation information */
#define HAL_SD_CARD_PROGRAMMING 0x00000007U /*!< Card is in programming state */
#define HAL_SD_CARD_DISCONNECTED 0x00000008U /*!< Card is disconnected */
#define HAL_SD_CARD_ERROR 0x000000FFU /*!< Card response Error */
/**
* @}
*/
SD卡在上电时会自动进行卡识别,识别过程中卡处于识别状态。识别完成后,卡在空闲时处于传输状态(HAL_SD_CARD_TRANSFER),注意,不是就绪状态。所以,要检查一个操作是否完成,只需看函数HAL_SD_GetCardState()返回的状态是否为HAL_SD_CARD_TRANSFER。
5、以轮询方式读写SD卡
SD卡读写数据的最小单位是块,一个块的大小是512字节。读写数据的方法有阻塞式(Blocking)和非阻塞式(Nonblocking),以中断或DMA方式读写数据是非阻塞式的,以轮询方式读写数据是阻塞式的。以轮询方式读取SD卡数据的函数是HAL_SD_ReadBlocks(),其原型定义如下:
/**
* @brief Reads block(s) from a specified address in a card. The Data transfer
* is managed by polling mode.
* @note This API should be followed by a check on the card state through
* HAL_SD_GetCardState().
* @param hsd: Pointer to SD handle
* @param pData: pointer to the buffer that will contain the received data
* @param BlockAdd: Block Address from where data is to be read
* @param NumberOfBlocks: Number of SD blocks to read
* @param Timeout: Specify timeout value
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_ReadBlocks(
SD_HandleTypeDef *hsd,
uint8_t *pData,
uint32_t BlockAdd,
uint32_t NumberOfBlocks,
uint32_t Timeout
)
其中,hsd是SD对象指针;pData是读出数据保存缓冲区的指针;BlockAdd是读取数据的起始块编号;NumberOfBlocks是要读取的块的个数,可以大于1;Timeout是超时等待节拍数,默认情况下单位就是毫秒。如果在Timeout时间内成功读取了数据,函数的返回值为HAL_OK。
读出的数据会保存到缓冲区pData里,这个缓冲区的大小应是BLOCKSIZE *NumberOfBlocks字节,BLOCKSIZE是在文件stm32f4xx_hal_sd.h中定义的宏常数,值为512,也就是一个块的字节数。
以轮询方式向SD卡写入数据的函数是HAL_SD_WriteBlocks(),其原型定义如下:
/**
* @brief Allows to write block(s) to a specified address in a card. The Data
* transfer is managed by polling mode.
* @note This API should be followed by a check on the card state through
* HAL_SD_GetCardState().
* @param hsd: Pointer to SD handle
* @param pData: pointer to the buffer that will contain the data to transmit
* @param BlockAdd: Block Address where data will be written
* @param NumberOfBlocks: Number of SD blocks to write
* @param Timeout: Specify timeout value
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_WriteBlocks(
SD_HandleTypeDef *hsd,
uint8_t *pData,
uint32_t BlockAdd,
uint32_t NumberOfBlocks,
uint32_t Timeout
)
其中,hsd是SD对象指针;pData是待写入数据缓冲区的指针;BlockAdd是要写入位置的起始块编号;NumberOfBlocks是要写入的块的个数,可以大于1;Timeout是超时等待节拍数。如果在Timeout时间内成功写入了数据,函数的返回值为HAL_OK。
在调用函数HAL_SD_WriteBlocks()写入数据块时,无须先执行块擦除操作,该函数内部会执行块擦除操作。
6、以中断方式读写SD卡
以中断方式读取SD卡数据的函数是HAL_SD_ReadBlocks_IT(),其原型定义如下:
/**
* @brief Reads block(s) from a specified address in a card. The Data transfer
* is managed in interrupt mode.
* @note This API should be followed by a check on the card state through
* HAL_SD_GetCardState().
* @note You could also check the IT transfer process through the SD Rx
* interrupt event.
* @param hsd: Pointer to SD handle
* @param pData: Pointer to the buffer that will contain the received data
* @param BlockAdd: Block Address from where data is to be read
* @param NumberOfBlocks: Number of blocks to read.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_ReadBlocks_IT(
SD_HandleTypeDef *hsd,
uint8_t *pData,
uint32_t BlockAdd,
uint32_t NumberOfBlocks
)
其中,hsd是SD对象指针;pData是读出数据保存缓冲区的指针;BlockAdd是读取数据的起始块编号;NumberOfBlocks是要读取的块的个数,可以大于1。
以中断方式读取数据是非阻塞式的,也就是数据未读取完,函数HAL_SD_ReadBlocks_IT()就退出了,继续执行后面的代码。如果开启了SDIO全局中断,在数据读取完成后,会调用回调函数HAL_SD_RxCpltCallback();如果需要在数据读取完成后做处理,就需要重新实现这个回调函数。
以中断方式向SD卡写数据的函数是HAL_SD_WriteBlocks_IT(),其原型定义如下:
/**
* @brief Writes block(s) to a specified address in a card. The Data transfer
* is managed in interrupt mode.
* @note This API should be followed by a check on the card state through
* HAL_SD_GetCardState().
* @note You could also check the IT transfer process through the SD Tx
* interrupt event.
* @param hsd: Pointer to SD handle
* @param pData: Pointer to the buffer that will contain the data to transmit
* @param BlockAdd: Block Address where data will be written
* @param NumberOfBlocks: Number of blocks to write
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_WriteBlocks_IT(
SD_HandleTypeDef *hsd,
const uint8_t *pData,
uint32_t BlockAdd,
uint32_t NumberOfBlocks
)
其中,hsd是SD对象指针;pData是待写入数据缓冲区的指针;BlockAdd是要写入位置的起始块编号;NumberOfBlocks是要写入的块的个数,可以大于1。
如果开启了SDIO全局中断,在数据写入完成后会调用回调函数HAL_SD_TxCpltCallback();如果需要在数据写入完成后做处理,就需要重新实现这个回调函数。
7、以DMA方式读写SD卡
以DMA方式传输数据可以减少CPU的负荷,提高系统运行效率。SDIO有SDIO_TX和SDIO_RX两个DMA请求,可以分别配置DMA流,因此,可以使用DMA方式读写SD卡。
以DMA方式读取SD卡数据的函数是HAL_SD_ReadBlocks_DMA(),其原型定义如下:
/**
* @brief Reads block(s) from a specified address in a card. The Data transfer
* is managed by DMA mode.
* @note This API should be followed by a check on the card state through
* HAL_SD_GetCardState().
* @note You could also check the DMA transfer process through the SD Rx
* interrupt event.
* @param hsd: Pointer SD handle
* @param pData: Pointer to the buffer that will contain the received data
* @param BlockAdd: Block Address from where data is to be read
* @param NumberOfBlocks: Number of blocks to read.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_ReadBlocks_DMA(
SD_HandleTypeDef *hsd,
uint8_t *pData,
uint32_t BlockAdd,
uint32_t NumberOfBlocks
)
HAL_SD_WriteBlocks_DMA(),其原型定义如下:
/**
* @brief Writes block(s) to a specified address in a card. The Data transfer
* is managed by DMA mode.
* @note This API should be followed by a check on the card state through
* HAL_SD_GetCardState().
* @note You could also check the DMA transfer process through the SD Tx
* interrupt event.
* @param hsd: Pointer to SD handle
* @param pData: Pointer to the buffer that will contain the data to transmit
* @param BlockAdd: Block Address where data will be written
* @param NumberOfBlocks: Number of blocks to write
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_WriteBlocks_DMA(
SD_HandleTypeDef *hsd,
const uint8_t *pData,
uint32_t BlockAdd,
uint32_t NumberOfBlocks
)
以DMA方式读取完数据后,会调用回调函数HAL_SD_RxCpltCallback()。以DMA方式写入数据完成后,会调用回调函数HAL_SD_TxCpltCallback()。注意,必须开启SDIO的全局中断,SDIO的DMA方式才能正常工作,否则可能出现异常。
四、示例:以轮询方式读写SD卡
1、示例功能与CubeMX项目设置
创建一个示例项目演示如何以轮询方式直接访问SD卡。本示例要用到串口和4个按键。
继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。 使用128M的micro SD卡。
(1)RCC
选择外部晶振。PCLK2=84MHz,SDIOCLK=48MHz。
(2)GPIO
(3)USART6、CodeGenerator、SYS
USART6选择默认设置;
CodeGenerator选择自动生成代码对;
SYS选择SerialWire;
(4)SDIO
SDIO在组件面板的Connectivity分组里,SDIO连接SD卡时,有1线或4线模式,还可以连接MMC卡。将模式设置为SD 1bits Wide bus,(如果选择4线数据线,SD卡初始化失败,不同的SD卡,不同的MCU平台,遇到的现象可能不同,读者可以在1线和4线之间都试一遍,能让SD卡完成是初始化的选择基本面上就是正确的)。
启用SDIO后,在时钟树上会自动启用48MHz时钟信号。如果这个时钟信号不是48MHz,会出现对话框提示解决时钟问题,使用自动解决时钟信号问题即可。PCLK2是SDIO模块的时钟,48MHz时钟信号作为SDIO适配器时钟信号SDIOCLK,用于产生SDIO_CK时钟信号驱动SD卡。
本示例使用轮询方式进行SDIO操作,所以无须启用其中断或设置DMA。
(5)NVIC
2、主程序与SDIO接口/SD卡初始化
(1)初始主程序
在CubeMX中完成设置后生成代码,还未添加任何用户程序的主程序代码如下。在外设初始化部分,函数MX_SDIO_SD_Init()用于对SDIO接口和SD卡进行初始化。
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "sdio.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include <stdio.h>
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t SDBuf_TX[BLOCKSIZE]; //Data Sending Cache, BLOCKSIZE=512
uint8_t SDBuf_RX[BLOCKSIZE]; //Data Received Cache
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART6_UART_Init();
MX_SDIO_SD_Init();
/* USER CODE BEGIN 2 */
// 先省略这部分内容
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
(2)SDIO接口和SD卡初始化
CubeMX自动生成了初始化函数MX_SDIO_SD_Init(),在文件sdio.h和sdio.c中自动定义和实现,其实现代码如下:
/* Includes ------------------------------------------------------------------*/
#include "sdio.h"
SD_HandleTypeDef hsd;
void MX_SDIO_SD_Init(void)
{
hsd.Instance = SDIO;
hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
hsd.Init.ClockDiv = 255;
if (HAL_SD_Init(&hsd) != HAL_OK)
{
Error_Handler();
}
}
void HAL_SD_MspInit(SD_HandleTypeDef* sdHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(sdHandle->Instance==SDIO)
{
/* USER CODE BEGIN SDIO_MspInit 0 */
/* USER CODE END SDIO_MspInit 0 */
/* SDIO clock enable */
__HAL_RCC_SDIO_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
/**SDIO GPIO Configuration
PC8 ------> SDIO_D0
PC12 ------> SDIO_CK
PD2 ------> SDIO_CMD
*/
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* USER CODE BEGIN SDIO_MspInit 1 */
/* USER CODE END SDIO_MspInit 1 */
}
}
文件sdio.c定义了一个SD_HandleTypeDef类型的变量hsd,用于表示SDIO接口,也用于表示连接的SD卡,所以称之为SD对象变量。
函数MX_SDIO_SD_Init()对hsd的一些成员变量赋值,这些赋值的代码与CubeMX中的设置是对应的。设置好SDIO的各种属性后,调用函数HAL_SD_Init()对SDIO接口和SD卡进行初始化,还调用了函数HAL_SD_ConfigWideBusOperation()将SDIO设置为1位总线宽度。
重新实现的MSP函数HAL_SD_Msplnit()对SDIO接口的GPIO引脚进行初始化,这个MSP函数在MX_SDIO_SD_Init()中被调用。
函数HAL_SD_Init()内部会调用HAL_SD_InitCard()进行SD卡的初始化,也就是进行卡识别,读取SD卡参数等。所以,执行函数MX_SDIO_SD_Init()完成初始化后,就可以直接操作SD卡了。
3、程序功能实现
(1)主程序
在CubeMX生成的初始化代码的基础上添加用户功能代码。将KEY_LED驱动程序目录添加到项目搜索路径,然后在主程序中添加代码。完成的主程序代码见上面的代码。
主程序在串口助手上显示了一个文字菜单,通过4个按键选择菜单,实现相应的操作,具体如下。
- 按下KeyUp键时,调用函数SDCard_ShowInfo()显示SD卡信息。
- 按下KeyLeft键时,调用函数SDCard_TestWrite()测试向Block 5写入数据。
- 按下KeyRight键时,调用函数SDCard_TestRead()测试从Block 5读取数据。
- 按下KeyDown键时,调用函数SDCard_EraseBlocks()擦除Block 0至10。
/* USER CODE BEGIN 2 */
uint8_t startstr[] = "Demo13_2: SD card R/W-DMA. \r\n";
HAL_UART_Transmit(&huart6,startstr,sizeof(startstr),0xFFFF);
uint8_t startstr1[] = "Read/write SD card via DMA. \r\n\r\n";
HAL_UART_Transmit(&huart6,startstr1,sizeof(startstr1),0xFFFF);
//show menu
printf("[S2]KeyUp =SD card info. \r\n");
printf("[S3]KeyDown =Erase 0-10 blocks. \r\n");
printf("[S4]KeyLeft =Write block. \r\n");
printf("[S1]KeyRight=Read block. \r\n\r\n");
while(1)
{
KEYS waitKey=ScanPressedKey(KEY_WAIT_ALWAYS); //Waiting for the key
if (waitKey==KEY_UP)
{
SDCard_ShowInfo();
printf("Reselect menu item or reset. \r\n");
}
else if (waitKey== KEY_DOWN)
{
SDCard_EraseBlocks(); //EraseBlocks 0-10
printf("Reselect menu item or reset. \r\n");
}
else if (waitKey== KEY_LEFT)
SDCard_TestWrite_DMA();
else if (waitKey== KEY_RIGHT)
SDCard_TestRead_DMA();
HAL_Delay(500);
}
/* USER CODE END 2 */
(2)显示SD卡信息
函数HAL_SD_GetCardInfo()获取SD卡信息,包括SD卡类型、数据块个数、容量等信息。自定义函数SDCard_ShowInfo()调用函数HAL_SD_GetCardInfo()获取信息,并在串口助手上显示,其代码如下:
/* USER CODE BEGIN 4 */
/* HAL_SD_GetCardInfo(), Display SD card information */
void SDCard_ShowInfo()
{
HAL_SD_CardInfoTypeDef cardInfo; //SD card information structure
HAL_StatusTypeDef res=HAL_SD_GetCardInfo(&hsd, &cardInfo);
if (res!=HAL_OK)
{
printf("HAL_SD_GetCardInfo() error. \r\n");
return;
}
printf("*** HAL_SD_GetCardInfo() info *** \r\n\r\n");
printf("Card Type= %ld \r\n", cardInfo.CardType);
printf("Card Version= %ld \r\n", cardInfo.CardVersion);
printf("Card Class= %ld \r\n", cardInfo.Class);
printf("Relative Card Address= %ld \r\n", cardInfo.RelCardAdd);
printf("Block Count= %ld \r\n", cardInfo.BlockNbr);
printf("Block Size(Bytes)= %ld \r\n", cardInfo.BlockSize);
printf("LogiBlockCount= %ld \r\n", cardInfo.LogBlockNbr);
printf("LogiBlockSize(Bytes)= %ld \r\n", cardInfo.LogBlockSize);
uint32_t cap= (cardInfo.BlockNbr*cardInfo.BlockSize); //bytes
cap = cap/(1e6); //MB
printf("SD Card Capacity(MB)= %ld \r\n", cap);
}
上述代码的主要功能就是调用函数HAL_SD_GetCardInfo()获取SD卡的信息,并将返回的信息存储在结构体HAL_SD_CardInfoTypeDef类型的变量cardInfo里,这个结构体各成员变量的意义如下。在开发板上插入一个128M的microSD卡,运行时,在串口助手上显示的SD卡信息如下:
*** HAL_SD_GetCardInfo() info ***
Card Type= 0
Card Version= 1
Card Class= 1333
Relative Card Address= 8525
Block Count= 122880
Block Size(Bytes)= 1024
LogiBlockCount= 245760
LogiBlockSize(Bytes)= 512
SD Card Capacity(MB)= 125
Reselect menu item or reset.
还可以使用函数HAL_SD_GetCardCSD()和HAL_SD_GetCardCID()获取SD卡的CSD和CID寄存器的信息,用函数HAL_SD_GetCardStatus()获取SD卡的状态信息。
(3)擦除块
函数HAL_SD_Erase()用于擦除SD卡指定的数据块,自定义函数SDCard_EraseBlocks()演示了这个函数的用法,代码如下:
/* HAL_SD_Erase(), Erase SD card Blocks*/
void SDCard_EraseBlocks()
{
uint32_t BlockAddrStart=0; // Block 0, Addresses using block number
uint32_t BlockAddrEnd=10; // Block 10
printf("\r\n*** Erasing blocks *** \r\n\r\n");
if (HAL_SD_Erase(&hsd,BlockAddrStart, BlockAddrEnd)==HAL_OK)
{
printf("Erasing blocks,OK. \r\n");
printf("Blocks 0-10 is erased. \r\n");
}
else
printf("Erasing blocks,fail. \r\n");
HAL_SD_CardStateTypeDef cardState=HAL_SD_GetCardState(&hsd);
printf("GetCardState()= %ld \r\n", cardState);
// The following code has nothing to do with erasure and can be deleted.[wen]
while(cardState != HAL_SD_CARD_TRANSFER) //Wait for return to transmission status
{
HAL_Delay(1);
cardState=HAL_SD_GetCardState(&hsd);
return; //Otherwise, it will not come out after entering the loop
}
}
函数HAL_SD_Erase()用于擦除指定地址范围内的数据块,数据块的地址用的是块编号。执行完函数HAL_SD_Erase()后,应该调用函数HAL_SD_GetCardState()获取卡状态。SD卡完成各种操作后处于传输状态,即HAL_SD_CARD_TRANSFER,对应值为4。
(4)写入数据
SD卡读写数据是以块为单位的,函数HAL_SD_WriteBlocks()以轮询方式向SD卡写入数据。自定义函数SDCard_TestWrite()测试向SD卡写入数据,代码如下:
void SDCard_TestWrite( ) //TestWrite
{
printf("\r\n*** Writing blocks *** \r\n\r\n");
uint8_t pData[BLOCKSIZE]="Hello, welcome to YCZN. \0"; //BLOCKSIZE=512, Defined in stm32f4xx_hal.h
uint32_t BlockAddr=5; //Block address
uint32_t BlockCount=1; //Number of blocks
uint32_t TimeOut=1000; //Timeout waiting time, beat count
if (HAL_SD_WriteBlocks(&hsd,pData,BlockAddr,BlockCount,TimeOut)==HAL_OK) //Block will be erased automatically
{
printf("Write to block 5, OK. \r\n");
printf("The string is: %s \r\n", pData);
}
else
{
printf("Write to block 5, fail ***. \r\n");
return;
}
for(uint16_t i=0;i<BLOCKSIZE; i++)
pData[i]=i; //Generate data
BlockAddr=6;
if (HAL_SD_WriteBlocks(&hsd,pData,BlockAddr,BlockCount,TimeOut)==HAL_OK) //Block will be erased automatically
{
printf("Write block 6, OK. \r\n");
printf("Data in [10:15] is: %d ", pData[10]);
for (uint16_t j=11; j<=15;j++)
{
printf(", %d", pData[j]);
}
printf("\r\n");
}
else
printf("Write to block 6, fail ***. \r\n");
}
程序定义了一个uint8_t类型的数组pData,长度为BLOCKSIZE字节。BLOCKSIZE是在文件stm32f4xx_hal.h中定义的宏,其值为512,也就是SD卡一个块的大小。
函数HAL_SD_WriteBlocks()可以写入多个数据块。在调用函数HAL_SD_WriteBlocks()之前,我们无须调用函数HAL_SD_Erase()擦除数据块,函数HAL_SD_WriteBlocks()内部会自动擦除数据块,然后再写入。
(5)读取数据
函数HAL_SD_ReadBlocks()以轮询方式从SD卡读取数据。自定义函数SDCard_TestRead()测试从SD卡读取数据,代码如下:
void SDCard_TestRead() //TestRead
{
printf("\r\n*** Reading blocks *** \r\n\r\n");
uint8_t pData[BLOCKSIZE]; //BLOCKSIZE=512
uint32_t BlockAddr=5; //Block address
uint32_t BlockCount=1; //Number of blocks
uint32_t TimeOut=1000; //Timeout waiting time, beat count
if (HAL_SD_ReadBlocks(&hsd,pData,BlockAddr,BlockCount,TimeOut)== HAL_OK)
{
printf("Read block 5, OK. \r\n");
printf("The string is: %s \r\n", pData);
}
else
{
printf("Read block 5, fail ***. \r\n");
return;
}
BlockAddr=6; //Block address
if (HAL_SD_ReadBlocks(&hsd,pData,BlockAddr,BlockCount,TimeOut)== HAL_OK)
{
printf("Read block 6, OK. \r\n");
printf("Data in [10:15] is: %d ", pData[10]);
for (uint16_t j=11; j<=15;j++)
{
printf(", %d", pData[j]);
}
printf("\r\n");
}
}
使用函数HAL_SD_ReadBlocks()可以从SD卡某个数据块开始,将1个或多个块的数据读取到缓冲区。程序运行时会发现,从Block 5读出的字符串与SDCard_TestWrite()里向Block 5写入的字符串相同,复位或掉电重启后,读出的数据也一样,说明SD卡的读写操作是正确的。
(6)其它代码
串口发送:
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);
return ch;
}
/* USER CODE END 4 */
main.h私有函数声明:
/* USER CODE BEGIN EFP */
void SDCard_ShowInfo(); //TestShowInfo
void SDCard_ShowCSD(); //ShowCSD
void SDCard_ShowStatus(); //ShowStatus
void SDCard_ShowCID(); //ShowCID
void SDCard_EraseBlocks(); //TestErase
void SDCard_TestWrite(); //TestWrite
void SDCard_TestRead(); //TestRead
/* USER CODE END EFP */
4、运行与调试
编译下载后,运行并调试。现象并不完整,设置为1线数据线后,10次下载9次不能自动显示开始菜单,这是由于SD卡的驱动有瑕疵,或因为ST的驱动,或因为SD卡是山寨版。按下开发板的复位键后,能显示正确的菜单,接着,可以操作各个菜单。如果设置为4线数据线,根本就不能完成SD卡的驱动。
下载或复位后:
按下S2:
按下S3:
按下S4:
按下S1: