STM32开发学习笔记之四【SD卡U盘】
前言
以前一直想做个单片机U盘,但是也没行动,今天来学学这东西是个啥东西。接下来我们通过STM32CubeMX软件平台用基于HAL库的方案配置硬件架构,建立程序主框架,导入到MDK软件开发平台后继续补充剩余代码,编译链接后下载到开发板实现一个读卡器的功能。
一、用STM32CubeMX创建STM32工程
本文中不涉及STM32CubeMX的下载及安装,需要的请到 https://www.st.com/en/development-tools/stm32cubemx.html#get-software自行下载安装。
(一)新建工程
新建工程文件项目名称设为“led”,本笔记中项目文件存放在“E:\prj\stm32\led”路径下。启动Stm32CubeMX程序在File菜单单击【新建项目】进入CPU选择界面

(二)选择芯片型号
开发板CPU型号为STM32H743VIT6,所以在Commercial Part Number编辑框输入型号。如果事先没有确定型号而是通过项目需求筛选的话可以在左侧导航栏筛选。输入型号后右侧会显示具体封装的列表,双击正确CPU进入系统引脚与配置窗口

如果出现下面窗口选“NO”,因为本项目不涉及复杂的内核配置。

进入引脚与配置窗口 后如下图:

(三)GPIO配置
GPIO用 PE3 闪亮 LED ,详情参考 STM32开发学习笔记之一【点亮led】。
(四)SD卡外设配置
展开左侧【Connectivity】单击【SDMMC1】,【Mode】选择【SD 4 bits Wide bus】,下面SD卡的分频系数设为4。这里SD卡的工作频率满足下面公式:
SDMMC_CK = SDMMCCLK / (CLKDIV + 2)
SDMMC_CK为实际SD卡接口时钟频率,SDMMCCLK为SDMMC外设时钟源频率,CLKDIV为分频系数(0-255)。SDMMCCLK本案例选择了150MHz,如果让SD卡工作在25MHz的情况下,根据公式推得分频系数为4。

打开SD卡中断

(五)USB接口外设配置
展开左侧【Connectivity】单击【USB_OTG_FS】,【Mode】选择【Device Only】

打开USB中断,剩下选项默认即可

(六)USB中间件程序配置
展开左侧【Middleware …】单击【USB_DEVICE】,【Mode】选择【Mass Storage Class】,下面的缓冲区调大一点设为4096。

(七)RCC时钟源配置
1、HSE时钟配置
进入工程主设计界面后,首先打开高速时钟源 HSE,设为外部驱动。如图所示。

2、时钟树配置
进入 【Clock Configuration】 配置栏之后可以看到,界面展现一个完整的 STM32H7 时钟系统框图。从这个时钟树配置图可以看出,配置的主要是外部晶振大小,分频系数,倍频系数以及选择器。在我们配置的工程中,时钟值会动态更新,如果某个时钟值在配置过程中超过允许值,那么相应的选项框会红色提示。

上图时钟配置的关键点是:确定主频、外频、USB外设和SD卡外设时钟,接下来在时钟树下面进一步配置USB外设和SD卡外设时钟。

(八)生成工程文件
1、工程设置
接下来我们设置生成一个工程,如下图所示。选择 Project Manager-> Project选项用来配置工程的选项,我们了解一下里面的信息。
Project Name:工程名称,填入工程名称(这里填udisk)(半角,不能有中文字符)
Project Location:工程保存路径,点击 Browse 选择保存的位置(这里填E:\prj\stm32\led)(半角,不能有中文字符)
Toolchain Folder Location:工具链文件夹位置,默认即可。
Application Structure:应用的结构,选择 Advanced,不勾选 Do not generate the main(),因为我们要其生成 main 函数。
Toolchain/IDE:工具链/集成开发环境,我们使用 Keil,因此选择 MDK-ARM,Min Version 选择 V5.27,这里根据 CubeMX 的版本可能会有差异,我们默认使用 V5 以上的版本即可。
Linker Settings 链接器设置:
Minimum Heap Size 最小堆大小,调整到2000。
Minimum Stack Size 最小栈大小,调整到2000。
MCU and Firmware Package 是 MCU 及固件包设置:
MCU Reference:目标 MCU 系列名称。
Firmware Package Name and Version:固件包名称及版本。
勾选 Use Default Firmware Location,文本框里面的路径就是固件包的存储地址,我们使用默认地址即可。(这里因为我有两个版本的固件包,所以它默认使用最新的,这个关系不大,就用新的)。
2、代码生成器设置
打开 Project Manager-> Code Generator 选项,Generated files 生成文件选项,勾选 Generate peripheral initialization as a pair of ‘.c/.h’files per peripheral,勾选这个选项的话将会将每个外设单独分开成一组.c、.h 文件,使得代码结构更加的清晰,如图所示。

由于 CubeMX 默认勾选了复制所有的库,即工程中不使用到的代码也会复制进来,为了节省 CubeMX 生成工程的空间,我们勾选生成工程时只复制用到的库(这一步是可选操作,大家根据自己的实际选择)
3、保存工程

至此工程最基础配置就已经完成,点击蓝色按钮(SENERATE CODE)就可以生成工程。
4、生成工程
如果我们的 CubeMX 工程放置配置路径中没有中文,生成代码后会弹出类似下图的提示窗口,点击 【Open Project】 就打开 MDK 工程(如果是中文路径则会报错)。

