一、FatFs簡介
FatFs 是面向小型嵌入式系統的一種通用的 FAT 檔案系統,它完全是由 ANSI C 語言撰寫并且完全獨立于底層的 I/O 介質,因此它可以很容易地不加修改地移植到其他的處理器當中,如 8051、PIC、AVR、SH、Z80、H8、ARM 等,FatFs 支持 FAT12、FAT16、FAT32 等格式,所以我們利用前面寫好的 SPI Flash 芯片驅動,把 FatFs 檔案系統代碼移植到工程之中,就可以利用檔案系統的各種函式,對 SPI Flash 芯片以“檔案”格式進行讀寫操作了,
FatFs 檔案系統的原始碼可以從 fatfs 官網下載:
http://elm-chan.org/fsw/ff/00index_e.html
1.1 FatFs檔案系統布局

簇是檔案存盤的最小單元,FAT32磁區大小與對應簇空間大小關系如下表示:
| 磁區空間大小 | 簇空間大小 | 每個簇包含的扇區數 |
|---|---|---|
| < 8GB | 4KB | 8 |
| [ 8GB, 16GB ) | 8KB | 16 |
| [ 16GB, 32GB ) | 16KB | 32 |
| >= 32GB | 32KB | 64 |
例如:創建一個50位元組的test.txt檔案,檔案大小是50位元組,但是占用磁盤空間為4096位元組(一個簇)

1.2 FatFs層次結構

-
最頂層是應用層:使用者只需要呼叫FATFS模塊提供給用戶的一系列應用介面函式(如f_open, f_read, f_write和f_close等),就可以像在PC上讀寫檔案那樣簡單
-
中間層FATFS模塊:實作了FAT檔案讀寫協議;它提供了ff.c和ff.h檔案,一般情況下不用修改,使用時將頭檔案包含進去即可
-
最底層是FATFS模塊的底層介面:包括存盤媒介讀寫介面和供給檔案創建修改時間的實時時鐘,需要在移植時撰寫對應的代碼
FATFS原始碼相關檔案介紹如下表示;移植FATFS模塊時,一般只需要修改2個檔案(即ffconf.h和diskio.c)
與平臺無關:
| 檔案 | 說明 |
|---|---|
| ffconf.h | FATFS模塊組態檔 |
| ff.h | FATFS和應用模塊公用的包含檔案 |
| ff.c | FATFS模塊 |
| diskio.h | FATFS和disk I/O模塊公用的包含檔案 |
| interger.h | 資料型別定義 |
| option | 可選的外部功能(比如支持中文) |
與平臺相關:
| 檔案 | 說明 |
|---|---|
| diskio.c | FATFS和disk I/O模塊介面層檔案 |
二、新建工程
1. 打開 STM32CubeMX 軟體,點擊“新建工程”

2. 選擇 MCU 和封裝

3. 配置時鐘
RCC 設定,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)

選擇 Clock Configuration,配置系統時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后,輸入回車,軟體會自動修改所有配置

4. 配置除錯模式
非常重要的一步,否則會造成第一次燒錄程式后續無法識別除錯器
SYS 設定,選擇 Debug 為 Serial Wire

三、SPI1
3.1 引數配置
在 Connectivity 中選擇 SPI1 設定,并選擇 Full-Duplex Master 全雙工主模式,不開啟 NSS 即不使用硬體片選信號

原理圖中雖然將 CS 片選接到了硬體 SPI1 的 NSS 引腳,因為硬體 NSS 使用比較麻煩,所以后面直接把 PA4 配置為普通 GPIO,手動控制片選信號,
在右邊圖中找到 SPI1 NSS 對應引腳,選擇 GPIO_Output,糾正:野火STM32F103指南者開發板SPI1 NSS須配置為PC0

修改輸出高電平 High,標簽為 W25Q64_CHIP_SELECT,

SPI 為默認設定不作修改,只需注意一下,Prescaler 分頻系數最低為 4,波特率 (Baud Rate) 為 18.0 MBits/s,這里被限制了,SPI1 最高通信速率可達 36Mbtis/s,

