当前位置: 首页 > news >正文

STM32 FATFS - 在flash上运行fatfs

参考文章cSTM32CubeMX学习笔记(25)——FatFs文件系统使用(操作SPI Flash)_stm32 fatfs-CSDN博客
例程地址: STM32FatFS: 基于stm32的fatfs例程,配合博客文章
基于野火STM32MINI开发板
在W25Q64上进行FatFS的工作。这里先讲一下W25Q64的基本概念。
W25Q64是一款8MB容量的Flash芯片,由128个64KB的Block组成,每个Block包含16个4KB的Sector,支持以Sector(4KB)为最小擦除单位进行管理。
Page=256Byte
Sector=16Page=16x256=4096Byte=4KByte
Block=16Sector=16x4K=64KByte
W25Q64=128Block=128x64K=8192K=8MByte

STM32配置

配置系统模式
串口配置,用于打印log
系统时钟配置,使用内部时钟,配置为64M,这部分不是必须的,只是想让处理更快,不配置完全可以
SPI配置,根据原理图使用的是SPI1端口,这里配置为全双工主模式,并且关闭硬件NSS,波特率分频为4,使用16MHz通讯,W25Q64 在标准 SPI 模式(单线模式)下的最大 SPI 时钟频率为 80 MHz,所以只要硬件设计的好,速率完全可以满足。根据 FLASH 芯片的说明,它支持 SPI 模式0 及 模式 3,支持双线全双工,使用 MSB 先行模式,数据帧长度为 8 位。
这里没有启用硬件NSS是因为尝试开启硬件NSS未成功,应该是连续读写时序不满足W25Q64。
配置FATFS
这里选择User-defined,因为我们是使用自己的SPI驱动。
CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows) 支持简体中文编码
USE_LFN(Use Long Filename): 支持长文件名,并指定使用栈空间为缓冲区,这里不建议使用,因为这个支持很耗费flash,如果你的单片机小于256k可能都配不上这个功能,只能限制一下自己的程序,控制文件名长度了。
关于长文件名的支持使用堆还是栈有如下说明:
长文件名的选择,选择了对应的方式后需要修改工程的堆栈大小。
  • 堆:适合需要动态、大容量且跨函数共享的数据,但需手动管理,易产生碎片。
  • 栈:适合临时变量和局部数据,自动管理高效,但容量有限且生命周期受限。
  • 优先选择栈:如果硬件资源有限(如STM32F1/F0等小容量芯片),且文件名长度较短、操作简单,通过增大栈空间并合理设计代码结构。
  • 选择堆:如果硬件资源充足(如ESP32、STM32F4/F7等),且需要处理复杂或动态的文件名,或处于多任务环境。
VOLUMES(Logical drivers): 1 指定物理设备数量,我们只有一个 SPI Flash 芯片
MAX_SS(Maximum Sector Size): 4096 指定扇区大小的最大值。SD 卡扇区大小一般都为 512 字节,SPI Flash 芯片扇区大小一般设置为 4096 字节,所以需要把 _MAX_SS 改为 4096
MIN_SS(Minimum Sector Size): 512 指定扇区大小的最小值
配置NSS的IO
因为驱动里面使用的W25Q64_CHIP_SELECT名字,这里需要添加label
配置FREERTOS
这里只能选择V1,因为FATFS调用了V1中的函数,如果选择V2编译不通过,这个只能等ST支持了。因为我在任务中打印了很多,所以需要增加任务的堆大小。
任务堆改为了2048
工程管理
设置为独立文件,这样后面引用头文件会方便些
如果你前面使用了长文件名支持需要在这里修改工程堆栈大小

代码

创建w25q64的驱动文件,我是图省事直接添加到了Core文件夹中,分别创建w25q64.h和w25q64.c
w25q64.h文件
#ifndef W25Q64_H
#define W25Q64_H

#include "spi.h"

// W25Q64的页大小是256字节
#define SPI_FLASH_PageSize              256

