第24章 QSPI—讀寫串行FLASH


本章參考資料:《STM32F76xxx參考手冊》、《STM32F76xxx規格書》、庫幫助文檔《STM32F779xx_User_Manual.chm》及《SPI總線協議介紹》。

若對SPI通訊協議不了解,可先閱讀《SPI總線協議介紹》文檔的內容學習。

關於FLASH存儲器,請參考“常用存儲器介紹”章節,實驗中FLASH芯片的具體參數,請參考其規格書《W25Q128》來了解。

24.1  QSPI協議簡介

QSPIQueued SPI的簡寫,是Motorola公司推出的SPI接口的擴展,比SPI應用更加廣泛。在SPI協議的基礎上,Motorola公司對其功能進行了增強,增加了隊列傳輸機制,推出了隊列串行外圍接口協議(即QSPI協議)。QSPI 是一種專用的通信接口,連接單、雙或四(條數據線) SPI Flash 存儲介質。

該接口可以在以下三種模式下工作:

① 間接模式:使用 QSPI 寄存器執行全部操作

② 狀態輪詢模式:周期性讀取外部 Flash 狀態寄存器,而且標志位置 1 時會產生中斷(如擦除或燒寫完成,會產生中斷)

③ 內存映射模式:外部 Flash 映射到微控制器地址空間,從而系統將其視作內部存儲器

采用雙閃存模式時,將同時訪問兩個 Quad-SPI Flash,吞吐量和容量均可提高二倍。

1.1.1  QSPI功能框圖

QSPI功能框圖,雙閃存模式禁止見 24-1。

 

 

24-1 QUADSPI 功能框圖(雙閃存模式禁止)

我們的開發板采用的是雙閃存禁止的模式連接單片QSPI FlashQSPI 使用 6 個信號連接Flash,分別是四個數據線BK1_IO0~BK1_IO3,一個時鍾輸出CLK,一個片選輸出(低電平有效)BK1_nCS,它們的作用介紹如下:

(1) BK1_nCS:片選輸出(低電平有效),適用於 FLASH 1。如果 QSPI 始終在雙閃存模式下工作,則其也可用於 FLASH 2從設備選擇信號線。QSPI通訊以BK1_nCS線置低電平為開始信號,以BK1_nCS線被拉高作為結束信號。

(2) CLK:時鍾輸出,適用於兩個存儲器,用於通訊數據同步。它由通訊主機產生,決定了通訊的速率,不同的設備支持的最高時鍾頻率不一樣,如STM32QSPI時鍾頻率最大為fpclk/2,兩個設備之間通訊時,通訊速率受限於低速設備。

(3) BK1_IO0:在雙線 / 四線模式中為雙向 IO,單線模式中為串行輸出,適用於FLASH 1。

(4) BK1_IO1:在雙線 / 四線模式中為雙向 IO,單線模式中為串行輸入,適用於FLASH 1。

(5) BK1_IO2在四線模式中為雙向 IO,適用於 FLASH 1

(6) BK1_IO3在四線模式中為雙向 IO,適用於 FLASH 1

 

24.1.2  QSPI命令序列

QUADSPI 通過命令與 Flash 通信 每條命令包括指令、地址、交替字節、空指令和數據這五個階段 任一階段均可跳過,但至少要包含指令、地址、交替字節或數據階段之一。nCS 在每條指令開始前下降,在每條指令完成后再次上升。先看看QSPI四線模式下的讀命令時序,見 24-2。

 

 

 

24-2 四線模式下的讀命令時序

1. 指令階段

這一階段,將在 QUADSPI_CCR[7:0] 寄存器的 INSTRUCTION 字段中配置的一條 8 位指令發送到 Flash,指定待執行操作的類型。

盡管大多數 Flash IO0/SO 信號(單線 SPI 模式)只能以一次 1 位的方式接收指令,但指令階段可選擇一次發送 2 位(在雙線 SPI 模式中通過 IO0/IO1)或一次發送 4 位(在四線SPI 模式中通過 IO0/IO1/IO2/IO3)。這可通過 QUADSPI_CCR[9:8] 寄存器中的 IMODE[1:0]字段進行配置。

IMODE = 00,則跳過指令階段,命令序列從地址階段(如果存在)開始。

 

1. 地址階段

在地址階段,將1-4字節發送到Flash,指示操作地址。待發送的地址字節數在QUADSPI_CCR[13:12]寄存器的ADSIZE[1:0]字段中進行配置。在間接模式和自動輪詢模式下,待發送的地址字節在QUADSPI_AR寄存器的ADDRESS[31:0]中指定在內存映射模式下,則通過 AHB(來自於 Cortex ® DMA)直接給出地址。地址階段可一次發送1 位(在單線SPI模式中通過SO)、2位(在雙線SPI模式中通過IO0/IO1)或4位(在四線 SPI 模式中通過 IO0/IO1/IO2/IO3)。這可通過QUADSPI_CCR[11:10]寄存器中的ADMODE[1:0]字段進行配置。

ADMODE = 00,則跳過地址階段,命令序列直接進入下一階段(如果存在)。

2. 交替字節階段

在交替字節階段,將 1-4 字節發送到 Flash,一般用於控制操作模式。待發送的交替字節數在 QUADSPI_CCR[17:16] 寄存器的 ABSIZE[1:0] 字段中進行配置。待發送的字節在QUADSPI_ABR 寄存器中指定。

交替字節階段可一次發送 1 位(在單線 SPI 模式中通過 SO)、2 位(在雙線 SPI 模式中通過 IO0/IO1)或 4 位(在四線 SPI 模式中通過 IO0/IO1/IO2/IO3)。這可通過QUADSPI_CCR[15:14] 寄存器中的 ABMODE[1:0] 字段進行配置。

ABMODE = 00,則跳過交替字節階段,命令序列直接進入下一階段(如果存在)。交替字節階段存在僅需發送單個半字節而不是一個全字節的情況,比如采用雙線模式並且僅使用兩個周期發送交替字節時。在這種情況下,固件可采用四線模式 (ABMODE = 11) 並發送一個字節,方法是 ALTERNATE 的位 7 3 置“1”(IO3 保持高電平)且位 6 2 置“0”(IO2 線保持低電平)。此時,半字節的高 2 位存放在 ALTERNATE 的位 4:3,低 2位存放在位 1 0 中。例如,如果半字節 2 (0010) 通過 IO0/IO1 發送,則 ALTERNATE 應設置為 0x8A (1000_1010)

3. 空指令周期階段

在空指令周期階段,給定的 1-31 個周期內不發送或接收任何數據,目的是當采用更高的時鍾頻率時,給 Flash 留出准備數據階段的時間。這一階段中給定的周期數在QUADSPI_CCR[22:18] 寄存器的 DCYC[4:0] 字段中指定。在 SDR DDR 模式下,持續時間被指定為一定個數的全時鍾周期。若 DCYC 為零,則跳過空指令周期階段,命令序列直接進入數據階段(如果存在)。空指令周期階段的操作模式由 DMODE 確定。為確保數據信號從輸出模式轉變為輸入模式有足夠的“周轉”時間,使用雙線和四線模式從Flash 接收數據時,至少需要指定一個空指令周期。

4. 數據階段

在數據階段,可從 Flash 接收或向其發送任意數量的字節。

在間接模式和自動輪詢模式下,待發送/接收的字節數在 QUADSPI_DLR 寄存器中指定。在間接寫入模式下,發送到 Flash 的數據必須寫入 QUADSPI_DR 寄存器。在間接讀取模式下,通過讀取 QUADSPI_DR 寄存器獲得從 Flash 接收的數據。在內存映射模式下,讀取的數據通過 AHB 直接發送回 Cortex DMA。數據階段可一次發送/接收 1 位(在單線 SPI 模式中通過 SO)、2 位(在雙線 SPI 模式中通過 IO0/IO1)或 4 位(在四線 SPI 模式中通過 IO0/IO1/IO2/IO3)。這可通過QUADSPI_CCR[15:14] 寄存器中的 ABMODE[1:0] 字段進行配置。若 DMODE = 00,則跳過數據階段,命令序列在拉高 nCS 時立即完成。這一配置僅可用於僅間接寫入模式。

 

