本章參考資料:《STM32F76xxx參考手冊》、《STM32F76xxx規格書》、庫幫助文檔《STM32F779xx_User_Manual.chm》及《SPI總線協議介紹》。
若對SPI通訊協議不了解,可先閱讀《SPI總線協議介紹》文檔的內容學習。
關於FLASH存儲器,請參考“常用存儲器介紹”章節,實驗中FLASH芯片的具體參數,請參考其規格書《W25Q128》來了解。
24.1 QSPI協議簡介
QSPI是Queued 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 Flash。QSPI 使用 6 個信號連接Flash,分別是四個數據線BK1_IO0~BK1_IO3,一個時鍾輸出CLK,一個片選輸出(低電平有效)BK1_nCS,它們的作用介紹如下:
(1) BK1_nCS:片選輸出(低電平有效),適用於 FLASH 1。如果 QSPI 始終在雙閃存模式下工作,則其也可用於 FLASH 2從設備選擇信號線。QSPI通訊以BK1_nCS線置低電平為開始信號,以BK1_nCS線被拉高作為結束信號。
(2) CLK:時鍾輸出,適用於兩個存儲器,用於通訊數據同步。它由通訊主機產生,決定了通訊的速率,不同的設備支持的最高時鍾頻率不一樣,如STM32的QSPI時鍾頻率最大為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]) 為 0,QUADSPI 在單倍數據速率 (SDR) 模式下工作。在 SDR 模式下,當 QUADSPI 驅動 IO0/SO、IO1、IO2、IO3 信號時,這些信號僅在 CLK的下降沿發生轉變。在 SDR 模式下接收數據時,QUADSPI 假定 Flash 也通過 CLK 的下降沿發送數據。默認情況下 (SSHIFT = 0 時),將使用 CLK 后續的邊沿(上升沿)對信號進行采樣。
24.2.5 DDR 模式
若 DDRM 位 (QUADSPI_CCR[31]) 置 1,則 QUADSPI 在雙倍數據速率 (DDR) 模式下工作。在 DDR 模式下,當 QUADSPI 在地址/交替字節/數據階段驅動 IO0/SO、IO1、IO2、IO3 信號時,將在 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]) 為 1,QUADSPI 處於雙閃存模式。QUADSPI 使用兩個外部四線 SPI Flash(FLASH 1 和 FLASH 2),在每個周期中發送/接收 8 位(在 DDR 模式下為16 位),能夠有效地將吞吐量和容量擴大一倍。每個 Flash 使用同一個 CLK 並可選擇使用同一個 nCS 信號,但其 IO0、IO1、IO2 和 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 字節 FIFO。FLEVEL[5:0](QUADSPI_SR[13:8]) 指示 FIFO 目前保存了多少字節。
在間接寫入模式下 (FMODE = 00),固件寫入 QUADSPI_DR 時,將在 FIFO 中加入數據。字寫入將在 FIFO 中增加 4 個字節,半字寫入增加 2 個字節,而字節寫入僅增加 1 個字節。如果固件在 FIFO 中加入的數據過多(超過 DL[31:0] 指示的值),將在寫入操作結束(TCF置 1)時從 FIFO 中清除超出的字節。
對 QUADSPI_DR 的字節/半字訪問必須僅針對該 32 位寄存器的最低有效字節/半字。FTHRES[3:0] 用於定義 FIFO 的閾值 如果達到閾值,FTF(FIFO 閾值標志)置 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 < FTHRES,FTF 標志將保持為“0”。
24.4 QUADSPI Flash 配置
外部 SPI Flash的參數可以通過配置寄存器 (QUADSPI_DCR)實現。這里配置Flash的容量是設置FSIZE[4:0] 字段,使用下面的公式定義外部存儲器的大小:
FSIZE+1 是對 Flash 尋址所需的地址位數。在間接模式下,Flash 容量最高可達 4GB(使用32 位進行尋址),但在內存映射模式下的可尋址空間限制為 256MB。如果 DFM = 1,FSIZE 表示兩個 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],取值范圍是0—255,可以實現1—256級別的分頻。僅可在 BUSY = 0 時修改該字段。
(2) FifoThreshold
本成員設置FIFO 閾值級別,對應寄存器QUADSPI_CR [12:8]即FTHRES[4:0],定義在間接模式下 FIFO 中將導致 FIFO 閾值標志(FTF,QUADSPI_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容量最高可達4GB(32位尋址),但是在內存映射模式下限制為256MB,如果是雙閃存則可以達到512MB。
(5) ChipSelectHighTime
本成員設置片選高電平時間,對應寄存器QUADSPI_CR [10:8]的CSHT[2:0]位,定義片選 (nCS) 在發送至 Flash 的命令之間必須保持高電平的最少 CLK 周期數。可以取值1~8個周期。
(6) ClockMode
本成員設置時鍾模式,對應寄存器QUADSPI_CR [0]位,指示CLK在命令之間的電平,可以選模式0,1: nCS 為高電平(片選釋放)時,CLK 必須保持低電平;或者模式3 ,1:nCS 為高電平(片選釋放)時,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存儲芯片的讀寫實驗為大家講解STM32的QSPI使用方法。實驗中STM32的QSPI外設采用主模式,通過查詢事件的方式來確保正常通訊。
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~A23”指FLASH芯片內部存儲器組織的地址;“M0~M7”為廠商號(MANUFACTURER ID);“ID0-ID15”為FLASH芯片的ID;“dummy”指該處可為任意數據;“D0~D7”為FLASH內部存儲矩陣的內容。
在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 FLASH讀ID指令“JEDEC ID”的時序(摘自規格書《W25Q128》)
主機首先通過DIO(對應STM32的QUADSPI_BK1_IO0)線向FLASH芯片發送第一個字節數據為“9F h”,當FLASH芯片收到該數據后,它會解讀成主機向它發送了“JEDEC指令”,然后它就作出該命令的響應:通過DO(對應STM32的QUADSPI_BK1_IO1)線把它的廠商ID(M7-M0)及芯片類型(ID15-0)發送給主機,主機接收到指令響應后可進行校驗。常見的應用是主機端通過讀取設備ID來測試硬件是否連接正常,或用於識別設備。
對於FLASH芯片的其它指令,都是類似的,只是有的指令包含多個字節,或者響應包含更多的數據。
實際上,編寫設備驅動都是有一定的規律可循的。首先我們要確定設備使用的是什么通訊協議。如上一章的EEPROM使用的是I2C,本章的FLASH使用的是QSPI。那么我們就先根據它的通訊協議,選擇好STM32的硬件模塊,並進行相應的I2C或SPI模塊初始化。接着,我們要了解目標設備的相關指令,因為不同的設備,都會有相應的不同的指令。如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 、引腳號、引腳源以及復用功能映射都以宏封裝起來。
初始化QSPI的 GPIO
利用上面的宏,編寫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外設模式的配置。在配置STM32的QSPI模式前,我們要先了解從機端的QSPI模式。本例子中可通過查閱FLASH數據手冊《W25Q128》獲取。根據FLASH芯片的說明,它支持SPI模式0及模式3,支持四線模式,支持最高通訊時鍾為104MHz,數據幀長度為8位。我們要把STM32的QSPI外設中的這些參數配置一致。見代碼清單 24-5。
(6) 配置QSPI接口模式;時鍾三分頻最高通訊時鍾為72MHz(Flash最高支持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 /* 設置四路使能的狀態寄存器,使能四通道IO2和IO3引腳 */
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測試的調試信息。