STM32CubeMX 下载和安装 详细教程
【HAL库】STM32CubeMX开发----STM32F407----目录
STM32F407:SD卡存储----SDIO基础通信和DMA读写----程序源码
一、SD卡基础信息
SD卡,全称Secure Digital Memory Card(安全数码卡),是嵌入式设备上常用的一种存储介质。
1. 物理尺寸
(1)标准 SD 卡:相机、记录仪常用,尺寸 32×24×2.1mm。
(2)Micro SD(TF 卡):单片机、STM32、摄像头、OTA 升级主流,15×11×1mm。
(3)Mini SD:淘汰,几乎不用。
每种卡形状大小不一,但功能一样:遵循相同的 SD卡协议 、相同的命令集、相同的块大小(512)。只需确保SDIO引脚配置正确,并且遵循SD卡协议发送正确的命令,程序即可通用。
目前,STM32开发板、Linux开发板 等,预留的卡座,一般是TF卡座,因为它占用空间最少。
2. 容量等级
| 类型 | 容量范围 | 文件系统 | 块大小 | 单片机适配 |
|---|---|---|---|---|
| SDSC(标准容量) | ≤2GB | FAT12 / FAT16 | 512 字节 | 全兼容 |
| SDHC(高容量) | 4GB ~ 32GB | FAT32 | 512 字节 | STM32 SPI/SDIO 都支持 |
| SDXC(超大容量) | 64GB ~ 2TB | exFAT | 512 字节 | FATFS 需开启 exFAT 配置 |
| SDUC | 2TB 以上 | exFAT | 512 字节 | 极少单片机支持 |
重点: STM32 做 OTA / 日志存储优先 8GB/16GB/32GB SDHC FAT32,兼容性最好。
3. 速度等级
SD卡是Flash存储,读写速度特点是:读快、写慢。
SD卡的最低写入速度,用Class等级来标识。
在表面丝印上,一般会有Class字样,它后面的数表示最低写入速度,单位是:MB/s。或者,会用一个外面带半圆的数字表示。
| 等级 | 最低持续写入 | 适用场景 |
|---|---|---|
| C2 | 2MB/s | 低速音频、小日志,现已淘汰 |
| C4 | 4MB/s | 简易录音、小文件存储 |
| C6 | 6MB/s | 720P 录像、普通数据记录 |
| C10 | 10MB/s | 1080P 录像、单片机日志、OTA 升级(嵌入式最低推荐) |
4. 存储结构
存储层级
(1)扇区 Sector:512Byte,软件操作最小单元
(2)簇 Cluster:多个扇区组成,文件系统分配空间单位(FAT32 常见 4/8/16 扇区一簇)
(3)块 Block:硬件擦写单元(若干扇区)
- 标准 SD:1 扇区 = 512 字节(FATFS 固定操作单位)
- 所有读写、擦除必须按 512 字节对齐,不能单字节随意写
- 一次读写最小 512 字节,读 / 写不足 512 字节也要填充凑整扇区
- 最小擦除单元 = N × 512Byte 数据块
5. 擦写寿命
SD 内部 NAND Flash 最小擦写单元为 Block(几十 KB)。
| 颗粒类型 | 单块擦写寿命 | 适用场景 |
|---|---|---|
| SLC | 5 万~10 万次 | 工业级 SD 卡、长期循环日志 |
| MLC | 3000 次 | 监控记录仪、工业设备首选 |
| TLC | 1000 次 | 消费级手机、普通存储卡 |
| QLC | 300~500 次 | 廉价大容量卡,不适合循环写入 |
SD 卡自带硬件 FTL 磨损均衡
SD 卡内部集成主控 + FTL 闪存转换层,主机不用管磨损:
- 主机只操作 512B 逻辑扇区;
- 卡内部自动把逻辑扇区映射到不同物理擦除块;
- 均匀分散擦写压力,不会某一块提前报废;
- 写入量不大时,实际使用寿命会比裸 Flash 提升几十倍。
二、SD卡接口SDIO
STM32的SDIO外设与SD卡通信,通用接线如下图。
注:当使用弹簧式SD卡座,会有第9个脚(CD),可不接。它用于判断SD卡是否插入,当插入SD卡时,此脚输出低电平。

三、使用STM32CubeMX----新建工程
步骤1:打开 STM32CubeMX 软件
具体 STM32CubeMX 界面如下:

步骤2:选择 从MCU开始我的项目