24.2  QUADSPI 信號接口協議模式

24.2.1  單線 SPI 模式

傳統 SPI 模式允許串行發送/接收單獨的 1 位。在此模式下,數據通過 SO 信號(其 I/O IO0 共享)發送到 Flash。從 Flash 接收到的數據通過 SI(其 I/O IO1 共享)送達。通過將(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段設置為 01,可對不同的命令階段分別進行配置,以使用此單個位模式。在每個已配置為單線模式的階段中:

  • IO0 (SO) 處於輸出模式
  • IO1 (SI) 處於輸入模式(高阻抗)
  • IO2 處於輸出模式並強制置“0”(以禁止“寫保護”功能)
  • IO3 處於輸出模式並強制置“1”(以禁止“保持”功能)

DMODE = 01,這對於空指令階段也同樣如此。

24.2.2  雙線 SPI 模式

在雙線模式下,通過 IO0/IO1 信號同時發送/接收兩位。通過將 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段設置為 10,可對不同的命令階段分別進行配置,以使用雙線 SPI 模式。在每個已配置為單線模式的階段中:

  • IO0/IO1 在數據階段進行讀取操作時處於高阻態(輸入),在其他情況下為輸出
  • IO2 處於輸出模式並強制置“0”
  • IO3 處於輸出模式並強制置“1” 

在空指令階段,若 DMODE = 01,則 IO0/IO1 始終保持高阻態。

24.2.3  四線 SPI 模式

在四線模式下,通過 IO0/IO1/IO2/IO3 信號同時發送/接收四位。通過將 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段設置為 11,可對不同的命令階段分別進行配置,以使用四線 SPI 模式。在每個已配置為四線模式的階段中,IO0/IO1/IO2/IO3 在數據階段進行讀取操作時均處於高阻態(輸入),在其他情況下為輸出。在空指令階段中,若 DMODE = 11,則 IO0/IO1/IO2/IO3 均為高阻態。IO2 IO3 僅用於 Quad SPI 模式 如果未配置任何階段使用四線 SPI 模式,即使 UADSPI激活,對應 IO2 IO3 的引腳也可用於其他功能。

24.2.4  SDR 模式

默認情況下,DDRM (QUADSPI_CCR[31]) 0QUADSPI 在單倍數據速率 (SDR) 模式下工作。在 SDR 模式下,當 QUADSPI 驅動 IO0/SOIO1IO2IO3 信號時,這些信號僅在 CLK的下降沿發生轉變。在 SDR 模式下接收數據時,QUADSPI 假定 Flash 也通過 CLK 的下降沿發送數據。默認情況下 (SSHIFT = 0 ),將使用 CLK 后續的邊沿(上升沿)對信號進行采樣。

24.2.5  DDR 模式

DDRM (QUADSPI_CCR[31]) 1,則 QUADSPI 在雙倍數據速率 (DDR) 模式下工作。在 DDR 模式下,當 QUADSPI 在地址/交替字節/數據階段驅動 IO0/SOIO1IO2IO3 信號時,將在 CLK 的每個上升沿和下降沿發送 1 位。指令階段不受 DDRM 的影響。始終通過 CLK 的下降沿發送指令。在 DDR 模式下接收數據時,QUADSPI 假定 Flash 通過 CLK 的上升沿和下降沿均發送數據。若 DDRM = 1,固件必須清零 SSHIFT (QUADSPI_CR[4])。因此,在半個 CLK 周期后(下一個反向邊沿)對信號采樣。四線模式下DDR命令時序見 24-3。

 

24-3 四線模式下DDR命令時序

 

24.2.6  雙閃存模式

DFM (QUADSPI_CR[6]) 1QUADSPI 處於雙閃存模式。QUADSPI 使用兩個外部四線 SPI FlashFLASH 1 FLASH 2),在每個周期中發送/接收 8 位(在 DDR 模式下為16 位),能夠有效地將吞吐量和容量擴大一倍。每個 Flash 使用同一個 CLK 並可選擇使用同一個 nCS 信號,但其 IO0IO1IO2 IO3 信號是各自獨立的。雙閃存模式可與單比特模式、雙比特模式以及四比特模式結合使用,也可與 SDR DDR

式相結合。Flash 的大小在 FSIZE[4:0] (QUADSPI_DCR[20:16]) 中指定,指定的值應能夠反映 Flash 的總容量,即單個組件容量的 2 倍。如果地址 X 為偶數,QUADSPI 賦給地址 X 的字節是存放於 FLASH 1 的地址 X/2 中的字節,QUADSPI 賦給地址 X+1 的字節是存放於 FLASH 2 的地址 X/2 中的字節。也就是說,偶地址中的字節存儲於 FLASH 1,奇地址中的字節存儲於 FLASH 2

在雙閃存模式下讀取 Flash 狀態寄存器時,需要讀取的字節數是單閃存模式下的 2 倍。這意味着在狀態寄存器獲取指令到達后,如果每個 Flash 給出 8 個有效位,則 QUADSPI 必須配置為 2 個字節(16 位)的數據長度,它將從每個 Flash 接收 1 個字節。如果每個 Flash 給出一個 16 位的狀態,則 QUADSPI 必須配置為讀取 4 字節,以在雙閃存模式下可獲取兩個Flash 的所有狀態位。結果(在數據寄存器中)的最低有效字節是 FLASH 1 狀態寄存器的最低有效字節,而下一個字節是 FLASH 2 狀態寄存器的最低有效字節。數據寄存器的第三個字節是 FLASH 1 的第二個字節,第四個字節是 FLASH 2 的第二個字節(Flash 具有 16 位狀態寄存器時)。

偶數個字節必須始終在雙閃存模式下訪問。因此,若 DRM = 1,則數據長度字段(QUADSPI_DLR[0]) 的位 0 始終保持為 1

在雙閃存模式下,FLASH 1 接口信號的行為基本上與正常模式下相同。在指令、地址、交替字節以及空指令周期階段,FLASH 2 接口信號具有與 FLASH 1 接口信號完全相同的波形。也就是說,每個 Flash 總是接收相同的指令與地址。然后,在數據階段,BK1_IOx BK2_IOx 總線並行傳輸數據,但發送到 FLASH 1(或從其接收)的數據與 FLASH 2 中的不同。

24.3  QUADSPI 間接模式

在間接模式下,通過寫入 QUADSPI 寄存器來觸發命令;並通過讀寫數據寄存器來傳輸數據,就如同對待其他通信外設那樣。

FMODE = 00 (QUADSPI_CCR[27:26]),則 QUADSPI 處於間接寫入模式,字節在數據階段中發送到 Flash。通過寫入數據寄存器 (QUADSPI_DR) 的方式提供數據。

FMODE = 01,則 QUADSPI 處於間接讀取模式,在數據階段中從 Flash 接收字節。通過讀取 QUADSPI_DR 來獲取數據。

讀取/寫入的字節數在數據長度寄存器 QUADSPI_DLR) 中指定。

如果 QUADSPI_DLR =0xFFFF_FFFF(全為“1”),則數據長度視為未定義,QUADSPI 將繼續傳輸數據,直到到達(由 FSIZE 定義的)Flash 的結尾。如果不傳輸任何字節,DMODE (QUADSPI_CCR[25:24])應設置為 00。如果 QUADSPI_DLR = 0xFFFF_FFFF 並且 FSIZE = 0x1F(最大值指示一個 4GB Flash),在此特殊情況下,傳輸將無限繼續下去,僅在出現終止請求或 QUADSPI 被禁止后停止。在讀取最后一個存儲器地址后(地址為 0xFFFF_FFFF),將從地址 = 0x0000_0000開始繼續讀取。

當發送或接收的字節數達到編程設定值時,如果 TCIE = 1,則 TCF 1 並產生中斷。在數據數量不確定的情況下,將根據 QUADSPI_CR 中定義的 Flash 大小,在達到外部 SPI 的限制時,TCF 1

