赞
踩
在前四篇文章中自己介绍了如何配置freeRTOS以及如何配置LWIP,并使用lwip实现一个httpd服务器,使浏览器可以访问,并利用CGI功能,实现通过网页来控制单片机的一个LED灯的电平翻转。
在这篇文章中,本人使用CubeMX软件配置STM32驱动FLASH实现文件系统,为以后使用FTP远程登录做准备。
自己写的另外三篇文章
从零开始Cubemx配置STM32搭载freeRTOS实现多路ADC(一)
从零开始Cubemx配置STM32搭载freeRTOS以及lwip实现tcp网络通信(二)
从零开始使用CubeMX配置STM32使用lwip实现httpd服务器以及使用vscode编辑阅读keil代码(三)
CubeMX配置STM32实现httpd服务器CGI功能并使用网页控制STM32单片机(四)
配置过程主要看了这位大佬的文章 STM32CUBEIDE之SPI读写FLASH进阶串行FLASH文件系统FatFs以及野火的课程https://www.bilibili.com/video/BV18X4y1M763?p=64&spm_id_from=pageDriver的P57讲的部分,在这里表示感谢,我这篇文章主要自己做了一遍,然后根据自己的理解总结一下。
移植基本不需要怎么改动,需要改动的是将编码改成简体中文,打开长文件名选项。由于我的程序带有操作系统,因此我就放在HEAP也就是堆中了。
放在HEAP中的话记得把HEAP的大小改大。
W25Q128是16M spi flash,一共有256个block ,每个Block 64KB。
一个Block可以分割为16个扇区(small sector),每个扇区4096字节
由于我用的是野火的板级支持包,因此SPI通信的FLASH我就不配置了,直接移植就可以了。其他的型号按照如何驱动存储介质的操作进行驱动就行了,这一部分一般是驱动工程师的工作。
上面的东西配置完成后,生成代码。
代码部分需要我们做的主要是驱动的移植。我们打开user_diskio.c文件。我们的驱动文件就移植到这个文件里面。
#ifdef USE_OBSOLETE_USER_CODE_SECTION_0 /* * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0) * To be suppressed in the future. * Kept to ensure backward compatibility with previous CubeMx versions when * migrating projects. * User code previously added there should be copied in the new user sections before * the section contents can be deleted. */ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ #endif /* USER CODE BEGIN DECL */ /* Includes ------------------------------------------------------------------*/ #include <string.h> #include "ff_gen_drv.h" #include "bsp_spi_flash.h" /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ #define SPI_FLASH 0 // 外部SPI Flash /* Private variables ---------------------------------------------------------*/ /* Disk status */ static volatile DSTATUS Stat = STA_NOINIT; /* USER CODE END DECL */ /* Private function prototypes -----------------------------------------------*/ DSTATUS USER_initialize (BYTE pdrv); DSTATUS USER_status (BYTE pdrv); DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count); #if _USE_WRITE == 1 DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count); #endif /* _USE_WRITE == 1 */ #if _USE_IOCTL == 1 DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff); #endif /* _USE_IOCTL == 1 */ 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, #endif /* _USE_IOCTL == 1 */ }; /* Private functions ---------------------------------------------------------*/ /** * @brief Initializes a Drive * @param pdrv: Physical drive number (0..) * @retval DSTATUS: Operation status */ DSTATUS USER_initialize ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { /* USER CODE BEGIN INIT */ uint16_t i; DSTATUS status = STA_NOINIT; switch (pdrv) { case SPI_FLASH: /* SPI Flash */ /* 初始化SPI Flash */ SPI_FLASH_Init(); /* 延时一小段时间 */ i=500; while(--i); /* 唤醒SPI Flash */ SPI_Flash_WAKEUP(); /* 获取SPI Flash芯片状态 */ status=USER_status(SPI_FLASH); break; default: status = STA_NOINIT; } return status; /* 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 */ DSTATUS status = STA_NOINIT; switch (pdrv) { case SPI_FLASH: /* SPI Flash状态检测:读取SPI Flash 设备ID */ if(sFLASH_ID == SPI_FLASH_ReadID()) { /* 设备ID读取结果正确 */ status &= ~STA_NOINIT; } else { /* 设备ID读取结果错误 */ status = STA_NOINIT;; } break; default: status = STA_NOINIT; } return status; /* 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 */ DRESULT status = RES_PARERR; switch (pdrv) { case SPI_FLASH: /* 扇区偏移6MB,外部Flash文件系统空间放在SPI Flash后面10MB空间 */ sector+=1536; SPI_FLASH_BufferRead(buff, sector <<12, count<<12); status = RES_OK; break; default: status = RES_PARERR; } return status; /* USER CODE END READ */ } /** * @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; DRESULT status = RES_PARERR; if (!count) { return RES_PARERR; /* Check parameter */ } switch (pdrv) { case SPI_FLASH: /* 扇区偏移6MB,外部Flash文件系统空间放在SPI Flash后面10MB空间 */ sector+=1536; write_addr = sector<<12; SPI_FLASH_SectorErase(write_addr); SPI_FLASH_BufferWrite((uint8_t *)buff,write_addr,count<<12); status = RES_OK; break; default: status = RES_PARERR; } return status; /* 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 */ DRESULT status = RES_PARERR; switch (pdrv) { case SPI_FLASH: switch (cmd) { /* 扇区数量:2560*4096/1024/1024=10(MB) */ case GET_SECTOR_COUNT: *(DWORD * )buff = 2560; break; /* 扇区大小 */ case GET_SECTOR_SIZE : *(WORD * )buff = 4096; break; /* 同时擦除扇区个数 */ case GET_BLOCK_SIZE : *(DWORD * )buff = 1; break; } status = RES_OK; break; default: status = RES_PARERR; } return status; /* USER CODE END IOCTL */ } #endif /* _USE_IOCTL == 1 */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
读这个代码可以发现,我们需要根据实际使用的硬件来填写这几个函数。
USER_initialize,
USER_status,
USER_read,
USER_write,
USER_ioctl,
比如写操作,我们需要写入SPI_FLASH的实际操作过程,这也就是驱动。SPI对FLASH的操作一般由驱动工程师或者硬件厂家完成。
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; DRESULT status = RES_PARERR; if (!count) { return RES_PARERR; /* Check parameter */ } switch (pdrv) { case SPI_FLASH: /* 扇区偏移6MB,外部Flash文件系统空间放在SPI Flash后面10MB空间 */ sector+=1536; write_addr = sector<<12; SPI_FLASH_SectorErase(write_addr); SPI_FLASH_BufferWrite((uint8_t *)buff,write_addr,count<<12); status = RES_OK; break; default: status = RES_PARERR; } return status; /* USER CODE END WRITE */ } #endif /* _USE_WRITE == 1 */
SPI_FLASH的驱动,我直接用的野火的FLASH板级支持包bsp_spi_flash.c,这里吗有FLASH的驱动实现,以及SPI_FLASH_BufferWrite()等函数的实现,到这里我们以及移植好了,可以进行代码编写了。
实际使用主要参考http://elm-chan.org/fsw/ff/00index_e.html。全部API都有相应的解释。
自己的代码写在fatfs.c文件里,在这里贴出一个我用的测试案例。
/** ****************************************************************************** * @file fatfs.c * @brief Code for fatfs applications ****************************************************************************** * @attention * * <h2><center>© Copyright (c) 2022 STMicroelectronics. * All rights reserved.</center></h2> * * This software component is licensed by ST under Ultimate Liberty license * SLA0044, the "License"; You may not use this file except in compliance with * the License. You may obtain a copy of the License at: * www.st.com/SLA0044 * ****************************************************************************** */ #include "fatfs.h" uint8_t retUSER; /* Return value for USER */ char USERPath[4]; /* USER logical drive path */ FATFS USERFatFS; /* File system object for USER logical drive */ FIL USERFile; /* File object for USER */ /* USER CODE BEGIN Variables */ BYTE work[_MAX_SS]; UINT fnum; BYTE WriteBuffer[] = "hello world 你好!!"; /* USER CODE END Variables */ void MX_FATFS_Init(void) { /*## FatFS: Link the USER driver ###########################*/ retUSER = FATFS_LinkDriver(&USER_Driver, USERPath); /* USER CODE BEGIN Init */ retUSER = f_mount(&USERFatFS,"",1); if(retUSER == FR_NO_FILESYSTEM) { printf("FLASH is not formatted\r\n"); retUSER=f_mkfs("", FM_ANY, 0, work, sizeof work); if(retUSER == FR_OK) { printf("The FLASH formats the file system successfully\r\n"); retUSER = f_mount(NULL,"",1); retUSER = f_mount(&USERFatFS,"",1); } else { printf("Formatting failure\r\n"); while(1); } } else if(retUSER!=FR_OK) { printf("The external Flash failed to mount the file system. Procedure (%d)\r\n",retUSER); printf("Possible cause: The SPI Flash initialization fails\r\n"); while(1); } else { printf("The file system is mounted successfully, and you can perform read/write tests\r\n"); } /*----------------------- 文件系统测试:写测试 -----------------------------*/ /* 打开文件,如果文件不存在则创建它 */ printf("\r\n****** 即将进行文件写入测试... ******\r\n"); retUSER = f_open(&USERFile, "0:FatUSERFatFS读写测试文件.txt",FA_CREATE_ALWAYS | FA_WRITE ); if (retUSER == FR_OK) { printf("》打开/创建FatUSERFatFS读写测试文件.txt文件成功,向文件写入数据。\r\n"); /* 将指定存储区内容写入到文件内 */ retUSER=f_write(&USERFile,WriteBuffer,sizeof(WriteBuffer),&fnum); if(retUSER==FR_OK) { printf("》文件写入成功,写入字节数据:%d\n",fnum); printf("》向文件写入的数据为:\r\n%s\r\n",WriteBuffer); } else { printf("!!文件写入失败:(%d)\n",retUSER); } /* 不再读写,关闭文件 */ f_close(&USERFile); } else { printf("!!打开/创建文件失败。\r\n"); } printf("****** 即将进行文件读取测试... ******\r\n"); retUSER = f_open(&USERFile, "FatUSERFatFS读写测试文件.txt", FA_OPEN_EXISTING | FA_READ); if(retUSER == FR_OK) { printf("》打开文件成功。\r\n"); retUSER = f_read(&USERFile, work, sizeof(work), &fnum); if(retUSER==FR_OK) { printf("》文件读取成功,读到字节数据:%d\r\n",fnum); printf("》读取得的文件数据为:\r\n%s \r\n", work); } else { printf("!!文件读取失败:(%d)\n",retUSER); } } else { printf("!!打开文件失败。\r\n"); } /* 不再读写,关闭文件 */ f_close(&USERFile); /* additional user code for init */ /* USER CODE END Init */ } /** * @brief Gets Time from RTC * @param None * @retval Time in DWORD */ DWORD get_fattime(void) { /* USER CODE BEGIN get_fattime */ return 0; /* USER CODE END get_fattime */ } /* USER CODE BEGIN Application */ /* USER CODE END Application */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
案例其实没有什么讲的,移植FatFs后的文件,操作就与win下的一样了,都是f_open打开,f_write写一类的操作,不同的地方就是使用前需要挂载一下。实验结果如下所示:
到此FatFs移植成功,并能正常使用。
代码野火课程P63讲的比较详细,在这里我简要叙述一下。
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,
#endif /* _USE_IOCTL == 1 */
};
这几个函数,也就是我们需要写的实现函数,在这个结构体中被赋值给了函数指针(也可以认为是回调函数),声明是这个样子的。
typedef struct
{
DSTATUS (*disk_initialize) (BYTE); /*!< Initialize Disk Drive */
DSTATUS (*disk_status) (BYTE); /*!< Get Disk Status */
DRESULT (*disk_read) (BYTE, BYTE*, DWORD, UINT); /*!< Read Sector(s) */
#if _USE_WRITE == 1
DRESULT (*disk_write) (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0 */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT (*disk_ioctl) (BYTE, BYTE, void*); /*!< I/O control operation when _USE_IOCTL = 1 */
#endif /* _USE_IOCTL == 1 */
}Diskio_drvTypeDef;
定义好后,通过这个函数,将驱动进行链接。
retUSER = FATFS_LinkDriver(&USER_Driver, USERPath);
结构体指针在这个函数中被赋值
uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, uint8_t lun) { uint8_t ret = 1; uint8_t DiskNum = 0; if(disk.nbr < _VOLUMES) { disk.is_initialized[disk.nbr] = 0; disk.drv[disk.nbr] = drv; //这个就是自己的驱动函数 disk.lun[disk.nbr] = lun; DiskNum = disk.nbr++; path[0] = DiskNum + '0'; path[1] = ':'; path[2] = '/'; path[3] = 0; ret = 0; } return ret; }
到这里已经链接上了。
DRESULT disk_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 */
)
{
DRESULT res;
res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);
return res;
}
这里看一个上层的读函数,f_read调用了disk_read函数,自己写的驱动函数,也就是控制SPI_FLASH的读操作已经赋值给了disk.drv。这样一切就耦合起来了。
这次遇到的问题是开始不知道驱动怎么写,因为自己主要做的是网络通信方面,虽然知道驱动,但是一直不知道如何将软件以及硬件耦合起来,经过这次的项目,终于知道软件层以及硬件层是如何耦合到一起的,也将之前学的IIC,SPI等应用了起来。
下一步我继续网络方面,使用FTP远程连接,向文件系统中写入文件。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。