#define ManufactDeviceID_CMD    0x90
#define READ_STATU_REGISTER_1   0x05
#define READ_STATU_REGISTER_2   0x35
#define READ_DATA_CMD           0x03
#define WRITE_ENABLE_CMD        0x06
#define WRITE_DISABLE_CMD       0x04
#define SECTOR_ERASE_CMD        0x20
#define CHIP_ERASE_CMD          0xc7
#define PAGE_PROGRAM_CMD        0x02

 /**
  * @brief  使用SPI发送一个字节的数据
  * @param  byte:要发送的数据
  * @retval 返回接收到的数据
  */
uint8_t SPI_FLASH_SendByte(uint8_t byte);

/**
 * @brief   读取Flash内部的ID
 * @param   none
 * @retval  成功返回device_id
 */
uint16_t W25QXX_ReadID(void);

/**
 * @brief    W25QXX写使能,将S1寄存器的WEL置位
 * @param    none
 * @retval
 */
void W25QXX_Write_Enable(void);

/**
 * @brief    W25QXX写禁止,将WEL清零
 * @param    none
 * @retval    none
 */
void W25QXX_Write_Disable(void);

/**
 * @brief    W25QXX擦除一个扇区
 * @param   sector_addr    —— 扇区地址 根据实际容量设置
 * @retval  none
 * @note    阻塞操作
 */
void W25QXX_Erase_Sector(uint32_t sector_addr);

/**
 * @brief    页写入操作
 * @param    dat —— 要写入的数据缓冲区首地址
 * @param    WriteAddr —— 要写入的地址
 * @param   byte_to_write —— 要写入的字节数(0-256)
 * @retval    none
 */
void W25QXX_PageProgram(uint8_t* dat, uint32_t WriteAddr, uint16_t nbytes);

/**
 * @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
 * @param   pBuffer,要写入数据的指针
 * @param  WriteAddr,写入地址
 * @param  NumByteToWrite,写入数据长度
 * @retval 无
 */
void W25QXX_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);

 /**
  * @brief  读取FLASH数据
  * @param  pBuffer,存储读出数据的指针
  * @param   ReadAddr,读取地址
  * @param   NumByteToRead,读取数据长度
  * @retval 无
  */
void W25QXX_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);

#endif // W25Q64_H

w25q64.c文件

#include "w25q64.h"
#include "main.h"
#include <stdio.h>

#define SPI_FLASH_CS_LOW()      HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
#define SPI_FLASH_CS_HIGH()     HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);

/**
 * @brief    SPI发送指定长度的数据
 * @param    buf  —— 发送数据缓冲区首地址
 * @param    size —— 要发送数据的字节数
 * @retval   成功返回HAL_OK
 */
static HAL_StatusTypeDef SPI_Transmit(uint8_t* send_buf, uint16_t size)
{
    return HAL_SPI_Transmit(&hspi1, send_buf, size, 100);
}
/**
 * @brief   SPI接收指定长度的数据
 * @param   buf  —— 接收数据缓冲区首地址
 * @param   size —— 要接收数据的字节数
 * @retval  成功返回HAL_OK
 */
static HAL_StatusTypeDef SPI_Receive(uint8_t* recv_buf, uint16_t size)
{
   return HAL_SPI_Receive(&hspi1, recv_buf, size, 100);
}
/**
 * @brief   SPI在发送数据的同时接收指定长度的数据
 * @param   send_buf  —— 接收数据缓冲区首地址
 * @param   recv_buf  —— 接收数据缓冲区首地址
 * @param   size —— 要发送/接收数据的字节数
 * @retval  成功返回HAL_OK
 */
static __attribute__((unused)) HAL_StatusTypeDef SPI_TransmitReceive(uint8_t* send_buf, uint8_t* recv_buf, uint16_t size)
{
   return HAL_SPI_TransmitReceive(&hspi1, send_buf, recv_buf, size, 100);
}

/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT         ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
static __IO uint32_t  SPITimeout = SPIT_LONG_TIMEOUT;   
/**
  * @brief  等待超时回调函数
  * @param  None.
  * @retval None.
  */
