读取SD卡中的bmp,jpg图片

一、SD卡的基础知识

1、SD卡外设接口

可以看到SD卡类型分为四种,而F1和F4用的SDIO外设通过查手册可以看到是只支持SD v2.0版本

所以通过SDIO外设和SD卡通信的话,只能支持到32GB的

而H7芯片则是有SDMMC外设通过查手册可以看到是支持到SD v4.1版本的

所以H7通过SDMMC和SD卡通信时,最大可以支持32GB-2TB的SD卡

2、FATFS文件系统格式

一般用在小型嵌入式设备上的时候即用在stm32芯片上的时候,选用FAT/exFAT的文件系统格式,而使用的SD卡用什么文件格式比较好呢,这个就和你的SD卡容量有关了,一般32GB的SD卡是建议用FAT32格式的,而32GB-2TB的SD卡是建议用exFAT格式的文件系统,我也实际测试了一下,把32GB的SD卡格式化为exFAT格式,也是能正常使用的,所以我的理解是32GB的SD卡其实也能用exFAT格式的文件系统的,不过一般是建议用FAT32的文件格式(不一定正确,如果有读者对这方面比较熟悉,可以说明一下哦)

感觉有一篇文章说的也不错,可以参考一下:ExFAT格式适合SD卡吗(内存卡NTFS和FAT32的区别) | 多听号 (duotin.com)icon-default.png?t=N7T8https://www.duotin.com/en/58790.html

3、FATFS文件系统操作原理

想要使用FATFS文件系统,那么首先得先明白FATFS文件是怎么操作,最后才能实际作用到我们的硬件上。

从上图中可以知道FATFS文件系统为我们提供了各种文件系统操作函数,如 f_write,f_read等,但是怎么才能通过这些函数来实际操作到底层硬件呢,这就要看FATFS文件系统给我们提供的接口函数了,而这些接口都在diskio.c文件里,像 f_write 在diskio.c里会有一个接口函数,我们只需要在这个接口函数里根据实际硬件调用相应的硬件操作函数就可以了,f_read这些也是类似的。所以像SD卡使用FATFS文件系统操作时,只需要在SD卡工程中添加FATFS的固件包,并且根据实际开启ffconf.h中的宏定义和在相应的文件操作系统的接口中调用实际硬件的操作函数就可以了。

二、移植FATFS文件系统

1、资料下载

这个是FATFS的官网,各种FATFS函数的功能和ffconf.h里的宏定义说明都能在这里找到相应的说明和使用方法:FatFs - Generic FAT Filesystem Module (elm-chan.org)icon-default.png?t=N7T8http://elm-chan.org/fsw/ff/

另外不同版本的FATFS资料下载可以到这里进行下载:

FatFs - Archivesicon-default.png?t=N7T8http://elm-chan.org/fsw/ff/archives.html

2、将FATFS添加到工程里

我们在移植的时候先下载这整个文件添加到自己的工程中,添加好之后就是编译,然后把报错全部都解决了,我是在SD卡的基础上进行移植FATFS的,所以后面FATFS的系统操作都是对于SD卡来说的。

可以看到,将文件添加到工程中编译会出现这些错误,可以看到最后的get_fattime的报错,可以到ffconf.h文件中,把FF_FS_NORTC改为1,不是用RTC时钟即可把这个报错解决

而warning则是因为工程没有找到相应FATFS的头文件而产生的警告,只需要在c/c++里将刚刚添加的头文件和源文件的所在文件路径添加上就能解决警告的报错

可以看到,剩下的就是报没有那些函数,这个就很简单了,只要到我们底层diskio.c文件中,在相应的接口函数中调用我们写好的硬件底层操作函数就可以了,比如我这里是通过FATFS操作SD卡的,所以需要在相应diskio.c中文件挂载初始化中调用sd卡的初始化函数,这里以 f_mount 中调用到的 disk_initialize 为例说明,下面是FATFS的diskio.c文件自带的

在说明 f_mount 函数之前,还需要先了解一个操作文件系统的关键,那就是盘符,比如FATFS系统在挂载的时候怎么知道我要挂载SD卡还是RAM还是其他的设备呢,是怎么识别的呢,其实就是用到了这个盘符,这个可以到官网看一下详细说明,和这个盘符有关的还有 ffconf.h 中的几个宏定义,分别是:FF_VOLUMES ,FF_STR_VOLUME_ID ,FF_VOLUME_STRS,这三个,当你只需要用 "0:" 这些数字类来表示盘符的时候只需要设置FF_VOLUMES就可以了,但是用 "0:" 来表示盘符有的时候可能并不能直接看出当前打开的底层硬件到底是什么,所以为了更好的看出当前操作的底层硬件,这时候就能用到FF_STR_VOLUME_ID和FF_VOLUME_STRS了。

3、FF_VOLUMES,FF_STR_VOLUME_ID ,FF_VOLUME_STRS宏定义介绍

上图是官网对于这三个宏定义的详细说明。

FF_VOLUMES所描述的就是可以通过设置这个宏定义的大小来描述当前FATFS系统支持多少个底层硬件,最高是可以支持10个,而我程序中只用到了一个SD卡,所以这个宏定义设置为1,表示我有一个硬件底层设备需要FATFS文件系统的支持,如果你的工程中还用到了像spi flash 和 nand flash的话,加上sd卡,一共是三个,那么这个宏定义就修改为3。