步骤3:选择MCU芯片
我们本次实验选择 STM32F407VET6 芯片做 MCU。

进入工程编辑,具体界面如下:

步骤4:设置烧写方式为 SWD模式
这一步必须选择一种烧写方式,否则烧写一次程序后,芯片中就没法再次烧写程序了。需要硬件重启才可以重新烧写程序。

步骤5:设置时钟源
设置外部晶振为芯片系统时钟源。

步骤6:设置SDIO
- Mode:选择SD的四线模式,即 SD 4 bits Wide bus.
- 参数部分:F4系列不用修改配置,默认即可。F103系列,需把时钟分频系数修改为 6,即SDIOCLK Clock divide factor这一项,由默认0改为6, 不然会通信失败。

步骤7:时钟配置

重点: 箭头所指的Q值,它用于控制USB 、SDIO和随机数生成器的时钟,必须为 48M。
步骤8:项目编辑

步骤9:代码生成编辑

步骤10:点击 GENERATE CODE 生成代码

新建的工程,已经生成,接下来就是对工程代码的编辑。
四、使用keil5----编辑程序
想要使用keil5打开工程,需要电脑先安装 keil5编译环境,具体操作流程。
可以点击下方文章链接:Keil5编译环境搭建流程----STM32和GD32
步骤1:使用keil5打开工程
点击工程文件中的MDK-ARM 文件

打开keil5工程

打开代码,编译运行一下,没有错误和告警为正常。

步骤2:用户代码 编辑位置
用户代码 要写在 USER xxxx BEGIN xxx 和 USER xxxx END xxx 之间。
这样下次使用 STM32CubeMX 重新生成代码后,自己写的代码不会被删除。

步骤3:修改代码
CubeMX生成的SDIO初始化代码,有一个bug,需要手动修改,操作如下:
- 编译后,右击 main.c 文件中函数 MX_SDIO_SD_Init(),
- 在弹出菜单中:Go To Definition Of …; 将跳转到SD卡初始化函数;

跳转到 sdio.c文件的 MX_SDIO_SD_Init()函数内部后,
把下图位置中的 4B,改为 1B ;
重点:每次重新生成后,都要手动修改一次。如果不修改,初始化过程会导致程序卡死。

步骤4:使用到的HAL库函数
typedef struct
{
uint32_t CardType; //SD卡类型
uint32_t CardVersion; //SD卡版本
uint32_t Class; //SD卡等级
uint32_t RelCardAdd; //SD卡地址
uint32_t BlockNbr; //SD卡容量(块)
uint32_t BlockSize; //SD卡块大小(字节)
}HAL_SD_CardInfoTypeDef; //SD卡信息结构体
typedef uint32_t HAL_SD_CardStateTypeDef; //SD卡状态信息
#define HAL_SD_CARD_READY 0x00000001U //SD卡状态准备就绪
#define HAL_SD_CARD_IDENTIFICATION 0x00000002U //SD卡识别状态
#define HAL_SD_CARD_STANDBY 0x00000003U //SD卡待机状态
#define HAL_SD_CARD_TRANSFER 0x00000004U //SD卡传输状态
#define HAL_SD_CARD_SENDING 0x00000005U //SD卡正在发送状态
#define HAL_SD_CARD_RECEIVING 0x00000006U //SD卡正在接收状态
#define HAL_SD_CARD_PROGRAMMING 0x00000007U //SD卡编程状态
#define HAL_SD_CARD_DISCONNECTED 0x00000008U //SD卡断开连接状态
#define HAL_SD_CARD_ERROR 0x000000FFU //SD卡响应状态
// 1、获取 SD 卡的状态---参数:SD卡的句柄
HAL_SD_CardStateTypeDef HAL_SD_GetCardState(SD_HandleTypeDef *hsd);
// 2、获取 SD 卡的信息---参数:SD卡的句柄、SD卡信息
HAL_StatusTypeDef HAL_SD_GetCardInfo(SD_HandleTypeDef *hsd, HAL_SD_CardInfoTypeDef *pCardInfo);
//3、读数据---参数:SD卡的句柄、数据、块地址、块数量、超时ms
HAL_StatusTypeDef HAL_SD_ReadBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout);
//4、写数据---参数:SD卡的句柄、数据、块地址、块数量、超时ms
HAL_StatusTypeDef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout);
//5、擦除数据---参数:SD卡的句柄、块起始地址、块结束地址
HAL_StatusTypeDef HAL_SD_Erase(SD_HandleTypeDef *hsd, uint32_t BlockStartAdd, uint32_t BlockEndAdd);
//6、通过DMA读取数据---参数:SD句柄、数据、块起始地址、需要读取的块数量;
HAL_StatusTypeDef HAL_SD_ReadBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks);
//7、通过DMA写入数据---参数:SD句柄、数据、块起始地址、需要写入的块数量;
HAL_StatusTypeDef HAL_SD_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks);
五、使用keil5----调试和运行
主要使用串口1通过printf函数打印运行数据,这部分操作主要参考:【HAL库】STM32CubeMX开发----STM32F407----Uart串口通信实验
实验1:获取SD卡信息代码
在main函数中,初始化位置,调用以下函数。
烧写代码到板子,打印SD卡信息,信息没有问题,再做后面的实验。
void User_SDCardInfo(void)// 获取SD卡信息
{
HAL_SD_CardInfoTypeDef pCardInfo = {0}; // SD卡信息结构体
uint8_t status = HAL_SD_GetCardState(&hsd); // SD卡状态标志值
if (status == HAL_SD_CARD_TRANSFER) //传输状态
{
HAL_SD_GetCardInfo(&hsd, &pCardInfo); // 获取 SD 卡的信息
printf("\r\n*** 获取SD卡信息 *** \r\n");
printf("卡类型:%d \r\n", pCardInfo.CardType); // 类型返回:0-SDSC、1-SDHC/SDXC、3-SECURED
printf("卡版本:%d \r\n", pCardInfo.CardVersion); // 版本返回:0-CARD_V1、1-CARD_V2
printf("块数量:%d \r\n", pCardInfo.BlockNbr); // 可用的块数量
printf("块大小:%d 字节 \r\n", pCardInfo.BlockSize); // 每个块的大小; 单位:字节
printf("卡容量:%llu G \r\n", ((uint64_t)pCardInfo.BlockSize * pCardInfo.BlockNbr) / 1024 / 1024 / 1024); // 计算卡的容量; 单位:GB
}
}
运行结果