static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
  /* 等待超时后的处理,输出错误信息 */
  printf("SPI 等待超时!errorCode = %d",errorCode);
  return 0;
}
 /**
  * @brief  使用SPI发送一个字节的数据
  * @param  byte:要发送的数据
  * @retval 返回接收到的数据
  */
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
  SPITimeout = SPIT_FLAG_TIMEOUT;

  /* 等待发送缓冲区为空,TXE事件 */
  while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_TXE ) == RESET)
   {
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
   }

  /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
  WRITE_REG(hspi1.Instance->DR, byte);

  SPITimeout = SPIT_FLAG_TIMEOUT;

  /* 等待接收缓冲区非空,RXNE事件 */
  while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_RXNE ) == RESET)
   {
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
   }

  /* 读取数据寄存器,获取接收缓冲区数据 */
  return READ_REG(hspi1.Instance->DR);
}

/**
 * @brief   读取Flash内部的ID
 * @param   none
 * @retval  成功返回device_id
 */
uint16_t W25QXX_ReadID(void)
{
    uint8_t recv_buf[2] = {0};    //recv_buf[0]存放Manufacture ID, recv_buf[1]存放Device ID
    uint16_t device_id = 0;
    uint8_t send_data[4] = {ManufactDeviceID_CMD,0x00,0x00,0x00};   //待发送数据,命令+地址
    
    /* 使能片选 */
    SPI_FLASH_CS_LOW();
    
    /* 发送并读取数据 */
    if (HAL_OK == SPI_Transmit(send_data, 4)) 
    {
        if (HAL_OK == SPI_Receive(recv_buf, 2)) 
        {
            device_id = (recv_buf[0] << 8) | recv_buf[1];
        }
    }
    
    /* 取消片选 */
    SPI_FLASH_CS_HIGH();
    
    return device_id;
}
/**
 * @brief     读取W25QXX的状态寄存器,W25Q64一共有2个状态寄存器
 * @param     reg  —— 状态寄存器编号(1~2)
 * @retval    状态寄存器的值
 */
static uint8_t W25QXX_ReadSR(uint8_t reg)
{
    uint8_t result = 0; 
    uint8_t send_buf[4] = {0x00,0x00,0x00,0x00};
    switch(reg)
    {
        case 1:
            send_buf[0] = READ_STATU_REGISTER_1;
        case 2:
            send_buf[0] = READ_STATU_REGISTER_2;
        case 0:
        default:
            send_buf[0] = READ_STATU_REGISTER_1;
    }
    
    /* 使能片选 */
    SPI_FLASH_CS_LOW();
    
    if (HAL_OK == SPI_Transmit(send_buf, 4)) 
    {
        if (HAL_OK == SPI_Receive(&result, 1)) 
        {
            /* 取消片选 */
            SPI_FLASH_CS_HIGH();
            
            return result;
        }
    }
    
    /* 取消片选 */
    SPI_FLASH_CS_HIGH();

    return 0;
}
/**
 * @brief   阻塞等待Flash处于空闲状态
 * @param   none
 * @retval  none
 */
static void W25QXX_Wait_Busy(void)
{
    while((W25QXX_ReadSR(1) & 0x01) == 0x01); // 等待BUSY位清空
}
/**
 * @brief    W25QXX写使能,将S1寄存器的WEL置位
 * @param    none
 * @retval
 */
void W25QXX_Write_Enable(void)
{
    uint8_t cmd= WRITE_ENABLE_CMD;
    
    SPI_FLASH_CS_LOW();
    
    SPI_Transmit(&cmd, 1);
    
    SPI_FLASH_CS_HIGH();
    
    W25QXX_Wait_Busy();

}
/**
 * @brief    W25QXX写禁止,将WEL清零
 * @param    none
 * @retval    none
 */
