先展示最终实现的功能效果如下:
1.目的与意义
为什么选用SD卡?
使用Nor-flash(W25Q系列)进行图片的存取,需要先把图片通过对应软件批量处理为二进制bin文件,再通过SPI等通讯方式将 bin文件烧写进Nor-flash才能进行使用,使用时还要记住每张图片的首地址和对应字节数,MCU才能准确的读出来并显示,所以在要更换图片或者读取显示图片上会显得十分繁琐麻烦;相对来说,SD(TF)卡虽然价格较贵,但通过SD(TF)卡和读卡器直接连接电脑可以将SD(TF)卡虚拟为U盘,直接往里面拷贝图片即可,更换图片就显得方便简单;
为什么要使用FATFS文件系统?
MCU要跟SD卡之间进行通讯,可以使用SPI和SDIO通讯方式,不移植FATFS文件系统的话也可以像Nor-flash一样通过对地址及扇区字节进行读取,但是此方法较麻烦,为了让MCU可以直接对SD卡内的各类文件格式进行读取识别,所以需要一个相同的文件系统,又由于fatfs系统在现阶段最广泛兼容,且STM32CUBEMX支持移植,所以就选用了该文件系统;
为什么使用Tinyjpeg解码库?
STM32F4系列具有较大的flash和ram,所以可以直接移植LVGL或Emwin图形库对图片格式进行解码,同时STM32F4及以上系列的MCU,STM32CubeMX也已经支持Tinyjpeg解码库的直接移植:
所以证明Tinyjpeg解码库还是挺受欢迎的;
而STM32H系列价格昂贵,但具有JEPG硬件解码,所以不需要软件解码库;
STM32F1系列作为STM32家族中的廉价产品,其外设及内存肯定也较少,即没有硬件JPEG解码,flash和ram又较小,所以在使用显示屏显示图片时,移植Tinyjpeg库就是比较好的选择了,通过软件多写一点,就能节省MCU的价格,相信大部分人还是愿意做的。
2.使用STM32CubeMX建立工程
这里先给出我使用的TF卡的硬件原理图:
这里我设计成了只要TF卡插入卡槽,LED灯就会被点亮,同时这个CD脚也是后面配置FATFS文件系统要用到的,所以才在这里给出原理图。
通过STM32CubeMX我们要完成创建对SD卡的SDIO通讯,FATFS文件系统的移植,Tinyjpeg则只需要拷贝几个C文件和h文件即可:
1.使能SYS的serial Wire,选择晶振及配置时钟树,这些创建基本工程也都要进行配置,这里我就不具体给出设置参数了,我这边使用的是外部晶振配置为72MHz时钟:
2.配置SD卡的SDIO通讯方式(因为MCU又要从SD卡读取文件,又要将数据发送到TFTLCD进行显示,所以SDIO这里使用DMA方式,减少对MCU线程的占用):
这里配置了SDIO的基本参数,开启4线通讯(对应4个IO口才会使能),然后使能硬件流(看过很多博主都说使能了硬件流之后SD卡初始化成功概率高很多,我自己测试也确实是),最后设置工作频率1Mhz(计算方式为:SDIO的时钟频率/(SDIOCLK clock divide factor+2),通过时钟树可以看到SDIO的时钟频率为36MHz ,如果SD卡通讯失败率很高,则可以再调小频率进行尝试,若改小后效果仍然很差,需检查硬件布线是否存在较大线长差异或线路干扰等);
接下来开启SDIO的DMA通道及中断使能:
由于是从SD卡读取数据到MCU,所以方向选择外设到内存;
这里设置好后要到中断优先级NVIC里面,将DMA的中断优先级改低,一般DMA的中断优先级都调到比其他重要中断低,防止大规模传输数据时打断其他重要中断:
这样SDIO就配置完成了。
3.移植FATFS文件系统:
这里也没啥需要进行配置修改的,由于大家都是中国人,难免会用到中文给文件夹起名,所以这里将CODE_PAGE修改为simplified Chinese即可,同时为了避免长文件名出错,所以也使能了USE_LFN使用栈的方式。由于这里我们只用了一个外部存储器(TF卡),所以VOLUMES默认为1即可,操作块(MAX_SS及MIN_SS)为512字节也是默认即可。
然后配置其设备检测IO口(即上面原理图跟CD脚相连的MCU的IO口,低电平触发,所以配置为上拉输入即可):
至此SDIO(DMA)跟FATFS文件系统也配置完成。
接下来就是Keil生成工程,这里把堆栈可申请空间都稍微调大至4KB,确保FATFS和Tinyjpeg操作时有足够的空间。
3.对工程进行修改,并测试MCU跟SD卡正常通讯及挂载FATFS系统
打开KEIL工程的main.c文件,找到SDIO初始化的位置将其数据总线改为1位,这里仅是做初始化用(初始化用1位数据总线,400KHz以下频率),初始化完成后程序会切换到4位数据总线:
接下来编写SD卡的测试函数:
1.配置uart对接printf函数:
#include "stdio.h"#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */PUTCHAR_PROTOTYPE //重定义usart1,之后使用printf()函数将自动通过串口1输出
{HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF); //改变&huart1为&huart2可以选择串口2return ch;
}
然后勾选USE MicroLIB库:
2.读取SD卡的