实验2:SD卡-读写数据
在初始化的时候,调用SD卡-写数据,写入数据到SD卡,然后在运行时周期读取SD卡数据。
SD卡-读数据
void User_SD_ReadData(void)
{
/*读测试:读出测试位置原数据,保存在ReadData[] */
uint8_t ReadData[1024] = {0};
printf("\r读取测试块的原数据\r\n");
if (HAL_SD_ReadBlocks(&hsd, ReadData, 8, 2, 3000) == HAL_OK) // 读SD卡数据块; 参数:SD结构体、数据、块起始地址、读的块数量、超时时间;
{
while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束
for (uint32_t i = 0; i < 1024; i++) // 打印 原数据
{
printf("%X ", ReadData[i]);
}
printf("\r\n");
}
else
{
printf("SD卡 读测试 失败!\n");
}
}
SD卡-写数据
void User_SD_WriteData(void)
{
/* 写测试:在测试的块上写入数据 */
uint8_t WriteData[1024] = {0};
for (uint32_t i = 0; i < 1024; i++)
{
WriteData[i]=i;
}
printf("\rSD卡 写入测试\r\n");
if (HAL_SD_WriteBlocks(&hsd, WriteData, 8, 2, 3000) == HAL_OK) // 向SD卡写入数据块; 参数:SD结构体、数据、块起始地址、写入的块数量、超时时间;
{
while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束
printf("对指定块写入结束! \r写入的数据是:\n");
for (uint32_t i = 0; i < 1024; i++) // 打印 写入的数据
{
printf("%X ", WriteData[i]);
}
printf("\r\n");
}
else
{
printf("SD卡 写测试 失败!\n");
}
}
运行结果


实验3:SD卡-擦除数据
初始化时,调用SD卡擦除数据函数,运行时周期读取SD卡数据。
void User_SD_Erase(void)
{
/*擦除测试:擦除指定块上的数据 */
printf("\r擦除块测试\r\n");
if (HAL_SD_Erase(&hsd, 8, 9) == HAL_OK) // 擦除SD卡上的数据; 参数:SD结构体、块的起始地址、块的结束地址
{
while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束
printf("擦除 成功! \r\n");
}
else
{
printf("擦除 失败! \r\n");
}
}
运行结果


429

被折叠的 条评论
为什么被折叠?