void W25QXX_Write_Disable(void)
{
    uint8_t cmd = WRITE_DISABLE_CMD;

    SPI_FLASH_CS_LOW();
    
    SPI_Transmit(&cmd, 1);
    
    SPI_FLASH_CS_HIGH();
    
    W25QXX_Wait_Busy();
}
/**
 * @brief    W25QXX擦除一个扇区
 * @param   sector_addr    —— 扇区地址 根据实际容量设置
 * @retval  none
 * @note    阻塞操作
 */
void W25QXX_Erase_Sector(uint32_t sector_addr)
{   
    W25QXX_Write_Enable();  //擦除操作即写入0xFF,需要开启写使能
    W25QXX_Wait_Busy();     //等待写使能完成
   
    /* 使能片选 */
    SPI_FLASH_CS_LOW();
    
    /* 发送扇区擦除指令*/
    SPI_FLASH_SendByte(SECTOR_ERASE_CMD);
    /*发送擦除扇区地址的高位*/
    SPI_FLASH_SendByte((sector_addr & 0xFF0000) >> 16);
    /* 发送擦除扇区地址的中位 */
    SPI_FLASH_SendByte((sector_addr & 0xFF00) >> 8);
    /* 发送擦除扇区地址的低位 */
    SPI_FLASH_SendByte(sector_addr & 0xFF);
    
    /* 取消片选 */
    SPI_FLASH_CS_HIGH();
    
    W25QXX_Wait_Busy();       //等待扇区擦除完成
}
/**
 * @brief    页写入操作
 * @param    dat —— 要写入的数据缓冲区首地址
 * @param    WriteAddr —— 要写入的地址
 * @param   byte_to_write —— 要写入的字节数(0-256)
 * @retval    none
 */
void W25QXX_PageProgram(uint8_t* dat, uint32_t WriteAddr, uint16_t nbytes)
{
    uint8_t cmd = PAGE_PROGRAM_CMD;
    
//    WriteAddr <<= 8;
    
    W25QXX_Write_Enable();
    
    /* 使能片选 */
    SPI_FLASH_CS_LOW();
    
    SPI_Transmit(&cmd, 1);

//    SPI_Transmit((uint8_t*)&WriteAddr, 3);
    uint8_t addr;
    HAL_StatusTypeDef status;
    /* 发送 读 地址高位 */
    addr = (WriteAddr & 0xFF0000) >> 16;
    status = SPI_Transmit(&addr, 1);
    /* 发送 读 地址中位 */
    addr = (WriteAddr & 0xFF00) >> 8;
    status = SPI_Transmit(&addr, 1);
    /* 发送 读 地址低位 */
    addr = WriteAddr & 0xFF;
    status = SPI_Transmit(&addr, 1);
    (void) status;
    
    SPI_Transmit(dat, nbytes);
    
    /* 取消片选 */
    SPI_FLASH_CS_HIGH();
    
    W25QXX_Wait_Busy();
}
/**
 * @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
 * @param   pBuffer,要写入数据的指针
 * @param  WriteAddr,写入地址
 * @param  NumByteToWrite,写入数据长度
 * @retval 无
 */
