我使用的是SPI_Flash芯片是W25Q256,兼容MX25L256。驱动程序在https://blog.csdn.net/sudaroot/article/details/93158309
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
#include
#include
/* 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 */
}
效果:
全篇完。
本人博客仅仅代表我个人见解方便记录成长笔记。
若有与 看官老爷见解有冲突,我坚信看官老爷见解是对的,我的是错的。
感谢~!