使用vscode + gcc进行 STM32 单片机开发(三)DMA读写SD卡,移植FATFS文件系统
背景
在本系列的前两篇文章(
使用vscode + gcc进行 STM32 单片机开发(一)编译及调试
使用vscode + gcc进行 STM32 单片机开发(二)gcc环境 移植rtthread)
的基础上,我想再多写一点,一方面是想试试vscode环境开发STM32到底怎么样;另一方面是在和同事的私下讨论的时候,讨论到STM32的SD卡不怎么好用。
综上所示,本文主要描述的是在vscode + gcc的环境下,配合rt-thread RTOS系统,实现STM32的SD卡读写及文件系统的移植,并给出最终的SD卡读写速率测试结果。
相关知识
在阅读本文之前,为了确保有一个良好的阅读体验,需要提前了解一些知识。
- 本系列的前两篇文章,具体链接请见本文开头
- SD卡的基础知识,包括但不限于:
· SD卡、MicroSD卡 、MMC卡、TF卡
· 主机SD卡的接口定义
· 主机和SD卡的通讯传输流程及协议(了解即可) - DMA(Direct Memory Access)传输的概念
- 文件系统 的基本概念,常见的文件系统:FAT、NTFS、Ext等
开始
废话少说,言归正传。 下面开始我们的移植工作。
准备工作
先准备一下:
- 硬件环境:
- 一块STM32开发板,带SD卡接口。
- 一张SD卡
- 软件环境:
- 本系列的前两篇文章所搭建的vscode + gcc + rtthread开发环境
第一步:在STM32CubeMX中开启SD卡接口
直接点点点就行了,如下图所示:
选择 Connectivity - SDMMC1
, 选择 SD 4 bits Wide bus
,下面开启NVIC中断。
这里有个比较关键的参数
SDMMC clock divide factor
,指的是SD卡硬件接口的时钟分频参数,此参数决定最终的时钟频率。
根据SD卡接口的官方文档,可以查阅到其频率一般为1MHz~50MHz。 所以这里我们后面再根据实际情况动态的调整。
设置完成,生成代码 OK
这一步完成后,我们既可以简单的实现往SD卡里面读写二进制的数据了。 但是绝大多数情况下,我们往SD卡读写的都是以文件的形式,文件是有格式的二进制数据。 所以接下来我们开始第二步。
第二步:移植文件系统
文件系统的概念这里不再累述,请自行查阅。 这里我们选择移植FATFS文件系统,官方网站为:http://elm-chan.org/fsw/ff/00index_e.html。
这是好奇的同学就要问了,为什么你直接就选了FATFS呢? 为什么不选其他类似于 NTFS、Ext这些文件系统呢?
我的回答是: 因为我查了一圈资料,就只查到了FATFS的移植文档,其他文件系统我连源码都没找到 /汗
首先我们去上述的官网上下载FATFS的源码,一共就6、7个C文件和H文件。 下载后随意在项目里面找个顺眼的位置放着,等下把这个位置加到makefile里面进行编译。
这里我放到了 Core/Src
目录下,如下图所示:
接下来把源文件和头文件加到Makefile里面去,这里需要你稍微去学习一下makefile,有很多种方法都可以添加,我这里随便选择一种,代码如下所示:
OBJECTS += Core/Src/ff14b/source/diskio.o Core/Src/ff14b/source/ff.o Core/Src/ff14b/source/ffsystem.o Core/Src/ff14b/source/ffunicode.o \Core/Src/sd_card_speed.o
C_INCLUDES += -ICore/Src/ff14b/source \
ok,接下来进行编译。
不出意外的话,你会编译失败,报错部分函数未定义。 这是当然的咯,上述我们只是下载了FATFS源码而已,还没有和STM32的SD卡读写函数进行对接(移植)。
因此接下来我们进行移植。
根据报错提示,我们打开 diskio.c
这个文件,参考官方的移植指南http://elm-chan.org/fsw/ff/doc/appnote.html#port。 做填空题,把里面的函数填上STM32的SD卡相关读写函数。
根据实际情况不同,这里的函数实现都不同,下面我给一个我的写法:
disk_status
函数:如下图所示,根据GPIO9的状态(我的开发板上是这个引脚)判断SD卡是否插入
STATUS disk_status (BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{DSTATUS stat;int result;GPIO_PinState card_det_pin;switch (pdrv) {case DEV_RAM :card_det_pin = HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_9);result = (card_det_pin == GPIO_PIN_RESET);// translate the reslut code herestat = (result ? 0 : STA_NODISK);return stat;case DEV_MMC ://result = MMC_disk_status();// translate the reslut code herereturn STA_NODISK;case DEV_USB ://result = USB_disk_status();// translate the reslut code herereturn STA_NODISK;}return STA_NOINIT;
}
disk_initialize
函数:无需改动
disk_read
函数:如下图所示,先声明一段DMA的buffer,buffer制定一个“段名”.dma
,这样的话后续在编译时,可以根据.dma
这个段名,把buffer强制固定到特定的内存段上。 这是因为STM32H7系列,有多个RAM内存地址,分别为:
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
而SD卡要求的DMA必须在DTCMRAM上,否则无法工作。具体请查询STM32H7的官方手册,有详细说明。
然后再声明一个DMA读取完毕的回调函数,其中把RxCplt置1。
disk_read函数中发起一次DMA读取,然后就一直等待变量RxCplt置1。
volatile uint8_t RxCplt=0;;
static uint8_t __attribute__((section(".dma"))) dma_rx_buffer[10240]; //10kb
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{SCB_InvalidateDCache_by_Addr((uint32_t*)dma_rx_buffer, sizeof(dma_rx_buffer)/4);RxCplt=1;
}DRESULT disk_read (BYTE pdrv, /* Physical drive nmuber to identify the drive */BYTE *buff, /* Data buffer to store read data */LBA_t sector, /* Start sector in LBA */UINT count /* Number of sectors to read */
)
{DRESULT res;HAL_StatusTypeDef result;switch (pdrv) {case DEV_RAM :// translate the arguments herewhile(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);// result = HAL_SD_ReadBlocks(&hsd1, (uint8_t*)buff, (uint32_t)sector, (uint32_t)count, 500);result = HAL_SD_ReadBlocks_DMA(&hsd1, (uint8_t*)dma_rx_buffer, (uint32_t)sector, (uint32_t)count);if(result!=HAL_OK){return result;}while(RxCplt==0);RxCplt=0;SCB_CleanDCache_by_Addr(dma_rx_buffer, sizeof(dma_rx_buffer)/4);memcpy(buff, dma_rx_buffer, count*BLOCKSIZE);// translate the reslut code hereswitch (result){case HAL_OK:return RES_OK;case HAL_ERROR:return RES_ERROR;case HAL_BUSY:return RES_ERROR;case HAL_TIMEOUT:return RES_ERROR;default:return RES_ERROR;}case DEV_MMC :// translate the arguments here//result = MMC_disk_read(buff, sector, count);// translate the reslut code herereturn RES_NOTRDY;case DEV_USB :// translate the arguments here//result = USB_disk_read(buff, sector, count);// translate the reslut code herereturn RES_NOTRDY;}return RES_PARERR;
}
disk_write
函数和disk_read
函数类似,如下图所示,不再累述:
volatile uint8_t TxCplt=0;;
static uint8_t __attribute__((section(".dma"))) dma_tx_buffer[10240]; //10kbvoid HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{TxCplt=1;
}
DRESULT disk_write (BYTE pdrv, /* Physical drive nmuber to identify the drive */const BYTE *buff, /* Data to be written */LBA_t sector, /* Start sector in LBA */UINT count /* Number of sectors to write */
)
{DRESULT res;HAL_StatusTypeDef result;int retry = 5;switch (pdrv) {case DEV_RAM :// translate the arguments herewhile(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);memcpy(dma_tx_buffer, buff, count*BLOCKSIZE);SCB_CleanDCache_by_Addr(dma_tx_buffer, sizeof(dma_tx_buffer)/4);result = HAL_SD_WriteBlocks_DMA(&hsd1, (uint8_t*)dma_tx_buffer, (uint32_t)sector, (uint32_t)count);while(TxCplt==0);TxCplt=0;// for(;retry>0;retry--){// while(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);// result = HAL_SD_WriteBlocks(&hsd1, (uint8_t*)buff, (uint32_t)sector, (uint32_t)count, 5000);// if(result == HAL_OK) break;// }// translate the reslut code hereswitch (result){case HAL_OK:return RES_OK;case HAL_ERROR:return RES_ERROR;case HAL_BUSY:return RES_ERROR;case HAL_TIMEOUT:return RES_ERROR;default:return RES_ERROR;}// translate the reslut code herereturn res;case DEV_MMC :// translate the arguments here// result = MMC_disk_write(buff, sector, count);// translate the reslut code herereturn RES_NOTRDY;case DEV_USB :// translate the arguments here// result = USB_disk_write(buff, sector, count);// translate the reslut code herereturn RES_NOTRDY;}return RES_PARERR;
}
disk_ioctl
函数:whille循环等待SD读写完毕,如下图所示:
DRESULT disk_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff /* Buffer to send/receive control data */
)
{DRESULT res;int result;switch (pdrv) {case DEV_RAM :// Process of the command for the RAM driveswitch (cmd){case CTRL_SYNC:/* code */while(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);return RES_OK;break;case GET_SECTOR_COUNT:*(DWORD*)buff = hsd1.SdCard.BlockNbr;return RES_OK;break;case GET_BLOCK_SIZE:*(DWORD*)buff = hsd1.SdCard.BlockSize;return RES_OK;break;default:break;}return RES_ERROR;case DEV_MMC :// Process of the command for the MMC/SD cardreturn RES_NOTRDY;case DEV_USB :// Process of the command the USB drivereturn RES_NOTRDY;}return RES_PARERR;
}
上述函数写完后,再进行编译,应该就没问题了。
第三步:测试SD卡读写速度
这一步其实就相对比较简单了,我们写入一个100MB的文件,来测试一下写入的速度,测试代码如下:
#include <stdio.h>
#include <rtthread.h>
#include <stdint.h>
#include "sd_card_speed.h"
static const int SIZE_MB=100;
static const long long SIZE_BYTE = SIZE_MB*1024*1024;/*
@brief: 此函数测试sd卡的读写速度,
@note: 测试方法,写入200MB的文件,
@return: 返回速度 单位 b/s
*/
static int write_count=0;
int sd_card_write_speed(FATFS* fs){char filename[64]={0};int ret;ret = snprintf(&filename, sizeof(filename), "/SD_CARD_SPEED_TEST_%dMB", SIZE_MB);if(ret < 0){rt_kprintf("snprintf error\r\n");return -1;}// write 200MB in// cpu have 1MB RAM, so we write 200KB each timeint data_size_each = 200*1024;uint8_t *data = rt_malloc(data_size_each);if(data == NULL){rt_kprintf("rt_malloc failed!\r\n");return -1;}FRESULT f_ret;FIL fil;f_ret = f_open(&fil, filename, FA_CREATE_ALWAYS | FA_WRITE);if(f_ret != FR_OK){rt_kprintf("f_open failed, error code is %d\r\n", f_ret);return -1;}for(int i=0;i< SIZE_BYTE/(long long)data_size_each;i++){int bw = 0;f_ret = f_write(&fil, data, data_size_each, &bw);if(f_ret != FR_OK){rt_kprintf("f_write failed, error code is %d\r\n", f_ret);return -1;}if(bw != data_size_each){rt_kprintf("bw != data_size_each, bw is %d\r\n", bw);}write_count++;}f_close(&fil);return 0;
}int sd_card_read_speed(){}
在main函数里面调用这个函数,并打印一下执行前后的时间t1和t2,即可算出SD卡的读写速度。
TODO:补图,SD卡的测试结果
结语
最后,代码写的不怎么样,我就没有上传了。 如果有任何疑问,欢迎在下面留言,我看到了就会回复。
使用vscode + gcc进行 STM32 单片机开发(三)DMA读写SD卡,移植FATFS文件系统相关推荐
- 【FatFs】基于STM32 SD卡移植FatFs文件系统
相关文章 <[SDIO]SDIO.SD卡.FatFs文件系统相关文章索引> 1.前言 FatFs是一个通用的FAT/exFAT文件系统模块,用于小型嵌入式系统.它完全是由 ANSI C 语 ...
- 使用STM32在SPI模式下读写SD卡
使用STM32在SPI模式下读写SD卡 代码分享 一.实验原理图 二.SD卡 三.实验操作 1.在stm32cubemx建立工程 2.根据上面的原理图连接硬件 3.keil编译和烧录 四.实验结果 总 ...
- STM32挂载SD卡基于Fatfs文件系统读取文件信息
STM32挂载SD卡基于Fatfs文件系统读取文件信息
- STM32+雷龙SD NAND(贴片SD卡)完成FATFS文件系统移植与测试
一.前言 在STM32项目开发中,经常会用到存储芯片存储数据. 比如:关机时保存机器运行过程中的状态数据,上电再从存储芯片里读取数据恢复:在存储芯片里也会存放很多资源文件.比如,开机音乐,界面上的菜单 ...
- 基于SD卡的FatFs文件系统(FatFs移植到STM32)
平台:STM32ZET6(核心板)+ST-LINK/V2+SD卡+USB串口线 工程介绍:主要文件在USER组中,bsp_sdio_sdcard.c,bsp_sdio_sdcard.h和main.c, ...
- STM32 通过USB接口读写挂载的SD卡(支持fatfs文件系统)
通过USB接口读写挂载的SD卡 HAL库 标准库 本文以STM32F407为例分别用HAL库(STM32CubeMX)和标准库实现SD卡对fatfs支持,以及通过USB接口读写SD卡.首先看电路连接: ...
- 基于STM32采用CS创世 SD NAND(贴片SD卡)完成FATFS文件系统移植与测试
一.前言 在STM32项目开发中,经常会用到存储芯片存储数据. 比如:关机时保存机器运行过程中的状态数据,上电再从存储芯片里读取数据恢复:在存储芯片里也会存放很多资源文件.比如,开机音乐,界面上的菜单 ...
- 基于STM32采用CS创世 SD NAND(贴片SD卡)完成FATFS文件系统移植与测试(中篇)
3.2 SPI硬件时序方式 上面的3.1小节是采用SPI模拟时序驱动SD NAND,STM32本身集成有SPI硬件模块,可以直接利用STM32硬件SPI接口读写. 下面贴出底层的适配代码. 上面贴出的 ...
- VSCode搭建STM32单片机开发环境
前言 由于之前的学习过程中使用过了VSCode.Source Insight这类强大的代码编辑器,感觉实在是太好用了.但是最近自己要用到Keil进行STM32单片机的开发,因为使用过了VSCode这类 ...
最新文章
- C++第10周项目4参考——完数
- 2011-02 Emacs相关闪存
- mybatis 解析Integer为0的属性,解析成空字符串
- BZOJ-1082-[SCOI2005]栅栏(二分+dfs判定)
- 跟随光标下划线导航插件
- ai与虚拟现实_将AI推向现实世界
- MySQL启用SSL连接
- Oracle字符拆分函数,Oracle拆分字符串函数有哪些呢?
- 2017计算机c语言大纲,2017年计算机考研大纲
- python︱Python进程、线程、协程详解、运行性能、效率(tqdm)
- 对软件未来走向的看法
- CentOS下启动mysql出现“MySQL Daemon failed to start”解决办法
- paip.web数据绑定 下拉框的api设计 选择框 uapi python .net java swing jsf总结
- 【实用工具】如何录制电脑屏幕gif动图?
- java围棋对弈实例
- 思维导图使用技巧:手把手教你怎么画思维导图
- Cisco Packet Tracer 命令大全 【思科交换机模拟器命令大全】
- 基于浏览器的Linux
- WOL远程开机,实际落地成功。
- 淘宝客推广(一):利用淘礼金做拉新