void W25QXX_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    uint8_t NumOfPage = 0;
    uint8_t NumOfSingle = 0;
    uint8_t Addr = 0;
    uint8_t count = 0;
    uint8_t temp = 0;
    
    /*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
    Addr = WriteAddr % SPI_FLASH_PageSize;
    
    /*差count个数据值,刚好可以对齐到页地址*/
    count = SPI_FLASH_PageSize - Addr;  
    /*计算出要写多少整数页*/
    NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
    /*mod运算求余,计算出剩余不满一页的字节数*/
    NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

    /* Addr=0,则WriteAddr 刚好按页对齐 aligned  */
    if(Addr == 0) 
    {
        /* NumByteToWrite < SPI_FLASH_PageSize */
        if(NumOfPage == 0) 
        {
            W25QXX_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
        }
        /* NumByteToWrite > SPI_FLASH_PageSize */
        else
        {
            /*先把整数页都写了*/
            while(NumOfPage--)
            {
                W25QXX_PageProgram(pBuffer, WriteAddr, SPI_FLASH_PageSize);
                WriteAddr +=  SPI_FLASH_PageSize;
                pBuffer += SPI_FLASH_PageSize;
            }
            
            /*若有多余的不满一页的数据,把它写完*/
            W25QXX_PageProgram(pBuffer, WriteAddr, NumOfSingle);
        }
    }
    /* 若地址与 SPI_FLASH_PageSize 不对齐  */
    else 
    {
        /* NumByteToWrite < SPI_FLASH_PageSize */
        if(NumOfPage == 0) 
        {
            /*当前页剩余的count个位置比NumOfSingle小,写不完*/
            if(NumOfSingle > count) 
            {
                temp = NumOfSingle - count;
                
                /*先写满当前页*/
                W25QXX_PageProgram(pBuffer, WriteAddr, count);
                WriteAddr += count;
                pBuffer += count;
                
                /*再写剩余的数据*/
                W25QXX_PageProgram(pBuffer, WriteAddr, temp);
            }
            /*当前页剩余的count个位置能写完NumOfSingle个数据*/
            else
            {               
                W25QXX_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
            }
        }
        /* NumByteToWrite > SPI_FLASH_PageSize */
        else
        {
            /*地址不对齐多出的count分开处理,不加入这个运算*/
            NumByteToWrite -= count;
            NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
            NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

            W25QXX_PageProgram(pBuffer, WriteAddr, count);
            WriteAddr +=  count;
            pBuffer += count;
            
            /*把整数页都写了*/
            while(NumOfPage--)
            {
                W25QXX_PageProgram(pBuffer, WriteAddr, SPI_FLASH_PageSize);
                WriteAddr +=  SPI_FLASH_PageSize;
                pBuffer += SPI_FLASH_PageSize;
            }
            /*若有多余的不满一页的数据,把它写完*/
            if(NumOfSingle != 0)
            {
                W25QXX_PageProgram(pBuffer, WriteAddr, NumOfSingle);
            }
        }
    }
}
 /**
  * @brief  读取FLASH数据
  * @param  pBuffer,存储读出数据的指针
  * @param   ReadAddr,读取地址
  * @param   NumByteToRead,读取数据长度
  * @retval 无
  */
void W25QXX_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{  
    W25QXX_Wait_Busy();
    
    /* 选择FLASH: CS低电平 */
    SPI_FLASH_CS_LOW();

    /* 发送 读 指令 */
    uint8_t cmd = READ_DATA_CMD;
    SPI_Transmit(&cmd, 1);

// 不知道为什么连起来发不行
//    ReadAddr = ReadAddr << 8;
//    SPI_Transmit((uint8_t*)&ReadAddr, 3);
    
    uint8_t addr;
    HAL_StatusTypeDef status;
    /* 发送 读 地址高位 */
    addr = (ReadAddr & 0xFF0000) >> 16;
    status = SPI_Transmit(&addr, 1);
    /* 发送 读 地址中位 */
    addr = (ReadAddr& 0xFF00) >> 8;
    status = SPI_Transmit(&addr, 1);
    /* 发送 读 地址低位 */
    addr = ReadAddr & 0xFF;
    status = SPI_Transmit(&addr, 1);
    
    if(HAL_OK == status)
    {
        SPI_Receive(pBuffer, NumByteToRead);
    }

    /* 停止信号 FLASH: CS 高电平 */
    SPI_FLASH_CS_HIGH();
}

以上是驱动部分。

在mian.c中添加支持printf的函数

/* USER CODE BEGIN 0 */

int __io_putchar(int ch)
{
    /* Implementation of __io_putchar */
	/* e.g. write a character to the UART1 and Loop until the end of transmission */
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFFFFFF);

    return ch;
}
int __io_getchar(void)
{
  /* Implementation of __io_getchar */
    char rxChar;

    // This loops in case of HAL timeout, but if an ok or error occurs, we continue
    while (HAL_UART_Receive(&huart1, (uint8_t *)&rxChar, 1, 0xFFFFFFFF) == HAL_TIMEOUT);

    return rxChar;
}
/* USER CODE END 0 */

