查看原文
其他

让QSPI FLASH(W25Q64)支持Fatfs文件系统

杨源鑫 嵌入式应用研究院 2022-07-15

今天是过年放假的第一天(一共16天年假),但是说实话放不放假对我们做技术的人来说有放跟没放其实区别不大,因为自驱力是我们维持自身实力和饭碗的根本,16天的假期可以做很多事情学不少东西了,唯一的区别是终于可以好好睡个觉了,然后睡醒接着干就完了!想着小熊派板子上带了一个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

即可获取本次实验工程全部代码。

往期精彩

基于小熊派气体传感器MQ-2综合实践

RTOS支持STemWin(以RT-Thread为例)

关于MCU产品开发参数存储的几种方案(开源项目持续收集整理中)

整理了很久之前在码云/Github/CSDN上收藏的嵌入式产品级项目分享开源

觉得本次分享的文章对您有帮助,随手点[在看]并转发分享,也是对我的支持。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存