24.3.1  觸發命令啟動

從本質上講,在固件給出命令所需的最后一點信息時,命令即會啟動。根據 QUADSPI 的配置,在間接模式下有三種觸發命令啟動的方式。在出現以下情形時,命令立即啟動:

1、  INSTRUCTION[7:0] (QUADSPI_CCR) 執行寫入操作,如果沒有地址是必需的(當ADMODE = 00)並且不需要固件提供數據(當 FMODE = 01 DMODE = 00);

2、  ADDRESS[31:0] (QUADSPI_AR) 執行寫入操作,如果地址是必需的(當 ADMODE =00)並且不需要固件提供數據 (當 FMODE = 01 DMODE = 00);

3、  DATA[31:0] (QUADSPI_DR) 執行寫入操作,如果地址是必需的(當 ADMODE != 00)並且需要固件提供數據(當 FMODE = 00 並且 DMODE != 00)。

寫入交替字節寄存器 (QUADSPI_ABR) 始終不會觸發命令啟動。如果需要交替字節,必須預先進行編程。如果命令啟動,BUSY 位(QUADSPI_SR 的位 5)將自動置 1

24.3.2  FIFO 和數據管理

在間接模式中,數據將通過 QUADSPI 內部的一個 32 字節 FIFOFLEVEL[5:0](QUADSPI_SR[13:8]) 指示 FIFO 目前保存了多少字節。

在間接寫入模式下 (FMODE = 00),固件寫入 QUADSPI_DR 時,將在 FIFO 中加入數據。字寫入將在 FIFO 中增加 4 個字節,半字寫入增加 2 個字節,而字節寫入僅增加 1 個字節。如果固件在 FIFO 中加入的數據過多(超過 DL[31:0] 指示的值),將在寫入操作結束(TCF1)時從 FIFO 中清除超出的字節。

QUADSPI_DR 的字節/半字訪問必須僅針對該 32 位寄存器的最低有效字節/半字。FTHRES[3:0] 用於定義 FIFO 的閾值 如果達到閾值,FTFFIFO 閾值標志)置 1 在間接讀取模式下,從 FIFO 中讀取的有效字節數超過閾值時,FTF 1。從 Flash 中讀取最后一個字節后,如果 FIFO 中依然有數據,則無論 FTHRES 的設置為何,FTF 也都會置 1。在間接寫入模式下,當 FIFO 中的空字節數超過閾值時,FTF 1

如果 FTIE = 1,則 FTF 1 時產生中斷。如果 DMAEN = 1,則 FTF 1 時啟動數據傳送。如果閾值條件不再為“真”(CPU DMA 傳輸了足夠的數據后),則 FTF HW 清零。在間接模式下,當 FIFO 已滿,QUADSPI 將暫時停止從 Flash 讀取字節以避免上溢。請注意,只有在 FIFO 中的 4 個字節為空 (FLEVEL 11) 時才會重新開始讀取 Flash。因此,若FTHRES 13,應用程序必須讀取足夠的字節以確保 QUADSPI 再次從 Flash 檢索數據。否則,只要 11 < FLEVEL < FTHRESFTF 標志將保持為“0”。

24.4  QUADSPI Flash 配置

外部 SPI Flash的參數可以通過配置寄存器 (QUADSPI_DCR)實現。這里配置Flash的容量是設置FSIZE[4:0] 字段,使用下面的公式定義外部存儲器的大小:

 

FSIZE+1 是對 Flash 尋址所需的地址位數。在間接模式下,Flash 容量最高可達 4GB(使用32 位進行尋址),但在內存映射模式下的可尋址空間限制為 256MB。如果 DFM = 1FSIZE 表示兩個 Flash 容量的總和。QUADSPI 連續執行兩條命令時,它在兩條命令之間將片選信號 (nCS) 置為高電平默認僅一個 CLK 周期時長。如果 Flash 需要命令之間的時間更長,可使用片選高電平時間 (CSHT) 字段指定 nCS 必須保持高電平的最少 CLK 周期數(最大為 8)。時鍾模式 (CKMODE) 位指示命令之間的 CLK 信號邏輯電平(nCS = 1 時)。

24.5  QSPI初始化結構體詳解

跟其它外設一樣,STM32 HAL庫提供了QSPI初始化結構體及初始化函數來配置SPI外設。初始化結構體及函數定義在庫文件“stm32f7xx_hal_spi.h”及“stm32f7xx_hal _spi.c”中,編程時我們可以結合這兩個文件內的注釋使用或參考庫幫助文檔。了解初始化結構體后我們就能對SPI外設運用自如了,見代碼清單 241

代碼清單 241 QSPI_InitTypeDef初始化結構體

1 typedef struct {

 2     uint32_t ClockPrescaler;     //預分頻因子

 3     uint32_t FifoThreshold;      //FIFO中的閾值

 4     uint32_t SampleShifting;     //采樣移位

 5     uint32_t FlashSize;          //Flash大小

 6     uint32_t ChipSelectHighTime; //片選高電平時間

 7     uint32_t ClockMode;          //時鍾模式

 8     uint32_t FlashID;            //Flash ID

 9     uint32_t DualFlash;          //雙閃存模式

10 } QSPI_InitTypeDef;

這些結構體成員說明如下,其中括號內的文字是對應參數在STM32 HAL庫中定義的宏:

(1) ClockPrescaler

本成員設置預分頻因子,對應寄存器QUADSPI_CR [31:24]PRESCALER[7:0],取值范圍是0255,可以實現1256級別的分頻。僅可在 BUSY = 0 時修改該字段。

(2) FifoThreshold

本成員設置FIFO 閾值級別,對應寄存器QUADSPI_CR [12:8]FTHRES[4:0],定義在間接模式下 FIFO 中將導致 FIFO 閾值標志(FTFQUADSPI_SR[2])置 1 的字節數閾值。

(3) SampleShifting

本成員設置采樣,對應寄存器QUADSPI_CR [4],默認情況下,QUADSPI Flash 驅動數據后過半個 CLK 周期開始采集數據。使用該位,可考慮外部信號延遲,推遲數據采集。可以取值0:不發生移位;1:移位半個周期。在 DDR 模式下 (DDRM = 1),固件必須確保 SSHIFT = 0

(4) FlashSize

本成員設置FLASH大小,對應寄存器QUADSPI_CCR [20:16]FSIZE[4:0]。定義外部存儲器的大小,簡介模式Flash容量最高可達4GB32位尋址),但是在內存映射模式下限制為256MB,如果是雙閃存則可以達到512MB

(5) ChipSelectHighTime

本成員設置片選高電平時間,對應寄存器QUADSPI_CR [10:8]CSHT[2:0],定義片選 (nCS) 在發送至 Flash 的命令之間必須保持高電平的最少 CLK 周期數。可以取值1~8個周期。

(6) ClockMode

本成員設置時鍾模式,對應寄存器QUADSPI_CR [0],指示CLK在命令之間的電平,可以選模式01nCS 為高電平(片選釋放)時,CLK 必須保持低電平;或者模式3 1nCS 為高電平(片選釋放)時,CLK 必須保持高電平。

(7) FlashID

本成員用於選擇Flash1或者Flash2,單閃存模式下選擇需要訪問的flash

(8) DualFlash

本成員用於激活雙閃存模式0:禁止雙閃存模式;1:使能雙閃存模式。雙閃存模式可以使系統吞吐量和容量擴大一倍。

代碼清單 242 QSPI_CommandTypeDe通信配置命令結構體

1 typedef struct {

 2     uint32_t Instruction;        //指令

 3     uint32_t Address;            //地址

 4     uint32_t AlternateBytes;     //交替字節

 5     uint32_t AddressSize;        //地址長度

 6     uint32_t AlternateBytesSize; //交替字節長度

 7     uint32_t DummyCycles;        //空指令周期

 8     uint32_t InstructionMode;    //指令模式

 9     uint32_t AddressMode;        //地址模式

10     uint32_t AlternateByteMode;  //交替字節模式

11     uint32_t DataMode;           //數據模式

12     uint32_t NbData;             //數據長度

13     uint32_t DdrMode;            //雙倍數據速率模式

14     uint32_t DdrHoldHalfCycle;   //DDR保持周期

15     uint32_t SIOOMode;           //僅發送指令一次模式

16 } QSPI_CommandTypeDef; 