修改FATFS\Target\user_diskio.c文件

添加头文件

修改USER_initialize函数

DSTATUS USER_initialize (
	BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
  /* USER CODE BEGIN INIT */
  /* 延时一小段时间 */
  osDelay(10);
  Stat = STA_NOINIT;
  if (W25QXX_ReadID() != 0) {
      Stat &= ~STA_NOINIT;
  }
  return Stat;
  /* USER CODE END INIT */
}

修改USER_status函数

DSTATUS USER_status (
	BYTE pdrv       /* Physical drive number to identify the drive */
)
{
  /* USER CODE BEGIN STATUS */
    Stat &= ~STA_NOINIT;
    return Stat;
  /* USER CODE END STATUS */
}

修改USER_read函数

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;
    if (!count) {
        return RES_PARERR;  // count不能等于0,否则返回参数错误
    }
    W25QXX_BufferRead(buff, sector<<12, count<<12);
    status = RES_OK;
    return status;
  /* USER CODE END READ */
}

这里有<<12操作,是因为USER_read传入的是扇区号,但是W25QXX_BufferRead函数是扇区地址,每个扇区大小为4096字节,所有使用<<12,其实就是乘以了4096
修改USER_write函数

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 */
    }
    write_addr = sector;
    W25QXX_Erase_Sector(write_addr<<12);
    W25QXX_BufferWrite((uint8_t *)buff, write_addr<<12, count<<12);
    status = RES_OK;
    return status;
  /* USER CODE END WRITE */
}

修改USER_ioctl函数

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_OK;
    switch (cmd) {
    case CTRL_SYNC:
        break;
    /* 扇区数量:128个 Block × 16个 Sector/Block */
    case GET_SECTOR_COUNT:
        *(DWORD *)buff = 2048;
        break;
    /* 扇区大小  */
    case GET_SECTOR_SIZE:
        *(WORD *)buff = 4096;
        break;
    /* 同时擦除扇区个数 */
    case GET_BLOCK_SIZE:
        *(DWORD *)buff = 1;
        break;
    case CTRL_TRIM:
        break;
    default:
        status = RES_PARERR;
        break;
    }
    return status;
  /* USER CODE END IOCTL */
}

下面编写freertos中的实现功能
修改freertos.c文件
添加头文件

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "fatfs.h"
#include "w25q64.h"
/* USER CODE END Includes */

在默认任务中添加测试内容

