CubeMX配置STM32实现FatFS文件系统(五)

引言

在前四篇文章中自己介绍了如何配置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单片机(四)

CubeMX配置FatFS功能

配置过程主要看了这位大佬的文章 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>&copy; 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远程连接,向文件系统中写入文件。

  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值