- Clock Polarity(CPOL):SPI 通訊設備處于空閑狀態時,SCK 信號線的電平信號(即 SPI 通訊開始前、 NSS 線為高電平時 SCK 的狀態),CPOL=0 時, SCK 在空閑狀態時為低電平,CPOL=1 時,則相反,
- Clock Phase(CPHA):指資料的采樣的時刻,當 CPHA=0 時,MOSI 或 MISO 資料線上的信號將會在 SCK 時鐘線的“奇數邊沿”被采樣,當 CPHA=1 時,資料線在 SCK 的“偶數邊沿”采樣,

根據 FLASH 芯片的說明,它支持 SPI模式0及模式 3,支持雙線全雙工,使用 MSB 先行模式,資料幀長度為 8 位,

所以這里配置 CPOL 為Low,CPHA 為1 Edge即 SPI模式0,

四、FATFS
4.1 引數配置
在 Middleware 中選擇 FATFS 設定,并勾選 User-defined 因為 SPI Flash 在上面沒有

-
Function Parameters
跳過 -
Locale and Namespace Parameters:
- CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows)
支持簡體中文編碼 - USE_LFN(Use Long Filename): Enabled with dynamic working buffer on the STACK
支持長檔案名,并指定使用堆疊空間為緩沖區
- CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows)
快取作業區為什么放在堆疊?其實fatfs提供了三個選項:BSS,STACK , HEAP,根據個人情況選一個,
在BSS上啟用帶有靜態作業緩沖區的LFN,不能動態分配,
如果選擇了HEAP(堆)且自己有屬于自己的malloc就去重寫ff_memalloc ff_memfree函式,如果是庫的malloc就不需要,
一般都選擇使用STACK(堆疊),能動態分配,
當使用堆疊作為作業緩沖區時,請注意堆疊溢位,
- Physical Drive Parameters:
- VOLUMES(Logical drivers): 2
指定物理設備數量,這里設定為 2,包括預留 SD 卡和 SPI Flash 芯片 - MAX_SS(Maximum Sector Size): 4096
指定扇區大小的最大值,SD 卡扇區大小一般都為 512 位元組,SPI Flash 芯片扇區大小一般設定為 4096 位元組,所以需要把 _MAX_SS 改為 4096 - MIN_SS(Minimum Sector Size): 512
指定扇區大小的最小值
- VOLUMES(Logical drivers): 2

4.2 增大堆疊空間
將最小堆疊空間改到 0x1000

4.3 生成代碼
輸入專案名和專案路徑

選擇應用的 IDE 開發環境 MDK-ARM V5

每個外設生成獨立的 ’.c/.h’ 檔案
不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應的外設檔案, 如 GPIO 初始化代碼生成在 gpio.c 中,

點擊 GENERATE CODE 生成代碼