void StartDefaultTask(void const * argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
  UINT fnum;                          /* 文件成功读写数量 */
  int8_t ReadBuffer[1024] = {0};        /* 读缓冲区 */
  int8_t WriteBuffer[]= "Hello World!\n";
  W25QXX_Erase_Sector(0);
  printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");

  // 在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
  retUSER = f_mount(&USERFatFS, USERPath, 1);
  /*----------------------- 格式化测试 -----------------*/  
  /* 如果没有文件系统就格式化创建创建文件系统 */
  if(retUSER == FR_NO_FILESYSTEM)
  {
    printf("》FLASH还没有文件系统,即将进行格式化...\r\n");
    /* 格式化 */
    retUSER = f_mkfs(USERPath, 0, 0);	    
		
    if(retUSER == FR_OK)
    {
      printf("》FLASH已成功格式化文件系统。\r\n");
      /* 格式化后,先取消挂载 */
      retUSER = f_mount(NULL, USERPath, 1);

      /* 重新挂载	*/			
      retUSER = f_mount(&USERFatFS, USERPath, 1);
      printf("》文件系统重新挂载 %s\r\n", retUSER == FR_OK ? "成功" : "失败");
    }
    else
    {
      printf("《《格式化失败。》》\r\n");
      while(1) osDelay(1);
    }
  }
  else if(retUSER != FR_OK)
  {
    printf("!!外部Flash挂载文件系统失败。(%d)\r\n", retUSER);
    printf("!!可能原因:SPI Flash初始化不成功。\r\n");
    while(1) osDelay(1);
  }
  else
  {
    printf("》文件系统挂载成功,可以进行读写测试\r\n");
  }
  
  /*----------------------- 文件系统测试:写测试 -------------------*/
  printf("\r\n****** 即将进行文件写入测试... ******\r\n");	
  retUSER = f_open(&USERFile, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
  if(retUSER == FR_OK)
  {
    printf("》打开/创建FatFs读写文件test.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, "test.txt",FA_OPEN_EXISTING | FA_READ); 	 
	if(retUSER == FR_OK)
	{
		printf("》打开文件成功。\r\n");
		retUSER = f_read(&USERFile, ReadBuffer, sizeof(ReadBuffer), &fnum); 
    if(retUSER==FR_OK)
    {
      printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
      printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);	
    }
    else
    {
      printf("!!文件读取失败:(%d)\n",retUSER);
    }		
	}
	else
	{
		printf("!!打开文件失败。\r\n");
	}
	/* 不再读写,关闭文件 */
	f_close(&USERFile);	

  /*----------------------- 文件系统测试:读取目录测试 -------------------*/
  printf("\r\n****** 即将进行文件系统目录读取测试... ******\r\n");
  DIR dir;
  FILINFO fno;
  retUSER = f_opendir(&dir, USERPath);
  if (retUSER == FR_OK) {
    printf("》目录打开成功,目录内容如下:\r\n");
    for (;;) {
      retUSER = f_readdir(&dir, &fno);
      if (retUSER != FR_OK || fno.fname[0] == 0) break;
      if (fno.fattrib & AM_DIR) {
        printf("  [DIR]  %s\r\n", fno.fname);
      } else {
        printf("  [FILE] %s (Size: %ld bytes)\r\n", fno.fname, fno.fsize);
      }
    }
    f_closedir(&dir);
  } else {
    printf("!!目录打开失败。(%d)\r\n", retUSER);
  }
  
	/* 不再使用文件系统,取消挂载文件系统 */
	f_mount(NULL,"1:",1);

  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END StartDefaultTask */
}

运行结果如下:

相关文章:

  • 文章记单词 | 第15篇(六级)
  • Java笔记2——编程基础
  • 高效内存位操作:如何用C++实现数据块交换的性能飞跃?
  • STM32技能综合巩固
  • 使用 Vue 重构 RAGFlow 实现聊天功能
  • 第十二届蓝桥杯省赛软件类(cc++组)
  • 一场国际安全厂商的交流会议简记
  • 移动端浏览器调用摄像头失败,需要https,配置开发环境证书
  • 尚硅谷2019版Java异常处理篇笔记记录
  • Ranger一分钟
  • 步进电机控制频率解析:500 Hz 还是 500×256 Hz?
  • 基于大模型的房间隔缺损手术全流程预测与方案优化研究报告
  • 【56】数组指针:指针穿梭数组间
  • Python数据类型-list
  • 传统汽车 HMI 设计 VS 新能源汽车 HMI 设计,有何不同?
  • Spring Boot向Vue发送消息通过WebSocket实现通信
  • 解决ubuntu18.04无法进入系统桌面
  • 【文献阅读】Vision-Language Models for Vision Tasks: A Survey
  • Spark,HDFS概述
  • Android7 Input(三)EventHub
  • 越秀地产约41.49亿元出售北京海淀功德寺项目公司65%股权,此前已质押给华润置地
  • 每日475.52元!最高检公布最新侵犯公民人身自由的赔偿金标准
  • 山西资深公益人士孙超因突发急病离世,终年37岁
  • 遇见东方:18世纪俄罗斯宫殿中的“中国风”
  • 事关政府信息公开,最高法发布最新司法解释
  • 重庆一男大学生掉进化粪池死亡,重庆对外经贸学院:以学校通报为准