STM32CubeMX學(xué)習(xí)筆記(25)——FatFs文件系統(tǒng)使用(操作SPI Flash)

一、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.hdiskio.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ū)

緩存工作區(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ū)大小的最小值

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 NUSER 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末跪另,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子煤搜,更是在濱河造成了極大的恐慌免绿,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宅楞,死亡現(xiàn)場離奇詭異针姿,居然都是意外死亡袱吆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門距淫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绞绒,“玉大人,你說我怎么就攤上這事榕暇∨詈猓” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵彤枢,是天一觀的道長狰晚。 經(jīng)常有香客問我,道長缴啡,這世上最難降的妖魔是什么壁晒? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮业栅,結(jié)果婚禮上秒咐,老公的妹妹穿的比我還像新娘。我一直安慰自己碘裕,他們只是感情好携取,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帮孔,像睡著了一般雷滋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上文兢,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天晤斩,我揣著相機與錄音,去河邊找鬼禽作。 笑死尸昧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的旷偿。 我是一名探鬼主播烹俗,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼萍程!你這毒婦竟也來了幢妄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤茫负,失蹤者是張志新(化名)和其女友劉穎蕉鸳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡潮尝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年榕吼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勉失。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡羹蚣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乱凿,到底是詐尸還是另有隱情顽素,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布徒蟆,位于F島的核電站胁出,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏段审。R本人自食惡果不足惜全蝶,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寺枉。 院中可真熱鬧裸诽,春花似錦、人聲如沸型凳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甘畅。三九已至,卻和暖如春往弓,著一層夾襖步出監(jiān)牢的瞬間疏唾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工函似, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留槐脏,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓撇寞,卻偏偏與公主長得像顿天,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蔑担,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容