FF_STR_VOLUME_ID描述的意思是当你对这个字节设定为0和1和2的时候,可以支持的盘符格式是不一样的,当这里设置为0的时候,表示盘符只能用数字来表示,即用 "0:" 或者 "1:" 来表示某一个盘符,比如我现在想用 "0:" 来表示sd卡的盘符,那么就在diskio.c文件中定义一下。

如果要用 "1:" 来表示sd卡的盘符的话,这里就相应改1,不过建议是从0开始。

当FF_STR_VOLUME_ID这个宏定义定义为1的时候,则表示可以用两种方式来表示sd卡的盘符,一种还是像上面的用 "0:" 来表示盘符,另一种就是需要和下一个宏定义FF_VOLUME_STRS一起来使用,用字符的方式来表示盘符,像下面我将FF_STR_VOLUME_ID设置为1,并且FF_VOLUMES为1的情况下

FF_VOLUME_STRS定义 "sd",就表示当我调用 f_mount来挂载的时候,可以使用 "sd:" 来表示sd卡的这个盘符,当然也可以使用上面定义的 "0:" 来挂载这个sd卡,还有FF_VOLUME_STRS里定义的字符串不能超过FF_VOLUMES中定义的值,因为一超过,文件系统就不知道该操作谁了嘛,所以这里定义的字符串个数一般是小于等于FF_VOLUMES中的值的,这里先用最后成功使用的FATFS文件来做演示。

可以看到我在初始化函数里加了相应的打印,并且挂载成功也会进行打印,实际输出的如下图

可以看到,使用字符进行挂载,挂载成功,并串口成功打印

其实实际工程中是可以挂载三个设备的,如果要挂载三个

可以打开这句,那么挂载nand flash的时候就可以使用 f_mount(fs[2], "nandflash:", 1); 来挂载nandflash,刚刚上面说了FF_VOLUME_STRS中定义的字符串个数不能大于FF_VOLUMES中定义的个数,否则会报错。

如上图,三个磁盘,但是却声明了4个字符串,就会报错,所以实际使用的时候FF_VOLUME_STRS中定义的字符串个数要小于等于FF_VOLUMES的值

下图是上面用创建好的FATFS工程演示的 fs[i] 的内存声明

其实就是一个单纯的申请内存,后续这边会有更直接的申请方式来进行内存申请,一样的效果。

至于FF_STR_VOLUME_ID为2的情况时,这边没有过多了解和使用,便不在这里说明了。

好了,说到这里,应该对盘符的定义和使用有了更多的了解,那么接下来,就继续说 f_mount 这个函数了,这个函数的底层调用了 disk_initialize ,而可以看到,实际就是根据 pdrv这个盘符或者字符串来实际初始化我们的底层硬件了,这里我用到了sd卡,所以如下图2修改即可

这样在调用 f_mount 进行挂载的时候,用 "0:" 进行挂载就能和我们实际硬件的sd卡初始化结合在一起了。其他的 f_write 和 f_read 这些函数也是类似的写法,比较需要注意的是disk_ioct1函数,主要用于辅助文件系统获取各种状态的。

后面的disk_write和disk_read,其实都是差不多的写法,我这里是简单的挂载和读写,实际工程使用的时候,肯定是需要在写和读函数中根据实际操作返回值做处理的,比如写不成功,可以根据返回值判断是否是因为盘符没挂载所致,是的话就重新挂载,还是需要考虑得比较全面的,这里只是简单的测试和验证,我就没在程序里做处理了,实际使用还是需要处理的。

可以看到disk_write上还有个宏定义的预编译:#if FF_FS_READONLY == 0 说明只有当前的文件系统支持读写的时候disk_write函数才有效,宏定义在使用FATFS时也是非常重要的。

这些是一般需要开启或者设置的宏定义,把这些都设置好后,就可以开始来使用FATFS的函数了。

三、实验现象

1、通过文件系统操作sd卡里的 txt 文件,实现对 txt 文件的写入和读取

这里主要是测试挂载写入和读取函数,实验现象如下:

程序写的一开始打开sd卡盘符下的sd_test.txt文件,并且对文件属性进行设置

可以看到设置的是可读可写并在文件不存在的时候进行创建。之后就是对打开的文件进行读写了,需要注意的是每次写好,鼠标所在光标都会停留在当前位置,比如你往文件写入5678,那么光标会停在8之后

所以如果此时想将写入的5678读取出来还需要调用一下 f_lseek(fp,0); 表示将当前光标移动到5之前

这样子读取4个数据的时候,才能把5678读取出来,还要注意一个小坑,一开始我把写的两个数据一起定义,导致出错

后来排查发现是不能一起定义,要分开定义,不然前一个数据赋值是没有生效的

2、通过函数实现找出文件夹下的bmp和 jpg 文件

我在程序中用了picture_format_flag作为标志位,当该标志位置一的时候,说明当前读取到的是bmp 或者 jpg 图片,此时调用下面不是目录的打印,而 first_spot_flag 则是表示,当前读取到的第一个 . 的位置,比如 1.bmp 的这个 . 符号,为什么要加这个标志位呢,因为 f_readdir(&dp,&fil); 读取到 FILINFO fil 的 fname 里面时,是覆盖获取的。

通过仿真可以看到,当后面打开的文件名短于前一个打开的文件名时,那个 .bmp还是留在fil.fname 里的,所以需要加个识别到首次的点后,就判断之后的三位,而不是一直判断点后的三位,一旦判断一次后就跳出本次判断,这样才能正确获取到相应后缀的文件。

可以看到可以正确找出bmp和 jpg 的图片。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值