五、添加SPI Flash操作函式
在 user_diskio.c 中加入
//#define SPI_FLASH_PageSize 4096
#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256
#define ManufactDeviceID_CMD 0x90
#define READ_STATU_REGISTER_1 0x05
#define READ_STATU_REGISTER_2 0x35
#define READ_DATA_CMD 0x03
#define WRITE_ENABLE_CMD 0x06
#define WRITE_DISABLE_CMD 0x04
#define SECTOR_ERASE_CMD 0x20
#define CHIP_ERASE_CMD 0xc7
#define PAGE_PROGRAM_CMD 0x02
#define SPI_FLASH_CS_LOW() HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
#define SPI_FLASH_CS_HIGH() HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
extern SPI_HandleTypeDef hspi1;
/**
* @brief SPI發送指定長度的資料
* @param buf —— 發送資料緩沖區首地址
* @param size —— 要發送資料的位元組數
* @retval 成功回傳HAL_OK
*/
static HAL_StatusTypeDef SPI_Transmit(uint8_t* send_buf, uint16_t size)
{
return HAL_SPI_Transmit(&hspi1, send_buf, size, 100);
}
/**
* @brief SPI接收指定長度的資料
* @param buf —— 接收資料緩沖區首地址
* @param size —— 要接收資料的位元組數
* @retval 成功回傳HAL_OK
*/
static HAL_StatusTypeDef SPI_Receive(uint8_t* recv_buf, uint16_t size)
{
return HAL_SPI_Receive(&hspi1, recv_buf, size, 100);
}
/**
* @brief SPI在發送資料的同時接收指定長度的資料
* @param send_buf —— 接收資料緩沖區首地址
* @param recv_buf —— 接收資料緩沖區首地址
* @param size —— 要發送/接收資料的位元組數
* @retval 成功回傳HAL_OK
*/
static HAL_StatusTypeDef SPI_TransmitReceive(uint8_t* send_buf, uint8_t* recv_buf, uint16_t size)
{
return HAL_SPI_TransmitReceive(&hspi1, send_buf, recv_buf, size, 100);
}
/*等待超時時間*/
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
static __IO uint32_t SPITimeout = SPIT_LONG_TIMEOUT;
/**
* @brief 等待超時回呼函式
* @param None.
* @retval None.
*/
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* 等待超時后的處理,輸出錯誤資訊 */
printf("SPI 等待超時!errorCode = %d",errorCode);
return 0;
}
/**
* @brief 使用SPI發送一個位元組的資料
* @param byte:要發送的資料
* @retval 回傳接收到的資料
*/
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待發送緩沖區為空,TXE事件 */
while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_TXE ) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
}
/* 寫入資料暫存器,把要寫入的資料寫入發送緩沖區 */
WRITE_REG(hspi1.Instance->DR, byte);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待接識訓沖區非空,RXNE事件 */
while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_RXNE ) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
}
/* 讀取資料暫存器,獲取接識訓沖區資料 */
return READ_REG(hspi1.Instance->DR);
}
/**
* @brief 讀取Flash內部的ID
* @param none
* @retval 成功回傳device_id
*/
uint16_t W25QXX_ReadID(void)
{
uint8_t recv_buf[2] = {0}; //recv_buf[0]存放Manufacture ID, recv_buf[1]存放Device ID
uint16_t device_id = 0;
uint8_t send_data[4] = {ManufactDeviceID_CMD,0x00,0x00,0x00}; //待發送資料,命令+地址
/* 使能片選 */
SPI_FLASH_CS_LOW();
/* 發送并讀取資料 */
if (HAL_OK == SPI_Transmit(send_data, 4))
{
if (HAL_OK == SPI_Receive(recv_buf, 2))
{
device_id = (recv_buf[0] << 8) | recv_buf[1];
}
}
/* 取消片選 */
SPI_FLASH_CS_HIGH();
return device_id;
}
/**
* @brief 讀取W25QXX的狀態暫存器,W25Q64一共有2個狀態暫存器
* @param reg —— 狀態暫存器編號(1~2)
* @retval 狀態暫存器的值
*/
static uint8_t W25QXX_ReadSR(uint8_t reg)
{
uint8_t result = 0;
uint8_t send_buf[4] = {0x00,0x00,0x00,0x00};
switch(reg)
{
case 1:
send_buf[0] = READ_STATU_REGISTER_1;
case 2:
send_buf[0] = READ_STATU_REGISTER_2;
case 0:
default:
send_buf[0] = READ_STATU_REGISTER_1;
}
/* 使能片選 */
SPI_FLASH_CS_LOW();
if (HAL_OK == SPI_Transmit(send_buf, 4))
{
if (HAL_OK == SPI_Receive(&result, 1))
{
/* 取消片選 */
SPI_FLASH_CS_HIGH();
return result;
}
}
/* 取消片選 */
SPI_FLASH_CS_HIGH();
return 0;
}
/**
* @brief 阻塞等待Flash處于空閑狀態
* @param none
* @retval none
*/
static void W25QXX_Wait_Busy(void)
{
while((W25QXX_ReadSR(1) & 0x01) == 0x01); // 等待BUSY位清空
}
/**
* @brief W25QXX寫使能,將S1暫存器的WEL置位
* @param none
* @retval
*/
void W25QXX_Write_Enable(void)
{
uint8_t cmd= WRITE_ENABLE_CMD;
SPI_FLASH_CS_LOW();
SPI_Transmit(&cmd, 1);
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy();
}
/**
* @brief W25QXX寫禁止,將WEL清零
* @param none
* @retval none
*/
void W25QXX_Write_Disable(void)
{
uint8_t cmd = WRITE_DISABLE_CMD;
SPI_FLASH_CS_LOW();
SPI_Transmit(&cmd, 1);
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy();
}
/**
* @brief W25QXX擦除一個扇區
* @param sector_addr —— 扇區地址 根據實際容量設定
* @retval none
* @note 阻塞操作
*/
void W25QXX_Erase_Sector(uint32_t sector_addr)
{
W25QXX_Write_Enable(); //擦除操作即寫入0xFF,需要開啟寫使能
W25QXX_Wait_Busy(); //等待寫使能完成
/* 使能片選 */
SPI_FLASH_CS_LOW();
/* 發送扇區擦除指令*/
SPI_FLASH_SendByte(SECTOR_ERASE_CMD);
/*發送擦除扇區地址的高位*/
SPI_FLASH_SendByte((sector_addr & 0xFF0000) >> 16);
/* 發送擦除扇區地址的中位 */
SPI_FLASH_SendByte((sector_addr & 0xFF00) >> 8);
/* 發送擦除扇區地址的低位 */
SPI_FLASH_SendByte(sector_addr & 0xFF);
/* 取消片選 */
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy(); //等待扇區擦除完成
}
/**
* @brief 頁寫入操作
* @param dat —— 要寫入的資料緩沖區首地址
* @param WriteAddr —— 要寫入的地址
* @param byte_to_write —— 要寫入的位元組數(0-256)
* @retval none
*/
void W25QXX_PageProgram(uint8_t* dat, uint32_t WriteAddr, uint16_t nbytes)
{
uint8_t cmd = PAGE_PROGRAM_CMD;
// WriteAddr <<= 8;
W25QXX_Write_Enable();
/* 使能片選 */
SPI_FLASH_CS_LOW();
SPI_Transmit(&cmd, 1);
// SPI_Transmit((uint8_t*)&WriteAddr, 3);
uint8_t addr;
HAL_StatusTypeDef status;
/* 發送 讀 地址高位 */
addr = (WriteAddr & 0xFF0000) >> 16;
status = SPI_Transmit(&addr, 1);
/* 發送 讀 地址中位 */
addr = (WriteAddr & 0xFF00) >> 8;
status = SPI_Transmit(&addr, 1);
/* 發送 讀 地址低位 */
addr = WriteAddr & 0xFF;
status = SPI_Transmit(&addr, 1);
SPI_Transmit(dat, nbytes);
/* 取消片選 */
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy();
}
/**
* @brief 對FLASH寫入資料,呼叫本函式寫入資料前需要先擦除扇區
* @param pBuffer,要寫入資料的指標
* @param WriteAddr,寫入地址
* @param NumByteToWrite,寫入資料長度
* @retval 無
*/
void W25QXX_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint8_t NumOfPage = 0;
uint8_t NumOfSingle = 0;
uint8_t Addr = 0;
uint8_t count = 0;
uint8_t temp = 0;
/*mod運算求余,若writeAddr是SPI_FLASH_PageSize整數倍,運算結果Addr值為0*/
Addr = WriteAddr % SPI_FLASH_PageSize;
/*差count個資料值,剛好可以對齊到頁地址*/
count = SPI_FLASH_PageSize - Addr;
/*計算出要寫多少整數頁*/
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
/*mod運算求余,計算出剩余不滿一頁的位元組數*/
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* Addr=0,則WriteAddr 剛好按頁對齊 aligned */
if(Addr == 0)
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if(NumOfPage == 0)
{
W25QXX_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
}
/* NumByteToWrite > SPI_FLASH_PageSize */
else
{
/*先把整數頁都寫了*/
while(NumOfPage--)
{
W25QXX_PageProgram(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不滿一頁的資料,把它寫完*/
W25QXX_PageProgram(pBuffer, WriteAddr, NumOfSingle);
}
}
/* 若地址與 SPI_FLASH_PageSize 不對齊 */
else
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if(NumOfPage == 0)
{
/*當前頁剩余的count個位置比NumOfSingle小,寫不完*/
if(NumOfSingle > count)
{
temp = NumOfSingle - count;
/*先寫滿當前頁*/
W25QXX_PageProgram(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
/*再寫剩余的資料*/
W25QXX_PageProgram(pBuffer, WriteAddr, temp);
}
/*當前頁剩余的count個位置能寫完NumOfSingle個資料*/
else
{
W25QXX_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
}
}
/* NumByteToWrite > SPI_FLASH_PageSize */
else
{
/*地址不對齊多出的count分開處理,不加入這個運算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
W25QXX_PageProgram(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
/*把整數頁都寫了*/
while(NumOfPage--)
{
W25QXX_PageProgram(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不滿一頁的資料,把它寫完*/
if(NumOfSingle != 0)
{
W25QXX_PageProgram(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
/**
* @brief 讀取FLASH資料
* @param pBuffer,存盤讀出資料的指標
* @param ReadAddr,讀取地址
* @param NumByteToRead,讀取資料長度
* @retval 無
*/
void W25QXX_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
W25QXX_Wait_Busy();
/* 選擇FLASH: CS低電平 */
SPI_FLASH_CS_LOW();
/* 發送 讀 指令 */
uint8_t cmd = READ_DATA_CMD;
SPI_Transmit(&cmd, 1);
// 不知道為什么連起來發不行
// ReadAddr = ReadAddr << 8;
// SPI_Transmit((uint8_t*)&ReadAddr, 3);
uint8_t addr;
HAL_StatusTypeDef status;
/* 發送 讀 地址高位 */
addr = (ReadAddr & 0xFF0000) >> 16;
status = SPI_Transmit(&addr, 1);
/* 發送 讀 地址中位 */
addr = (ReadAddr& 0xFF00) >> 8;
status = SPI_Transmit(&addr, 1);
/* 發送 讀 地址低位 */
addr = ReadAddr & 0xFF;
status = SPI_Transmit(&addr, 1);
if(HAL_OK == status)
{
SPI_Receive(pBuffer, NumByteToRead);
}
/* 停止信號 FLASH: CS 高電平 */
SPI_FLASH_CS_HIGH();
}
六、修改diskio介面函式
在 user_diskio.c 中修改以下幾個函式:
/* 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;
i = 500;
while(--i);
Stat = STA_NOINIT;
if(W25QXX_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 status = RES_PARERR;
if(!count)
{
return RES_PARERR; //count不能等于0,否則回傳引數錯誤
}
// /* 扇區偏移2MB,外部Flash檔案系統空間放在SPI Flash后面6MB空間 */
// sector += 512;
W25QXX_BufferRead(buff, sector << 12, count << 12);
status = RES_OK;
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 */
uint32_t write_addr;
DRESULT status = RES_PARERR;
if(!count)
{
return RES_PARERR; /* Check parameter */
}
///* 扇區偏移2MB,外部Flash檔案系統空間放在SPI Flash后面6MB空間 */
//sector += 512;
write_addr = sector << 12;
W25QXX_Erase_Sector(write_addr);
W25QXX_BufferWrite((uint8_t *)buff, write_addr, count << 12);
status = RES_OK;
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_OK;
switch(cmd)
{
case CTRL_SYNC :
break;
/* 扇區數量:1536*4096/1024/1024=6(MB) */
case GET_SECTOR_COUNT:
*(DWORD * )buff = 1536;
break;
/* 扇區大小 */
case GET_SECTOR_SIZE :
*(WORD * )buff = 4096;
break;
/* 同時擦除扇區個數 */
case GET_BLOCK_SIZE :
*(DWORD * )buff = 1;
break;
case CTRL_TRIM:
break;
default:
status = RES_PARERR;
break;
}
return status;
/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */
七、修改main函式
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
UINT fnum; /* 檔案成功讀寫數量 */
BYTE ReadBuffer[1024] = {0}; /* 讀緩沖區 */
BYTE WriteBuffer[]= "Hello World!\n";
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
MX_FATFS_Init();
/* USER CODE BEGIN 2 */
printf("****** 這是一個SPI FLASH 檔案系統實驗 ******\r\n");
// 在外部SPI Flash掛載檔案系統,檔案系統掛載時會對SPI設備初始化
retUSER = f_mount(&USERFatFS, USERPath, 1);
/*----------------------- 格式化測驗 -----------------*/
/* 如果沒有檔案系統就格式化創建創建檔案系統 */
if(retUSER == FR_NO_FILESYSTEM)
{
printf("》FLASH還沒有檔案系統,即將進行格式化...\r\n");
/* 格式化 */
retUSER = f_mkfs(USERPath, 0, 0);
if(retUSER == FR_OK)
{
printf("》FLASH已成功格式化檔案系統,\r\n");
/* 格式化后,先取消掛載 */
retUSER = f_mount(NULL, USERPath, 1);
/* 重新掛載 */
retUSER = f_mount(&USERFatFS, USERPath, 1);
}
else
{
printf("《《格式化失敗,》》\r\n");
while(1);
}
}
else if(retUSER != FR_OK)
{
printf("!!外部Flash掛載檔案系統失敗,(%d)\r\n", retUSER);
printf("!!可能原因:SPI Flash初始化不成功,\r\n");
while(1);
}
else
{
printf("》檔案系統掛載成功,可以進行讀寫測驗\r\n");
}
/*----------------------- 檔案系統測驗:寫測驗 -------------------*/
/* 打開檔案,每次都以新建的形式打開,屬性為可寫 */
printf("\r\n****** 即將進行檔案寫入測驗... ******\r\n");
retUSER = f_open(&USERFile, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
if(retUSER == FR_OK)
{
printf("》打開/創建FatFs讀寫測驗檔案.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, "test.txt",FA_OPEN_EXISTING | FA_READ);
if(retUSER == FR_OK)
{
printf("》打開檔案成功,\r\n");
retUSER = f_read(&USERFile, ReadBuffer, sizeof(ReadBuffer), &fnum);
if(retUSER==FR_OK)
{
printf("》檔案讀取成功,讀到位元組資料:%d\r\n",fnum);
printf("》讀取得的檔案資料為:\r\n%s \r\n", ReadBuffer);
}
else
{
printf("!!檔案讀取失敗:(%d)\n",retUSER);
}
}
else
{
printf("!!打開檔案失敗,\r\n");
}
/* 不再讀寫,關閉檔案 */
f_close(&USERFile);
/* 不再使用檔案系統,取消掛載檔案系統 */
f_mount(NULL,"1:",1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
八、查看列印
串口列印功能查看 STM32CubeMX學習筆記(6)——USART串口使用

九、工程代碼
鏈接:(https://pan.baidu.com/s/1WAfBj091e7IJVLwaL1HyHA)[https://pan.baidu.com/s/1WAfBj091e7IJVLwaL1HyHA] 提取碼:w24p
十、注意事項
用戶代碼要加在 USER CODE BEGIN N 和 USER CODE END N 之間,否則下次使用 STM32CubeMX 重新生成代碼后,會被洗掉,

? 由 Leung 寫于 2021 年 4 月 2 日
? 參考:【STM32CubeMx你不知道的那些事】第九章:STM32CubeMx的SPI外置FLASH+檔案系統(FATFS)
STM32CubeMX系列|FATFS檔案系統
使用STM32CUBEMX生成FatFS代碼,操作SPI FLASH
STM32CUBEIDE之SPI讀寫FLASH進階串行FLASH檔案系統FatFs
3.1、CUBEMX使用FATFS讀寫SPI_FLASH
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/271888.html
標籤:其他
上一篇:物聯網簡史
下一篇:三菱Q系列PLC資料采集隨筆