這些結構體成員說明如下,其中括號內的文字是對應參數在STM32 HAL庫中定義的宏:

(1) Instruction

本成員設置通信指令,指定要發送到外部 SPI 設備的指令。僅可在 BUSY = 0 時修改該字段。

(2) Address

本成員指定要發送到外部 Flash 的地址,BUSY = 0 FMODE = 11(內存映射模式)時,將忽略寫入該字段。在雙閃存模式下,由於地址始終為偶地址,ADDRESS[0] 自動保持為“0”。

(3) AlternateBytes

本成員指定要在地址后立即發送到外部 SPI 設備的可選數據,僅可在 BUSY = 0 時修改該字段。

(4) AddressSize

本成員定義地址長度,可以是8位,16位,24位或者32位。

(5) AlternateBytesSize

本成員定義交替字節長度,可以是8位,16位,24位或者32位。

(6) DummyCycles

本成員定義空指令階段的持續時間,在 SDR DDR 模式下,它指定 CLK 周期數 (0-31)

(7) InstructionMode

本成員定義指令階段的操作模式,00:無指令;01:單線傳輸指令;10:雙線傳輸指令;11:四線傳輸指令。

(8) AddressMode

本成員定義地址階段的操作模式,00:無地址;01:單線傳輸地址;10:雙線傳輸地址;11:四線傳輸地址。

(9) AlternateByteMode

本成員定義交替字節階段的操作模式00:無交替字節;01:單線傳輸交替字節;10:雙線傳輸交替字節;11:四線傳輸交替字節。

(10) DataMode

本成員定義數據階段的操作模式,00:無數據;01:單線傳輸數據;10:雙線傳輸數據;11:四線傳輸數據。該字段還定義空指令階段的操作模式。

(11) NbData

本成員設置數據長度,在間接模式和狀態輪詢模式下待檢索的數據數量(值 + 1)。對狀態輪詢模式應使用不大於 3 的值(表示 4 字節)。

(12) DdrMode

本成員為地址、交替字節和數據階段設置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。

(13) DdrHoldHalfCycle

本成員設置DDR 模式下數據輸出延遲 1/4 QUADSPI 輸出時鍾周期,0:使用模擬延遲來延遲數據輸出;1:數據輸出延遲 1/4 QUADSPI 輸出時鍾周期。僅在 DDR 模式下激活。

(14) SIOOMode

本成員設置僅發送指令一次模式,。IMODE = 00 時,該位不起作用。0:在每個事務中發送指令;1:僅為第一條命令發送指令。

24.6  QSPI—讀寫串行FLASH實驗

FLSAH存儲器又稱閃存,它與EEPROM都是掉電后數據不丟失的存儲器,但FLASH存儲器容量普遍大於EEPROM,現在基本取代了它的地位。我們生活中常用的U盤、SD卡、SSD固態硬盤以及我們STM32芯片內部用於存儲程序的設備,都是FLASH類型的存儲器。在存儲控制上,最主要的區別是FLASH芯片只能一大片一大片地擦寫,而在“I2C章節”中我們了解到EEPROM可以單個字節擦寫。

本小節以一種使用QSPI通訊的串行FLASH存儲芯片的讀寫實驗為大家講解STM32QSPI使用方法。實驗中STM32QSPI外設采用主模式,通過查詢事件的方式來確保正常通訊。

24.6.1  硬件設計

 

 

24-4 SPI串行FLASH硬件連接圖

本實驗板中的FLASH芯片(型號:W25Q128)是一種使用QSPI/SPI通訊協議的NOR FLASH存儲器,它的CS/CLK/D0/D1/D2/D3引腳分別連接到了STM32對應的QSPI引腳QUADSPI_BK1_NCS/ QUADSPI_CLK / QUADSPI_BK1_IO0/ QUADSPI_BK1_IO1/ QUADSPI_BK1_IO2/ QUADSPI_BK1_IO3上,這些引腳都是STM32的復用引腳。

關於FLASH芯片的更多信息,可參考其數據手冊《W25Q128》來了解。若您使用的實驗板FLASH的型號或控制引腳不一樣,只需根據我們的工程修改即可,程序的控制原理相同。

24.6.2  軟件設計

為了使工程更加有條理,我們把讀寫FLASH相關的代碼獨立分開存儲,方便以后移植。在“工程模板”之上新建“bsp_qspi_flash.c”及“bsp_qspi_ flash.h”文件,這些文件也可根據您的喜好命名,它們不屬於STM32 HAL庫的內容,是由我們自己根據應用需要編寫的。

1. 編程要點

(1) 初始化通訊使用的目標引腳及端口時鍾;

(2) 使能SPI外設的時鍾;

(3) 配置SPI外設的模式、地址、速率等參數並使能SPI外設;

(4) 編寫基本SPI按字節收發的函數;

(5) 編寫對FLASH擦除及讀寫操作的的函數;

(6) 編寫測試程序,對讀寫數據進行校驗。

2. 代碼分析
控制FLASH的指令

FLASH芯片自定義了很多指令,我們通過控制STM32利用QSPI總線向FLASH芯片發送指令,FLASH芯片收到后就會執行相應的操作。

而這些指令,對主機端(STM32)來說,只是它遵守最基本的QSPI通訊協議發送出的數據,但在設備端(FLASH芯片)把這些數據解釋成不同的意義,所以才成為指令。查看FLASH芯片的數據手冊《W25Q128》,可了解各種它定義的各種指令的功能及指令格式,見 24-1。

24-1 FLASH常用芯片指令表(摘自規格書《W25Q128)

指令

第一字節(指令編碼)

第二字節

第三字節

第四字節

第五字節

第六字節

第七-N字節

Write Enable

06h

 

 

 

 

 

 

Write Disable

04h

 

 

 

 

 

 

Read Status Register

05h

(S7–S0)

 

 

 

 

 

Write Status Register

01h

(S7–S0)

 

 

 

 

 

Read Data

03h

A23–A16

A15–A8

A7–A0

(D7–D0)

(Next byte)

continuous

Fast Read

0Bh

A23–A16

A15–A8

A7–A0

dummy

(D7–D0)

(Next Byte) continuous

Fast Read Dual Output

3Bh

A23–A16

A15–A8

A7–A0

dummy

I/O = (D6,D4,D2,D0) O = (D7,D5,D3,D1)

(one byte per 4 clocks, continuous)

Page Program

02h

A23–A16

A15–A8

A7–A0

D7–D0

Next byte

Up to 256 bytes

Block Erase(64KB)

D8h

A23–A16

A15–A8

A7–A0

 

 

 

Sector Erase(4KB)

20h

A23–A16

A15–A8

A7–A0

 

 

 

Chip Erase

C7h

 

 

 

 

 

 

Power-down

B9h

 

 

 

 

 

 

Release Power- down / Device ID

ABh

dummy

dummy

dummy

(ID7-ID0)

 

 

Manufacturer/ Device ID

90h

dummy

dummy

00h

(M7-M0)

(ID7-ID0)

 

JEDEC ID

9Fh

(M7-M0)

生產廠商

(ID15-ID8)

 存儲器類型

(ID7-ID0) 容量

 

 

 

該表中的第一列為指令名,第二列為指令編碼,第三至第N列的具體內容根據指令的不同而有不同的含義。其中帶括號的字節參數,方向為FLASH向主機傳輸,即命令響應,不帶括號的則為主機向FLASH傳輸。表中“A0~A23FLASH芯片內部存儲器組織的地址;“M0~M7為廠商號(MANUFACTURER ID);ID0-ID15”為FLASH芯片的ID;“dummy”指該處可為任意數據;“D0~D7FLASH內部存儲矩陣的內容。

