本文章基于 NUCLEO-G0B1RE开发板;
一、实验目的
使用CubeMX配置FATFS,实现使用FATFS创建一个TXT格式的文件,存储在FLASH中。
二、实验原理
FATFS: 负责管理和存储文件信息的软件机构,在磁盘上组织文件的方法。
FATFS官网:http://elm-chan.org/fsw/ff/00index_e.html
移植步骤:
数据类型:在integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数据类型,并根据编译器定义好数据类型 (使用 CubeMX 生成可不关心)
配置:通过ffconf.h配置FATFS的相关功能,以满足你的需要。
函数编写:打开diskio.c,进行底层驱动编写,一般需要编写6 个接口函数
配置相关宏:
_MAX_SS 扇区缓冲最大值,一般为512
_VOLUMES 支持的逻辑设备数目
_MAX_LFN 文件名的最大长度
_USE_LFN 是否支持长文件名,值不同存储的位置不同
_CODE_PAGE 设置语言936-中文GBK编码
_USE_MKFS 是否启用格式化
_USE_FASTSEEK 使能快速定位
_USE_LABEL 是否支持磁盘盘符的设置和读取
常用API函数:
f_open 打开/创建一个文件
f_close 关闭一个文件
f_read 读文件
f_write 写文件
f_lseek 移动文件读/写指针
f_sync 刷新缓存的数据
f_mount 注册/取消注册卷的工作区
f_mkfs 在逻辑驱动器上创建FAT卷
三、CubeMX 配置
配置SPI,选择PB0 作为软件CS。
随后配置串口,作为人机交互。
配置FATFS,修改语言为 US
四、程序配置
在程序中添加 FLASH的驱动库。
BSP_SPI_FLASH.h
#ifndef __BSP_SPI_FLASH_H
#define __BSP_SPI_FLASH_H
#include "stm32g0xx_hal.h"
#include <stdio.h>
#include "spi.h"
/**定义FLASH的ID*/
#define sFLASH_ID 0x0B13 //XT25F08B
/**定义SPI CS使能*/
#define SPI_FLASH_CS_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET)
/**定义SPI CS失能*/
#define SPI_FLASH_CS_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET)
/**
*@name 指令表
*@{
*/
#define FLASH_WriteEnable 0x06
#define FLASH_WriteDisable 0x04
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadStatusReg2 0x35
#define FLASH_ReadStatusReg3 0x15
#define FLASH_WriteStatusReg1 0x01
#define FLASH_WriteStatusReg2 0x31
#define FLASH_WriteStatusReg3 0x11
#define FLASH_ReadData 0x03
#define FLASH_FastReadData 0x0B
#define FLASH_FastReadDual 0x3B
#define FLASH_PageProgram 0x02
#define FLASH_BlockErase 0xD8
#define FLASH_SectorErase 0x20
#define FLASH_ChipErase 0xC7
#define FLASH_PowerDown 0xB9
#define FLASH_ReleasePowerDown 0xAB
#define FLASH_DeviceID 0xAB
#define FLASH_ManufactDeviceID 0x90
#define FLASH_JedecDeviceID 0x9F
#define FLASH_Enable4ByteAddr 0xB7
#define FLASH_Exit4ByteAddr 0xE9
/** @} */
void BSP_SPI_FLASH_Init(void);
uint16_t BSP_SPI_FLASH_ReadID(void); //读取FLASH ID
uint8_t BSP_SPI_FLASH_ReadSR(uint8_t regno); //读取状态寄存器
void BSP_SPI_FLASH_4ByteAddr_Enable(void); //使能4字节地址模式
void BSP_SPI_FLASH_Write_SR(uint8_t regno,uint8_t sr); //写状态寄存器
void BSP_SPI_FLASH_Write_Enable(void); //写使能
void BSP_SPI_FLASH_Write_Disable(void); //写保护
void BSP_SPI_FLASH_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void BSP_SPI_FLASH_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead); //读取flash
void BSP_SPI_FLASH_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//写入flash
void BSP_SPI_FLASH_Erase_Chip(void); //整片擦除
void BSP_SPI_FLASH_Erase_Sector(uint32_t Dst_Addr); //扇区擦除
void BSP_SPI_FLASH_Wait_Busy(void); //等待空闲
void BSP_SPI_FLASH_PowerDown(void); //进入掉电模式
void BSP_SPI_FLASH_WAKEUP(void); //唤醒
#endif
BSP_SPI_FLASH.c
#include "BSP_SPI_FLASH.h"
/** 定义定义BSP_SPI_FLASH芯片型号*/
uint16_t BSP_SPI_FLASH_TYPE;
/**
* @Brief FLASH读写1byte函数
* @Param byte 需要写入的数据
* @Return ucReadData 接收到的数据
* @Note None
*/
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
uint8_t ucReadData;
HAL_SPI_TransmitReceive(&hspi1, &byte, &ucReadData, 1, 1000);
return ucReadData;/*返回收到的数据*/
}
/**
* @Brief FLASH初始化函数
* @Param None
* @Return None
* @Note 获取FLASH的ID,判断是否成功通讯
*/
void BSP_SPI_FLASH_Init(void)
{
BSP_SPI_FLASH_TYPE=BSP_SPI_FLASH_ReadID(); //读取FLASH ID.
if(BSP_SPI_FLASH_TYPE == sFLASH_ID) //SPI FLASH为XT25F08B
{
}
}
/**
* @Brief FLASH读取状态寄存器函数
* @Param regno 状态寄存器号
* @Return byte 状态寄存器值
* @Note None
*/
uint8_t BSP_SPI_FLASH_ReadSR(uint8_t regno)
{
uint8_t byte=0,command=0;
switch(regno)
{
case 1:
command=FLASH_ReadStatusReg1; //读状态寄存器1指令
break;
case 2:
command=FLASH_ReadStatusReg2; //读状态寄存器2指令
break;
case 3:
command=FLASH_ReadStatusReg3; //读状态寄存器3指令
break;
default:
command=FLASH_ReadStatusReg1;
break;
}
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(command); //发送读取状态寄存器命令
byte=SPI_FLASH_SendByte(0Xff); //读取一个字节
SPI_FLASH_CS_HIGH(); //取消片选
return byte;
}
/**
* @Brief FLASH写状态寄存器函数
* @Param regno 状态寄存器号
* @Param sr 写入的状态寄存器值
* @Return None
* @Note None
*/
void BSP_SPI_FLASH_Write_SR(uint8_t regno,uint8_t sr)
{
uint8_t command=0;
switch(regno)
{
case 1:
command=FLASH_WriteStatusReg1; //写状态寄存器1指令
break;
case 2:
command=FLASH_WriteStatusReg2; //写状态寄存器2指令
break;
case 3:
command=FLASH_WriteStatusReg3; //写状态寄存器3指令
break;
default:
command=FLASH_WriteStatusReg1;
break;
}
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(command); //发送写取状态寄存器命令
SPI_FLASH_SendByte(sr); //写入一个字节
SPI_FLASH_CS_HIGH(); //取消片选
}
/**
* @Brief FLASH写使能函数
* @Param None
* @Return None
* @Note None
*/
void BSP_SPI_FLASH_Write_Enable(void)
{
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(FLASH_WriteEnable); //发送写使能
SPI_FLASH_CS_HIGH(); //取消片选
}
/**
* @Brief FLASH写禁止函数
* @Param None
* @Return None
* @Note None
*/
void BSP_SPI_FLASH_Write_Disable(void)
{
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(FLASH_WriteDisable); //发送写禁止指令
SPI_FLASH_CS_HIGH(); //取消片选
}
/**
* @Brief FLASH读芯片ID函数
* @Param None
* @Return 芯片的ID
* @Note 测试芯片是XT25F08B,ID为0x0B13
*/
uint16_t BSP_SPI_FLASH_ReadID(void)
{
uint16_t Temp = 0;
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(0x90);//发送读取ID命令
SPI_FLASH_SendByte(0x00);
SPI_FLASH_SendByte(0x00);
SPI_FLASH_SendByte(0x00);
Temp|=SPI_FLASH_SendByte(0xFF)<<8;
Temp|=SPI_FLASH_SendByte(0xFF);
SPI_FLASH_CS_HIGH();
return Temp;
}
/**
* @Brief FLASH读函数
* @Param pBuffer 数据存储区
* @Param ReadAddr 开始读取的地址(24bit)
* @Param NumByteToRead 要读取的字节数(最大65535)
* @Return None
* @Note 在指定地址开始读取指定长度的数据
*/
void BSP_SPI_FLASH_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{
uint16_t i;
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(FLASH_ReadData); //发送读取命令
SPI_FLASH_SendByte((uint8_t)((ReadAddr)>>16)); //发送24bit地址
SPI_FLASH_SendByte((uint8_t)((ReadAddr)>>8));
SPI_FLASH_SendByte((uint8_t)ReadAddr);
for(i=0; i<NumByteToRead; i++)
{
pBuffer[i]=SPI_FLASH_SendByte(0XFF); //循环读数
}
SPI_FLASH_CS_HIGH();
}
/**
* @Brief FLASH页写函数
* @Param pBuffer 数据存储区
* @Param WriteAddr 开始写入的地址(24bit)
* @Param NumByteToWrite 要写入的字节数(最大256),该数不应该超过该页的剩余字节数
* @Return None
* @Note 在一页(0~65535)内写入少于256个字节的数据
*/
void BSP_SPI_FLASH_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint16_t i;
BSP_SPI_FLASH_Write_Enable(); //SET WEL
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(FLASH_PageProgram); //发送写页命令
SPI_FLASH_SendByte((uint8_t)((WriteAddr)>>16)); //发送24bit地址
SPI_FLASH_SendByte((uint8_t)((WriteAddr)>>8));
SPI_FLASH_SendByte((uint8_t)WriteAddr);
for(i=0; i<NumByteToWrite; i++)SPI_FLASH_SendByte(pBuffer[i]); //循环写数
SPI_FLASH_CS_HIGH(); //取消片选
BSP_SPI_FLASH_Wait_Busy(); //等待写入结束
}
/**
* @Brief FLASH页无检验写函数
* @Param pBuffer 数据存储区
* @Param WriteAddr 开始写入的地址(24bit)
* @Param NumByteToWrite 要写入的字节数(最大65535)
* @Return None
* @Note 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败
* 具有自动换页功能
* 在指定地址开始写入指定长度的数据,但是要确保地址不越界
*/
void BSP_SPI_FLASH_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)
{
BSP_SPI_FLASH_Write_Page(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个字节了
}
};
}
/** FLASH 缓存数组*/
uint8_t BSP_SPI_FLASH_BUFFER[4096];
/**
* @Brief FLASH写函数
* @Param pBuffer 数据存储区
* @Param WriteAddr 开始写入的地址(24bit)
* @Param NumByteToWrite 要写入的字节数(最大65535)
* @Return None
* @Note 在指定地址开始写入指定长度的数据
* 该函数带擦除操作!
*/
void BSP_SPI_FLASH_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t * BSP_SPI_FLASH_BUF;
BSP_SPI_FLASH_BUF=BSP_SPI_FLASH_BUFFER;
secpos=WriteAddr/4096;//扇区地址
secoff=WriteAddr%4096;//在扇区内的偏移
secremain=4096-secoff;//扇区剩余空间大小
//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节
while(1)
{
BSP_SPI_FLASH_Read(BSP_SPI_FLASH_BUF,secpos*4096,4096);//读出整个扇区的内容
for(i=0; i<secremain; i++) //校验数据
{
if(BSP_SPI_FLASH_BUF[secoff+i]!=0XFF)break;//需要擦除
}
if(i<secremain)//需要擦除
{
BSP_SPI_FLASH_Erase_Sector(secpos);//擦除这个扇区
for(i=0; i<secremain; i++) //复制
{
BSP_SPI_FLASH_BUF[i+secoff]=pBuffer[i];
}
BSP_SPI_FLASH_Write_NoCheck(BSP_SPI_FLASH_BUF,secpos*4096,4096);//写入整个扇区
}
else BSP_SPI_FLASH_Write_NoCheck(pBuffer,WriteAddr,secremain); //写已经擦除了的,直接写入扇区剩余区间.
if(NumByteToWrite==secremain)break;//写入结束了
else//写入未结束
{
secpos++;//扇区地址增1
secoff=0;//偏移位置为0
pBuffer+=secremain; //指针偏移
WriteAddr+=secremain;//写地址偏移
NumByteToWrite-=secremain; //字节数递减
if(NumByteToWrite>4096)secremain=4096; //下一个扇区还是写不完
else secremain=NumByteToWrite; //下一个扇区可以写完了
}
};
}
/**
* @Brief FLASH擦除整个芯片函数
* @Param None
* @Return None
* @Note 需要等待很长时间
*/
void BSP_SPI_FLASH_Erase_Chip(void)
{
BSP_SPI_FLASH_Write_Enable(); //SET WEL
BSP_SPI_FLASH_Wait_Busy();
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(FLASH_ChipErase); //发送片擦除命令
SPI_FLASH_CS_HIGH(); //取消片选
BSP_SPI_FLASH_Wait_Busy(); //等待芯片擦除结束
}
/**
* @Brief FLASH擦除一个扇区函数
* @Param Dst_Addr 扇区地址 根据实际容量设置
* @Return None
* @Note 擦除一个扇区的最少时间:150ms
*/
void BSP_SPI_FLASH_Erase_Sector(uint32_t Dst_Addr)
{
//监视falsh擦除情况,测试用
//printf("fe:%x\r\n",Dst_Addr);
Dst_Addr*=4096;
BSP_SPI_FLASH_Write_Enable(); //SET WEL
BSP_SPI_FLASH_Wait_Busy();
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(FLASH_SectorErase); //发送扇区擦除指令
SPI_FLASH_SendByte((uint8_t)((Dst_Addr)>>16)); //发送24bit地址
SPI_FLASH_SendByte((uint8_t)((Dst_Addr)>>8));
SPI_FLASH_SendByte((uint8_t)Dst_Addr);
SPI_FLASH_CS_HIGH(); //取消片选
BSP_SPI_FLASH_Wait_Busy(); //等待擦除完成
}
/**
* @Brief FLASH等待空闲函数
* @Param None
* @Return None
* @Note None
*/
void BSP_SPI_FLASH_Wait_Busy(void)
{
while((BSP_SPI_FLASH_ReadSR(1)&0x01)==0x01); // 等待BUSY位清空
}
/**
* @Brief FLASH进入掉电模式函数
* @Param None
* @Return None
* @Note None
*/
void BSP_SPI_FLASH_PowerDown(void)
{
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(FLASH_PowerDown); //发送掉电命令
SPI_FLASH_CS_HIGH(); //取消片选
HAL_Delay(2); //等待TPD
}
/**
* @Brief FLASH唤醒函数
* @Param None
* @Return None
* @Note None
*/
void BSP_SPI_FLASH_WAKEUP(void)
{
SPI_FLASH_CS_LOW(); //使能器件
SPI_FLASH_SendByte(FLASH_ReleasePowerDown); // send FLASH_PowerDown command 0xAB
SPI_FLASH_CS_HIGH(); //取消片选
HAL_Delay(2); //等待TRES1
}
在 usart.c 中,重定向 printf 函数,需要添加头文件 #include “stdio.h”
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart2 , (uint8_t *)&ch, 1 , 0xffff);
return ch;
打开 user_diskio.c 函数,准备修改读写的底层驱动。
定义外部FLASH的驱动器号和容量,为了方便,设定为1个块,2048 个扇区,每个扇区512字节。
/** 定义外部FLASH物理驱动器号为0*/
#define EX_FLASH 0 /* Example: Map Ramdisk to physical drive 0 */
/**
*@name 定义FLASH的大小
*@{
*/
/** 定义FLAH 一个扇区的大小*/
#define FLASH_SECTOR_SIZE 512
/** 定义FLASH 扇区的数量*/
#define FLASH_SECTOR_COUNT 8*256
/** 定义FLASH 块的数量*/
#define FLASH_BLOCK_SIZE 1
/** @} */
在 USER_initialize 函数中添加初始化。
MX_SPI1_Init();
if(BSP_SPI_FLASH_ReadID() == 0X0B13 )
{
Stat = RES_OK;
}
else
{
Stat = STA_NOINIT;
}
return Stat;
在 USER_status 添加状态获取。
if(BSP_SPI_FLASH_ReadID() == 0X0B13 )
{
Stat = RES_OK;
}
else
{
Stat = STA_NOINIT;
}
return Stat;
在 USER_read 中添加 读函数
switch(pdrv)
{
case EX_FLASH :
BSP_SPI_FLASH_Read(buff,sector*FLASH_SECTOR_SIZE,count*FLASH_SECTOR_SIZE);
return RES_OK;
}
return RES_PARERR;
在USER_write 函数中添加写函数
uint8_t res=0;
if (!count)return RES_PARERR;
switch(pdrv)
{
case EX_FLASH:
BSP_SPI_FLASH_Write((uint8_t*)buff,sector*FLASH_SECTOR_SIZE,count*FLASH_SECTOR_SIZE);
res=0;
break;
default:
res=1;
}
if(res == 0x00)return RES_OK;
else return RES_ERROR;
在 USER_ioctl 中添加 FLASH 的大小。
DRESULT res = RES_ERROR;
if(pdrv==EX_FLASH) //外部FLASH
{
switch(cmd)
{
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = FLASH_SECTOR_SIZE;
res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = FLASH_BLOCK_SIZE;
res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = FLASH_SECTOR_COUNT;
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
}
else res=RES_ERROR;//其他的不支持
return res;
在main.c 中 或者新添加 fatfs_demo.c ,去添加测试程序。
先定义存储的变量。
/** 定义空文件系统存储变量*/
FATFS flash_fs;
/** 定义空文件存储变量*/
FIL testfp;
/** 定义格式化的工作缓冲区*/
BYTE work[_MIN_SS];
/** 定义每次写入的数据长度*/
UINT bw;
/** 定于整体写入的数据长度*/
uint32_t FATFS_Addr=0;
/** 定义输出的数据值*/
char Data_Buff[20]="123456";
/** 定义读出的数据值*/
char Read_Buff[20]="";
/** 返回函数状态*/
FRESULT myret;
添加测试初始化函数。
在第一次进行初始化时,一定要全部擦除FLASH ,然后强制使用 f_mkfs 函数进行格式化。
/**
* @Brief 文件系统初始化函数
* @Param None
* @RetVal None
* @Note 判断是否需要进行格式化
*/
void fatfs_init(void)
{
FRESULT res_flash;
printf( "\r\n文件系统测试\r\n" );
res_flash =f_mount(&flash_fs,"0:",0);
if(res_flash == FR_NO_FILESYSTEM)
{
printf( "\r\n 即将进行格式化... \r\n");
res_flash = f_mkfs("0:",FM_ANY,0,work,sizeof(work));
if(res_flash == FR_OK)
{
printf( "\r\n 已成功格式化r\n");
res_flash=f_mount(NULL,"0:",0);
res_flash=f_mount(&flash_fs,"0:",0);
}
else
{
printf( "\r\n 格式化失败r\n");
while(1);
}
}
else if(res_flash != FR_OK )
{
printf( "\r\n 文件系统挂载失败r\n");
while(1);
}
else
{
printf("》文件系统挂载成功,可以进行读写测试\r\n");
}
}
添加测试demo,进行读写测试。
/**
* @Brief 文件系统测试函数
* @Param None
* @RetVal None
* @Note 创建TXT格式文件
*/
void fatfs_test(void)
{
myret = f_open(&testfp,"0:/test.txt",FA_OPEN_ALWAYS|FA_WRITE|FA_READ);
if(myret== FR_OK)
{
printf("创建文件成功\r\n");
f_lseek(&testfp,FATFS_Addr);
myret = f_write(&testfp, (const void*)Data_Buff, sizeof(Data_Buff)-1, &bw);
if(myret== FR_OK)
{
printf("写入成功\r\n");
}
f_close(&testfp);
}
myret = f_open(&testfp,"0:/test.txt",FA_READ);
if(myret== FR_OK)
{
printf("打开文件成功\r\n");
f_lseek(&testfp,FATFS_Addr);
myret = f_read(&testfp, (void*)Read_Buff, sizeof(Read_Buff)-1, &bw);
if(myret== FR_OK)
{
printf("文件读取成功,读到的字节数据:%d\r\n",bw);
printf("读取的文件数据为:%s\r\n",Read_Buff);
}
else
{
printf("读取失败:%d\r\n",myret);
}
f_close(&testfp);
}
}
五、实验结果
串口有数据显示,也可以通过KEIL 仿真查看。