想着小熊派板子上带了一个QSPI,有8MB的存储空间,那可不能浪费了呀!之前写的那些开源项目的图片资源其实放在这上面的,如何实现呢?方法如下:
- 使用SD卡将文件拷贝到QSPI FLASH(采用fatfs文件系统)
- 写一个QSPI FLASH MDK下载算法,直接将图片数据放在主程序中
接下来进入正文:
小熊派上自带了一个QSPI接口的8M大小的SPI_FLASH,如下图所示:
小熊派官方也提供了驱动编写的视频教程以及代码编写例程,关于怎么实现的,这里就不多说了,如果想详细了解原理,可以看看世伟兄以及小熊派之前写的文章:
STM32Cube-18 | 使用QSPI读写SPI Flash(W25Q64)
单片机基础 —— 使用QSPI读写SPI Flash(W25Q64)
今天我们主要来讲解下Fatfs系统功能的配置,在进入正题之前,我已经按上面的教程将QSPI Flash正常驱动起来了,接下来进入主题,如下图所示:
在中间件的地方选择fatfs,然后再Mode处选择User-defined,因为这个不是官方默认支持的,需要用户自己去实现Fatfs关于底层的驱动接口。
1、功能参数配置
其中,关于功能参数的配置,主要是用到了才去配置,不用到的选项默认就行了,这部分请参考ST官网有关STM32cube Fatfs的应用开发文档,如下所示:
2、几个重要参数配置说明
CODE PAGE这个选项主要是提供编码格式的支持,根据个人需求配置,这里配置为简体中文:
USE_LFN这个选项主要是为了支持长文件名,并且当需要支持这个功能的时候需要提供缓存区存放,fatfs提供了BSS、STACK、HEAP三种方式。
根据个人需求选择存放在STACK中,因为存放在BSS上,则是带有静态工作缓冲区的LFN,不能进行动态分配,而存放在HEP上,则需要重写实现fatfs提供的ff_memalloc和ff_memfree函数,所以一般情况下就把它放在栈区即可。
MAX_SS这个选项配置为4096,为什么要配置为4096呢?请看W25Q64的手册描述:
如上,W25Q64这款芯片的最小擦除单位是4KB,也就是4096字节,为了提高擦写效率,一般情况下就直接写4096。
其余的参数用到的时候再去做进一步的配置,均系统默认即可。
由于对长文件名做了支持,缓存区是在栈区的,所以把堆栈加大一些,自己喜欢就好,只要不溢出就行,根据个人习惯随便填了两个参数,然后生成代码工程。
3、Fatfs驱动QSPI接口实现
对于fatfs,ST官方多封装了一层抽象接口给用户进行填写函数,这个文件是:user_diskio.c
,主要提供了如下给用户编写的接口:
Diskio_drvTypeDef USER_Driver = { USER_initialize, //初始化驱动盘 USER_status, //获取硬盘状态函数 USER_read, //读磁盘 #if _USE_WRITE USER_write, //写磁盘 #endif /* _USE_WRITE == 1 */ #if _USE_IOCTL == 1 USER_ioctl, //I/O操作 #endif /* _USE_IOCTL == 1 */ };
以上这些函数直接操作的就是以下fatfs原生的接口:
接下来我们需要依次实现它们:
初始化磁盘实现:
DSTATUS USER_initialize ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { /* USER CODE BEGIN INIT */ uint32_t id ; id = hal_spi_flash_get_id(); if(0xEF4017 == id) { printf("读取ID:0x%x\n",id); return RES_OK; } else return RES_ERROR ; /* USER CODE END INIT */ }
磁盘状态函数实现(可以留空):
/** * @brief Gets Disk Status * @param pdrv: Physical drive number (0..) * @retval DSTATUS: Operation status */ DSTATUS USER_status ( BYTE pdrv /* Physical drive number to identify the drive */ ) { /* USER CODE BEGIN STATUS */ return RES_OK; /* USER CODE END STATUS */ }
读磁盘函数实现:
/** * @brief Reads Sector(s) * @param pdrv: Physical drive number (0..) * @param *buff: Data buffer to store read data * @param sector: Sector address (LBA) * @param count: Number of sectors to read (1..128) * @retval DRESULT: Operation result */ DRESULT USER_read ( BYTE pdrv, /* Physical drive nmuber to identify the drive */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address in LBA */ UINT count /* Number of sectors to read */ ) { /* USER CODE BEGIN READ */ // 以4K字节为单位 hal_spi_flash_read(buff, count << 12, sector << 12); return RES_OK; /* USER CODE END READ */ }
写磁盘函数实现:
SPI_FLASH的特性,需要先擦除后写入。
/** * @brief Writes Sector(s) * @param pdrv: Physical drive number (0..) * @param *buff: Data to be written * @param sector: Sector address (LBA) * @param count: Number of sectors to write (1..128) * @retval DRESULT: Operation result */ #if _USE_WRITE == 1 DRESULT USER_write ( BYTE pdrv, /* Physical drive nmuber to identify the drive */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Sector address in LBA */ UINT count /* Number of sectors to write */ ) { /* USER CODE BEGIN WRITE */ /* USER CODE HERE */ uint32_t write_addr; write_addr = sector << 12; // 以4K字节为单位 hal_spi_flash_erase_write((uint8_t *)buff, count << 12, write_addr); return RES_OK; /* USER CODE END WRITE */ } #endif /* _USE_WRITE == 1 */
磁盘命令操作实现:
/** * @brief I/O control operation * @param pdrv: Physical drive number (0..) * @param cmd: Control code * @param *buff: Buffer to send/receive control data * @retval DRESULT: Operation result */ #if _USE_IOCTL == 1 DRESULT USER_ioctl ( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE cmd, /* Control code */ void *buff /* Buffer to send/receive control data */ ) { /* USER CODE BEGIN IOCTL */ switch (cmd) { case GET_SECTOR_COUNT: *(DWORD * )buff = 2048; // 总的扇区数 break; case GET_SECTOR_SIZE : *(WORD * )buff = 4096; // 定义一个扇区大小为4K break; case GET_BLOCK_SIZE : *(DWORD * )buff = 65536; // 定义一个块大小为64K break; } return RES_OK ; /* USER CODE END IOCTL */ } #endif /* _USE_IOCTL == 1 */
4、编写测试QSPI FLASH fatfs的程序
测试案例如下:
int main(void) { /* USER CODE BEGIN 1 */ uint8_t res ; uint32_t Total = 0; //读取FLASH总容量 uint32_t Free = 0; //读取FLASH剩余容量 /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); MX_QUADSPI_Init(); MX_FATFS_Init(); /* USER CODE BEGIN 2 */ Mount_Fatfs(); f_GetTotal_Free((uint8_t*)"0:", &Total, &Free); //获取SD卡总容量和剩余容量 printf("当前Fatfs总容量:%dKB==>%dMB 剩余容量:%dKB==>%dMB\n", Total, Total / 1024, Free, Free / 1024); /*----------------------- 文件系统测试:写测试 -----------------------------*/ printf("\r\n****** 即将进行文件写入测试... ******\r\n"); res = f_open(&USERFile, "0:BearPi.txt", FA_OPEN_ALWAYS | FA_WRITE); if(res == FR_OK) { printf("打开/创建BearPi.txt文件成功,向文件写入数据。\r\n"); res = f_write(&USERFile, write_buf, strlen((const char *)write_buf), &count); if(res != FR_OK) { printf("f_write 发生错误,err = %d\r\n", res); printf("关闭打开的BearPi.txt文件\r\n"); count = 0; f_close(&USERFile); } else { printf("文件写入成功,写入字节数据:%d\n", count); printf("向文件写入的数据为:\r\n%s\r\n", write_buf); printf("关闭打开的BearPi.txt文件\r\n"); count = 0; f_close(&USERFile); } } else printf("打开/创建BearPi.txt文件失败,err = %d\r\n", res); /*------------------- 文件系统测试:读测试 ------------------------------------*/ printf("****** 即将进行文件读取测试... ******\r\n"); res = f_open(&USERFile, "0:BearPi.txt", FA_OPEN_EXISTING | FA_READ); if(res == FR_OK) { printf("打开BearPi.txt文件成功\r\n"); res = f_read(&USERFile, read_buf, sizeof(read_buf), &count); if(res != FR_OK) { printf("f_read 发生错误,err = %d\r\n", res); printf("关闭打开的BearPi.txt文件\r\n"); f_close(&USERFile); } else { printf("文件读取成功,读取字节数据:%d\n", count); printf("向文件读取的数据为:\r\n%s\r\n", read_buf); printf("关闭打开的BearPi.txt文件\r\n"); f_close(&USERFile); } } else printf("打开BearPi.txt文件失败,err = %d\r\n", res); /*------------------- 不再使用文件系统,取消挂载文件系统 ------------------------------------*/ printf("不再使用文件系统,取消挂载文件系统\r\n"); res = f_mount(NULL, "0:", 1); if(res == FR_OK) printf("取消挂载文件系统成功\r\n"); else printf("取消挂载文件系统失败,err = %d\r\n", res); printf("文件系统测试结束\r\n"); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
运行结果:
获取完整demo:
码云仓库:https://gitee.com/morixinguan/bear-pi/tree/master/19.QSPI_Fatfs
获取方法:
git clone https://gitee.com/morixinguan/bear-pi.git
即可获取本次实验工程全部代码。
往期精彩
关于MCU产品开发参数存储的几种方案(开源项目持续收集整理中)