细说STM32单片机使用轮询模式直接访问SD卡的方法及其应用

目录

一、SD卡简介

1、 SD卡的分类

(1)外形尺寸

(2)存储容量

(3)总线速度

2、常规SD卡的接口

二、SDIO接口硬件电路

1、 STM32F407的SDIO接口

2、开发板上的microSD卡连接电路

三、SDIO接口和SD卡的HAL驱动程序

1、 SD驱动程序概述

2、初始化和配置函数

3、读取SD卡的参数信息

(1)SD卡的寄存器

(2)读取CID的函数HAL_SD_GetCardCID()

(3)读取CSD的函数HAL_SD_GetCardCSD()

(4)读取SSR的函数HAL_SD_GetCardStatus()

(5)获取SD卡信息的函数HAL_SD_GetCardInfo()

4、获取SD卡的当前状态

5、以轮询方式读写SD卡

6、以中断方式读写SD卡

7、以DMA方式读写SD卡

四、示例:以轮询方式读写SD卡

1、示例功能与CubeMX项目设置

(1)RCC

(2)GPIO

(3)USART6、CodeGenerator、SYS

(4)SDIO

(5)NVIC

2、主程序与SDIO接口/SD卡初始化

(1)初始主程序

(2)SDIO接口和SD卡初始化

3、程序功能实现

(1)主程序

(2)显示SD卡信息

(3)擦除块

(4)写入数据

(5)读取数据

(6)其它代码 

4、运行与调试


        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的时钟频率必须满足f_{PCLK2}\geqslant \frac{3}{8}f_{SDIO-CK}

        SDIO_CK是与SD卡连接的时钟信号,由SDIO适配器产生,其频率由SDIO适配器的一个分频器和SDIOCLK产生:f_{SDIO-CK}=\frac{f_{SDIOCLK}}{N+2}

        其中,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: 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wenchm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值