最近有一个关于SPIFLASH嵌入FATFS的需求,自己也是第一次搞,在此做一个实验记录,方便后期查看。
注:本博客借鉴了Carry_王的博客,修正了一下小错误之后,能够直接实现FATFS的移植和使用,大部分代码是参照上面码哥的,大家也可以自行对比一下。
顺便一提,所有动作的本质还是MCU通过SPI总线与Flash通信,只是借助了FATFS协议栈对Flash进行管理,FATFS由纯软件构成。
整体结构
这里我使用的平台是硬石的YS-F4Pro运动控制板,MCU是STM32F407IGTb,片外Flash使用的是16M的W25Q128,具体思路如下:
- 利用HAL库实现标准的MCU利用SPI与Flash通信进行读写;
- 此时基本功能【此时ID读取即可】
- 在STM32CubeMX使能FATFS;
- 在FATFS的用户接口中写入访问片外Flash的读写以及控制等函数;
- 此时FATFS是否能正常使用【测试标准FATFS读写】。
操作步骤
-
打开STM32CubeMX,选择芯片:
-
配置系统Debug方式以及时钟来源
-
配置外部时钟
-
配置SPI(这里要根据硬件电路对应的SPI来进行匹配,片选PI0,输出PC2,输入PI3,时钟PI1)
-
配置片选信号PI0,该处不具体将SPI的通信,如有不解需参考SPI的通信说明
-
为了后面不麻烦,这里把文件系统也配置进去,因为使用的是W25Q128,每个扇区大小为4096,所以此处将最大最小扇区定义为4096
-
时钟树配置,主频为72M
-
完成代码配置(选择必要的文件这项点上之后不会添加额外库文件,能减少无谓的编译时间)
-
内嵌Flash芯片的驱动文件(这里直接贴我修改后的源码)
代码的具体意思我就不分析了,注释大部分都有
SPIFLASH.c
#ifndef __SPIFLASH_H__
#define __SPIFLASH_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "SPIFLASH.h"
#include "spi.h"
#define W25Q80 0XEF13
#define W25Q16 0XEF14
#define W25Q32 0XEF15
#define W25Q64 0XEF16
#define W25Q128 0XEF17
#define M25P64_FLASH_ID 0x202017//C22017
#define M25P40_FLASH_ID 0x202013
/* USER CODE BEGIN Includes */
#define sFLASH_CMD_WRITE 0x02 /*!< Write to Memory instruction */
#define sFLASH_CMD_WRSR 0x01 /*!< Write Status Register instruction */
#define sFLASH_CMD_WREN 0x06 /*!< Write enable instruction */
#define sFLASH_CMD_READ 0x03 /*!< Read from Memory instruction */
#define sFLASH_CMD_RDSR 0x05 /*!< Read Status Register instruction */
#define sFLASH_CMD_RDID 0x9F /*!< Read identification */
#define sFLASH_CMD_SE 0x20 /*!< Sector Erase instruction (4k)*/
#define sFLASH_CMD_BE 0xD8 /*!< Block Erase instruction (64k)*/
#define sFLASH_CMD_CE 0xC7 /*!< Chip Erase instruction (Chip Erase)*/
#define sFLASH_WIP_FLAG 0x01 /*!< Write In Progress (WIP) flag */
#define sFLASH_CMD_RDID 0x9F /*!< Read identification */
#define sFLASH_CMD_DeviceID 0xAB
#define sFLASH_CMD_ManufactDeviceID 0x90
#define sFLASH_CMD_JedecDeviceID 0x9F
#define sFLASH_DUMMY_BYTE 0xFF
uint8_t sFlashBuff[4096];
//片选CS拉低
void sFLASH_CS_LOW(void)
{
HAL_GPIO_WritePin(SPIFLASH_CS_GPIO_Port,SPIFLASH_CS_Pin,GPIO_PIN_RESET);
}
//片选CS拉高
void sFLASH_CS_HIGH(void)
{
HAL_GPIO_WritePin(SPIFLASH_CS_GPIO_Port,SPIFLASH_CS_Pin,GPIO_PIN_SET);
}
//FLASH 发送一个字节
char sFLASH_SendByte(uint8_t byte)
{
// unsigned char dr;
// /*!< Loop while DR register in not emplty */
// //while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
// while((SPI2->SR & SPI_SR_TXE) == 0);
// /*!< Send byte through the SPI1 peripheral */
// //SPI_I2S_SendData(SPI1, byte);
// SPI2->DR = byte;
// /*!< Wait to receive a byte */
// //while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
// while((SPI2->SR & SPI_SR_RXNE) == 0);
// /*!< Return the byte read from the SPI bus */
// //return SPI_I2S_ReceiveData(SPI1);
// dr = SPI2->DR;
// return dr;
uint8_t d_read,d_send = byte;
if(HAL_SPI_TransmitReceive(&hspi2,&d_send,&d_read,1,0xFFFF) != HAL_OK)
{
d_read = sFLASH_DUMMY_BYTE;
}
return d_read;
}
//FLASH写使能
void sFLASH_WriteEnable(void)
{
/*!< Select the FLASH: Chip Select low */
sFLASH_CS_LOW();
/*!< Send "Write Enable" instruction */
sFLASH_SendByte(sFLASH_CMD_WREN);
/*!< Deselect the FLASH: Chip Select high */
sFLASH_CS_HIGH();
}
//FLASH读取一个字节
uint8_t sFLASH_ReadByte(void)
{
return (sFLASH_SendByte(sFLASH_DUMMY_BYTE));
}
//FLASH等待写完成
void sFLASH_WaitForWriteEnd(void)
{
uint8_t flashstatus = 0;
HAL_Delay(1);
/*!< Select the FLASH: Chip Select low */
sFLASH_CS_LOW();
HAL_Delay(1);
/*!< Send "Read Status Register" instruction */
sFLASH_SendByte(sFLASH_CMD_RDSR);
/*!< Loop as long as the memory is busy with a write cycle */
do
{
/*!< Send a dummy byte to generate the clock needed by the FLASH
and put the value of the status register in FLASH_Status variable */
flashstatus = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
}
while ((flashstatus & sFLASH_WIP_FLAG) == SET); /* Write in progress */
/*!< Deselect the FLASH: Chip Select high */
sFLASH_CS_HIGH();
}
//FLASH擦除一个扇区
void sFLASH_EraseSector(uint32_t SectorAddr)
{
/*!< Send write enable instruction */
sFLASH_WriteEnable();
/*!< Sector Erase */
/*!< Select the FLASH: Chip Select low */
sFLASH_CS_LOW();
/*!< Send Sector Erase instruction */
sFLASH_SendByte(sFLASH_CMD_SE);
/*!< Send SectorAddr high nibble address byte */
sFLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
/*!< Send SectorAddr medium nibble address byte */
sFLASH_SendByte((SectorAddr & 0xFF00) >> 8);
/*!< Send SectorAddr low nibble address byte */
sFLASH_SendByte(SectorAddr & 0xFF);
/*!< Deselect the FLASH: Chip Select high */
sFLASH_CS_HIGH();
/*!< Wait the end of Flash writing */
sFLASH_WaitForWriteEnd();
}
//FLASH擦除整个片
void sFLASH_EraseChip(void)
{
/*!< Send write enable instruction */
sFLASH_WriteEnable();
/*!< Bulk Erase */
/*!< Select the FLASH: Chip Select low */
sFLASH_CS_LOW();
/*!< Send Bulk Erase instruction */
sFLASH_SendByte(sFLASH_CMD_CE);
/*!< Deselect the FLASH: Chip Select high */
sFLASH_CS_HIGH();
/*!< Wait the end of Flash writing */
sFLASH_WaitForWriteEnd();
}
//FLASH写一个页
void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
sFLASH_WriteEnable();
sFLASH_CS_LOW();
sFLASH_SendByte(sFLASH_CMD_WRITE);
sFLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
sFLASH_SendByte((WriteAddr & 0xFF00) >> 8);
sFLASH_SendByte(WriteAddr & 0xFF);
while (NumByteToWrite--)
{
sFLASH_SendByte(*pBuffer);
pBuffer++;
}
sFLASH_CS_HIGH();
sFLASH_WaitForWriteEnd();
}
//FLASH读取0-65536个
void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
sFLASH_CS_LOW();
sFLASH_SendByte(sFLASH_CMD_READ);
sFLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
sFLASH_SendByte((ReadAddr& 0xFF00) >> 8);
sFLASH_SendByte(ReadAddr & 0xFF);
while (NumByteToRead--) /*!< while there is data to be read */
{
*pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
pBuffer++;
}
sFLASH_CS_HIGH();
}
//读取FLASH ID
uint32_t sFLASH_ReadID(void)
{
uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
sFLASH_CS_LOW();
HAL_Delay(1);
sFLASH_SendByte(sFLASH_CMD_RDID);
Temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
Temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
Temp2 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
sFLASH_CS_HIGH();
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
return Temp;
}
//无检验写SPI FLASH
void sFLASH_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint16_t pageremain;
pageremain=256-WriteAddr%256; //单页剩余的字节数
if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
while(1)
{
sFLASH_WritePage(pBuffer,WriteAddr,pageremain);
if(NumByteToWrite==pageremain)
{
break;//写入结束了
}
else //NumByteToWrite>pageremain
{
pBuffer+=pageremain;
WriteAddr+=pageremain;
NumByteToWrite-=pageremain; //减去已经写入了的字节数
if(NumByteToWrite>256)
{
pageremain=256; //一次可以写入256个字节
}
else
{
pageremain=NumByteToWrite; //不够256个字节了
}
}
}
}
//带擦除的写0-65536个字节函数
void sFLASH_WriteBuffer(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t * psFlashBuff;
psFlashBuff=sFlashBuff;
secpos=WriteAddr/4096;//扇区地址
secoff=WriteAddr%4096;//在扇区内的偏移
secremain=4096-secoff;//扇区剩余空间大小
if(NumByteToWrite<=secremain)
{
secremain=NumByteToWrite;//不大于4096个字节
}
while(1)
{
sFLASH_ReadBuffer(psFlashBuff,secpos*4096,4096);//读出整个扇区的内容
sFLASH_EraseSector(secpos*4096); //擦除这个扇区
for(i=0;i<secremain;i++) //复制
{
psFlashBuff[i+secoff]=pBuffer[i];
}
sFLASH_Write_NoCheck(psFlashBuff,secpos*4096,4096);//写入整个扇区
if(NumByteToWrite==secremain)
{
break;//写入结束了
}
else//写入未结束
{
secpos++;//扇区地址增1
secoff=0;//偏移位置为0
pBuffer+=secremain; //指针偏移
WriteAddr+=secremain; //写地址偏移
NumByteToWrite-=secremain; //字节数递减
if(NumByteToWrite>4096)
{
secremain=4096;//下一个扇区还是写不完
}
else
{
secremain=NumByteToWrite; //下一个扇区可以写完了
}
}
}
}
#endif
SPIFLASH.h
#ifndef __SPIFLASH_H__
#define __SPIFLASH_H__
/**Includes************************************************************************************/
#include "stm32f4xx_hal.h"
#include "spi.h"
/**Function declaration************************************************************************/
void sFLASH_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void sFLASH_EraseChip(void);
void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
uint32_t sFLASH_ReadID(void);
char sFLASH_SendByte(uint8_t byte);
void FileTest(void);//该句代码为测试代码,函数在user_diskio.c中定义
#endif
- 在main.c函数调用如下函数检查是否读取出Flash的ID值,读取成功则说明SPI设置没问题
uint32_t Flash_ID = 0;
Flash_ID = sFLASH_ReadID();
- 在文件系统接口函数user_diskio.c中,修改为如下内容,我直接贴文件全部内容,以防因未粘贴部分设置问题导致的无法使用:
user_diskio.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file user_diskio.c
* @brief This file includes a diskio driver skeleton to be completed by the user.
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 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
*
******************************************************************************
*/
/* USER CODE END Header */
#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 "spi.h"
#include "SPIFLASH.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;
#define PAGE_SIZE 256
#define SECTOR_SIZE 4096
#define SECTOR_COUNT 200
#define BLOCK_SIZE 65536
#define FLASH_PAGES_PER_SECTOR SECTOR_SIZE/PAGE_SIZE
FATFS fs;
FIL file; /* 文件对象 */
FRESULT f_res; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE ReadBuffer[1024]={0}; /* 读缓冲区 */
BYTE WriteBuffer[]= "WWB is the high hand\n";
BYTE work[4096];
char USER_Path[4];
void mount_disk(void)
{
f_res = f_mount(&fs, USER_Path, 0);
return;
}
void format_disk(void)
{
uint8_t res = 0;
//这里根据版本不同函数输入参数不一样
f_res = f_mkfs(USER_Path, 1, 4096, work, sizeof(work));
}
UINT bw;
void create_file(void)
{
FIL file;
FIL *pf = &file;
uint8_t res;
f_res = f_open(pf, "0:/test.txt", FA_OPEN_ALWAYS | FA_WRITE);
f_res = f_write(&file, WriteBuffer, sizeof(WriteBuffer), &bw);
f_res = f_close(pf);
}
void read_file(void)
{
FIL file;
FRESULT res;
uint8_t rbuf[100] = {0};
f_res = f_open(&file, "0:/test.txt", FA_READ);
f_res = f_read(&file, ReadBuffer, sizeof(WriteBuffer), &bw);
f_res = f_close(&file);
}
void FileTest(void)
{
mount_disk(); //文件系统注册
format_disk(); //格式化文件系统
create_file(); //建立文件并写入"WWB is the high hand\n"
read_file(); //读取文件放到ReadBuffer中
}
/* 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 == 1
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 */
Stat = STA_NOINIT;
if(sFLASH_ReadID() != 0)
{
Stat &= ~STA_NOINIT;
}
return Stat;
/* 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 */
Stat &= ~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 */
DRESULT res = RES_ERROR;
UINT i;
for(i = 0;i < count;i++)
{
sFLASH_ReadBuffer(buff + i * 4096,sector * 4096 + i * 4096,4096 );
}
return RES_OK;
/* 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 */
DRESULT res = RES_ERROR;
UINT i;
for(i = 0;i < count;i++)
{
sFLASH_WriteBuffer((void *)(buff + i * 4096),sector * 4096 + i * 4096,4096 );
}
res = RES_OK;
/* USER CODE HERE */
return res;
/* 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 res = RES_OK;
switch(cmd)
{
case CTRL_SYNC :
break;
case CTRL_TRIM:
break;
case GET_BLOCK_SIZE:
*(DWORD*)buff = BLOCK_SIZE;
break;
case GET_SECTOR_SIZE:
*(DWORD*)buff = SECTOR_SIZE;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = SECTOR_COUNT;
break;
default:
res = RES_PARERR;
break;
}
return res;
/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
- 在main.c中调用FileTest();函数
测试
编译下载后进入Debug模式,监视ReadBuffer数组【数据为空】和WriteBuffer数组配【数据为需要写入的数据】
点击左上角运行代码,查看ReadBuffer数组是否读取到WriteBuffer中的写入值
点击之后,稍微等待2秒左右【期间要初始化各种外设以及文件系统,再与Flash通信】得到如下结果,ReadBuffer内读取到了之前写入的内容
到此为止,打完收工,顺便祝大家端午假快乐
Good Game!!!!!!
ASWaterbenben专注于STM32CubeMX的使用研究
有需要的猿们敬请关注!!!!!