到此为止CubeMX任务就结束了。
二、STM32系统C程序设计
打开生成的C代码项目已经假设在你的计算机安装了MDK,如果MDK还没有安装请自行下载并安装。https://www.keil.com/download/product/
注意!不要下载最新版,有可能存在问题!
(一)用MDK打开工程
在MDK的keil编程界面中打开工程后的主界面如下图:

经过STM32cubeMX的配置,系统主框架代码自动生成且能编译通过了。但是目前USB外设和SD卡外设虽然分别都好用了,但是二者还没有连接起来。
(二)编写U盘操控代码
在CubeMX项目程序框架中找到usbd_storage_if.c文件打开,需要实现存储介质的接口函数都在这里,该项目主要的编程工作是往usbd_storage_if.c文件中添加代码。从原始代码中我们能找到下面显示的函数
int8_t STORAGE_Init_FS(uint8_t lun);
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size);
int8_t STORAGE_IsReady_FS(uint8_t lun);
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun);
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
int8_t STORAGE_GetMaxLun_FS(void);
STORAGE_Init_FS: U盘初始化函数,常用于初始化基于外部存储(如SD卡、NAND Flash)的文件系统,在 USB 设备枚举时被调用。
STORAGE_GetCapacity_FS: 获取U盘信息函数,用于获取文件系统的总容量和可用容量信息,向PC机返回块大小和数量,当U盘插入USB接口时调用完STORAGE_Init_FS函数后调用
STORAGE_IsReady_FS: 检查U盘是否在就绪状态函数
STORAGE_IsWriteProtected_FS: 检查U盘是否在写保护状态函数
STORAGE_Read_FS: 读SD卡函数
STORAGE_Write_FS: 写SD卡函数
STORAGE_GetMaxLun_FS: 检查 U盘子设备个数
这七个函数我们主要改写STORAGE_GetCapacity_FS、STORAGE_Read_FS和STORAGE_Write_FS函数,其它暂时可以用默认内容。
1、获取SD卡容量信息
STORAGE_GetCapacity_FS函数用于获取U盘的容量,因此需要访问SD卡,又因为SD卡的变量声明不在usbd_storage_if.c文件,所以我们要在usbd_storage_if.c文件开头位置声明一个外部的SD卡全局变量
extern SD_HandleTypeDef hsd1;

该全局变量hsd1是打开SD卡的结构指针。接下来在STORAGE_GetCapacity_FS函数里添加读SD卡信息的代码
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
HAL_SD_CardInfoTypeDef SDinfo;
uint8_t sector0[512];
uint8_t* entry = NULL;
uint8_t type;
uint32_t sizeLBA = 0;
// 获取SD卡总容量
if (HAL_SD_GetCardInfo(&hsd1, &SDinfo) != HAL_OK)
{
return (USBD_FAIL);
}
// 读取分区表(Type:0x0B FAT32CHS,0x0C FAT32LBA,0x07 NTFS)
if(HAL_SD_ReadBlocks(&hsd1, sector0, 0, 1, 1000) == HAL_OK)
{
for(int i = 0; i < 4; i++)
{
entry = sector0 + 0x1BE + i * 16;
type = entry[4];
if(type == 0x0B || type == 0x0C || type == 0x07)
{
sizeLBA = *(uint32_t*)&entry[12];
break;
}
}
}
// 返回值
if(sizeLBA<SDinfo.BlockNbr)
{
*block_num = sizeLBA;
}
else
{
*block_num = SDinfo.BlockNbr - 1;
}
*block_size = SDinfo.BlockSize;
return (USBD_OK);
}

上面代码通过读取SD卡信息获得其容量,之后PC端会继续用STORAGE_Read_FS函数继续读取SD卡信息并对容量信息进行比对。这部分尝试了好多次,对于不同的SD卡会存在一定问题,当SD卡总容量大于32Gb时整张盘就无法格式化成FAT32格式了。要想强行格式化成FAT32的话只能先将SD卡分区分成32Gb再进行格式化,但此时HAL_SD_GetCardInfo函数就失去作用了因为它只能检测到整张卡的大小检测不到分区。因此要通过读分区表来看磁盘大小,我改进了代码但是检测的还是不太准,以后有时间再研究,至少现在U盘可以用。
2、读SD卡数据
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
if(HAL_SD_ReadBlocks(&hsd1, buf, blk_addr, blk_len, 1000) != HAL_OK)
{
return (USBD_FAIL);
}
while(HAL_SD_GetCardState(&hsd1) != HAL_SD_CARD_TRANSFER){};
return (USBD_OK);
}

在USB读函数里添加读SD卡数据代码,写过程中循环等待
3、写SD卡数据
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
if(HAL_SD_WriteBlocks(&hsd1, buf, blk_addr, blk_len, 1000) != HAL_OK)
{
return (USBD_FAIL);
}
while(HAL_SD_GetCardState(&hsd1) != HAL_SD_CARD_TRANSFER){};
return (USBD_OK);
}

在USB写函数里添加写SD卡数据代码,写过程中循环等待
(三)编译下载
单击编译按钮等一段时间系统编译完成会有如上图的错误或者警告信息(都是0最好)。编译通过以后,通过项目设置窗口的Debug标签确认程序下载器是否正确连接并安装好驱动程序

如果下载器没问题单击download下载按钮,下载器一边闪烁一边把编好的程序下载到STM32的FLASH当中,按下复位键电脑里U盘就出现可以读写了!
有问题欢迎留言,我再学新东西的时候和刚入门的小伙伴一起分享…
8244

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