FLSAH芯片內部,存儲有固定的廠商編號(M7-M0)和不同類型FLASH芯片獨有的編號(ID15-ID0),見 24-2。

24-2 FLASH數據手冊的設備ID說明

FLASH型號

廠商號(M7-M0)

FLASH型號(ID15-ID0)

W25Q64

EF h

4017 h

W25Q128

EF h

4018 h

通過指令表中的讀ID指令“JEDEC ID”可以獲取這兩個編號,該指令編碼為9F h”,其中“9F h”是指16進制數“9F(相當於C語言中的0x9F)。緊跟指令編碼的三個字節分別為FLASH芯片輸出的“(M7-M0)”、“(ID15-ID8)”及“(ID7-ID0) 。

此處我們以該指令為例,配合其指令時序圖進行講解,見 24-5。

 

24-5 FLASHID指令“JEDEC ID”的時序(摘自規格書《W25Q128)

主機首先通過DIO(對應STM32QUADSPI_BK1_IO0)線向FLASH芯片發送第一個字節數據為“9F h,當FLASH芯片收到該數據后,它會解讀成主機向它發送了“JEDEC指令”,然后它就作出該命令的響應:通過DO(對應STM32QUADSPI_BK1_IO1)把它的廠商ID(M7-M0)及芯片類型(ID15-0)發送給主機,主機接收到指令響應后可進行校驗。常見的應用是主機端通過讀取設備ID來測試硬件是否連接正常,或用於識別設備。

對於FLASH芯片的其它指令,都是類似的,只是有的指令包含多個字節,或者響應包含更多的數據。

實際上,編寫設備驅動都是有一定的規律可循的。首先我們要確定設備使用的是什么通訊協議。如上一章的EEPROM使用的是I2C,本章的FLASH使用的是QSPI。那么我們就先根據它的通訊協議,選擇好STM32的硬件模塊,並進行相應的I2CSPI模塊初始化。接着,我們要了解目標設備的相關指令,因為不同的設備,都會有相應的不同的指令。如EEPROM中會把第一個數據解釋為內部存儲矩陣的地址(實質就是指令)。而FLASH則定義了更多的指令,有寫指令,讀指令,讀ID指令等等。最后,我們根據這些指令的格式要求,使用通訊協議向設備發送指令,達到控制設備的目標。

定義FLASH指令編碼表

為了方便使用,我們把FLASH芯片的常用指令編碼使用宏來封裝起來,后面需要發送指令編碼的時候我們直接使用這些宏即可,見代碼清單 24-3。

代碼清單 24-3 FLASH指令編碼表

1 /**

 2   * @brief  W25Q128FV 指令

 3   */

 4 /* 復位操作 */

 5 #define RESET_ENABLE_CMD                     0x66

 6 #define RESET_MEMORY_CMD                     0x99

 7

 8 #define ENTER_QPI_MODE_CMD                   0x38

 9 #define EXIT_QPI_MODE_CMD                    0xFF

10

11 /* 識別操作 */

12 #define READ_ID_CMD                          0x90

13 #define DUAL_READ_ID_CMD                     0x92

14 #define QUAD_READ_ID_CMD                     0x94

15 #define READ_JEDEC_ID_CMD                    0x9F

16

17 /* 讀操作 */

18 #define READ_CMD                             0x03

19 #define FAST_READ_CMD                        0x0B

20 #define DUAL_OUT_FAST_READ_CMD               0x3B

21 #define DUAL_INOUT_FAST_READ_CMD             0xBB

22 #define QUAD_OUT_FAST_READ_CMD               0x6B

23 #define QUAD_INOUT_FAST_READ_CMD             0xEB

24

25 /* 寫操作 */

26 #define WRITE_ENABLE_CMD                     0x06

27 #define WRITE_DISABLE_CMD                    0x04

28

29 /* 寄存器操作 */

30 #define READ_STATUS_REG1_CMD                  0x05

31 #define READ_STATUS_REG2_CMD                  0x35

32 #define READ_STATUS_REG3_CMD                  0x15

33

34 #define WRITE_STATUS_REG1_CMD                 0x01

35 #define WRITE_STATUS_REG2_CMD                 0x31

36 #define WRITE_STATUS_REG3_CMD                 0x11

37

38

39 /* 編程操作 */

40 #define PAGE_PROG_CMD                        0x02

41 #define QUAD_INPUT_PAGE_PROG_CMD             0x32

42 #define EXT_QUAD_IN_FAST_PROG_CMD            0x12

43

44 /* 擦除操作 */

45 #define SECTOR_ERASE_CMD                     0x20

46 #define CHIP_ERASE_CMD                       0xC7

47

48 #define PROG_ERASE_RESUME_CMD                0x7A

49 #define PROG_ERASE_SUSPEND_CMD               0x75

SPI硬件相關宏定義

我們把SPI硬件相關的配置都以宏的形式定義到 “bsp_qspi_ flash.h”文件中,見代碼清單 244。

代碼清單 244  SPI硬件配置相關的宏

1 #define QSPI_FLASH                         QUADSPI

 2 #define QSPI_FLASH_CLK_ENABLE()            __QSPI_CLK_ENABLE()

 3

 4 #define QSPI_FLASH_CLK_PIN                 GPIO_PIN_2

 5 #define QSPI_FLASH_CLK_GPIO_PORT           GPIOB

 6 #define QSPI_FLASH_CLK_GPIO_ENABLE()       __GPIOB_CLK_ENABLE()

 7 #define QSPI_FLASH_CLK_GPIO_AF             GPIO_AF9_QUADSPI

 8

 9 #define QSPI_FLASH_BK1_IO0_PIN             GPIO_PIN_8

10 #define QSPI_FLASH_BK1_IO0_PORT            GPIOF

11 #define QSPI_FLASH_BK1_IO0_CLK_ENABLE()    __GPIOF_CLK_ENABLE()

12 #define QSPI_FLASH_BK1_IO0_AF              GPIO_AF10_QUADSPI

13

14 #define QSPI_FLASH_BK1_IO1_PIN             GPIO_PIN_9

15 #define QSPI_FLASH_BK1_IO1_PORT            GPIOF

16 #define QSPI_FLASH_BK1_IO1_CLK_ENABLE()    __GPIOF_CLK_ENABLE()

17 #define QSPI_FLASH_BK1_IO1_AF              GPIO_AF10_QUADSPI

18

19 #define QSPI_FLASH_BK1_IO2_PIN             GPIO_PIN_7

20 #define QSPI_FLASH_BK1_IO2_PORT            GPIOF

21 #define QSPI_FLASH_BK1_IO2_CLK_ENABLE()    __GPIOF_CLK_ENABLE()

22 #define QSPI_FLASH_BK1_IO2_AF              GPIO_AF9_QUADSPI

23

24 #define QSPI_FLASH_BK1_IO3_PIN             GPIO_PIN_6

25 #define QSPI_FLASH_BK1_IO3_PORT            GPIOF

26 #define QSPI_FLASH_BK1_IO3_CLK_ENABLE()    __GPIOF_CLK_ENABLE()

27 #define QSPI_FLASH_BK1_IO3_AF              GPIO_AF9_QUADSPI

28

29 #define QSPI_FLASH_CS_PIN                 GPIO_PIN_6

30 #define QSPI_FLASH_CS_GPIO_PORT           GPIOB

31 #define QSPI_FLASH_CS_GPIO_CLK_ENABLE()   __GPIOB_CLK_ENABLE()

32 #define QSPI_FLASH_CS_GPIO_AF             GPIO_AF10_QUADSPI

以上代碼根據硬件連接,把與FLASH通訊使用的QSPI 、引腳號、引腳源以及復用功能映射都以宏封裝起來。

初始化QSPIGPIO 

利用上面的宏,編寫QSPI的初始化函數,見代碼清單 24-5。

