爲了使單片機能在復位或掉電重啓後還能保存之前的參數或數據,就需要用到一些非易失存儲器,如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模塊。
特性:
- 與DOS / Windows兼容的FAT / exFAT文件系統。
- 與平臺無關, 易於移植。
- 程序代碼和工作區的空間佔用非常小。
- 支持以下各種配置選項:
ANSI / OEM或Unicode中的長文件名。
exFAT文件系統,可存儲大文件的64位LBA和GPT。
滿足RTOS的線程安全。
支持多個卷(物理驅動器和分區)。
支持可變扇區大小。
支持包括DBCS等的多個代碼頁。
只讀,可選API,I / O緩衝區等。
官網地址: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