主頁 >  其他 > STM32CubeMX學習筆記(25)——FatFs檔案系統使用(操作SPI Flash)

STM32CubeMX學習筆記(25)——FatFs檔案系統使用(操作SPI Flash)

2021-04-03 13:21:17 其他

一、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磁區大小與對應簇空間大小關系如下表示:

磁區空間大小簇空間大小每個簇包含的扇區數
< 8GB4KB8
[ 8GB, 16GB )8KB16
[ 16GB, 32GB )16KB32
>= 32GB32KB64

例如:創建一個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.hdiskio.c

與平臺無關:

檔案說明
ffconf.hFATFS模塊組態檔
ff.hFATFS和應用模塊公用的包含檔案
ff.cFATFS模塊
diskio.hFATFS和disk I/O模塊公用的包含檔案
interger.h資料型別定義
option可選的外部功能(比如支持中文)

與平臺相關:

檔案說明
diskio.cFATFS和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 支持長檔案名,并指定使用堆疊空間為緩沖區

快取作業區為什么放在堆疊?其實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 指定扇區大小的最小值

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 NUSER 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資料采集隨筆

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more