代碼清單 24-5 QSPI的初始化函數

 1 /**

 2   * @brief  QSPI_FLASH引腳初始化

 3   * @param  

 4   * @retval

 5   */

 6 void QSPI_FLASH_Init(void)

 7 {

 8

 9     GPIO_InitTypeDef GPIO_InitStruct;

10

11     /* 使能 QSPI GPIO 時鍾 */

12     QSPI_FLASH_CLK_ENABLE();

13     QSPI_FLASH_CLK_GPIO_ENABLE();

14     QSPI_FLASH_BK1_IO0_CLK_ENABLE();

15     QSPI_FLASH_BK1_IO1_CLK_ENABLE();

16     QSPI_FLASH_BK1_IO2_CLK_ENABLE();

17     QSPI_FLASH_BK1_IO3_CLK_ENABLE();

18     QSPI_FLASH_CS_GPIO_CLK_ENABLE();

19

20     //設置引腳

21     /*!< 配置 QSPI_FLASH 引腳: CLK */

22     GPIO_InitStruct.Pin = QSPI_FLASH_CLK_PIN;

23     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;

24     GPIO_InitStruct.Pull = GPIO_NOPULL;

25     GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;

26     GPIO_InitStruct.Alternate = QSPI_FLASH_CLK_GPIO_AF;

27

28     HAL_GPIO_Init(QSPI_FLASH_CLK_GPIO_PORT, &GPIO_InitStruct);

29

30     /*!< 配置 QSPI_FLASH 引腳: IO0 */

31     GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO0_PIN;

32     GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO0_AF;

33     HAL_GPIO_Init(QSPI_FLASH_BK1_IO0_PORT, &GPIO_InitStruct);

34

35     /*!< 配置 QSPI_FLASH 引腳: IO1 */

36     GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO1_PIN;

37     GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO1_AF;

38     HAL_GPIO_Init(QSPI_FLASH_BK1_IO1_PORT, &GPIO_InitStruct);

39

40     /*!< 配置 QSPI_FLASH 引腳: IO2 */

41     GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO2_PIN;

42     GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO2_AF;

43     HAL_GPIO_Init(QSPI_FLASH_BK1_IO2_PORT, &GPIO_InitStruct);

44

45     /*!< 配置 QSPI_FLASH 引腳: IO3 */

46     GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO3_PIN;

47     GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO3_AF;

48     HAL_GPIO_Init(QSPI_FLASH_BK1_IO3_PORT, &GPIO_InitStruct);

49

50     /*!< 配置 SPI_FLASH_SPI 引腳: NCS */

51     GPIO_InitStruct.Pin = QSPI_FLASH_CS_PIN;

52     GPIO_InitStruct.Alternate = QSPI_FLASH_CS_GPIO_AF;

53     HAL_GPIO_Init(QSPI_FLASH_CS_GPIO_PORT, &GPIO_InitStruct);

54

55     /* QSPI_FLASH 模式配置 */

56     QSPIHandle.Instance = QUADSPI;

57     QSPIHandle.Init.ClockPrescaler = 2;

58     QSPIHandle.Init.FifoThreshold = 4;

59     QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;

60     QSPIHandle.Init.FlashSize = 23;

61     QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_8_CYCLE;

62     QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;

63     HAL_QSPI_Init(&QSPIHandle);

64     /*初始化QSPI接口*/

65     BSP_QSPI_Init();

66 }

與所有使用到GPIO的外設一樣,都要先把使用到的GPIO引腳模式初始化,配置好復用功能。GPIO初始化流程如下:

(1) 使用GPIO_InitTypeDef定義GPIO初始化結構體變量,以便下面用於存儲GPIO配置;

(2) 調用宏定義使能QSPI引腳使用的GPIO端口時鍾和QSPI外設時鍾。

(3) GPIO初始化結構體賦值,把CLK/IO0/IO1/IO2/IO3/NCS引腳初始化成復用推挽模式。

(4) 使用以上初始化結構體的配置,調用HAL_GPIO_Init函數向寄存器寫入參數,完成GPIO的初始化。

(5) 以上只是配置了QSPI使用的引腳,對QSPI外設模式的配置。在配置STM32QSPI模式前,我們要先了解從機端的QSPI模式。本例子中可通過查閱FLASH數據手冊《W25Q128》獲取。根據FLASH芯片的說明,它支持SPI模式0及模式3,支持四線模式,支持最高通訊時鍾為104MHz,數據幀長度為8位。我們要把STM32QSPI外設中的這些參數配置一致。見代碼清單 24-5。

(6) 配置QSPI接口模式;時鍾三分頻最高通訊時鍾為72MHzFlash最高支持104MHz);FIFO 閾值為 4 個字節;采樣移位半個周期;SPI FLASH 大小;W25Q128 大小為16M 字節,即這里地址位數為23+1=24,所以取值23;片選高電平時間為 1個時鍾(9.2*6=55.2ns),即手冊里面的 ;時鍾模式選擇為0;根據硬件連接選擇第一片Flash;最后調用HAL_QSPI_Init函數初始化QSPI模式。

初始化QSPI存儲器

初始化好QSPI外設后,還要初始化初始化QSPI存儲器,需要先復位存儲器,使能寫操作,配置狀態寄存器才可進行數據讀寫操作,見代碼清單 24-6。

代碼清單 24-6初始化QSPI存儲器

1 /**

 2   * @brief  初始化QSPI存儲器

 3   * @retval QSPI存儲器狀態

 4   */

 5 uint8_t BSP_QSPI_Init(void)

 6 {

 7     QSPI_CommandTypeDef s_command;

 8     uint8_t value = W25Q128FV_FSR_QE;

 9

10     /* QSPI存儲器復位 */

11     if (QSPI_ResetMemory() != QSPI_OK) {

12         return QSPI_NOT_SUPPORTED;

13     }

14

15     /* 使能寫操作 */

16     if (QSPI_WriteEnable() != QSPI_OK) {

17         return QSPI_ERROR;

18     }

19     /* 設置四路使能的狀態寄存器,使能四通道IO2IO3引腳 */

20     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

21     s_command.Instruction       = WRITE_STATUS_REG2_CMD;

22     s_command.AddressMode       = QSPI_ADDRESS_NONE;

23     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

24     s_command.DataMode          = QSPI_DATA_1_LINE;

25     s_command.DummyCycles       = 0;

26     s_command.NbData            = 1;

27     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

28     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

29     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

30     /* 配置命令 */

31if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){

32         return QSPI_ERROR;

33     }

34     /* 傳輸數據 */

35if (HAL_QSPI_Transmit(&QSPIHandle, &value, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){

36         return QSPI_ERROR;

37     }

38     /* 自動輪詢模式等待存儲器就緒 */

39     if (QSPI_AutoPollingMemReady(W25Q128FV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK) {

40         return QSPI_ERROR;

41     }

42     return QSPI_OK;

43 }

使用QSPI讀取大量數據

我們要從存取器中讀取大量數據,首先要用一個指針指向讀回來數據,並確定數據的首地址,數據大小,通過庫函數HAL_QSPI_Command發送配置命令,然后調用庫函數HAL_QSPI_Receive接收數據,最后等待操作完成,我們看看它的代碼實現,見代碼清單 247。

代碼清單 247 使用QSPI讀取大量數據

1 /**

 2   * @brief  QSPI存儲器中讀取大量數據.

 3   * @param  pData: 指向要讀取的數據的指針

 4   * @param  ReadAddr: 讀取起始地址

 5   * @param  Size: 要讀取的數據大小

 6   * @retval QSPI存儲器狀態

 7   */

 8 uint8_t BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)

 9 {

10     QSPI_CommandTypeDef s_command;

11     /* 初始化讀命令 */

12     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

13     s_command.Instruction       = READ_CMD;

14     s_command.AddressMode       = QSPI_ADDRESS_1_LINE;

15     s_command.AddressSize       = QSPI_ADDRESS_24_BITS;

16     s_command.Address           = ReadAddr;

17     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

18     s_command.DataMode          = QSPI_DATA_1_LINE;

19     s_command.DummyCycles       = 0;

20     s_command.NbData            = Size;

21     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

22     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

23     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

24

25     /* 配置命令 */

26if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){

27         return QSPI_ERROR;

28     }

