STM32F407 系列文章 - SDIO-To-SD Card(九)
目录
前言
一、SDIO接口
二、SD卡
三、实现程序
1.SD卡结构体参数说明
2.头文件定义
3.函数sd_init()
4.函数HAL_SD_MspInit()
5.函数get_sd_card_info()
6.函数get_sd_card_state()
7.函数sd_read_disk()
8.函数sd_write_disk()
9.函数show_sdcard_info()
总结
前言
当单片机需要保存大量数据时,靠它自身的容量往往是不能满足需求的,一般采取的措施是外挂一个存储器。目前市面上存储器的种类繁多,根据它们各自特点,选择一款最适合单片机存储器,莫过于SD卡了,它不仅价格便宜、体积小、速度快、而且容量可以做到很大,并支持SPI/SDIO驱动,能满足单片机的要求。只需要少数几个IO口即可外扩一个高达32GB或以上的外部存储器,容量从几十M到几十G选择范围很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。一般MCU都会自带SDIO接口,因此要实现此功能,需准备一块带SD卡接口的开发板,在本章中,将向大家介绍,如何通过SDIO通讯接口实现对Micor SD卡数据的读取。
一、SDIO接口
SDIO(Secure Digital Input and Output,安全数字输入输出),最早由SD协会(SD Association, SDA)于2001年发布,是在SD标准上定义的一种外设接口,不仅支持传统的存储功能,还允许设备通过SD卡接口进行输入输出操作,常见的多媒体卡(MMC卡)、SD存储卡、SDI/O卡和CEATA设备都有SDIO接口。关于SDIO更为详细的介绍可以参考下知乎上面的一篇文章,链接如下SDIO协议从入门到精通 - 知乎 (zhihu.com)。
SDIO遵循SD物理标准,因此支持SD卡的设备通常也能够兼容SDIO设备。常见的SDIO卡尺寸和普通的SD卡相同,包括标准尺寸(SD)、迷你尺寸(miniSD)和微型尺寸(microSD),本文将选用microSD作为设计目标,另采用的407芯片使用自带的SDIO接口驱动,使用4位数据总线模式,最高通信速度可达 48Mhz(分频器旁路时),最高每秒可传输数据24M字节,对于一般应用足够了。407芯片的SDIO控制器包含2部分,SDIO适配器模块和APB2总线接口,其功能框图如下所示:
二、SD卡
SD卡(Secure Digital Memory Card,SD存储卡,简称SD卡),SD卡主要有SD、Mini SD和microSD三种类型,Mini SD已经被microSD取代,使用得不多。本文选用microSD作为设计目标,microSD原名Trans-flash Card(TF卡),2004年正式更名为Micro SD Card,由SanDisk(闪迪)公司发明,主要应用于嵌入式设备,具体介绍参考Micro SD卡_百度百科 (baidu.com)。
市场上卖microSD卡样式如下(示例),及对应SD通讯模式下相应引脚的含义:
与microSD卡匹配的卡座(自锁式)样式如下:
SD卡的驱动方式之一是用SDIO接口通讯,F407芯片自带SDIO接口,MCU单片机与SD卡连接示意图如下所示:
三、实现程序
SD卡的驱动
1.SD卡结构体参数说明
该结构体参数定义来自于STM32的HAL库,主要是完成对SD卡详细信息以参数化定义,具体说明及代码如下:
/**
* @brief SD 操作句柄结构体定义
*/
typedef struct {
SD_TypeDef *Instance; /* SD 相关寄存器基地址 */
SD_InitTypeDef Init; /* SDIO 初始化变量 */
HAL_LockTypeDef Lock; /* 互斥锁,用于解决外设访问冲突 */
uint8_t *pTxBuffPtr; /* SD 发送数据指针 */
uint32_t TxXferSize; /* SD 发送缓存按字节数的大小 */
uint8_t *pRxBuffPtr; /* SD 接收数据指针 */
uint32_t RxXferSize; /* SD 接收缓存按字节数的大小 */
__IO uint32_t Context; /* HAL 库对 SD 卡的操作阶段 */
__IO HAL_SD_StateTypeDef State; /* SD 卡操作状态 */
__IO uint32_t ErrorCode; /* SD 卡错误代码 */
DMA_HandleTypeDef *hdmatx; /* SD DMA 数据发送指针 */
DMA_HandleTypeDef *hdmarx; /* SD DMA 数据接收指针 */
HAL_SD_CardInfoTypeDef SdCard; /* SD 卡信息的 */
uint32_t CSD[4]; /* 保存 SD 卡 CSD 寄存器信息 */
uint32_t CID[4]; /* 保存 SD 卡 CID 寄存器信息 */
} SD_HandleTypeDef;
/**
* @brief SD 卡信息结构定义
*/
typedef struct {
uint32_t CardType; /* 存储卡类型标记:标准卡、高速卡 */
uint32_t CardVersion; /* 存储卡版本 */
uint32_t Class; /* 卡类型 */
uint32_t RelCardAdd; /* 卡相对地址 */
uint32_t BlockNbr; /* 卡存储块数 */
uint32_t BlockSize; /* SD 卡每个存储块大小 */
uint32_t LogBlockNbr; /* 以块表示的卡逻辑容量 */
uint32_t LogBlockSize; /* 以字节为单位的逻辑块大小 */
} HAL_SD_CardInfoTypeDef;
2.头文件定义
这里完成对IO引脚的定义和操作方式,并定义了SD卡的一些操作函数定义,代码如下(示例):
/* SDIO的信号线: SD_D0 ~ SD_D3/SD_CLK/SD_CMD 引脚 定义
* 如果你使用了其他引脚做SDIO的信号线,修改这里写定义即可适配.
*/
#define SD_D0_GPIO_PORT GPIOC
#define SD_D0_GPIO_PIN GPIO_PIN_8
/* 所在IO口时钟使能 */
#define SD_D0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
#define SD_D1_GPIO_PORT GPIOC
#define SD_D1_GPIO_PIN GPIO_PIN_9
/* 所在IO口时钟使能 */
#define SD_D1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
#define SD_D2_GPIO_PORT GPIOC
#define SD_D2_GPIO_PIN GPIO_PIN_10
/* 所在IO口时钟使能 */
#define SD_D2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
#define SD_D3_GPIO_PORT GPIOC
#define SD_D3_GPIO_PIN GPIO_PIN_11
/* 所在IO口时钟使能 */
#define SD_D3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
#define SD_CLK_GPIO_PORT GPIOC
#define SD_CLK_GPIO_PIN GPIO_PIN_12
/* 所在IO口时钟使能 */
#define SD_CLK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
#define SD_CMD_GPIO_PORT GPIOD
#define SD_CMD_GPIO_PIN GPIO_PIN_2
/* 所在IO口时钟使能 */
#define SD_CMD_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)
#define SD_TIMEOUT ((uint32_t)100000000) /* 超时时间 */
#define SD_TRANSFER_OK ((uint8_t)0x00)
#define SD_TRANSFER_BUSY ((uint8_t)0x01)
/* 根据 SD_HandleTypeDef 定义的宏,用于快速计算容量 */
#define SD_TOTAL_SIZE_BYTE(__Handle__) (((uint64_t)((__Handle__)->SdCard.LogBlockNbr) * ((__Handle__)->SdCard.LogBlockSize)) >> 0)
#define SD_TOTAL_SIZE_KB(__Handle__) (((uint64_t)((__Handle__)->SdCard.LogBlockNbr) * ((__Handle__)->SdCard.LogBlockSize)) >> 10)
#define SD_TOTAL_SIZE_MB(__Handle__) (((uint64_t)((__Handle__)->SdCard.LogBlockNbr) * ((__Handle__)->SdCard.LogBlockSize)) >> 20)
#define SD_TOTAL_SIZE_GB(__Handle__) (((uint64_t)((__Handle__)->SdCard.LogBlockNbr) * ((__Handle__)->SdCard.LogBlockSize)) >> 30)
/*
* SD传输时钟分频,由于HAL库运行效率低,很容易产生上溢(读SD卡时)/下溢错误(写SD卡时)
* 使用4bit模式时,需降低SDIO时钟频率,将该宏改为 1,SDIO时钟频率:48/( SDIO_TRANSF_CLK_DIV + 2 ) = 16M * 4bit = 64Mbps
* 使用1bit模式时,该宏SDIO_TRANSF_CLK_DIV改为 0,SDIO时钟频率:48/( SDIO_TRANSF_CLK_DIV + 2 ) = 24M * 1bit = 24Mbps
*/
#define SDIO_TRANSF_CLK_DIV 1
extern SD_HandleTypeDef g_sdcard_handler; /* SD卡句柄 */
extern HAL_SD_CardInfoTypeDef g_sd_card_info_handle; /* SD卡信息结构体 */
/* 函数声明 */
uint8_t sd_init(void); /* 初始化SD卡 */
uint8_t get_sd_card_info(HAL_SD_CardInfoTypeDef *cardinfo); /* 获取卡信息函数 */
uint8_t get_sd_card_state(void); /* 获取卡的状态 */
uint8_t sd_read_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt); /* 读SD卡 */
uint8_t sd_write_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt); /* 写SD卡 */
void show_sdcard_info(void);
3.函数sd_init()
SD卡初始化函数,被main函数调用,主要是完成对SDIO结构体的控制句柄填充,即将该结构体类型指针变量初始化,然后使用HAL库的HAL_SD_Init初始化函数即可,在此过程中HAL_SD_Init会调用函数 HAL_SD_MspInit回调函数,根据外设的情况,我们可以设置数据总线宽度为4位,代码如下(示例):
SD_HandleTypeDef g_sdcard_handler; /* SD卡句柄 */
HAL_SD_CardInfoTypeDef g_sd_card_info_handle; /* SD卡信息结构体 */
/**
* @brief 初始化SD卡
* @param 无
* @retval 返回值:0 初始化正确;其他值,初始化错误
*/
uint8_t sd_init(void)
{
uint8_t SD_Error;
/* 初始化时的时钟不能大于400KHZ */
g_sdcard_handler.Instance = SDIO;
g_sdcard_handler.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING; /* 上升沿 */
/* 不使用bypass模式,直接用HCLK进行分频得到SDIO_CK */
g_sdcard_handler.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
/* 空闲时不关闭时钟电源 */
g_sdcard_handler.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
g_sdcard_handler.Init.BusWide = SDIO_BUS_WIDE_1B; /* 1位数据线 */
/* 关闭硬件流控 */
g_sdcard_handler.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
g_sdcard_handler.Init.ClockDiv = SDIO_TRANSF_CLK_DIV; /* SD传输时钟频率最大25MHZ */
SD_Error = HAL_SD_Init(&g_sdcard_handler);
if (SD_Error != HAL_OK)
return 1;
HAL_SD_GetCardInfo(&g_sdcard_handler, &g_sd_card_info_handle); /* 获取SD卡信息 */
/* 使能4bit宽总线模式 */
SD_Error = HAL_SD_ConfigWideBusOperation(&g_sdcard_handler, SDIO_BUS_WIDE_4B);
if (SD_Error != HAL_OK)
return 2;
return 0;
}
4.函数HAL_SD_MspInit()
完成SDIO底层驱动,时钟使能,引脚配置,被sd_init()函数中的HAL_SD_Init函数调用,代码如下(示例):
/**
* @brief SDIO底层驱动,时钟使能,引脚配置
此函数会被HAL_SD_Init()调用
* @param hsd:SD卡句柄
* @retval 无
*/
void HAL_SD_MspInit(SD_HandleTypeDef *hsd)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_SDIO_CLK_ENABLE(); /* 使能SDIO时钟 */
SD_D0_GPIO_CLK_ENABLE(); /* D0引脚IO时钟使能 */
SD_D1_GPIO_CLK_ENABLE(); /* D1引脚IO时钟使能 */
SD_D2_GPIO_CLK_ENABLE(); /* D2引脚IO时钟使能 */
SD_D3_GPIO_CLK_ENABLE(); /* D3引脚IO时钟使能 */
SD_CLK_GPIO_CLK_ENABLE(); /* CLK引脚IO时钟使能 */
SD_CMD_GPIO_CLK_ENABLE(); /* CMD引脚IO时钟使能 */
gpio_init_struct.Pin = SD_D0_GPIO_PIN; /* SD_D0引脚模式设置 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 推挽复用 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = GPIO_AF12_SDIO; /* 复用为SDIO */
HAL_GPIO_Init(SD_D0_GPIO_PORT, &gpio_init_struct); /* 初始化 */
gpio_init_struct.Pin = SD_D1_GPIO_PIN; /* SD_D1引脚模式设置 */
HAL_GPIO_Init(SD_D1_GPIO_PORT, &gpio_init_struct); /* 初始化 */
gpio_init_struct.Pin = SD_D2_GPIO_PIN; /* SD_D2引脚模式设置 */
HAL_GPIO_Init(SD_D2_GPIO_PORT, &gpio_init_struct); /* 初始化 */
gpio_init_struct.Pin = SD_D3_GPIO_PIN; /* SD_D3引脚模式设置 */
HAL_GPIO_Init(SD_D3_GPIO_PORT, &gpio_init_struct); /* 初始化 */
gpio_init_struct.Pin = SD_CLK_GPIO_PIN; /* SD_CLK引脚模式设置 */
HAL_GPIO_Init(SD_CLK_GPIO_PORT, &gpio_init_struct); /* 初始化 */
gpio_init_struct.Pin = SD_CMD_GPIO_PIN; /* SD_CMD引脚模式设置 */
HAL_GPIO_Init(SD_CMD_GPIO_PORT, &gpio_init_struct); /* 初始化 */
}
5.函数get_sd_card_info()
获取卡信息函数,代码如下(示例):
/**
* @brief 获取卡信息函数
* @param cardinfo:SD卡信息句柄
* @retval 返回值:读取卡信息状态值
*/
uint8_t get_sd_card_info(HAL_SD_CardInfoTypeDef *cardinfo)
{
uint8_t sta;
sta = HAL_SD_GetCardInfo(&g_sdcard_handler, cardinfo);
return sta;
}
6.函数get_sd_card_state()
获取卡状态函数,判断SD卡是否可以传输(读写)数据,代码如下(示例):
/**
* @brief 判断SD卡是否可以传输(读写)数据
* @param 无
* @retval 返回值:SD_TRANSFER_OK 传输完成,可以继续下一次传输
SD_TRANSFER_BUSY SD 卡正忙,不可以进行下一次传输
*/
uint8_t get_sd_card_state(void)
{
return ((HAL_SD_GetCardState(&g_sdcard_handler) == HAL_SD_CARD_TRANSFER) ?
SD_TRANSFER_OK : SD_TRANSFER_BUSY);
}
7.函数sd_read_disk()
完成对SD卡中数据信息读取,代码如下(示例):
/**
* @brief 读SD卡(fatfs/usb调用)
* @param pbuf : 数据缓存区
* @param saddr : 扇区地址
* @param cnt : 扇区个数
* @retval 0, 正常; 其他, 错误代码(详见SD_Error定义);
*/
uint8_t sd_read_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt)
{
uint8_t sta = HAL_OK;
uint32_t timeout = SD_TIMEOUT;
long long lsector = saddr;
__disable_irq(); /* 关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!) */
sta = HAL_SD_ReadBlocks(&g_sdcard_handler, (uint8_t *)pbuf, lsector, cnt,
SD_TIMEOUT); /* 多个sector的读操作 */
/* 等待SD卡读完 */
while (get_sd_card_state() != SD_TRANSFER_OK)
{
if (timeout-- == 0)
sta = SD_TRANSFER_BUSY;
}
__enable_irq(); /* 开启总中断 */
return sta;
}
8.函数sd_write_disk()
完成对SD卡中数据信息写入,代码如下(示例):
/**
* @brief 写SD卡(fatfs/usb调用)
* @param pbuf : 数据缓存区
* @param saddr : 扇区地址
* @param cnt : 扇区个数
* @retval 0, 正常; 其他, 错误代码(详见SD_Error定义);
*/
uint8_t sd_write_disk(uint8_t *pbuf, uint32_t saddr, uint32_t cnt)
{
uint8_t sta = HAL_OK;
uint32_t timeout = SD_TIMEOUT;
long long lsector = saddr;
__disable_irq(); /* 关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!) */
sta = HAL_SD_WriteBlocks(&g_sdcard_handler, (uint8_t *)pbuf, lsector, cnt,
SD_TIMEOUT); /* 多个sector的写操作 */
/* 等待SD卡写完 */
while (get_sd_card_state() != SD_TRANSFER_OK)
{
if (timeout-- == 0)
sta = SD_TRANSFER_BUSY;
}
__enable_irq(); /* 开启总中断 */
return sta;
}
9.函数show_sdcard_info()
打印SD卡相关信息,代码如下(示例):
/**
* @brief 打印SD卡相关信息
* @param 无
* @retval 无
*/
void show_sdcard_info(void)
{
HAL_SD_CardCIDTypeDef sd_card_cid;
HAL_SD_GetCardCID(&g_sdcard_handler, &sd_card_cid); /* 获取CID */
get_sd_card_info(&g_sd_card_info_handle); /* 获取SD卡信息 */
switch (g_sd_card_info_handle.CardType)
{
case CARD_SDSC:
{
if (g_sd_card_info_handle.CardVersion == CARD_V1_X)
printf("Card Type:SDSC V1\r\n");
else if (g_sd_card_info_handle.CardVersion == CARD_V2_X)
printf("Card Type:SDSC V2\r\n");
}
break;
case CARD_SDHC_SDXC:
printf("Card Type:SDHC\r\n");
break;
default: break;
}
/* 制造商ID */
printf("Card ManufacturerID:%d\r\n", sd_card_cid.ManufacturerID);
/* 卡相对地址 */
printf("Card RCA:%d\r\n", g_sd_card_info_handle.RelCardAdd);
/* 显示逻辑块数量 */
printf("LogBlockNbr:%d \r\n", (uint32_t)(g_sd_card_info_handle.LogBlockNbr));
/* 显示逻辑块大小 */
printf("LogBlockSize:%d \r\n", (uint32_t)(g_sd_card_info_handle.LogBlockSize));
/* 显示容量 */
printf("Card Capacity:%d MB\r\n", (uint32_t)SD_TOTAL_SIZE_MB(&g_sdcard_handler));
/* 显示块大小 */
printf("Card BlockSize:%d\r\n\r\n", g_sd_card_info_handle.BlockSize);
}
总结
这章完成是STM32的SDIO通讯接口和SD卡介绍,为下一章FATFS文件系统做铺垫。
下面提供的代码,基于STM32F407ZGT芯片编写,可直接在原子开发板上运行,也可运行在各工程项目上,但需要注意各接口以及相应的引脚应和原子开发板上保持一致。
相应的代码链接:代码程序