为了使单片机能在复位或掉电重启后还能保存之前的参数或数据,就需要用到一些非易失存储器,如ROM、FLASH等,本文利用STM32F103的SPI功能挂接外部FLASH的方法实现该功能。
选用的FLASH参数及连接引脚如下:
关于STM32读写外部FLASH的程序可参考野火的相关教程,这里不再赘述。
STM32利用外部FLASH直接存储数据存在许多缺点,如难以记录有效数据的位置,难以确定
存储介质的剩余空间,以及应以何种格式来解读数据等,所以寻求完善的文件系统来管理数据是必要的,这里采用FATFS文件系统,记录其移植到STM32的步骤。
一、FATFS简介
FatFs是用于小型嵌入式系统的通用FAT / exFAT文件系统模块。 FatFs模块是依据ANSI C(C89)标准编写的,并且与磁盘I / O层完全分开。 因此,它的运行独立于平台。 可以将其合并到资源有限的小型微控制器中,例如8051,PIC,AVR,ARM,Z80,RX等。此处还提供了适用于小型微控制器的Petit FatFs模块。
特性:
官网地址:http://elm-chan.org/fsw/ff/00index_e.html
目前最新版本:R0.14(2019年10月14日发布)
二、FATFS文件系统的程序结构
下图是具备FatFs模块的嵌入式系统的典型配置,但非特定配置,显示了FatFs文件系统的程序调用关系。
对于单个存储器和多个存储器的调用结构如下图:
移植FatFs只需要编写所用的磁盘I/O功能,并且可以裁剪功能,例如,只读配置不需要任何写入功能。下表显示了FatFs功能与配置选项的对应关系。
在移植过程中主要编写disk_status、disk_initialize、disk_read、disk_write、disk_ioctl 这几个功能函数。
三、FATFS文件系统移植至STM32
1、移植准备
从官网下载源码后解压,documents文件夹存放FatFs的功能说明,source中存放源代码
将源代码添加到读写外部FLASH的Keil工程,如图:
2、修改diskio.c中的磁盘I/O函数
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2019 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
#include "./flash/bsp_spi_flash.h"//包含FLASH读写的文件
/* Definitions of physical drive number for each drive */
#define SPI_FLASH 0 /* 外部FLASH */
/*-----------------------------------------------------------------------*/
/* Get Drive Status 获取设备状态 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat = STA_NOINIT;
switch (pdrv)
{
case SPI_FLASH :
//读取FLASH的ID
if(sFLASH_ID == SPI_FLASH_ReadID())
{
//设备ID读取正确
stat &= ~STA_NOINIT;
}
else
{
//设备ID读取错误
stat = STA_NOINIT;
}
break;
default:
stat = STA_NOINIT;
}
return stat;
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive 初始化设备 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
uint16_t i;
DSTATUS stat = STA_NOINIT;
switch (pdrv)
{
case SPI_FLASH :
//初始化FLASH
SPI_FLASH_Init();
//延时
i = 500;
while(--i);
//唤醒FLASH
SPI_Flash_WAKEUP();
//获取FLASH状态
stat = disk_status(SPI_FLASH);
break;
default:
stat = STA_NOINIT;
}
return stat;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) 读扇区 */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
DRESULT res = RES_PARERR;
switch (pdrv)
{
case SPI_FLASH :
SPI_FLASH_BufferRead(buff, sector << 12,count << 12);
res = RES_OK;
break;
default:
res = RES_PARERR;
}
return res;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) 写扇区 */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
DRESULT res = RES_PARERR;
//如果写入的扇区个数为0,则报错
if(!count)
{
return RES_PARERR;
}
switch (pdrv)
{
case SPI_FLASH :
SPI_FLASH_SectorErase(sector << 12);
SPI_FLASH_BufferWrite((uint8_t *)buff, sector << 12, count << 12);
res = RES_OK;
break;
default:
res = RES_PARERR;
}
return res;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Function 设备控制 */
/*-----------------------------------------------------------------------*/
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res = RES_PARERR;
switch (pdrv)
{
case SPI_FLASH :
switch(cmd)
{
//扇区数量
case GET_SECTOR_COUNT:
*(DWORD * )buff = 4096;
break;
//扇区大小
case GET_SECTOR_SIZE:
*(WORD * )buff = 4096;
break;
//同时擦除扇区个数
case GET_BLOCK_SIZE:
*(DWORD * )buff = 1;
break;
}
res = RES_OK;
break;
default:
res = RES_PARERR;
}
return res;
}
3、配置ffconf.h
//启用 f_mkfs() 函数,用以创建FAT/exFAT卷
#define FF_USE_MKFS 1
//支持简体中文文件名
#define FF_CODE_PAGE 936
//在STACK上启用具有动态工作缓冲区的长文件名
#define FF_USE_LFN 2
//仅有一个存储器
#define FF_VOLUMES 1
//指定扇区大小的最小值和最大值,
#define FF_MIN_SS 512
#define FF_MAX_SS 4096
//不使用RTC
#define FF_FS_NORTC 1
4、编写main.c进行测试
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./flash/bsp_spi_flash.h"
#include "ff.h"
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_flash; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE ReadBuffer[1024]={0}; /* 读缓冲区 */
BYTE WriteBuffer[] = "FatFs文件系统测试\r\n"; /* 写缓冲区*/
BYTE work[FF_MAX_SS];
int main(void)
{
/* 初始化调试串口,一般为串口 */
USART_Config();
printf("****** FatFs文件系统实验 ******\r\n");
//在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
//初始化函数调用流程如下
//f_mount()->find_volume()->disk_initialize->SPI_FLASH_Init()
res_flash = f_mount(&fs,"0:",1);
/*----------------------- 格式化测试 -----------------*/
/* 如果没有文件系统就格式化创建创建文件系统 */
if(res_flash == FR_NO_FILESYSTEM)
{
printf("》FLASH还没有文件系统,即将进行格式化...\r\n");
/* 格式化 */
res_flash = f_mkfs("0:",0,work,sizeof work);
if(res_flash == FR_OK)
{
printf("》FLASH已成功格式化文件系统。\r\n");
/* 格式化后,先取消挂载 */
res_flash = f_mount(NULL,"0:",1);
/* 重新挂载 */
res_flash = f_mount(&fs,"0:",1);
}
else
{
printf("《《格式化失败。(%d)》》\r\n",res_flash);
while(1);
}
}
else if(res_flash!=FR_OK)
{
printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);
printf("!!可能原因:SPI Flash初始化不成功。\r\n");
while(1);
}
else
{
printf("》文件系统挂载成功,可以进行读写测试\r\n");
}
/*----------------------- 文件系统测试:写测试 -------------------*/
/* 打开文件,每次都以新建的形式打开,属性为可写 */
printf("\r\n****** 即将进行文件写入测试... ******\r\n");
res_flash = f_open(&fnew, "0:FatFs读写测试文件.txt",FA_CREATE_ALWAYS | FA_WRITE );
if ( res_flash == FR_OK )
{
printf("》打开/创建FatFs读写测试文件.txt文件成功,向文件写入数据。\r\n");
/* 将指定存储区内容写入到文件内 */
res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
if(res_flash==FR_OK)
{
printf("》文件写入成功,写入字节数据:%d\n",fnum);
printf("》向文件写入的数据为:\r\n%s\r\n",WriteBuffer);
}
else
{
printf("!!文件写入失败:(%d)\n",res_flash);
}
/* 不再读写,关闭文件 */
f_close(&fnew);
}
else
{
printf("!!打开/创建文件失败。\r\n");
}
/*------------------- 文件系统测试:读测试 --------------------------*/
printf("****** 即将进行文件读取测试... ******\r\n");
res_flash = f_open(&fnew, "0:FatFs读写测试文件.txt",FA_OPEN_EXISTING | FA_READ);
if(res_flash == FR_OK)
{
printf("》打开文件成功。\r\n");
res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
if(res_flash==FR_OK)
{
printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);
}
else
{
printf("!!文件读取失败:(%d)\n",res_flash);
}
}
else
{
printf("!!打开文件失败。\r\n");
}
/* 不再读写,关闭文件 */
f_close(&fnew);
/* 不再使用文件系统,取消挂载文件系统 */
f_mount(NULL,"0:",1);
/* 操作完成,停机 */
while(1)
{
}
}
5、测试结果
对于一块空的FLASH,程序调用 f_mkfs() 进行格式化
对于已存在文件系统的FLASH不再格式化,直接进行文件读写操作
END