29

30     /* 接收數據 */

31     if(HAL_QSPI_Receive(&QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) {

32         return QSPI_ERROR;

33     }

34     return QSPI_OK;

35 }

使用QSPI寫入大量數據

我們要從存取器中寫入大量數據,首先要用一個指針指寫入數據,並確定數據的首地址,數據大小,根據寫入地址及大小判斷存儲器的頁面,然后通過庫函數HAL_QSPI_Command發送配置命令,然后調用庫函數HAL_QSPI_Transmit逐頁寫入數據,最后等待操作完成,我們看看它的代碼實現,見代碼清單 248。

代碼清單 248 使用QSPI讀取大量數據

1 /**

 2   * @brief  將大量數據寫入QSPI存儲器

 3   * @param  pData: 指向要寫入數據的指針

 4   * @param  WriteAddr: 寫起始地址

 5   * @param  Size: 要寫入的數據大小

 6   * @retval QSPI存儲器狀態

 7   */

 8 uint8_t BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)

 9 {

10     QSPI_CommandTypeDef s_command;

11     uint32_t end_addr, current_size, current_addr;

12     /* 計算寫入地址和頁面末尾之間的大小 */

13     current_addr = 0;

14

15     while (current_addr <= WriteAddr) {

16         current_addr += W25Q128FV_PAGE_SIZE;

17     }

18     current_size = current_addr - WriteAddr;

19

20     /* 檢查數據的大小是否小於頁面中的剩余位置 */

21     if (current_size > Size) {

22         current_size = Size;

23     }

24

25     /* 初始化地址變量 */

26     current_addr = WriteAddr;

27     end_addr = WriteAddr + Size;

28

29     /* 初始化程序命令 */

30     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

31     s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;

32     s_command.AddressMode       = QSPI_ADDRESS_1_LINE;

33     s_command.AddressSize       = QSPI_ADDRESS_24_BITS;

34     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

35     s_command.DataMode          = QSPI_DATA_4_LINES;

36     s_command.DummyCycles       = 0;

37     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

38     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

39     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

40

41     /* 逐頁執行寫入 */

42     do {

43         s_command.Address = current_addr;

44         s_command.NbData  = current_size;

45

46         /* 啟用寫操作 */

47         if (QSPI_WriteEnable() != QSPI_OK) {

48             return QSPI_ERROR;

49         }

50

51         /* 配置命令 */

52if(HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {

53             return QSPI_ERROR;

54         }

55

56         /* 傳輸數據 */

57if(HAL_QSPI_Transmit(&QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {

58             return QSPI_ERROR;

59         }

60

61         /* 配置自動輪詢模式等待程序結束 */

62         if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK) {

63             return QSPI_ERROR;

64         }

65

66         /* 更新下一頁編程的地址和大小變量 */

67         current_addr += current_size;

68         pData += current_size;

69current_size = ((current_addr + W25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :

70                        W25Q128FV_PAGE_SIZE;

71     } while (current_addr < end_addr);

72     return QSPI_OK;

73 }

讀取FLASH芯片ID

根據JEDEC”指令的時序,我們把讀取FLASH  ID的過程編寫成一個函數,見代碼清單 249。

代碼清單 249 讀取FLASH芯片ID

1 /**

 2  * @brief  讀取FLASH ID

 3  * @param   

 4  * @retval FLASH ID

 5  */

 6 uint32_t QSPI_FLASH_ReadID(void)

 7 {

 8     QSPI_CommandTypeDef s_command;

 9     uint32_t Temp = 0;

10     uint8_t pData[3];

11     /* 讀取JEDEC ID */

12     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

13     s_command.Instruction       = READ_JEDEC_ID_CMD;

14     s_command.AddressMode       = QSPI_ADDRESS_1_LINE;

15     s_command.AddressSize       = QSPI_ADDRESS_24_BITS;

16     s_command.DataMode          = QSPI_DATA_1_LINE;

17     s_command.AddressMode       = QSPI_ADDRESS_NONE;

18     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

19     s_command.DummyCycles       = 0;

20     s_command.NbData            = 3;

21     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

22     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

23     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

24

25if(HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {

26         printf("something wrong ....\r\n");

27         /* 用戶可以在這里添加一些代碼來處理這個錯誤 */

28         while (1) {

29

30         }

31     }

32     if(HAL_QSPI_Receive(&QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) {

33         printf("something wrong ....\r\n");

34         /* 用戶可以在這里添加一些代碼來處理這個錯誤 */

35         while (1) {

36

37         }

38     }

39

40     Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );

41

42     return Temp;

43 }

這段代碼利用庫函數HAL_QSPI_Command發送讀取FLASH  ID指令,再調用庫函數HAL_QSPI_Receive讀取3個字節,獲取FLASH芯片對該指令的響應,最后把讀取到的這3個數據合並到一個變量Temp中。然后然后作為函數返回值,把該返回值與我們定義的宏“sFLASH_ID”對比,即可知道FLASH芯片是否正常。

FLASH寫使能以及讀取當前狀態

在向FLASH芯片存儲矩陣寫入數據前,首先要使能寫操作,通過“Write Enable”命令即可寫使能,見代碼清單 24-10。

代碼清單 24-10 寫使能命令

1 /**

 2   * @brief  發送寫入使能,等待它有效.

 3   * @param  QSPIHandle: QSPI句柄

 4   * @retval

 5   */

 6 static uint8_t QSPI_WriteEnable()

 7 {

 8     QSPI_CommandTypeDef     s_command;

 9     QSPI_AutoPollingTypeDef s_config;

10     /* 啟用寫操作 */

11     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

12     s_command.Instruction       = WRITE_ENABLE_CMD;

13     s_command.AddressMode       = QSPI_ADDRESS_NONE;

14     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

15     s_command.DataMode          = QSPI_DATA_NONE;

16     s_command.DummyCycles       = 0;

17     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

18     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

19     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

20if(HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){

21         return QSPI_ERROR;

22     }

23

24     /* 配置自動輪詢模式等待寫啟用 */

25     s_config.Match           = W25Q128FV_FSR_WREN;

26     s_config.Mask            = W25Q128FV_FSR_WREN;

27     s_config.MatchMode       = QSPI_MATCH_MODE_AND;

28     s_config.StatusBytesSize = 1;

29     s_config.Interval        = 0x10;

30     s_config.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;

31

32     s_command.Instruction    = READ_STATUS_REG1_CMD;

33     s_command.DataMode       = QSPI_DATA_1_LINE;

34     s_command.NbData         = 1;

35

36if(HAL_QSPI_AutoPolling(&QSPIHandle, &s_command, &s_config, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){

37         return QSPI_ERROR;

38     }

39     return QSPI_OK;

40 }

EEPROM一樣,由於FLASH芯片向內部存儲矩陣寫入數據需要消耗一定的時間,並不是在總線通訊結束的一瞬間完成的,所以在寫操作后需要確認FLASH芯片“空閑”時才能進行再次寫入。為了表示自己的工作狀態,FLASH芯片定義了一個狀態寄存器,見 24-6。

 

24-6 FLASH芯片的狀態寄存器

我們只關注這個狀態寄存器的第0位“BUSY”,當這個位為“1”時,表明FLASH芯片處於忙碌狀態,它可能正在對內部的存儲矩陣進行“擦除”或“數據寫入”的操作。

利用指令表中的Read Status Register”指令可以獲取FLASH芯片狀態寄存器的內容,其時序見 24-7。

 

24-7 讀取狀態寄存器的時序

只要向FLASH芯片發送了讀狀態寄存器的指令,FLASH芯片就會持續向主機返回最新的狀態寄存器內容,直到收到SPI通訊的停止信號。HAL庫提供了具有等待FLASH芯片寫入結束功能的函數,見代碼清單 2411。

代碼清單 2411 通過讀狀態寄存器等待FLASH芯片空閑

1 /**

 2   * @brief  讀取存儲器的SR並等待EOP

 3   * @param  QSPIHandle: QSPI句柄

 4   * @param  Timeout 超時

 5   * @retval

 6   */

 7 static uint8_t QSPI_AutoPollingMemReady(uint32_t Timeout)

 8 {

 9     QSPI_CommandTypeDef     s_command;

10     QSPI_AutoPollingTypeDef s_config;

11     /* 配置自動輪詢模式等待存儲器准備就緒 */

12     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

13     s_command.Instruction       = READ_STATUS_REG1_CMD;

14     s_command.AddressMode       = QSPI_ADDRESS_NONE;

15     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

16     s_command.DataMode          = QSPI_DATA_1_LINE;

17     s_command.DummyCycles       = 0;

18     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

19     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

20     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

21

22     s_config.Match           = 0x00;

23     s_config.Mask            = W25Q128FV_FSR_BUSY;

24     s_config.MatchMode       = QSPI_MATCH_MODE_AND;

25     s_config.StatusBytesSize = 1;

26     s_config.Interval        = 0x10;

27     s_config.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;

28

29     if (HAL_QSPI_AutoPolling(&QSPIHandle, &s_command, &s_config, Timeout) != HAL_OK) {

30         return QSPI_ERROR;

31     }

32     return QSPI_OK;

33 }

這段代碼直接調用HAL_QSPI_AutoPolling庫函數,設定命令參數及自動輪詢參數,最后設定超時返回,如果在超時等待時間內確定FLASH就緒則返回存儲器就緒狀態,否則返回存儲器錯誤。其實主要操作就是檢查它的“W25Q128FV_FSR_BUSY”(BUSY),一直等待到該標志表示寫入結束時才退出本函數,以便繼續后面與FLASH芯片的數據通訊。

FLASH扇區擦除

由於FLASH存儲器的特性決定了它只能把原來為“1”的數據位改寫成“0”,而原來為“0”的數據位不能直接改寫為“1”。所以這里涉及到數據“擦除”的概念,在寫入前,必須要對目標存儲矩陣進行擦除操作,把矩陣中的數據位擦除為“1”,在數據寫入的時候,如果要存儲數據“1”,那就不修改存儲矩陣 ,在要存儲數據“0”時,才更改該位。

通常,對存儲矩陣擦除的基本操作單位都是多個字節進行,如本例子中的FLASH芯片支持“扇區擦除”、“塊擦除”以及“整片擦除”,見 24-3。

24-3 本實驗FLASH芯片的擦除單位

擦除單位

大小

扇區擦除Sector Erase

4KB

塊擦除Block Erase

64KB

整片擦除Chip Erase

整個芯片完全擦除

FLASH芯片的最小擦除單位為扇區(Sector),而一個塊(Block)包含16個扇區,其內部存儲矩陣分布見 24-8。

 

 

24-8 FLASH芯片的存儲矩陣

使用扇區擦除指令Sector Erase”可控制FLASH芯片開始擦寫,其指令時序見錯誤!未找到引用源。

 

24-9 扇區擦除時序

扇區擦除指令的第一個字節為指令編碼,緊接着發送的3個字節用於表示要擦除的存儲矩陣地址。要注意的是在扇區擦除指令前,還需要先發送“寫使能”指令,發送扇區擦除指令后,通過讀取寄存器狀態等待扇區擦除操作完畢,代碼實現見代碼清單 24-12。

代碼清單 24-12 擦除扇區

1 /**

 2   * @brief  擦除QSPI存儲器的指定塊

 3   * @param  BlockAddress: 需要擦除的塊地址

 4   * @retval QSPI存儲器狀態

 5   */

 6 uint8_t BSP_QSPI_Erase_Block(uint32_t BlockAddress)

 7 {

 8     QSPI_CommandTypeDef s_command;

 9     /* 初始化擦除命令 */

10     s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;

11     s_command.Instruction       = SECTOR_ERASE_CMD;

12     s_command.AddressMode       = QSPI_ADDRESS_1_LINE;

13     s_command.AddressSize       = QSPI_ADDRESS_24_BITS;

14     s_command.Address           = BlockAddress;

15     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

16     s_command.DataMode          = QSPI_DATA_NONE;

17     s_command.DummyCycles       = 0;

18     s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;

19     s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;

20     s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

21

22     /* 啟用寫操作 */

23     if (QSPI_WriteEnable() != QSPI_OK) {

24         return QSPI_ERROR;

25     }

26

27     /* 發送命令 */

28if(HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {

29         return QSPI_ERROR;

30     }

31

32     /* 配置自動輪詢模式等待擦除結束 */

33     if (QSPI_AutoPollingMemReady(W25Q128FV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK) {

34         return QSPI_ERROR;

35     }

36     return QSPI_OK;

37 }

3. main函數

最后我們來編寫main函數,進行FLASH芯片讀寫校驗,見錯誤!未找到引用源。

代碼清單 2413 main函數

1 int main(void)

 2 {

 3     /* 使能指令緩存 */

 4     SCB_EnableICache();

 5

 6     /* 使能數據緩存 */

 7     SCB_EnableDCache();

 8

 9     /* 設定系統時鍾為216MHz */

10     SystemClock_Config();

11

12     LED_GPIO_Config();

13     LED_BLUE;

14

15     /* 配置串口1為:115200 8-N-1 */

16     DEBUG_USART_Config();

17

18     printf("\r\n這是一個16M串行flash(W25Q128)實驗(QSPI驅動) \r\n");

19

20     /* 16M串行flash W25Q128初始化 */

21     QSPI_FLASH_Init();

22

23     /* 獲取 Flash Device ID */

24     DeviceID = QSPI_FLASH_ReadDeviceID();

25

26     Delay( 200 );

27

28     /* 獲取 SPI Flash ID */

29     FlashID = QSPI_FLASH_ReadID();

30

31    printf("\r\nFlashID is 0x%X,  Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);

32

33     /* 檢驗 SPI Flash ID */

34     if (FlashID == sFLASH_ID) {

35         printf("\r\n檢測到QSPI FLASH W25Q128 !\r\n");

36

37         /* 擦除將要寫入的 QSPI FLASH 扇區,FLASH寫入前要先擦除 */

38         BSP_QSPI_Erase_Block(FLASH_SectorToErase);

39

40         /* 將發送緩沖區的數據寫到flash*/

41         BSP_QSPI_Write(Tx_Buffer, FLASH_WriteAddress, BufferSize);

42         printf("\r\n寫入的數據為:\r\n%s", Tx_Buffer);

43

44         /* 將剛剛寫入的數據讀出來放到接收緩沖區中 */

45         BSP_QSPI_Read(Rx_Buffer, FLASH_ReadAddress, BufferSize);

46         printf("\r\n讀出的數據為:\r\n%s", Rx_Buffer);

47

48         /* 檢查寫入的數據與讀出的數據是否相等 */

49         TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);

50

51         if ( PASSED == TransferStatus1 ) {

52             LED_GREEN;

53             printf("\r\n16M串行flash(W25Q128)測試成功!\n\r");

54         } else {

55             LED_RED;

56             printf("\r\n16M串行flash(W25Q128)測試失敗!\n\r");

57         }

58     }// if (FlashID == sFLASH_ID)

59     else {

60         LED_RED;

61         printf("\r\n獲取不到 W25Q128 ID!\n\r");

62     }

63

64     while (1);

65 }

函數中初始化了系統時鍾、LED、串口SPI外設,然后讀取FLASH芯片的ID進行校驗,若ID校驗通過則向FLASH的特定地址寫入測試數據,然后再從該地址讀取數據,測試讀寫是否正常。

 

注意:

由於實驗板上的FLASH芯片默認已經存儲了特定用途的數據,如擦除了這些數據會影響到某些程序的運行。所以我們預留了FLASH芯片的“第0扇區(0-4096地址)”專用於本實驗,如非必要,請勿擦除其它地址的內容。如已擦除,可在配套資料里找到“刷外部FLASH內容”程序,根據其說明給FLASH重新寫入出廠內容。

24.6.3  下載驗證

USB線連接開發板“USB TO UART”接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。在串口調試助手可看到FLASH測試的調試信息。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM