本文主要是介绍STM32CbueMX之Fatfs移植到SPI_Flash,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
我使用的是SPI_Flash芯片是W25Q256,兼容MX25L256。驱动程序在STM32CbueMX之W25Q256_sudaroot的博客-CSDN博客_w25q256驱动
STM32CubeMX配置图:
解释一下选项:
1、支持中文简体编码格式就没什么好说的。
2、缓存工作区为什么放在栈?其实fatfs提供了三个选项:BSS,STACK , HEAP,根据个人情况选一个。
在BSS上启用带有静态工作缓冲区的LFN,不能动态分配。
如果选择了HEAP(堆)且自己有属于自己的malloc就去重写ff_memalloc ff_memfree函数。如果是库的malloc就不需要。
一般都选择使用STACK(栈),能动态分配。
当使用堆栈作为工作缓冲区时,请注意堆栈溢出。
3、为什么最大扇区大小是4096Byte?一般别人都是512Byte? 其实这个是根据你自己使用的存储芯片和驱动相关的。因为我使用的W25Q256这款芯片是最小擦除单位是4096。不使用512byte是因为效率大大降低但是优点是空间利用率会大大提高。比如你文件系统最大分区是512,但是芯片最小擦除单位是4096,那么你在驱动就要实现先用缓存区把整个扇区4096byte全部读出来,然后判读其中写入512byte中有没有擦除过(即全0xFF),没有的话先擦除,在把数据写入缓存区最后写入芯片。所以步骤繁琐效率低,但是优点就是存储空间的利用率会大大提高,避免太多浪费。 我很懒,所以选择使用4096。
4、VOLUMES(逻辑驱动器):如果你是有多块储存芯片,每块存储芯片都是一个逻辑驱动器可以根据自己选择,我这个只有一块外挂的SPI flash存储芯片,故文章多块逻辑驱动器不做讨论(其实都过程差不多)。
我缓存区使用的STACK,所以我给了0x400(1024byte)。随便给的,不溢出就行了。
其实这个可以算一下,一般我们stack给0x200(512byte),带了文件系统的话,因为是启用了_USE_LFN对长文件名的支持,所以需要会大一下,我们最大支持_MAX_LFN个字节的文件名,我选的是支持_MAX_LFN = 255;
工作缓冲区在非exFAT时占用(_MAX_LFN + 1)* 2个字节;
工作缓冲区在启用exFAT时占用(_MAX_LFN + 1)* 2个字节和额外的608个字节。
计算过程如下:
一般我们格式化都是FAT12/16/32 所需stack = 0x200 + (_MAX_LFN + 1) * 2 = 0x200 + (255 + 1) * 2 = 1024byte = 0x400
启用exFAT时最大所需stack = 0x200 + (_MAX_LFN + 1) * 2 + 608 = 0x200 + (255 + 1) * 2 + 608 = 1632byte
生成工程。打开user_diskio.c
DSTATUS USER_initialize (BYTE pdrv)是初始化驱动盘。
因为我只有0号盘,spi_flash初始化就是初始化spi协议,所以简单读取一下ID号,正确就行了。
/*** @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 */uint8_t id[2] = {0};if(pdrv != 0) return STA_NOINIT;BSP_W25Q256_Init();BSP_W25Qx_Read_ID(id);if(id[0] == 0xEF && id[1] == 0x18) Stat = !STA_NOINIT;else Stat = STA_NOINIT;return Stat;/* USER CODE END INIT */
}
USER_status(BYTE pdrv)获取硬盘状态函数(简单些)
/*** @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 */if(pdrv != 0) return STA_NOINIT;return Stat;/* 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 */if(pdrv != 0 || count == 0) return RES_PARERR;if(BSP_W25Qx_Read(buff, sector * 4096, count * 4096) == W25Qx_OK)return RES_OK;else return RES_ERROR;/* 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 */uint8_t i = 0;if(pdrv != 0 || count == 0) return RES_PARERR;for(i = 0; i < count; i++){if(BSP_W25Qx_Erase_Sector((sector + i) * 4096) != W25Qx_OK)return RES_ERROR;if(BSP_W25Qx_Write((uint8_t*)buff + (i * 4096), (sector + i) * 4096, 4096) != W25Qx_OK)return RES_ERROR;}return RES_OK;/* USER CODE END WRITE */
}
这个USER_ioctl函数很重要。
/*** @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 res = RES_ERROR;if(pdrv != 0) return RES_PARERR;switch(cmd){case CTRL_SYNC:res = RES_OK;break;case GET_SECTOR_COUNT:*(DWORD*)buff = 8192;res = RES_OK;break;case GET_SECTOR_SIZE:*(WORD*)buff = 4096;res = RES_OK;break;case GET_BLOCK_SIZE:*(DWORD*)buff = 1;res = RES_OK;break;default:res = RES_PARERR;break;}return res;/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */
main.c
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
#include <W25Qx.h>
/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD *//* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
FATFS spi_fs;
FIL fil;
unsigned int count = 0;
unsigned char work[4096] = {0};
unsigned char read_buf[50] = {0};
unsigned char write_buf[50] = "hello sudaroot\r\n";
/* USER CODE END PM *//* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi5;UART_HandleTypeDef huart1;/* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI5_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int fputc(int ch, FILE* FILE)
{HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, HAL_MAX_DELAY);return ch;
}
/* USER CODE END 0 *//*** @brief The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 */uint32_t res = 0;/* 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_SPI5_Init();MX_USART1_UART_Init();MX_FATFS_Init();/* USER CODE BEGIN 2 */HAL_Delay(1000);printf("sduaroot fatfs test\r\n");printf("****** 擦除SPI_FLASH 扇区0 ******\r\n");BSP_W25Qx_Erase_Sector(0);HAL_Delay(1000);/*----------------------- 挂载测试 ---------------------------*/printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");res = f_mount(&spi_fs, "0:", 1);if(res != FR_OK){if(res == FR_NO_FILESYSTEM){printf("f_mount 没有文件系统,开始格式化spi-flash\r\n");res = f_mkfs("0:", FM_ANY, 0, &work, 4096);if(res != FR_OK){printf("f_mkfs 格式化失败,err = %d\r\n", res);while(1);}else{printf("格式化成功,开始重新挂载spi-flash\r\n");res = f_mount(&spi_fs, "0:", 1);if(res != FR_OK){printf("f_mount 发生错误,err = %d\r\n", res);}else printf("spi-flash文件系统挂载成功\r\n");}}else{printf("f_mount 发生其他错误,err = %d\r\n", res);while(1);}}else printf("spi-flash文件系统挂载成功\r\n");/*----------------------- 文件系统测试:写测试 -----------------------------*/printf("\r\n****** 即将进行文件写入测试... ******\r\n");res = f_open(&fil, "0:sudaroot.txt", FA_OPEN_ALWAYS | FA_WRITE);if(res == FR_OK){printf("打开/创建sudaroot.txt文件成功,向文件写入数据。\r\n");res = f_write(&fil, write_buf, strlen((const char *)write_buf), &count);if(res != FR_OK){printf("f_write 发生错误,err = %d\r\n", res);printf("关闭打开的sudaroot.txt文件\r\n");count = 0;f_close(&fil);}else{printf("文件写入成功,写入字节数据:%d\n", count);printf("向文件写入的数据为:\r\n%s\r\n", write_buf);printf("关闭打开的sudaroot.txt文件\r\n");count = 0;f_close(&fil);}}else printf("打开/创建sudaroot.txt文件失败,err = %d\r\n", res);/*------------------- 文件系统测试:读测试 ------------------------------------*/printf("****** 即将进行文件读取测试... ******\r\n");res = f_open(&fil, "0:sudaroot.txt", FA_OPEN_EXISTING | FA_READ);if(res == FR_OK){printf("打开sudaroot.txt文件成功\r\n");res = f_read(&fil, read_buf, sizeof(read_buf), &count);if(res != FR_OK){printf("f_read 发生错误,err = %d\r\n", res);printf("关闭打开的sudaroot.txt文件\r\n");f_close(&fil);}else{printf("文件读取成功,读取字节数据:%d\n", count);printf("向文件读取的数据为:\r\n%s\r\n", read_buf);printf("关闭打开的sudaroot.txt文件\r\n");f_close(&fil);}}else printf("打开sudaroot.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 */HAL_Delay(500);HAL_GPIO_TogglePin(RUN_LED_GPIO_Port, RUN_LED_Pin);}/* USER CODE END 3 */
}
效果:
全篇完。
本人是一个嵌入式未入门小白,博客仅仅代表我个人主观见解,记录成长笔记。
笔记是以最简单的方式,只展示最核心的原理。
若有与 大神大大 见解有歧义,我绝对坚信 大神大大 见解是对的,我的是错的。
若无积分等无法下载源码,可加入QQ群657407920下载交流经验。感谢~!
这篇关于STM32CbueMX之Fatfs移植到SPI_Flash的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!