- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
-
-
- /* Configure the system clock */
- SystemClock_Config();
-
-
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- MX_SDIO_SD_Init();
- MX_USART1_UART_Init();
- BSP_SD_Init();
- MX_FATFS_Init();
2、修改MX_FATFS_Init(void)函数
- 除了上面对初始化函数的调用以外,我们还需要一些别的修改。在这里,我希望调用BSP_SD_Init()初始化好了SD卡之后,在调用MX_FATFS_Init()初始化的时候就挂载SD卡。所以我们就在FatFs的初始化函数里面用户代码的部分加入了我们的f_mount函数。下面贴出MX_FATFS_Init()函数的代码,这个函数位于文件Application/User->fatfs.c中。
- 我还设置了一个简单的条件语句,如果挂载SD卡成功的话就发送一个成功的消息。
- void MX_FATFS_Init(void)
- {
- /*## FatFS: Link the SD driver ###########################*/
- retSD = FATFS_LinkDriver(&SD_Driver, SD_Path);
-
-
- /* USER CODE BEGIN Init */
- /* additional user code for init */
- if(f_mount(&SDCard, SD_Path,0) == 0)
- {
- HAL_UART_Transmit(&huart1, (uint8_t *)"Success!\n", 9, 500);
- }
- /* USER CODE END Init */
- }
- OK,到了这里我们对文件的修改就基本上完成了,但是仅仅如此只能让SD卡正常驱动和挂载,并不能够打开文件和读写操作。接下来的内容就是来解决这个问题的。
三、修改启动文件
1、malloc函数的介绍
- 原型
- extern void *malloc(unsigned int num_bytes);
- 头文件
- #include <stdlib.h>
- 函数声明
- void *malloc(size_t size);
- 备注:void* 表示未确定类型的指针,void *可以指向任何类型的数据,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者其他数据类型)。
- 功能
- 分配长度为num_bytes字节的内存块
- 返回值
- 如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。函数返回的指针一定要适当对齐,使其可以用于任何数据对象。
- 以上是百度百科对malloc函数的一些简介,在Keil中要使用这个函数需要包含stdlib.h头文件。同时在Keil的编译器设置中要选中microlib选项,不过使用STM32CubeMX生成的工程默认都选中了这一选项。这个函数是放在标准库里面的,所以不能找到这个的定义代码在哪里,编译的时候链接所属的库就可以了。
2、FatFs对malloc函数的调用
- 为了讨论FatFs和malloc函数的关系,我们要先打开MiddleWares/FatFs->syscall.c文件。在这个文件里面,我们可以看到有一些函数是分配内存的函数。滚动页面到最下面,可以看到最后两个函数都和分配内存有关。这些函数都调用了malloc函数来动态分配内存,在ST的手册中没有提到这个malloc。但是如果没有这个系统提供的malloc函数我们就需要自己来定义一个动态内存分配函数,同时把原来的malloc调用替换为我们提供的函数接口。
- 这样我们就知道了FatFs对malloc函数有调用。但是仅仅如此吗?不是这么简单。FatFs默认是开启了对长文件名支持的,这样就需要很多的内存来存储文件名这些信息。如果malloc内存初始化的时候内存设置的比较小的话,就不能成功分配内存了不是吗?这样一来调用FatFs的f_open函数的时候就会返回17,查询这个枚举值的定义就知道是内存分配不足的错误。感兴趣的话可以不进行下一步修改heap的大小来调用f_open同时把返回值发送到串口来看一看。
3、改变heap的内存大小
- malloc函数的内存是从堆(heap)里面分配的,如果使用了对长文件名的支持我们就需要malloc为FatFs提供内存分配的支持。在使用malloc之前会对malloc的内存池进行初始化,而这个初始化的操作是由我们的库提供的启动代码完成的。
- 下面贴出汇编语言的启动代码的一部分。
- <blockquote>Stack_Size EQU 0x00000400
- 这一段代码是初始化内存池的汇编语言代码,在这里我把Heap_Size设置为0x00000800,也就是2KB。默认的数值是512Bytes,这样的话分配给我们的FatFs使用会不足,分配内存会失败。只有这样子修改了之后,后面的f_open这些函数才可以正常使用。
- 进行了以上的步骤之后,我们就可以轻松愉快地使用FatFs提供的各种函数了。
四、使用FatFs
1、定义一些变量
- 在我们代码开始的部分,先定义一些变量供我们使用。这里选择几个来解析一下。
- 第一个FIL file;这个变量是文件的结构体变量,记录了我们打开的文件的信息。使用f_open等函数的时候都要用到。
- 第二个Words变量是一个字符串指针,我用这个指针来存储读取的字符信息。这里我们就使用了malloc函数来分配内存,我们通过修改启动代码提供了更多的堆内存所以这里就可以分配一些给我们使用。
- 第三个是Path这个字符串,这里保存的是文件所在的路径。关于文件路径的写法,请参考FatFs的官方文档 网站。
2、使用f_open
- 关于f_open函数的具体用法和详细说明,参见FatFs官方文档 网站。这里可以看看我在下方贴出的代码作为参考来使用。
3、使用f_read
- 关于f_read没有特别的说明,详细的用法去看看FatFs官方文档 网站。这里只想说一下第三个和第四个参数。这里的第三个参数是给定的要读取的字节数,而第四个参数是最后读取了的字节数的存储指针。这里要说明的是,不一定你要读取128个字节就一定会读取这么多,如果遇到了文件尾符号就会停止读取。停止读取的时候就会把读取的字节数写入到第四个参数指定的存储空间里。所以我们可以通过返回的读取字节数来发送到串口,不需要自己数读取了多少个字节。
- <blockquote>if(!f_open(&file, Path, FA_READ | FA_OPEN_EXISTING | FA_WRITE))
4、使用f_lseek
- 我在调用f_write函数之前,调用了一下f_lseek函数来移动文件指针。不过在这里并不需要,你们参考我的完整工程文件就知道。在这之前我已经进行了文件读取的操作,文件指针已经定位在了文件的最后一个字节这里。不过我还是调用了一下,因为有时候需要用到这个函数。这个函数的详细用法请参考FatFs的官方文档 网站。
5、使用f_write
- f_write的用法和f_read非常相似,这里就不在赘述。第三个参数和第四个参数的含义也是大同小异。想了解这个函数的详细说明,请参考FatFs的官方文档 网站。
- <blockquote> f_lseek(&file, Number);
6、使用f_close
- f_close这个函数的用法就非常的简单了,这里在写入操作之后要调用一次这个函数。调用了这个函数之后,写入的信息才可以保存在SD卡的上面,下一次才可以读取出来。如果没有使用这个函数的话,文件就会存储在缓冲区中,只有这一次读取可以成功。到了下一次复位的时候,读取的还是原先的文件内容。
7、再次读取文件内容
- 现在我们有写入了一些信息到了我们的文件里面,再次读取一下看看写入成功了吗。下面的代码实现了这一功能,大家参考一下。
- <blockquote>f_open(&file, Path, FA_READ | FA_OPEN_EXISTING);
五、上电测试
- 代码我们都准备好了,只要编译通过下载到我们的开发板上就行了。如果你的硬件连接都非常的正确,而且按照前面的步骤来的话就可以开始测试读取SD卡了。
- 我在SD卡的根目录下建立了/File/test.txt
- 初始的文件内容如下:
- This is a test file to confirm the library of FatFs.
- Don't support Chinese in this file.
- Wish you a good luck!
- 测试结果
-
-
- 观察两次的输出结果可以看得出来,第二次复位的时候文件的末尾又多了一行文字。这一现象符合我们的程序逻辑。大家可以对比一下这里的结果,自己做一个参考。
写在后面的话
- 注意!注意!注意!本人不是什么工程师,只不过是爱好嵌入式开发的学生一枚,如果你发现在这个帖子中的错误请及时提醒我。如果对本帖的内容有什么疑问请在下方留言,我会经常过来逛论坛的。
- 这次开发FatFs读写确实走了很多的弯路啊,结果最后发现不过是堆内存空间不够,不能分配的错误而已。为了发现这个错误我也是找了很多的资料什么的,不过这就是学习的过程吧。我觉得只有这样才能够锻炼我们发现问题和解决问题的能力。相信在这一次又一次的坎坷和曲折之中我一定会进步越来越大的。大家一起加油!
- 下一次给大家带来的是什么呢?我现在贴出一张照片,大家一起猜一猜。
-
- 下一次要更新的东西会比较复杂的吧,所以时间可能会隔得比较久,希望期待下一篇帖子的社区成员多多关注。