一、FatFs簡介
FatFs 是面向小型嵌入式系統(tǒng)的一種通用的 FAT 文件系統(tǒng)碑韵。它完全是由 ANSI C 語言編寫并且完全獨立于底層的 I/O 介質(zhì)叁怪。因此它可以很容易地不加修改地移植到其他的處理器當(dāng)中族展,如 8051森缠、PIC、AVR仪缸、SH贵涵、Z80、H8、ARM 等独悴。FatFs 支持 FAT12例书、FAT16、FAT32 等格式刻炒,所以我們利用前面寫好的 SPI Flash 芯片驅(qū)動决采,把 FatFs 文件系統(tǒng)代碼移植到工程之中,就可以利用文件系統(tǒng)的各種函數(shù)坟奥,對 SPI Flash 芯片以“文件”格式進行讀寫操作了树瞭。
FatFs 文件系統(tǒng)的源碼可以從 fatfs 官網(wǎng)下載:
http://elm-chan.org/fsw/ff/00index_e.html
1.1 FatFs文件系統(tǒng)布局
簇是文件存儲的最小單元,F(xiàn)AT32分區(qū)大小與對應(yīng)簇空間大小關(guān)系如下表示:
分區(qū)空間大小 | 簇空間大小 | 每個簇包含的扇區(qū)數(shù) |
---|---|---|
< 8GB | 4KB | 8 |
[ 8GB, 16GB ) | 8KB | 16 |
[ 16GB, 32GB ) | 16KB | 32 |
>= 32GB | 32KB | 64 |
例如:創(chuàng)建一個50字節(jié)的test.txt文件爱谁,文件大小是50字節(jié),但是占用磁盤空間為4096字節(jié)(一個簇)
1.2 FatFs層次結(jié)構(gòu)
最頂層是應(yīng)用層:使用者只需要調(diào)用FATFS模塊提供給用戶的一系列應(yīng)用接口函數(shù)(如f_open, f_read, f_write和f_close等),就可以像在PC上讀寫文件那樣簡單
中間層FATFS模塊:實現(xiàn)了FAT文件讀寫協(xié)議;它提供了ff.c和ff.h文件,一般情況下不用修改,使用時將頭文件包含進去即可
最底層是FATFS模塊的底層接口:包括存儲媒介讀寫接口和供給文件創(chuàng)建修改時間的實時時鐘,需要在移植時編寫對應(yīng)的代碼
FATFS源碼相關(guān)文件介紹如下表示;移植FATFS模塊時骇径,一般只需要修改2個文件(即ffconf.h和diskio.c)
與平臺無關(guān):
文件 | 說明 |
---|---|
ffconf.h | FATFS模塊配置文件 |
ff.h | FATFS和應(yīng)用模塊公用的包含文件 |
ff.c | FATFS模塊 |
diskio.h | FATFS和disk I/O模塊公用的包含文件 |
interger.h | 數(shù)據(jù)類型定義 |
option | 可選的外部功能(比如支持中文) |
與平臺相關(guān):
文件 | 說明 |
---|---|
diskio.c | FATFS和disk I/O模塊接口層文件 |
1.3 FatFs API
1.3.1 f_mount
功能 | 在FatFs模塊上注冊、注銷一個工作區(qū)(文件系統(tǒng)對象) |
---|---|
函數(shù)定義 | FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt) |
參數(shù) | fs:工作區(qū)(文件系統(tǒng)對象)指針 path:注冊/注銷工作區(qū)的邏輯驅(qū)動器號 opt:注冊或注銷選項 |
返回 | 操作結(jié)果 |
1.3.2 f_open
功能 | 創(chuàng)建/打開一個文件對象 |
---|---|
函數(shù)定義 | FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode) |
參數(shù) | fp:將被創(chuàng)建的文件對象結(jié)構(gòu)的指針 path:文件名指針,指定將創(chuàng)建或打開的文件名 mode:訪問類型和打開方法,由一下標(biāo)準(zhǔn)的一個組合指定的 |
返回 | 操作結(jié)果 |
模式 | 描述 |
---|---|
FA_READ | 指定讀訪問對象。可以從文件中讀取數(shù)據(jù)。 與FA_WRITE 結(jié) 合可以進行讀寫訪問。 |
FA_WRITE | 指定寫訪問對象∮辏可以向文件中寫入數(shù)據(jù)代咸。與FA_READ 結(jié)合 可以進行讀寫訪問奋岁。 |
FA_OPEN_EXISTING | 打開文件滨攻。如果文件不存在,則打開失敗。(默認(rèn)) |
FA_OPEN_ALWAYS | 如果文件存在,則打開;否則,創(chuàng)建一個新文件。 |
FA_CREATE_NEW | 創(chuàng)建一個新文件。如果文件已存在,則創(chuàng)建失敗。 |
FA_CREATE_ALWAYS | 創(chuàng)建一個新文件。如果文件已存在圃阳,則它將被截斷并覆蓋。 |
1.3.3 f_close
功能 | 關(guān)閉一個打開的文件 |
---|---|
函數(shù)定義 | FRESULT f_close(FIL* fp) |
參數(shù) | fp:指向?qū)⒈魂P(guān)閉的已打開的文件對象結(jié)構(gòu)的指針 |
返回 | 操作結(jié)果 |
1.3.4 f_read
功能 | 從一個打開的文件中讀取數(shù)據(jù) |
---|---|
函數(shù)定義 | FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br) |
參數(shù) | fp:指向?qū)⒈蛔x取的已打開的文件對象結(jié)構(gòu)的指針 buff:指向存儲讀取數(shù)據(jù)的緩沖區(qū)的指針 btr:要讀取的字節(jié)數(shù) br:指向返回已讀取字節(jié)數(shù)的UINT變量的指針晕城,返回為實際讀取的字節(jié)數(shù) |
返回 | 操作結(jié)果 |
1.3.5 f_write
功能 | 寫入數(shù)據(jù)到一個已打開的文件 |
---|---|
函數(shù)定義 | FRESULT f_write(FIL* fp, void* buff, UINT btw, UINT* bw) |
參數(shù) | fp:指向?qū)⒈粚懭氲囊汛蜷_的文件對象結(jié)構(gòu)的指針 buff:指向存儲寫入數(shù)據(jù)的緩沖區(qū)的指針 btw:要寫入的字節(jié)數(shù) bw:指向返回已寫入字節(jié)數(shù)的UINT變量的指針豌熄,返回為實際寫入的字節(jié)數(shù) |
返回 | 操作結(jié)果 |
另外FatFs還有很多API操作函數(shù)芯肤,在這里不再作詳細的介紹击蹲,詳細信息請查看FatFs文件系統(tǒng)官網(wǎng)。
二岭佳、新建工程
1. 打開 STM32CubeMX 軟件螟炫,點擊“新建工程”
2. 選擇 MCU 和封裝
3. 配置時鐘
RCC 設(shè)置掸屡,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
選擇 Clock Configuration,配置系統(tǒng)時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后然评,輸入回車仅财,軟件會自動修改所有配置
4. 配置調(diào)試模式
非常重要的一步,否則會造成第一次燒錄程序后續(xù)無法識別調(diào)試器
SYS 設(shè)置沾瓦,選擇 Debug 為 Serial Wire
三满着、SPI1
3.1 參數(shù)配置
在 Connectivity
中選擇 SPI1
設(shè)置,并選擇 Full-Duplex Master
全雙工主模式贯莺,不開啟 NSS
即不使用硬件片選信號
原理圖中雖然將 CS 片選接到了硬件 SPI1 的 NSS 引腳风喇,因為硬件 NSS 使用比較麻煩,所以后面直接把 PA4 配置為普通 GPIO缕探,手動控制片選信號魂莫。
在右邊圖中找到 SPI1 NSS 對應(yīng)引腳,選擇 GPIO_Output
爹耗。糾正:野火STM32F103指南者開發(fā)板SPI1 NSS須配置為PC0
修改輸出高電平 High
耙考,標(biāo)簽為 W25Q64_CHIP_SELECT
。
SPI 為默認(rèn)設(shè)置不作修改潭兽。只需注意一下倦始,Prescaler
分頻系數(shù)最低為 4
,波特率 (Baud Rate) 為 18.0 MBits/s
山卦。這里被限制了鞋邑,SPI1 最高通信速率可達 36Mbtis/s。
- Clock Polarity(CPOL):SPI 通訊設(shè)備處于空閑狀態(tài)時账蓉,SCK 信號線的電平信號(即 SPI 通訊開始前枚碗、 NSS 線為高電平時 SCK 的狀態(tài))。CPOL=0 時铸本, SCK 在空閑狀態(tài)時為低電平肮雨,CPOL=1 時,則相反箱玷。
-
Clock Phase(CPHA):指數(shù)據(jù)的采樣的時刻怨规,當(dāng) CPHA=0 時陌宿,MOSI 或 MISO 數(shù)據(jù)線上的信號將會在 SCK 時鐘線的“奇數(shù)邊沿”被采樣。當(dāng) CPHA=1 時椅亚,數(shù)據(jù)線在 SCK 的“偶數(shù)邊沿”采樣限番。
根據(jù) FLASH 芯片的說明,它支持 SPI模式0
及模式 3
呀舔,支持雙線全雙工,使用 MSB 先行模式扩灯,數(shù)據(jù)幀長度為 8 位媚赖。
所以這里配置 CPOL 為Low
,CPHA 為1 Edge
即 SPI模式0
珠插。
四惧磺、FATFS
4.1 參數(shù)配置
在 Middleware
中選擇 FATFS
設(shè)置,并勾選 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
支持長文件名捻撑,并指定使用椖グ空間為緩沖區(qū)
-
CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows)
緩存工作區(qū)為什么放在棧?其實fatfs提供了三個選項:BSS顾患,STACK , HEAP番捂,根據(jù)個人情況選一個。
在BSS上啟用帶有靜態(tài)工作緩沖區(qū)的LFN江解,不能動態(tài)分配设预。
如果選擇了HEAP(堆)且自己有屬于自己的malloc就去重寫ff_memalloc ff_memfree函數(shù)。如果是庫的malloc就不需要犁河。
一般都選擇使用STACK(棧)鳖枕,能動態(tài)分配。
當(dāng)使用堆棧作為工作緩沖區(qū)時桨螺,請注意堆棧溢出宾符。
-
Physical Drive Parameters:
-
VOLUMES(Logical drivers): 2
指定物理設(shè)備數(shù)量,這里設(shè)置為 2灭翔,包括預(yù)留 SD 卡和 SPI Flash 芯片
-
MAX_SS(Maximum Sector Size): 4096
指定扇區(qū)大小的最大值魏烫。SD 卡扇區(qū)大小一般都為 512 字節(jié),SPI Flash 芯片扇區(qū)大小一般設(shè)置為 4096 字節(jié)缠局,所以需要把 _MAX_SS 改為 4096
-
MIN_SS(Minimum Sector Size): 512
指定扇區(qū)大小的最小值
-
VOLUMES(Logical drivers): 2
4.2 增大椩虬拢空間
將最小棧空間改到 0x1000
注意:由于剛才設(shè)置長文件名動態(tài)緩存存儲在堆中狭园,故需要增大棧大小读处,如果不修改則程序運行時棧會生成溢出,程序進入硬件錯誤中斷(HardFault)唱矛,死循環(huán)罚舱。
4.3 生成代碼
輸入項目名和項目路徑
選擇應(yīng)用的 IDE 開發(fā)環(huán)境 MDK-ARM V5
每個外設(shè)生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應(yīng)的外設(shè)文件井辜。 如 GPIO 初始化代碼生成在 gpio.c 中。
點擊 GENERATE CODE 生成代碼
五管闷、添加SPI Flash操作函數(shù)
在 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發(fā)送指定長度的數(shù)據(jù)
* @param buf —— 發(fā)送數(shù)據(jù)緩沖區(qū)首地址
* @param size —— 要發(fā)送數(shù)據(jù)的字節(jié)數(shù)
* @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接收指定長度的數(shù)據(jù)
* @param buf —— 接收數(shù)據(jù)緩沖區(qū)首地址
* @param size —— 要接收數(shù)據(jù)的字節(jié)數(shù)
* @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在發(fā)送數(shù)據(jù)的同時接收指定長度的數(shù)據(jù)
* @param send_buf —— 接收數(shù)據(jù)緩沖區(qū)首地址
* @param recv_buf —— 接收數(shù)據(jù)緩沖區(qū)首地址
* @param size —— 要發(fā)送/接收數(shù)據(jù)的字節(jié)數(shù)
* @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 等待超時回調(diào)函數(shù)
* @param None.
* @retval None.
*/
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* 等待超時后的處理,輸出錯誤信息 */
printf("SPI 等待超時!errorCode = %d",errorCode);
return 0;
}
/**
* @brief 使用SPI發(fā)送一個字節(jié)的數(shù)據(jù)
* @param byte:要發(fā)送的數(shù)據(jù)
* @retval 返回接收到的數(shù)據(jù)
*/
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待發(fā)送緩沖區(qū)為空粥脚,TXE事件 */
while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_TXE ) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
}
/* 寫入數(shù)據(jù)寄存器,把要寫入的數(shù)據(jù)寫入發(fā)送緩沖區(qū) */
WRITE_REG(hspi1.Instance->DR, byte);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待接收緩沖區(qū)非空包个,RXNE事件 */
while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_RXNE ) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
}
/* 讀取數(shù)據(jù)寄存器刷允,獲取接收緩沖區(qū)數(shù)據(jù) */
return READ_REG(hspi1.Instance->DR);
}
/**
* @brief 讀取Flash內(nèi)部的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}; //待發(fā)送數(shù)據(jù),命令+地址
/* 使能片選 */
SPI_FLASH_CS_LOW();
/* 發(fā)送并讀取數(shù)據(jù) */
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的狀態(tài)寄存器碧囊,W25Q64一共有2個狀態(tài)寄存器
* @param reg —— 狀態(tài)寄存器編號(1~2)
* @retval 狀態(tài)寄存器的值
*/
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處于空閑狀態(tài)
* @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擦除一個扇區(qū)
* @param sector_addr —— 扇區(qū)地址 根據(jù)實際容量設(shè)置
* @retval none
* @note 阻塞操作
*/
void W25QXX_Erase_Sector(uint32_t sector_addr)
{
W25QXX_Write_Enable(); //擦除操作即寫入0xFF树灶,需要開啟寫使能
W25QXX_Wait_Busy(); //等待寫使能完成
/* 使能片選 */
SPI_FLASH_CS_LOW();
/* 發(fā)送扇區(qū)擦除指令*/
SPI_FLASH_SendByte(SECTOR_ERASE_CMD);
/*發(fā)送擦除扇區(qū)地址的高位*/
SPI_FLASH_SendByte((sector_addr & 0xFF0000) >> 16);
/* 發(fā)送擦除扇區(qū)地址的中位 */
SPI_FLASH_SendByte((sector_addr & 0xFF00) >> 8);
/* 發(fā)送擦除扇區(qū)地址的低位 */
SPI_FLASH_SendByte(sector_addr & 0xFF);
/* 取消片選 */
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy(); //等待扇區(qū)擦除完成
}
/**
* @brief 頁寫入操作
* @param dat —— 要寫入的數(shù)據(jù)緩沖區(qū)首地址
* @param WriteAddr —— 要寫入的地址
* @param byte_to_write —— 要寫入的字節(jié)數(shù)(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;
/* 發(fā)送 讀 地址高位 */
addr = (WriteAddr & 0xFF0000) >> 16;
status = SPI_Transmit(&addr, 1);
/* 發(fā)送 讀 地址中位 */
addr = (WriteAddr & 0xFF00) >> 8;
status = SPI_Transmit(&addr, 1);
/* 發(fā)送 讀 地址低位 */
addr = WriteAddr & 0xFF;
status = SPI_Transmit(&addr, 1);
SPI_Transmit(dat, nbytes);
/* 取消片選 */
SPI_FLASH_CS_HIGH();
W25QXX_Wait_Busy();
}
/**
* @brief 對FLASH寫入數(shù)據(jù),調(diào)用本函數(shù)寫入數(shù)據(jù)前需要先擦除扇區(qū)
* @param pBuffer糯而,要寫入數(shù)據(jù)的指針
* @param WriteAddr天通,寫入地址
* @param NumByteToWrite,寫入數(shù)據(jù)長度
* @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整數(shù)倍像寒,運算結(jié)果Addr值為0*/
Addr = WriteAddr % SPI_FLASH_PageSize;
/*差count個數(shù)據(jù)值,剛好可以對齊到頁地址*/
count = SPI_FLASH_PageSize - Addr;
/*計算出要寫多少整數(shù)頁*/
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
/*mod運算求余瓜贾,計算出剩余不滿一頁的字節(jié)數(shù)*/
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
{
/*先把整數(shù)頁都寫了*/
while(NumOfPage--)
{
W25QXX_PageProgram(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不滿一頁的數(shù)據(jù)诺祸,把它寫完*/
W25QXX_PageProgram(pBuffer, WriteAddr, NumOfSingle);
}
}
/* 若地址與 SPI_FLASH_PageSize 不對齊 */
else
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if(NumOfPage == 0)
{
/*當(dāng)前頁剩余的count個位置比NumOfSingle小,寫不完*/
if(NumOfSingle > count)
{
temp = NumOfSingle - count;
/*先寫滿當(dāng)前頁*/
W25QXX_PageProgram(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
/*再寫剩余的數(shù)據(jù)*/
W25QXX_PageProgram(pBuffer, WriteAddr, temp);
}
/*當(dāng)前頁剩余的count個位置能寫完NumOfSingle個數(shù)據(jù)*/
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;
/*把整數(shù)頁都寫了*/
while(NumOfPage--)
{
W25QXX_PageProgram(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不滿一頁的數(shù)據(jù)序臂,把它寫完*/
if(NumOfSingle != 0)
{
W25QXX_PageProgram(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
/**
* @brief 讀取FLASH數(shù)據(jù)
* @param pBuffer,存儲讀出數(shù)據(jù)的指針
* @param ReadAddr实束,讀取地址
* @param NumByteToRead奥秆,讀取數(shù)據(jù)長度
* @retval 無
*/
void W25QXX_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
W25QXX_Wait_Busy();
/* 選擇FLASH: CS低電平 */
SPI_FLASH_CS_LOW();
/* 發(fā)送 讀 指令 */
uint8_t cmd = READ_DATA_CMD;
SPI_Transmit(&cmd, 1);
// 不知道為什么連起來發(fā)不行
// ReadAddr = ReadAddr << 8;
// SPI_Transmit((uint8_t*)&ReadAddr, 3);
uint8_t addr;
HAL_StatusTypeDef status;
/* 發(fā)送 讀 地址高位 */
addr = (ReadAddr & 0xFF0000) >> 16;
status = SPI_Transmit(&addr, 1);
/* 發(fā)送 讀 地址中位 */
addr = (ReadAddr& 0xFF00) >> 8;
status = SPI_Transmit(&addr, 1);
/* 發(fā)送 讀 地址低位 */
addr = ReadAddr & 0xFF;
status = SPI_Transmit(&addr, 1);
if(HAL_OK == status)
{
SPI_Receive(pBuffer, NumByteToRead);
}
/* 停止信號 FLASH: CS 高電平 */
SPI_FLASH_CS_HIGH();
}
六、修改diskio接口函數(shù)
在 user_diskio.c
中修改以下幾個函數(shù):
/* 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咸灿,否則返回參數(shù)錯誤
}
// /* 扇區(qū)偏移2MB构订,外部Flash文件系統(tǒng)空間放在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 */
}
///* 扇區(qū)偏移2MB,外部Flash文件系統(tǒng)空間放在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;
/* 扇區(qū)數(shù)量:1536*4096/1024/1024=6(MB) */
case GET_SECTOR_COUNT:
*(DWORD * )buff = 1536;
break;
/* 扇區(qū)大小 */
case GET_SECTOR_SIZE :
*(WORD * )buff = 4096;
break;
/* 同時擦除扇區(qū)個數(shù) */
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函數(shù)
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
UINT fnum; /* 文件成功讀寫數(shù)量 */
BYTE ReadBuffer[1024] = {0}; /* 讀緩沖區(qū) */
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 文件系統(tǒng)實驗 ******\r\n");
// 在外部SPI Flash掛載文件系統(tǒng)悼瘾,文件系統(tǒng)掛載時會對SPI設(shè)備初始化
retUSER = f_mount(&USERFatFS, USERPath, 1);
/*----------------------- 格式化測試 -----------------*/
/* 如果沒有文件系統(tǒng)就格式化創(chuàng)建創(chuàng)建文件系統(tǒng) */
if(retUSER == FR_NO_FILESYSTEM)
{
printf("》FLASH還沒有文件系統(tǒng),即將進行格式化...\r\n");
/* 格式化 */
retUSER = f_mkfs(USERPath, 0, 0);
if(retUSER == FR_OK)
{
printf("》FLASH已成功格式化文件系統(tǒng)审胸。\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("L潭蟆!外部Flash掛載文件系統(tǒng)失敗碍庵。(%d)\r\n", retUSER);
printf("S称蟆悟狱!可能原因:SPI Flash初始化不成功。\r\n");
while(1);
}
else
{
printf("》文件系統(tǒng)掛載成功堰氓,可以進行讀寫測試\r\n");
}
/*----------------------- 文件系統(tǒng)測試:寫測試 -------------------*/
/* 打開文件挤渐,每次都以新建的形式打開,屬性為可寫 */
printf("\r\n****** 即將進行文件寫入測試... ******\r\n");
retUSER = f_open(&USERFile, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
if(retUSER == FR_OK)
{
printf("》打開/創(chuàng)建FatFs讀寫測試文件.txt文件成功双絮,向文件寫入數(shù)據(jù)浴麻。\r\n");
/* 將指定存儲區(qū)內(nèi)容寫入到文件內(nèi) */
retUSER = f_write(&USERFile, WriteBuffer, sizeof(WriteBuffer), &fnum);
if(retUSER == FR_OK)
{
printf("》文件寫入成功,寫入字節(jié)數(shù)據(jù):%d\n", fnum);
printf("》向文件寫入的數(shù)據(jù)為:\r\n%s\r\n", WriteBuffer);
}
else
{
printf("V腊睢白胀!文件寫入失敗:(%d)\n", retUSER);
}
/* 不再讀寫,關(guān)閉文件 */
f_close(&USERFile);
}
else
{
printf("!钉跷!打開/創(chuàng)建文件失敗该编。\r\n");
}
/*------------------- 文件系統(tǒng)測試:讀測試 --------------------------*/
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("》文件讀取成功,讀到字節(jié)數(shù)據(jù):%d\r\n",fnum);
printf("》讀取得的文件數(shù)據(jù)為:\r\n%s \r\n", ReadBuffer);
}
else
{
printf("H暇场胚委!文件讀取失敗:(%d)\n",retUSER);
}
}
else
{
printf("2嫘拧亩冬!打開文件失敗。\r\n");
}
/* 不再讀寫硼身,關(guān)閉文件 */
f_close(&USERFile);
/* 不再使用文件系統(tǒng)硅急,取消掛載文件系統(tǒng) */
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學(xué)習(xí)筆記(6)——USART串口使用
九佳遂、工程代碼
鏈接:https://pan.baidu.com/s/1WAfBj091e7IJVLwaL1HyHA 提取碼:w24p
十营袜、注意事項
用戶代碼要加在 USER CODE BEGIN N
和 USER CODE END N
之間,否則下次使用 STM32CubeMX 重新生成代碼后丑罪,會被刪除荚板。
? 由 Leung 寫于 2021 年 4 月 2 日
? 參考:【STM32CubeMx你不知道的那些事】第九章:STM32CubeMx的SPI外置FLASH+文件系統(tǒng)(FATFS)
STM32CubeMX系列|FATFS文件系統(tǒng)
使用STM32CUBEMX生成FatFS代碼,操作SPI FLASH
STM32CUBEIDE之SPI讀寫FLASH進階串行FLASH文件系統(tǒng)FatFs
3.1吩屹、CUBEMX使用FATFS讀寫SPI_FLASH