一、QSPI介紹
- 1.1、QSPI功能框圖(雙閃存模式禁止)
- 1.2、QSPI 時鐘源
- 1.3、間接模式
- 1.4、內存映射模式
- 1.5、命令序列(間接模式 或 內存映射模式)
- 1.6、指令、地址、交替字節(jié)、空指令周期、數(shù)據(jù)各階段
- 1.7、QSPI FLASH設置
- 1.8、QSPI 中斷類型
二、QSPI相關寄存器介紹
三、QSPI相關HAL庫驅動介紹
四、QSPI基本使用步驟
五、SPI FLASH簡介
六、SPI FLASH基本使用步驟
七、編程實戰(zhàn)
八、總結
SPI(Serial Peripheral Interface)根據(jù)其數(shù)據(jù)傳輸能力和硬件接口的不同,可以分為以下幾個類別:
-
Standard SPI(標準SPI)
- 標準SPI是一種全雙工通信協(xié)議,擁有四條主要通信線:
- CS(Chip Select,片選):用于選擇參與通信的從設備。
- SCLK(Serial Clock,串行時鐘):主設備提供時鐘信號,從設備據(jù)此進行數(shù)據(jù)采樣和傳輸。
- MOSI(Master Out, Slave In):主設備向從設備發(fā)送數(shù)據(jù)的線。
- MISO(Master In, Slave Out):從設備向主設備發(fā)送數(shù)據(jù)的線。
- 驅動SPI Flash時,可能還會涉及額外的控制線,如寫保護(WR)和保持(HOLD)引腳,用于保護數(shù)據(jù)安全和暫停通信。
- 標準SPI是一種全雙工通信協(xié)議,擁有四條主要通信線:
-
Dual SPI(雙線SPI)
- 雙線SPI是對標準SPI的一種改進,它將原本的MOSI和MISO引腳合并成一對雙向數(shù)據(jù)線,通常稱為IO0和IO1。
- 在一個時鐘周期內,通過這兩條數(shù)據(jù)線可以同時傳輸兩位數(shù)據(jù),這樣理論上可以將數(shù)據(jù)傳輸速率提高一倍,但這時SPI工作在半雙工模式下,即在同一時刻只能進行讀或寫操作。
-
Quad SPI(四線SPI)
- 四線SPI是在雙線SPI的基礎上進一步改進,增加了另外兩條雙向數(shù)據(jù)線IO2和IO3。
- 這四條數(shù)據(jù)線可以在一個時鐘周期內傳輸四位數(shù)據(jù),極大地提升了數(shù)據(jù)傳輸速率。
- 在一些實現(xiàn)中,原有的寫保護(WR)和保持(HOLD)引腳可能會被復用為數(shù)據(jù)線IO2和IO3,以減少硬件接口的數(shù)量。
總的來說,SPI的這幾個變種主要是為了提高數(shù)據(jù)傳輸效率,同時在一定程度上減小接口引腳數(shù)量,但也會帶來一些限制,如在高數(shù)據(jù)速率下只能進行半雙工通信。而在實際應用中,尤其是在與閃存設備交互時,SPI模式的選擇需要根據(jù)系統(tǒng)的性能需求、空間占用以及功耗預算等因素綜合考慮。
一、QSPI介紹
QSPI(Queued Serial Peripheral Interface)是SPI接口的一種高級擴展形式,由Motorola公司推出,后來在各類微控制器中廣泛應用,特別是在處理高速數(shù)據(jù)傳輸和與外部高性能Quad-SPI存儲器(如Flash)交互時表現(xiàn)出色。
QSPI的主要特點包括:
- 四線通信:支持1線、2線和4線模式,其中4線模式下,通過四條數(shù)據(jù)線同時傳輸數(shù)據(jù),大大提高了數(shù)據(jù)吞吐量,非常適合于大容量、高速度的SPI Flash存儲器訪問。
- 優(yōu)化操作模式:支持SDR(Single Data Rate,單倍數(shù)據(jù)速率)和DDR(Double Data Rate,雙倍數(shù)據(jù)速率)模式,DDR模式下可以在每個時鐘周期內傳輸兩次數(shù)據(jù),進一步提升傳輸速度。
-
三種操作模式:
- 間接模式:類似于標準SPI,通過QSPI寄存器執(zhí)行所有讀寫操作,適用于一般的編程和擦除操作。
- 狀態(tài)輪詢模式:通過周期性讀取外部Flash的狀態(tài)寄存器來監(jiān)控操作進度,當Flash狀態(tài)寄存器指示操作完成時,可以通過中斷告知微控制器。
- 內存映射模式:外部Quad-SPI Flash存儲器可以直接映射到STM32等微控制器的地址空間中,如同內部Flash一樣進行讀取操作,大大簡化了訪問流程,提高數(shù)據(jù)讀取速度。
簡單來說,QSPI是一種高度優(yōu)化和強化的SPI接口,尤其適用于高效地驅動和管理高性能的SPI Flash存儲器,提供更大的帶寬和更低延遲的訪問體驗。通過先進的硬件支持和靈活的操作模式,QSPI極大地提升了與SPI Flash等外部設備的通信效率。
1.1、QSPI功能框圖(雙閃存模式禁止)
QSPI(Quad Serial Peripheral Interface)在功能結構上相較于標準SPI增加了更多的數(shù)據(jù)線,用于實現(xiàn)更高的數(shù)據(jù)傳輸速率。在雙閃存模式禁止的情況下,其功能結構主要包括:
-
時鐘線CLK:
- 時鐘線是QSPI通信的同步信號,所有數(shù)據(jù)的傳輸都在時鐘信號的上升沿或下降沿進行。
-
片選線BK1_nCS:
- 片選線用于選擇與QSPI接口相連的特定閃存設備。當BK1_nCS信號為低電平時,選定的閃存設備被激活并開始進行數(shù)據(jù)傳輸。
-
數(shù)據(jù)線BK1_IO0~IO3:
- 在單線SPI模式下,可能只會使用到BK1_IO0作為數(shù)據(jù)線。
- 在雙線SPI模式下,BK1_IO0和BK1_IO1可作為雙向數(shù)據(jù)線,一次傳輸兩位數(shù)據(jù)。
- 在四線SPI(Quad SPI)模式下,所有四條數(shù)據(jù)線BK1_IO0~IO3都被用作雙向數(shù)據(jù)通道,能夠在單個時鐘周期內傳輸四位數(shù)據(jù),從而大大提高數(shù)據(jù)吞吐量。
根據(jù)不同模式,這些引腳的功能有所不同:
- 單線模式:僅使用BK1_IO0進行數(shù)據(jù)傳輸。
- 雙線模式:BK1_IO0和BK1_IO1用于數(shù)據(jù)傳輸,一次傳輸兩個數(shù)據(jù)位。
- 四線模式(Quad SPI):BK1_IO0、IO1、IO2和IO3共同工作,一次傳輸四個數(shù)據(jù)位。
在雙閃存模式禁止時,這意味著QSPI控制器不會同時處理兩個獨立的閃存設備,而是專注于單一的外部閃存設備。通過調整QSPI控制器的配置寄存器,可以靈活地在這幾種模式間切換,以適應不同的應用場景和性能需求。
時鐘輸入、QSPI輸出信號
在STM32H7系列微控制器中,QSPI接口的時鐘輸入和輸出信號說明如下:
-
時鐘輸入:
- 32位AHB總線:QSPI與處理器的內部總線接口使用32位AHB總線進行通信,AHB總線提供了高速的數(shù)據(jù)傳輸通道。
- quadspi_ker_ck:QSPI內核時鐘,它是QSPI模塊工作的基礎時鐘,通常來自系統(tǒng)時鐘或PLL分頻后的某個時鐘源。
- quadspi_hclk:QSPI AHB時鐘,它是QSPI與處理器內部AHB總線通信所需的時鐘信號,一般等于或小于quadspi_ker_ck。
-
QSPI輸出信號:
- 64位AXI總線:在某些STM32H7系列中,QSPI支持與64位AXI總線相連,提供更高的數(shù)據(jù)吞吐量,用于內存映射模式下訪問外部QSPI閃存。
- quadspi_it:QSPI中斷信號,當QSPI完成某項操作(如讀寫完成)時,會觸發(fā)此中斷信號通知CPU。
- quadspi_ft_trg:QSPI閃存?zhèn)鬏斢|發(fā)信號,用于啟動或控制對QSPI閃存的讀寫操作。
- quadspi_tc_trg:QSPI傳輸完成觸發(fā)信號,可能用于指示QSPI完成了一次完整的數(shù)據(jù)傳輸或操作。
請注意,以上信號名稱并非官方文檔中STM32H7系列的標準命名,但它們代表了QSPI接口常見的時鐘輸入和輸出信號類型。在實際使用時,請參考STM32H7系列的官方技術參考手冊(TRM)以獲得準確的信號名稱和功能描述。
1.2、QSPI 時鐘源
在STM32F7和STM32H7系列微控制器中,QSPI時鐘源的選擇可以根據(jù)系統(tǒng)設計需求進行配置。
-
STM32F7系列:
- 默認情況下,QSPI時鐘源可能選擇的是HCLK3,即系統(tǒng)高速時鐘(System High Speed Clock)的一個分頻版本。然而,這也依賴于具體的F7系列微控制器型號和應用需求,可以通過重新配置RCC(Reset and Clock Control)寄存器來選擇其他可用的時鐘源。
-
STM32H7系列(例如STM32H7 MINI PRO H750開發(fā)板):
- 在STM32H7系列中,QSPI時鐘源的選擇更為靈活。在某些應用案例中,可以選用PLL2的輸出作為QSPI的時鐘源,這通常是為了滿足更高數(shù)據(jù)傳輸速率的需求。PLL2可以提供一個比系統(tǒng)主時鐘更高的頻率,而且常常經(jīng)過適當?shù)姆诸l后作為QSPI的工作時鐘。
在實際項目開發(fā)中,你需要根據(jù)微控制器的規(guī)格書、參考手冊和應用需求來配置QSPI的時鐘源。通過查閱STM32CubeMX工具或者直接編程配置RCC寄存器,可以設置合適的時鐘源。
1.3、間接模式
間接模式是STM32H7系列微控制器QSPI接口中的一種操作模式,主要用于執(zhí)行讀寫和擦除操作。在此模式下,QSPI與外部SPI Flash之間的數(shù)據(jù)傳輸通過內部FIFO(First-In-First-Out)緩沖區(qū)來進行。
-
間接寫入模式:
- 開發(fā)者將待寫入的數(shù)據(jù)寫入QSPI的FIFO(QUADSPI_SR[13:8]位反映了FIFO的狀態(tài))。
- 數(shù)據(jù)隨后通過QSPI接口傳輸?shù)酵獠縎PI Flash。
-
間接讀取模式:
- 開發(fā)者配置好讀取操作后,QSPI從外部SPI Flash讀取數(shù)據(jù),并將數(shù)據(jù)存入內部FIFO。
- 應用程序可以從FIFO中讀取接收到的數(shù)據(jù)。
-
數(shù)據(jù)階段的控制:
- QSPI 控制寄存器 QUADSPI_CCR 中的 FMODE 字段決定操作模式,F(xiàn)MODE=00 表示間接寫入模式,F(xiàn)MODE=01 表示間接讀取模式。
- 若 CCR 中的 DMODE 設置為 00,表示不進行數(shù)據(jù)傳輸(適用于只發(fā)送命令和地址的情況)。
-
讀/寫字節(jié)數(shù)的設置:
- 通過 QUADSPI_DLR 寄存器設置讀寫操作的數(shù)據(jù)長度。若設置為 0xFFFFFFFF,則表示將持續(xù)傳輸數(shù)據(jù),直到遇到 Flash 存儲器的末尾。
-
啟動傳輸:
- 在配置好命令、地址和數(shù)據(jù)長度后,通過向相應的控制寄存器寫入適當?shù)闹祦韱訑?shù)據(jù)傳輸。
-
傳輸完成的標志:
- 當傳輸達到設定的字節(jié)數(shù)時,QUADSPI_SR 中的 TCF(Transfer Complete Flag)標志位將被置1。
- 如果啟用了 TCF 中斷(通過使能 TCIE,Transfer Complete Interrupt Enable),那么當傳輸完成時,將會觸發(fā)一個中斷通知應用程序。
1.4、內存映射模式
內存映射模式是STM32H7系列微控制器中QSPI接口的另一種工作模式,主要適用于以下場景:
-
讀取操作:
- 在內存映射模式下,外部Quad-SPI Flash存儲器可以直接映射到STM32H7的內存地址空間中,處理器可以通過訪問特定的內存地址來讀取存儲器中的數(shù)據(jù),就像訪問內部RAM一樣。
-
擴展內部存儲器:
- 外部Quad-SPI Flash存儲器被當作內部存儲器的一部分使用,其他主機(例如處理器內核或DMA控制器)可以直接讀取這些地址上的數(shù)據(jù),無需通過QSPI接口的特殊函數(shù)調用。
-
執(zhí)行代碼(XIP,Execute-In-Place):
- 由于Quad-SPI Flash存儲器被映射到了內存地址空間,因此可以直接從中執(zhí)行代碼,減少了將代碼從Flash復制到RAM的時間開銷,提高了系統(tǒng)的啟動速度和運行效率。
在STM32H7系列中,內存映射模式下,Quad-SPI接口可以管理的最大地址范圍是從0x9000 0000到0x9FFF FFFF,總計256MB的內存空間。這意味著在這個地址范圍內,處理器可以直接讀取外部Quad-SPI Flash的內容,實現(xiàn)無縫的數(shù)據(jù)訪問和代碼執(zhí)行。在實際應用中,需要根據(jù)具體的Quad-SPI Flash容量和實際需求來配置映射的地址區(qū)間。
1.5、命令序列(間接模式 或 內存映射模式)
在STM32H7系列微控制器的QSPI接口中,無論是間接模式還是內存映射模式,對SPI Flash進行數(shù)據(jù)讀寫操作時,都需要構建和發(fā)送一個命令序列。這個命令序列通常由五個可配置階段構成:
-
指令階段:
- 發(fā)送一個或多個字節(jié)的命令代碼,以指示SPI Flash執(zhí)行特定的操作,如讀取、寫入、擦除等。
-
地址階段:
- 發(fā)送用于定位數(shù)據(jù)在SPI Flash中的地址信息,地址的長度可配置,取決于具體的應用需求。
-
交替字節(jié)階段(Optional):
- 用于在某些特殊操作中傳遞額外的信息或控制字節(jié),不是所有操作都需要這個階段。
-
空周期階段(Dummy Cycle Phase,Optional):
- 在某些讀取操作中,為了滿足SPI Flash的時序要求,可能需要插入一定數(shù)量的空時鐘周期。這個階段的長度也可配置。
-
數(shù)據(jù)階段:
- 實際的數(shù)據(jù)傳輸階段,可以是向SPI Flash寫入數(shù)據(jù),也可以是從SPI Flash讀取數(shù)據(jù)。數(shù)據(jù)長度根據(jù)實際傳輸需求配置。
在配置命令序列時,開發(fā)人員可以靈活地控制每個階段是否啟動、每個階段的長度以及數(shù)據(jù)是在單線、雙線還是四線模式下傳輸。這些配置通常通過QSPI相關的控制寄存器(如QUADSPI_CR、QUADSPI_DCR、QUADSPI_AR、QUADSPI_ABR、QUADSPI_DDRAR等)來完成。在進行數(shù)據(jù)讀寫操作時,命令序列的具體構成和配置需遵循SPI Flash器件的數(shù)據(jù)手冊。
1.6、指令、地址、交替字節(jié)、空指令周期、數(shù)據(jù)各階段
1.7、QSPI FLASH設置
在STM32H7系列微控制器中,配置QSPI與外部SPI Flash通信時,需要根據(jù)所使用的FLASH芯片型號在QUADSPI_DCR(QuadSPI Device Configuration Register)寄存器中設置相應的參數(shù):
-
外部存儲器大小設置:
- DCR寄存器的FSIZE[4:0]字段用于指定外部SPI Flash的大小。例如,對于W25Q128(128Mbit),F(xiàn)SIZE[4:0]應設置為23,對應224(16,777,216)字節(jié);對于W25Q256(256Mbit),F(xiàn)SIZE[4:0]應設置為24,對應225(33,554,432)字節(jié)。
-
時鐘模式設置:
- 根據(jù)SPI Flash支持的工作模式和應用需求,在DCR寄存器中設置CKMODE位來決定時鐘極性。當CKMODE = 0時,選擇SPI模式0,即CLK在CS(片選)為高電平期間保持低電平;當CKMODE = 1時,選擇SPI模式3,即CLK在CS為高電平期間保持高電平。
-
片選高電平時間設置:
- 雖然你未提及具體的寄存器位,但通常微控制器會提供相關寄存器或位來設置片選(CS)信號在命令發(fā)送前或數(shù)據(jù)傳輸間隔期間保持高電平的時鐘周期數(shù)。這對于滿足某些SPI Flash的時序要求至關重要。
在內存映射模式下,雖然QSPI可以直接訪問高達256MB的外部存儲器空間,但在間接模式下,如果使用32位尋址,最大可尋址空間可以達到4GB。在配置QSPI接口時,請務必查閱STM32H7系列微控制器的數(shù)據(jù)手冊和所使用的SPI Flash的數(shù)據(jù)手冊,以確保正確的參數(shù)設置和兼容性。
1.8、QSPI 中斷類型
在STM32H7系列微控制器的QSPI接口中,支持多種類型的中斷,這些中斷在不同操作模式下有不同的觸發(fā)條件:
-
超時中斷:
- 當QSPI操作超過預設的時間限制仍未完成時,超時中斷被觸發(fā)。這有助于及時發(fā)現(xiàn)和處理潛在的通信故障。
-
狀態(tài)匹配中斷(狀態(tài)輪詢模式下):
- 在狀態(tài)輪詢模式下,QSPI監(jiān)視外部SPI Flash的狀態(tài)寄存器。當Flash狀態(tài)寄存器中的特定位匹配預設值時,狀態(tài)匹配中斷發(fā)生,例如擦除或寫入操作完成。
-
FIFO達到閾值中斷(間接模式下):
- 在間接模式下,QSPI內部的FIFO(First-In-First-Out緩沖區(qū))設有閾值。當FIFO中的數(shù)據(jù)到達預設的滿載或空載閾值時,會觸發(fā)相應的中斷,以提示CPU進行數(shù)據(jù)的讀取或寫入。
-
傳輸完成中斷(間接模式下DLR指定字節(jié)數(shù)的數(shù)據(jù)已經(jīng)發(fā)送完成):
- 在間接模式下,當QSPI完成了通過QUADSPI_DLR寄存器設定的字節(jié)數(shù)的數(shù)據(jù)傳輸后,會觸發(fā)傳輸完成中斷(TCF,Transfer Complete Flag)。
-
傳輸錯誤中斷(間接模式下地址越界或其他錯誤):
- 當QSPI在執(zhí)行間接模式操作時遇到錯誤,例如試圖訪問超出外部SPI Flash地址范圍(地址越界),或者發(fā)生其他傳輸錯誤時,會觸發(fā)傳輸錯誤中斷。這有助于及時捕獲并處理這類異常情況,保障系統(tǒng)的穩(wěn)定性與安全性。
這些中斷可以通過配置QSPI相關的中斷使能寄存器和狀態(tài)寄存器來管理和響應。在實際應用中,合理利用中斷能夠顯著提高系統(tǒng)實時性和任務處理效率。
二、QSPI相關寄存器介紹
以下是STM32H7系列微控制器中QSPI接口相關寄存器的詳細說明:
-
QUADSPI_CR(QuadSPI Control Register)
- 用途:用于配置QSPI的基本工作參數(shù),包括使能或禁止QSPI、設置工作模式(間接模式或內存映射模式)、選擇時鐘源、設置數(shù)據(jù)線數(shù)等。
-
QUADSPI_DCR(QuadSPI Device Configuration Register)
- 用途:主要用于配置與外部SPI Flash設備交互的基本參數(shù),如SPI Flash的大?。‵SIZE字段)、地址大小、等待狀態(tài)周期數(shù)目等。
-
QUADSPI_CCR(QuadSPI Communication Configuration Register)
- 用途:配置QSPI發(fā)送給SPI Flash的命令序列的各項屬性,包括命令長度、地址長度、交替字節(jié)長度、數(shù)據(jù)長度以及數(shù)據(jù)傳輸模式(單線、雙線、四線)等。
-
QUADSPI_SR(QuadSPI Status Register)
- 用途:用于查看QSPI當前的工作狀態(tài),包括讀取FIFO的狀態(tài)、傳輸狀態(tài)、錯誤標志等信息,是判斷當前操作是否完成、是否有錯誤的重要依據(jù)。
-
QUADSPI_FCR(QuadSPI Flag Clear Register)
- 用途:用于清除QSPI_SR中的一些狀態(tài)標志位,當這些標志位被硬件置位表示某種事件發(fā)生后,可以通過寫入FCR寄存器來清除它們,以便重新開始新的操作。
-
QUADSPI_DLR(QuadSPI Data Length Register)
- 用途:用于設置在間接模式下進行數(shù)據(jù)傳輸時的字節(jié)數(shù)目,當需要傳輸固定長度的數(shù)據(jù)時,將這一長度寫入DLR寄存器。
-
QUADSPI_AR(QuadSPI Address Register)
- 用途:在需要向SPI Flash發(fā)送地址信息的命令序列中,用于指定待訪問的SPI Flash地址。
-
QUADSPI_DR(QuadSPI Data Register)
- 用途:在間接模式下,用于向SPI Flash發(fā)送或接收數(shù)據(jù),即作為數(shù)據(jù)發(fā)送和接收的緩沖區(qū)。在進行數(shù)據(jù)傳輸前,可以預先將待發(fā)送的數(shù)據(jù)寫入此寄存器,或是從該寄存器讀取接收到的數(shù)據(jù)。
- 用途:在間接模式下,用于向SPI Flash發(fā)送或接收數(shù)據(jù),即作為數(shù)據(jù)發(fā)送和接收的緩沖區(qū)。在進行數(shù)據(jù)傳輸前,可以預先將待發(fā)送的數(shù)據(jù)寫入此寄存器,或是從該寄存器讀取接收到的數(shù)據(jù)。
三、QSPI相關HAL庫驅動介紹
在STM32 HAL庫中,QSPI相關的驅動函數(shù)與寄存器的關系及功能描述如下:
-
__HAL_RCC_QSPI_CLK_ENABLE:
- 關聯(lián)寄存器:AHB3ENR(Advanced High-performance Bus 3 Enable Register)
- 功能描述:該函數(shù)用于使能QSPI外設的時鐘,通過設置AHB3ENR寄存器中的相關位來開啟QSPI的時鐘源。
-
HAL_QSPI_Init:
- 關聯(lián)寄存器:CR(Control Register)和 DCR(Device Configuration Register)
- 功能描述:初始化QSPI外設,配置QSPI的基本工作模式、時鐘模式、數(shù)據(jù)線數(shù)等基本信息,同時也包括根據(jù)外部SPI Flash設備的特點配置DCR寄存器中的相關參數(shù)。
-
HAL_QSPI_MspInit:
- 功能描述:這是一個用戶自定義的初始化回調函數(shù),主要用于初始化QSPI相關的GPIO引腳和其他硬件資源,不屬于直接與寄存器關聯(lián)的函數(shù)。
-
HAL_QSPI_Command:
- 關聯(lián)寄存器:CCR(Communication Configuration Register)、AR(Address Register)和 DLR(Data Length Register)
- 功能描述:配置和發(fā)送QSPI命令序列,包括命令字、地址和可能的交替字節(jié)等,CCR用于配置命令序列的各個組成部分,AR用于設置地址信息,DLR用于設置數(shù)據(jù)長度。
-
HAL_QSPI_Receive 和 HAL_QSPI_Transmit:
- 關聯(lián)寄存器:CCR、DLR、AR、DR(Data Register)、SR(Status Register)和 FCR(Flag Clear Register)
- 功能描述:這兩個函數(shù)分別用于從QSPI接收數(shù)據(jù)和向QSPI發(fā)送數(shù)據(jù)。在發(fā)送和接收過程中,CCR、AR、DLR用于配置傳輸參數(shù),DR用于傳輸數(shù)據(jù),SR用于查詢當前狀態(tài),F(xiàn)CR則用于清除狀態(tài)標志位。
-
QSPI相關的結構體:
-
QSPI_HandleTypeDef
:包含了QSPI外設的所有句柄信息,包括指向各種寄存器的指針、緩沖區(qū)指針、傳輸長度等。 -
QSPI_InitTypeDef
:用于配置QSPI的基本工作參數(shù),如時鐘模式、數(shù)據(jù)線數(shù)等。 -
QSPI_CommandTypeDef
:用于配置和描述QSPI的命令結構,包括命令字、地址、數(shù)據(jù)長度、交替字節(jié)等信息。
-
四、QSPI基本使用步驟
QSPI(Quad Serial Peripheral Interface)在STM32上的基本使用步驟可以總結為:
-
QSPI相關GPIO口配置
- 根據(jù)所用QSPI閃存模式(例如單線、雙線、四線模式)和BANK(如果支持多BANK的話)確定需要用到的GPIO引腳。
- 將這些引腳配置為復用推挽輸出模式,即將它們映射到QSPI功能,并設置為合適的電氣特性以支持高速通信。
示例代碼片段(偽代碼):
GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_...; // 設置對應QSPI引腳 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 復用推挽輸出 GPIO_InitStruct.Pull = GPIO_NOPULL; // 通常不用上下拉電阻,視具體情況而定 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // 設置為高速模式 GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI; // 設置為QSPI功能 HAL_GPIO_Init(GPIOx, &GPIO_InitStruct); // 初始化GPIO
-
設置QSPI相關參數(shù)及時鐘
- 創(chuàng)建并填充
QSPI_HandleTypeDef
結構體,設置QSPI的工作模式、數(shù)據(jù)線數(shù)、時鐘速率等參數(shù)。 - 調用
HAL_RCCEx_GetPeriphCLKFreq()
獲取QSPI時鐘頻率。 - 調用
HAL_QSPI_Init()
函數(shù)進行初始化,傳入上述配置好的QSPI_HandleTypeDef
結構體。
示例代碼片段:
QSPI_HandleTypeDef hqspi; hqspi.Instance = QUADSPI; hqspi.Init.ClockPrescaler = ...; // 設置時鐘預分頻 hqspi.Init.FifoThreshold = ...; // 設置FIFO閾值 hqspi.Init.SampleShifting = ...; // 是否啟用樣本位移 ... HAL_QSPI_Init(&hqspi);
- 創(chuàng)建并填充
-
使能QSPI中斷及設置MPU(Memory Protection Unit,內存保護單元)(可選)
- 如果需要使用中斷功能,可以通過設置QSPI的中斷標志位并調用HAL_NVIC_EnableIRQ()函數(shù)來使能QSPI中斷。
- 若需要使用內存映射模式,可能需要配置MPU,以確保對映射到內存空間的QSPI Flash進行合理的權限和訪問控制。
-
編寫QSPI基本通信接口
- 使用HAL庫提供的函數(shù)進行命令發(fā)送、數(shù)據(jù)接收和數(shù)據(jù)發(fā)送:
- 發(fā)送命令:
HAL_QSPI_Command(&hqspi, ..., ...)
- 接收數(shù)據(jù):
HAL_QSPI_Receive(&hqspi, ..., ...)
- 發(fā)送數(shù)據(jù):
HAL_QSPI_Transmit(&hqspi, ..., ...)
- 發(fā)送命令:
- 使用HAL庫提供的函數(shù)進行命令發(fā)送、數(shù)據(jù)接收和數(shù)據(jù)發(fā)送:
在實際應用中,還需要結合具體的應用場景和閃存芯片的數(shù)據(jù)手冊進行詳細配置和操作。例如,在進行讀寫操作前,可能需要先發(fā)送特定的讀寫命令,并根據(jù)需要擦除或寫入扇區(qū)地址。在完成操作后,可能需要輪詢狀態(tài)寄存器或等待中斷來確認操作完成。
五、SPI FLASH簡介
W25Q128是一款16MB(16,777,216字節(jié))容量的SPI(Serial Peripheral Interface)接口的NOR型閃存芯片,具備高速讀寫性能和出色的耐用性,支持多次重復擦寫且在斷電后仍能保持數(shù)據(jù)完整性,數(shù)據(jù)保存期限長達20年。
在基本操作方面,W25Q128支持以下操作:
-
擦除:W25Q128的最小擦除單位是一個扇區(qū),也就是4KB(4096字節(jié))。這意味著用戶無法單獨擦除某個字節(jié)或字節(jié)組,而必須按照扇區(qū)為單位進行擦除操作。
-
寫入:寫入操作通常以頁為單位進行,每個扇區(qū)包含16個頁,每個頁大小為256字節(jié)。不過在寫入之前,所寫的扇區(qū)必須先被擦除。
-
讀取:支持隨機讀取任意位置的數(shù)據(jù),不受擦除或寫入操作的限制。
W25Q128內部存儲空間組織結構如下:
- 整體布局:16MB的總存儲空間劃分為256個塊(Block)。
- 塊大小:每個塊的大小為64KB(65,536字節(jié))。
- 扇區(qū)劃分:每個塊又被分成16個扇區(qū),每個扇區(qū)大小為4KB(4096字節(jié))。
- 頁結構:每個扇區(qū)內部進一步細分為16個頁,每頁256字節(jié)。
因此,在對W25Q128進行編程或應用開發(fā)時,應按照塊、扇區(qū)和頁的層級結構進行數(shù)據(jù)管理,確保符合器件的擦寫和讀取規(guī)則,以提高數(shù)據(jù)操作效率和延長閃存壽命。
W25Q128JV這款SPI閃存芯片支持多種SPI接口模式,以適應不同的應用需求和提高數(shù)據(jù)傳輸速率:
- 標準SPI(Single SPI):使用一條數(shù)據(jù)線(MOSI和MISO各一條),進行單線數(shù)據(jù)傳輸。
- 雙線SPI(DUAL SPI):使用兩條數(shù)據(jù)線進行并行數(shù)據(jù)傳輸,有效加倍了數(shù)據(jù)傳輸速度。
- 四線SPI(QUAD SPI或QSPI):使用四條數(shù)據(jù)線進行并行數(shù)據(jù)傳輸,數(shù)據(jù)吞吐量是標準SPI的四倍。
在高速模式下,W25Q128JV的最高時鐘頻率可以達到133MHz。在雙線SPI模式下,由于數(shù)據(jù)線翻倍,理論上的等效傳輸速率將達到266MHz;在四線SPI模式下,四條數(shù)據(jù)線并行工作,理論上其等效傳輸速率將進一步提高至532MHz。這種高速特性使得W25Q128JV在處理大量數(shù)據(jù)和需要快速讀取/寫入的應用中表現(xiàn)優(yōu)秀。不過,實際應用中,設備的實際工作時鐘頻率應根據(jù)微控制器的SPI接口性能和系統(tǒng)穩(wěn)定性綜合考慮,并在器件數(shù)據(jù)手冊規(guī)定的范圍內進行設置。
SPI FLASH(比如W25Q128)的基本操作指令集:
指令(HEX) | 名稱 | 作用 |
---|---|---|
0x06 | 寫使能(Write Enable) | 在執(zhí)行寫入數(shù)據(jù)或擦除操作之前,必須先發(fā)送此指令,以使SPI Flash進入可寫狀態(tài)。 |
0x05 | 讀狀態(tài)寄存器1(Read Status Register 1) | 用于檢測SPI Flash是否處于空閑狀態(tài),是否準備好接受新的擦除或寫入操作。 |
0x03 | 讀數(shù)據(jù)(Read Data) | 常規(guī)讀取SPI Flash中的數(shù)據(jù),不是快速讀取。 |
0xEB | 快速讀取數(shù)據(jù)(Fast Read) | 用于更快地讀取SPI Flash數(shù)據(jù),可能需要配合地址和dummy cycles(空閑時鐘周期)來提高數(shù)據(jù)傳輸速度。 |
0x32 | 頁寫(Page Program) | 用于向SPI Flash寫入數(shù)據(jù),每次操作最多寫入256字節(jié)(一頁)的數(shù)據(jù)。 |
0x20 | 扇區(qū)擦除(Sector Erase) | 對SPI Flash執(zhí)行最小擦除單位操作,即擦除一個扇區(qū)(通常為4096字節(jié))的數(shù)據(jù)。 |
關于狀態(tài)寄存器(Status Register, SR)相關的額外命令:
指令(HEX) | 名稱 | 作用 |
---|---|---|
0x35 | 讀狀態(tài)寄存器2(Read Status Register 2) | 用于讀取SR2中的內容,其中包括QE(Quad Enable)位,用于啟用四線SPI模式(Quad SPI)。 |
0x31 | 寫狀態(tài)寄存器2(Write Status Register 2) | 用于設置SR2中的QE位,使能四線SPI模式。 |
0x15 | 讀狀態(tài)寄存器3(Read Status Register 3) | 在某些SPI Flash中用于判斷地址模式(例如4字節(jié)地址模式)是否被啟用。 |
0x11 | 寫狀態(tài)寄存器3(Write Status Register 3) | 用于在上電時設置SPI Flash的工作模式,例如啟用4字節(jié)地址模式。 |
0xB7 | 使能4字節(jié)地址模式(Enter 4-byte Address Mode) | 某些SPI Flash需要發(fā)送特定命令來切換到4字節(jié)地址模式,以便訪問更大的存儲空間。 |
請注意,不同的SPI Flash芯片可能存在略微不同的指令集和功能,具體操作請參閱各自的數(shù)據(jù)手冊以獲取準確信息。
六、SPI FLASH基本使用步驟
SPI Flash W25Q128的基本使用步驟可以簡化描述如下:
1. QSPI配置
- 初始化QSPI相關的GPIO引腳為復用推挽輸出模式,并配置相關寄存器以設置QSPI的工作模式(單線、雙線、四線模式)、時鐘速率、數(shù)據(jù)位寬等參數(shù)。
- 調用HAL庫的初始化函數(shù)如
HAL_QSPI_Init()
對QSPI外設進行初始化。
2. W25Q128讀取
- 發(fā)送快速讀取指令(0xEB):使用單線傳輸指令,然后四線傳輸?shù)刂罚▽τ谒木€SPI模式),接著四線接收數(shù)據(jù)。
- 示例代碼(偽代碼):
// 設置地址和數(shù)據(jù)長度 uint32_t read_address = ...; uint32_t data_length = ...; // 構造讀取命令包 qspi_command_packet.command = 0xEB; // 快速讀取指令 qspi_command_packet.address = read_address; qspi_command_packet.dummy_cycles = ...; // 根據(jù)器件手冊配置 qspi_command_packet.data_length = data_length; // 發(fā)送讀取命令并接收數(shù)據(jù) HAL_QSPI_Command_IT(&hqspi, &qspi_command_packet); HAL_QSPI_Receive_IT(&hqspi, receive_buffer, data_length);
3. W25Q128扇區(qū)擦除
- 發(fā)送扇區(qū)擦除指令(0x20):使用單線傳輸指令,然后單線傳輸?shù)刂罚▽τ谒木€SPI模式,此處也是單線地址),擦除操作無需傳輸數(shù)據(jù)。
- 示例代碼(偽代碼):
// 設置要擦除的扇區(qū)地址 uint32_t erase_address = ...; // 構造擦除命令包 qspi_command_packet.command = 0x20; // 扇區(qū)擦除指令 qspi_command_packet.address = erase_address; qspi_command_packet.data_length = 0; // 擦除操作無數(shù)據(jù)傳輸 // 發(fā)送擦除命令 HAL_QSPI_Command_IT(&hqspi, &qspi_command_packet); // 等待擦除完成(通常通過讀取狀態(tài)寄存器或中斷實現(xiàn))
4. W25Q128寫入
- 可選:在寫入數(shù)據(jù)前,檢查目標地址所在的扇區(qū)是否需要先進行擦除操作。
- 發(fā)送頁寫入指令(0x02或0x32,此處使用0x32為例):使用單線傳輸指令,然后單線傳輸?shù)刂?,最后四線傳輸數(shù)據(jù)。
- 示例代碼(偽代碼):
// 確認擦除(如果需要) // ... // 設置寫入地址和數(shù)據(jù) uint32_t write_address = ...; uint8_t* write_data = ...; uint32_t write_length = ...; // 構造寫入命令包 qspi_command_packet.command = 0x32; // 頁寫入指令(四線模式) qspi_command_packet.address = write_address; qspi_command_packet.data_length = write_length; qspi_command_packet.pData = write_data; // 發(fā)送寫入命令并發(fā)送數(shù)據(jù) HAL_QSPI_Command_IT(&hqspi, &qspi_command_packet); // 等待寫入完成(同樣通過讀取狀態(tài)寄存器或中斷實現(xiàn))
SPI Flash驅動注意事項:
- 是否需要擦除:在寫入新數(shù)據(jù)前,要確保目標區(qū)域已被擦除,因為SPI Flash只能向已擦除的區(qū)域寫入新數(shù)據(jù)。
- 寫入數(shù)據(jù):在進行寫入操作時,要注意數(shù)據(jù)的寫入粒度是按照頁進行的,所以需要保證數(shù)據(jù)長度合適,并且地址對齊到頁的邊界。
- 遵循讀-改-寫原則:對于修改現(xiàn)有數(shù)據(jù)的情況,應遵循先讀取原有數(shù)據(jù)、修改部分數(shù)據(jù)、然后將整個頁的數(shù)據(jù)重新寫入的流程,這是因為SPI Flash不能直接覆蓋已寫入的數(shù)據(jù)。
七、編程實戰(zhàn)
源碼
qspi.c
#include "./BSP/QSPI/qspi.h"
QSPI_HandleTypeDef g_qspi_handle; /* QSPI句柄 */
/**
* @brief 等待狀態(tài)標志
* @param flag : 需要等待的標志位
* @param sta : 需要等待的狀態(tài)
* @param wtime: 等待時間
* @retval 0, 等待成功; 1, 等待失敗.
*/
uint8_t qspi_wait_flag(uint32_t flag, uint8_t sta, uint32_t wtime)
{
uint8_t flagsta = 0;
while (wtime)
{
flagsta = (QUADSPI->SR & flag) ? 1 : 0; /* 獲取狀態(tài)標志 */
if (flagsta == sta)
{
wtime--;
}
break;
}
if (wtime)
{
return 0;
}
else
{
return 1;
}
}
/**
* @brief 初始化QSPI接口
* @param 無
* @retval 0, 成功; 1, 失敗.
*/
uint8_t qspi_init(void)
{
g_qspi_handle.Instance = QUADSPI; /* QSPI */
g_qspi_handle.Init.ClockPrescaler = 1; /* QSPI分頻比,BY25Q128最大頻率為108M,
所以此處應該為2,QSPI頻率就為220/(1+1)=110MHZ
稍微有點超頻,可以正常就好,不行就只能降低頻率 */
g_qspi_handle.Init.FifoThreshold = 4; /* FIFO閾值為4個字節(jié) */
g_qspi_handle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; /* 采樣移位半個周期(DDR模式下,必須設置為0) */
g_qspi_handle.Init.FlashSize = 25 - 1; /* SPI FLASH大小,BY25Q128大小為32M字節(jié),2^25,所以取權值25 - 1 = 24 */
g_qspi_handle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_3_CYCLE; /* 片選高電平時間為3個時鐘(9.1 * 3 = 27.3ns),即手冊里面的tSHSL參數(shù) */
g_qspi_handle.Init.ClockMode = QSPI_CLOCK_MODE_3; /* 模式3 */
g_qspi_handle.Init.FlashID = QSPI_FLASH_ID_1; /* 第一片flash */
g_qspi_handle.Init.DualFlash = QSPI_DUALFLASH_DISABLE; /* 禁止雙閃存模式 */
if (HAL_QSPI_Init(&g_qspi_handle) == HAL_OK)
{
return 0; /* QSPI初始化成功 */
}
else
{
return 1;
}
}
/**
* @brief QSPI底層驅動,引腳配置,時鐘使能
* @param hqspi:QSPI句柄
* @note 此函數(shù)會被HAL_QSPI_Init()調用
* @retval 0, 成功; 1, 失敗.
*/
void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_QSPI_CLK_ENABLE(); /* 使能QSPI時鐘 */
__HAL_RCC_GPIOB_CLK_ENABLE(); /* GPIOB時鐘使能 */
__HAL_RCC_GPIOD_CLK_ENABLE(); /* GPIOD時鐘使能 */
__HAL_RCC_GPIOE_CLK_ENABLE(); /* GPIOE時鐘使能 */
gpio_init_struct.Pin = QSPI_BK1_NCS_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 復用 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
gpio_init_struct.Alternate = GPIO_AF10_QUADSPI; /* 復用為QSPI */
HAL_GPIO_Init(QSPI_BK1_NCS_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_NCS引腳 */
gpio_init_struct.Pin = QSPI_BK1_CLK_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 復用 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
gpio_init_struct.Alternate = GPIO_AF9_QUADSPI; /* 復用為QSPI */
HAL_GPIO_Init(QSPI_BK1_CLK_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_CLK引腳 */
gpio_init_struct.Pin = QSPI_BK1_IO0_GPIO_PIN;
HAL_GPIO_Init(QSPI_BK1_IO0_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_IO0引腳 */
gpio_init_struct.Pin = QSPI_BK1_IO1_GPIO_PIN;
HAL_GPIO_Init(QSPI_BK1_IO1_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_IO1引腳 */
gpio_init_struct.Pin = QSPI_BK1_IO2_GPIO_PIN;
HAL_GPIO_Init(QSPI_BK1_IO2_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_IO2引腳 */
gpio_init_struct.Pin = QSPI_BK1_IO3_GPIO_PIN;
HAL_GPIO_Init(QSPI_BK1_IO3_GPIO_PORT, &gpio_init_struct); /* 初始化QSPI_BK1_IO3引腳 */
}
/**
* @brief QSPI發(fā)送命令
* @param cmd : 要發(fā)送的指令
* @param addr: 發(fā)送到的目的地址
* @param mode: 模式,詳細位定義如下:
* @arg mode[1:0]: 指令模式; 00,無指令; 01,單線傳輸指令; 10,雙線傳輸指令; 11,四線傳輸指令.
* @arg mode[3:2]: 地址模式; 00,無地址; 01,單線傳輸?shù)刂? 10,雙線傳輸?shù)刂? 11,四線傳輸?shù)刂?
* @arg mode[5:4]: 地址長度; 00,8位地址; 01,16位地址; 10,24位地址; 11,32位地址.
* @arg mode[7:6]: 數(shù)據(jù)模式; 00,無數(shù)據(jù); 01,單線傳輸數(shù)據(jù); 10,雙線傳輸數(shù)據(jù); 11,四線傳輸數(shù)據(jù).
* @param dmcycle: 空指令周期數(shù)
* @retval 無
*/
void qspi_send_cmd(uint8_t cmd, uint32_t addr, uint8_t mode, uint8_t dmcycle)
{
QSPI_CommandTypeDef qspi_command_init;
qspi_command_init.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次都發(fā)送指令 */
qspi_command_init.DdrMode = QSPI_DDR_MODE_DISABLE; /* 關閉DDR模式,使用SDR模式 */
qspi_command_init.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式下,用于設置延遲半個時鐘周期再數(shù)據(jù)輸出 */
/* 指令階段 */
qspi_command_init.Instruction = cmd; /* 要發(fā)送的指令 */
/* 設置指令階段需要幾線模式 */
if (((mode >> 0) & 0x03) == 0)
qspi_command_init.InstructionMode = QSPI_INSTRUCTION_NONE; /* 不需要指令階段 */
if (((mode >> 0) & 0x03) == 1)
qspi_command_init.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 單線模式 */
if (((mode >> 0) & 0x03) == 2)
qspi_command_init.InstructionMode = QSPI_INSTRUCTION_2_LINES; /* 雙線模式 */
if (((mode >> 0) & 0x03) == 3)
qspi_command_init.InstructionMode = QSPI_INSTRUCTION_4_LINES; /* 四線模式 */
/* 地址階段 */
qspi_command_init.Address = addr; /* 要發(fā)送的地址 */
/* 設置地址長度 */
if (((mode >> 4) & 0x03) == 0)
qspi_command_init.AddressSize = QSPI_ADDRESS_8_BITS; /* 8位地址 */
if (((mode >> 4) & 0x03) == 1)
qspi_command_init.AddressSize = QSPI_ADDRESS_16_BITS; /* 16位地址 */
if (((mode >> 4) & 0x03) == 2)
qspi_command_init.AddressSize = QSPI_ADDRESS_24_BITS; /* 24位地址 */
if (((mode >> 4) & 0x03) == 3)
qspi_command_init.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */
/* 設置地址階段需要幾線模式 */
if (((mode >> 2) & 0x03) == 0)
qspi_command_init.AddressMode = QSPI_ADDRESS_NONE; /* 不需要地址階段 */
if (((mode >> 2) & 0x03) == 1)
qspi_command_init.AddressMode = QSPI_ADDRESS_1_LINE; /* 單線模式 */
if (((mode >> 2) & 0x03) == 2)
qspi_command_init.AddressMode = QSPI_ADDRESS_2_LINES; /* 雙線模式 */
if (((mode >> 2) & 0x03) == 3)
qspi_command_init.AddressMode = QSPI_ADDRESS_4_LINES; /* 四線模式 */
/* 交替字節(jié)階段 */
qspi_command_init.AlternateBytes = 0; /* 交替字節(jié)內容 */
qspi_command_init.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS; /* 交替字節(jié)長度 */
qspi_command_init.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 交替字節(jié)階段需要幾線模式 */
/* 空指令周期階段 */
qspi_command_init.DummyCycles = dmcycle; /* 空指令周期數(shù) */
/* 數(shù)據(jù)階段 */
/* 不設置NbData成員,在qspi_transmit/receive函數(shù)中指定 */
// qspi_command_init.NbData = ; /* 數(shù)據(jù)長度 */
/* 設置數(shù)據(jù)階段需要幾線模式 */
if (((mode >> 6) & 0x03) == 0)
qspi_command_init.DataMode = QSPI_DATA_NONE; /* 不需要數(shù)據(jù)階段 */
if (((mode >> 6) & 0x03) == 1)
qspi_command_init.DataMode = QSPI_DATA_1_LINE; /* 單線模式 */
if (((mode >> 6) & 0x03) == 2)
qspi_command_init.DataMode = QSPI_DATA_2_LINES; /* 雙線模式 */
if (((mode >> 6) & 0x03) == 3)
qspi_command_init.DataMode = QSPI_DATA_4_LINES; /* 四線模式 */
HAL_QSPI_Command(&g_qspi_handle, &qspi_command_init, 5000); /* 用于向QSPI FLASH發(fā)送命令 */
}
/**
* @brief QSPI發(fā)送指定長度的數(shù)據(jù)
* @param buf : 發(fā)送數(shù)據(jù)緩沖區(qū)首地址
* @param datalen : 要傳輸?shù)臄?shù)據(jù)長度
* @retval 0, 成功; 其他, 錯誤代碼
*/
uint8_t qspi_transmit(uint8_t *buf, uint32_t datalen)
{
g_qspi_handle.Instance->DLR = datalen - 1; /* 直接使用寄存器賦值的方式設置要發(fā)送的數(shù)據(jù)字節(jié)數(shù) */
if (HAL_QSPI_Transmit(&g_qspi_handle, buf, 5000) == HAL_OK)
{
return 0;
}
else
{
return 1;
}
}
/**
* @brief QSPI接收指定長度的數(shù)據(jù)
* @param buf : 接收數(shù)據(jù)緩沖區(qū)首地址
* @param datalen : 要傳輸?shù)臄?shù)據(jù)長度
* @retval 0, 成功; 其他, 錯誤代碼.
*/
uint8_t qspi_receive(uint8_t *buf, uint32_t datalen)
{
g_qspi_handle.Instance->DLR = datalen - 1; /* 直接使用寄存器賦值的方式設置要發(fā)送的數(shù)據(jù)字節(jié)數(shù) */
if (HAL_QSPI_Receive(&g_qspi_handle, buf, 5000) == HAL_OK)
{
return 0;
}
else
{
return 1;
}
}
qspi.h
#ifndef __QSPI_H
#define __QSPI_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* QSPI 相關 引腳 定義 */
#define QSPI_BK1_CLK_GPIO_PORT GPIOB
#define QSPI_BK1_CLK_GPIO_PIN GPIO_PIN_2
#define QSPI_BK1_CLK_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_CLK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE; }while(0) /* PB口時鐘使能 */
#define QSPI_BK1_NCS_GPIO_PORT GPIOB
#define QSPI_BK1_NCS_GPIO_PIN GPIO_PIN_6
#define QSPI_BK1_NCS_GPIO_AF GPIO_AF10_QUADSPI
#define QSPI_BK1_NCS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE; }while(0) /* PB口時鐘使能 */
#define QSPI_BK1_IO0_GPIO_PORT GPIOD
#define QSPI_BK1_IO0_GPIO_PIN GPIO_PIN_11
#define QSPI_BK1_IO0_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_IO0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0) /* PD口時鐘使能 */
#define QSPI_BK1_IO1_GPIO_PORT GPIOD
#define QSPI_BK1_IO1_GPIO_PIN GPIO_PIN_12
#define QSPI_BK1_IO1_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_IO1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0) /* PD口時鐘使能 */
#define QSPI_BK1_IO2_GPIO_PORT GPIOD
#define QSPI_BK1_IO2_GPIO_PIN GPIO_PIN_13
#define QSPI_BK1_IO2_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_IO2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0) /* PD口時鐘使能 */
#define QSPI_BK1_IO3_GPIO_PORT GPIOE
#define QSPI_BK1_IO3_GPIO_PIN GPIO_PIN_2
#define QSPI_BK1_IO3_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_IO3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE; }while(0) /* PE口時鐘使能 */
/******************************************************************************************/
uint8_t qspi_wait_flag(uint32_t flag, uint8_t sta, uint32_t wtime); /* QSPI等待某個狀態(tài) */
uint8_t qspi_init(void); /* 初始化QSPI */
void qspi_send_cmd(uint8_t cmd, uint32_t addr, uint8_t mode, uint8_t dmcycle); /* QSPI發(fā)送命令 */
uint8_t qspi_receive(uint8_t *buf, uint32_t datalen); /* QSPI接收數(shù)據(jù) */
uint8_t qspi_transmit(uint8_t *buf, uint32_t datalen); /* QSPI發(fā)送數(shù)據(jù) */
#endif
norflash.c
#include "./BSP/QSPI/qspi.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/NORFLASH/norflash.h"
uint16_t g_norflash_type = W25Q128; /* 默認是W25Q128 */
/* SPI FLASH 地址位寬 */
volatile uint8_t g_norflash_addrw = 2; /* SPI FLASH地址位寬, 在norflash_read_id函數(shù)里面被修改
* 2, 表示24bit地址寬度
* 3, 表示32bit地址寬度
*/
/**
* @brief 初始化NOR FLASH
* @param 無
* @retval 無
*/
void norflash_init(void)
{
uint8_t temp;
qspi_init(); /* 初始化QSPI */
norflash_qspi_disable(); /* 退出QPI模式(避免芯片之前進入這個模式,導致下載失敗) */
norflash_qe_enable(); /* 使能QE位 */
g_norflash_type = norflash_read_id(); /* 讀取FLASH ID. */
if (g_norflash_type == W25Q256) /* SPI FLASH為W25Q256, 必須使能4字節(jié)地址模式 */
{
temp = norflash_read_sr(3); /* 讀取狀態(tài)寄存器3,判斷地址模式 */
if ((temp & 0X01) == 0) /* 如果不是4字節(jié)地址模式,則進入4字節(jié)地址模式 */
{
norflash_write_enable(); /* 寫使能 */
temp |= 1 << 1; /* ADP=1, 上電4字節(jié)地址模式 */
norflash_write_sr(3, temp); /* 寫SR3 */
norflash_write_enable(); /* 寫使能 */
/* SPI, 使能4字節(jié)地址指令, 地址為0, 無數(shù)據(jù)_8位地址_無地址_單線傳輸指令, 無空指令周期 */
qspi_send_cmd(FLASH_Enable4ByteAddr, 0, (0 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
}
}
//printf("ID:%x\r\n", g_norflash_type);
}
/**
* @brief 等待空閑
* @param 無
* @retval 無
*/
static void norflash_wait_busy(void)
{
while ((norflash_read_sr(1) & 0x01) == 0x01); /* 等待BUSY位清空 */
}
/**
* @brief 退出QSPI模式
* @param 無
* @retval 無
*/
static void norflash_qspi_disable(void)
{
/* 退出QPI模式指令, 地址為0, 無數(shù)據(jù)_8位地址_無地址_4線傳輸指令, 無空周期 */
qspi_send_cmd(FLASH_ExitQPIMode, 0, (0 << 6) | (0 << 4) | (0 << 2) | (3 << 0), 0);
}
/**
* @brief 使能FLASH QE位,使能IO2/IO3
* @param 無
* @retval 無
*/
static void norflash_qe_enable(void)
{
uint8_t stareg2 = 0;
stareg2 = norflash_read_sr(2); /* 先讀出狀態(tài)寄存器2的原始值 */
//printf("stareg2:%x\r\n", stareg2);
if ((stareg2 & 0X02) == 0) /* QE位未使能 */
{
norflash_write_enable(); /* 寫使能 */
stareg2 |= 1 << 1; /* 使能QE位 */
norflash_write_sr(2, stareg2); /* 寫狀態(tài)寄存器2 */
}
}
/**
* @brief 25QXX寫使能
* @note 將SR1寄存器的WEL置位
* @param 無
* @retval 無
*/
void norflash_write_enable(void)
{
/* SPI, 寫使能指令, 地址為0, 無數(shù)據(jù)_8位地址_無地址_單線傳輸指令, 無空周期 */
qspi_send_cmd(FLASH_WriteEnable, 0, (0 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
}
/**
* @brief 25QXX寫禁止
* @note 將S1寄存器的WEL清零
* @param 無
* @retval 無
*/
void norflash_write_disable(void)
{
/* SPI, 寫禁止指令, 地址為0, 無數(shù)據(jù)_8位地址_無地址_單線傳輸指令, 無空周期 */
qspi_send_cmd(FLASH_WriteDisable, 0, (0 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
}
/**
* @brief 讀取25QXX的狀態(tài)寄存器,25QXX一共有3個狀態(tài)寄存器
* @note 狀態(tài)寄存器1:
* BIT7 6 5 4 3 2 1 0
* SPR RV TB BP2 BP1 BP0 WEL BUSY
* SPR:默認0,狀態(tài)寄存器保護位,配合WP使用
* TB,BP2,BP1,BP0:FLASH區(qū)域寫保護設置
* WEL:寫使能鎖定
* BUSY:忙標記位(1,忙;0,空閑)
* 默認:0x00
*
* 狀態(tài)寄存器2:
* BIT7 6 5 4 3 2 1 0
* SUS CMP LB3 LB2 LB1 (R) QE SRP1
*
* 狀態(tài)寄存器3:
* BIT7 6 5 4 3 2 1 0
* HOLD/RST DRV1 DRV0 (R) (R) WPS ADP ADS
*
* @param regno: 狀態(tài)寄存器號,范圍:1~3
* @retval 狀態(tài)寄存器值
*/
uint8_t norflash_read_sr(uint8_t regno)
{
uint8_t byte = 0, command = 0;
switch (regno)
{
case 1:
command = FLASH_ReadStatusReg1; /* 讀狀態(tài)寄存器1指令 */
break;
case 2:
command = FLASH_ReadStatusReg2; /* 讀狀態(tài)寄存器2指令 */
break;
case 3:
command = FLASH_ReadStatusReg3; /* 讀狀態(tài)寄存器3指令 */
break;
default:
command = FLASH_ReadStatusReg1;
break;
}
/* SPI, 發(fā)送command指令, 地址為0, 單線傳輸數(shù)據(jù)_8位地址_無地址_單線傳輸指令,無空周期 */
qspi_send_cmd(command, 0, (1 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
qspi_receive(&byte, 1); /* 讀狀態(tài)寄存器指令會返回1個字節(jié)數(shù)據(jù) */
return byte;
}
/**
* @brief 寫25QXX狀態(tài)寄存器
* @note 寄存器說明見norflash_read_sr函數(shù)說明
* @param regno: 狀態(tài)寄存器號,范圍:1~3
* @param sr : 要寫入狀態(tài)寄存器的值
* @retval 無
*/
void norflash_write_sr(uint8_t regno, uint8_t sr)
{
uint8_t command = 0;
switch (regno)
{
case 1:
command = FLASH_WriteStatusReg1; /* 寫狀態(tài)寄存器1指令 */
break;
case 2:
command = FLASH_WriteStatusReg2; /* 寫狀態(tài)寄存器2指令 */
break;
case 3:
command = FLASH_WriteStatusReg3; /* 寫狀態(tài)寄存器3指令 */
break;
default:
command = FLASH_WriteStatusReg1;
break;
}
/* SPI, 發(fā)送command指令, 地址為0, 單線傳輸數(shù)據(jù)_8位地址_無地址_單線傳輸指令,無空周期,1個字節(jié)數(shù)據(jù) */
qspi_send_cmd(command, 0, (1 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
qspi_transmit(&sr, 1); /* 寫狀態(tài)寄存器指令需要寫入1個字節(jié)數(shù)據(jù) */
}
/**
* @brief 讀取芯片ID
* @param 無
* @retval FLASH芯片ID
* @note 芯片ID列表見: norflash.h, 芯片列表部分
*/
uint16_t norflash_read_id(void)
{
uint8_t temp[2];
uint16_t deviceid;
qspi_init(); /* 進行庫函數(shù)調用前要先初始化 */
/* SPI, 讀id指令, 地址為0, 單線傳輸數(shù)據(jù)_24位地址_單線傳輸?shù)刂穇單線傳輸指令, 無空周期 */
qspi_send_cmd(FLASH_ManufactDeviceID, 0, (1 << 6) | (2 << 4) | (1 << 2) | (1 << 0), 0);
qspi_receive(temp, 2); /* 讀狀態(tài)寄存器指令會返回2個字節(jié)數(shù)據(jù) */
deviceid = (temp[0] << 8) | temp[1];
if (deviceid == W25Q256)
{
g_norflash_addrw = 3; /* 如果是W25Q256, 標記32bit地址寬度 */
}
return deviceid;
}
/**
* @brief 讀取SPI FLASH,僅支持QSPI模式
* @note 在指定地址開始讀取指定長度的數(shù)據(jù)
* @param pbuf : 數(shù)據(jù)存儲區(qū)
* @param addr : 開始讀取的地址(最大32bit)
* @param datalen : 要讀取的字節(jié)數(shù)(最大65535)
* @retval 無
*/
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
/* QSPI, 快速讀數(shù)據(jù)指令, 地址為addr, 4線傳輸數(shù)據(jù)_24/32位地址_4線傳輸?shù)刂穇1線傳輸指令, 6個空指令周期 */
qspi_send_cmd(FLASH_FastReadQuad, addr, (3 << 6) | (g_norflash_addrw << 4) | (3 << 2) | (1 << 0), 6);
qspi_receive(pbuf, datalen); /* 快速讀數(shù)據(jù)指令會返回設置的datalen個字節(jié)數(shù)據(jù) */
}
/**
* @brief SPI在一頁(0~65535)內寫入少于256個字節(jié)的數(shù)據(jù)
* @note 在指定地址開始寫入最大256字節(jié)的數(shù)據(jù)
* @param pbuf : 數(shù)據(jù)存儲區(qū)
* @param addr : 開始寫入的地址(最大32bit)
* @param datalen : 要寫入的字節(jié)數(shù)(最大256),該數(shù)不應該超過該頁的剩余字節(jié)數(shù)!!!
* @retval 無
*/
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
norflash_write_enable(); /* 寫使能 */
/* QSPI, 頁寫指令, 地址為addr, 4線傳輸數(shù)據(jù)_24/32位地址_1線傳輸?shù)刂穇1線傳輸指令, 無空周期 */
qspi_send_cmd(FLASH_PageProgramQuad, addr, (3 << 6) | (g_norflash_addrw << 4) | (1 << 2) | (1 << 0), 0);
qspi_transmit(pbuf, datalen); /* 頁寫指令會需要發(fā)送設置的datalen個字節(jié)數(shù)據(jù) */
norflash_wait_busy(); /* 等待寫入結束 */
}
/**
* @brief 無檢驗寫SPI FLASH
* @note 必須確保所寫的地址范圍內的數(shù)據(jù)全部為0XFF,否則在非0XFF處寫入的數(shù)據(jù)將失敗!
* 具有自動換頁功能
* 在指定地址開始寫入指定長度的數(shù)據(jù),但是要確保地址不越界!
*
* @param pbuf : 數(shù)據(jù)存儲區(qū)
* @param addr : 開始寫入的地址(最大32bit)
* @param datalen : 要寫入的字節(jié)數(shù)(最大65535)
* @retval 無
*/
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t pageremain;
pageremain = 256 - addr % 256; /* 單頁剩余的字節(jié)數(shù) */
if (datalen <= pageremain) /* 不大于256個字節(jié) */
{
pageremain = datalen;
}
while (1)
{
/* 當寫入字節(jié)比頁內剩余地址還少的時候, 一次性寫完
* 當寫入直接比頁內剩余地址還多的時候, 先寫完整個頁內剩余地址, 然后根據(jù)剩余長度進行不同處理
*/
norflash_write_page(pbuf, addr, pageremain);
if (datalen == pageremain) /* 寫入結束了 */
{
break;
}
else /* datalen > pageremain */
{
pbuf += pageremain; /* pbuf指針地址偏移,前面已經(jīng)寫了pageremain字節(jié) */
addr += pageremain; /* 寫地址偏移,前面已經(jīng)寫了pageremain字節(jié) */
datalen -= pageremain; /* 寫入總長度減去已經(jīng)寫入了的字節(jié)數(shù) */
if (datalen > 256) /* 剩余數(shù)據(jù)還大于一頁,可以一次寫一頁 */
{
pageremain = 256; /* 一次可以寫入256個字節(jié) */
}
else /* 剩余數(shù)據(jù)小于一頁,可以一次寫完 */
{
pageremain = datalen; /* 不夠256個字節(jié)了 */
}
}
}
}
/**
* @brief 寫SPI FLASH
* @note 在指定地址開始寫入指定長度的數(shù)據(jù) , 該函數(shù)帶擦除操作!
* SPI FLASH 一般是: 256個字節(jié)為一個Page, 4Kbytes為一個Sector, 16個扇區(qū)為1個Block
* 擦除的最小單位為Sector.
*
* @param pbuf : 數(shù)據(jù)存儲區(qū)
* @param addr : 開始寫入的地址(最大32bit)
* @param datalen : 要寫入的字節(jié)數(shù)(最大65535)
* @retval 無
*/
uint8_t g_norflash_buf[4096]; /* 扇區(qū)緩存 */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t *norflash_buf;
norflash_buf = g_norflash_buf;
secpos = addr / 4096; /* 扇區(qū)地址 */
secoff = addr % 4096; /* 在扇區(qū)內的偏移 */
secremain = 4096 - secoff; /* 扇區(qū)剩余空間大小 */
//printf("ad:%X,nb:%X\r\n", addr, datalen); /* 測試用 */
if (datalen <= secremain)
{
secremain = datalen; /* 不大于4096個字節(jié) */
}
while (1)
{
norflash_read(norflash_buf, secpos * 4096, 4096); /* 讀出整個扇區(qū)的內容 */
for (i = 0; i < secremain; i++) /* 校驗數(shù)據(jù) */
{
if (norflash_buf[secoff + i] != 0XFF)
{
break; /* 需要擦除, 直接退出for循環(huán) */
}
}
if (i < secremain) /* 需要擦除 */
{
norflash_erase_sector(secpos); /* 擦除這個扇區(qū) */
for (i = 0; i < secremain; i++) /* 復制 */
{
norflash_buf[i + secoff] = pbuf[i];
}
norflash_write_nocheck(norflash_buf, secpos * 4096, 4096); /* 寫入整個扇區(qū) */
}
else /* 寫已經(jīng)擦除了的,直接寫入扇區(qū)剩余區(qū)間. */
{
norflash_write_nocheck(pbuf, addr, secremain); /* 直接寫扇區(qū) */
}
if (datalen == secremain)
{
break; /* 寫入結束了 */
}
else /* 寫入未結束 */
{
secpos++; /* 扇區(qū)地址增1 */
secoff = 0; /* 偏移位置為0 */
pbuf += secremain; /* 指針偏移 */
addr += secremain; /* 寫地址偏移 */
datalen -= secremain; /* 字節(jié)數(shù)遞減 */
if (datalen > 4096)
{
secremain = 4096; /* 下一個扇區(qū)還是寫不完 */
}
else
{
secremain = datalen;/* 下一個扇區(qū)可以寫完了 */
}
}
}
}
/**
* @brief 擦除整個芯片
* @note 等待時間超長...
* @param 無
* @retval 無
*/
void norflash_erase_chip(void)
{
norflash_write_enable(); /* 寫使能 */
norflash_wait_busy(); /* 等待空閑 */
/* SPI, 寫全片擦除指令, 地址為0, 無數(shù)據(jù)_8位地址_無地址_1線傳輸指令, 無空周期 */
qspi_send_cmd(FLASH_ChipErase, 0, (0 << 6) | (0 << 4) | (0 << 2) | (1 << 0), 0);
norflash_wait_busy(); /* 等待芯片擦除結束 */
}
/**
* @brief 擦除一個扇區(qū)
* @note 注意,這里是扇區(qū)地址,不是字節(jié)地址!!
* 擦除一個扇區(qū)的最少時間:150ms
*
* @param saddr : 扇區(qū)地址 根據(jù)實際容量設置
* @retval 無
*/
void norflash_erase_sector(uint32_t saddr)
{
//printf("fe:%x\r\n", saddr); /* 監(jiān)視falsh擦除情況,測試用 */
saddr *= 4096;
norflash_write_enable(); /* 寫使能 */
norflash_wait_busy(); /* 等待空閑 */
/* SPI, 寫扇區(qū)擦除指令, 地址為0, 無數(shù)據(jù)_24/32位地址_1線傳輸?shù)刂穇1線傳輸指令, 無空周期 */
qspi_send_cmd(FLASH_SectorErase, saddr, (0 << 6) | (g_norflash_addrw << 4) | (1 << 2) | (1 << 0), 0);
norflash_wait_busy(); /* 等待擦除完成 */
}
norflash.h
#ifndef __norflash_H
#define __norflash_H
#include "./SYSTEM/sys/sys.h"
/* FLASH芯片列表 */
#define W25Q80 0XEF13 /* W25Q80 芯片ID */
#define W25Q16 0XEF14 /* W25Q16 芯片ID */
#define W25Q32 0XEF15 /* W25Q32 芯片ID */
#define W25Q64 0XEF16 /* W25Q64 芯片ID */
#define W25Q128 0XEF17 /* W25Q128 芯片ID */
#define W25Q256 0XEF18 /* W25Q256 芯片ID */
#define BY25Q64 0X6816 /* BY25Q64 芯片ID */
#define BY25Q128 0X6817 /* BY25Q128 芯片ID */
#define NM25Q64 0X5216 /* NM25Q64 芯片ID */
#define NM25Q128 0X5217 /* NM25Q128 芯片ID */
extern uint16_t norflash_TYPE; /* 定義FLASH芯片型號 */
/* 指令表 */
#define FLASH_WriteEnable 0x06
#define FLASH_WriteDisable 0x04
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadStatusReg2 0x35
#define FLASH_ReadStatusReg3 0x15
#define FLASH_WriteStatusReg1 0x01
#define FLASH_WriteStatusReg2 0x31
#define FLASH_WriteStatusReg3 0x11
#define FLASH_ReadData 0x03
#define FLASH_FastReadData 0x0B
#define FLASH_FastReadDual 0x3B
#define FLASH_FastReadQuad 0xEB
#define FLASH_PageProgram 0x02
#define FLASH_PageProgramQuad 0x32
#define FLASH_BlockErase 0xD8
#define FLASH_SectorErase 0x20
#define FLASH_ChipErase 0xC7
#define FLASH_PowerDown 0xB9
#define FLASH_ReleasePowerDown 0xAB
#define FLASH_DeviceID 0xAB
#define FLASH_ManufactDeviceID 0x90
#define FLASH_JedecDeviceID 0x9F
#define FLASH_Enable4ByteAddr 0xB7
#define FLASH_Exit4ByteAddr 0xE9
#define FLASH_SetReadParam 0xC0
#define FLASH_EnterQPIMode 0x38
#define FLASH_ExitQPIMode 0xFF
/* 靜態(tài)函數(shù) */
static void norflash_wait_busy(void); /* 等待空閑 */
static void norflash_qe_enable(void); /* 使能QE位 */
static void norflash_qspi_disable(void); /* 退出QPI模式 */
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 寫入page */
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 寫flash,不帶擦除 */
/* 普通函數(shù) */
void norflash_init(void); /* 初始化25QXX */
uint16_t norflash_read_id(void); /* 讀取FLASH ID */
void norflash_write_enable(void); /* 寫使能 */
void norflash_write_disable(void); /* 寫保護 */
uint8_t norflash_read_sr(uint8_t regno); /* 讀取狀態(tài)寄存器 */
void norflash_write_sr(uint8_t regno,uint8_t sr); /* 寫狀態(tài)寄存器 */
void norflash_erase_chip(void); /* 整片擦除 */
void norflash_erase_sector(uint32_t saddr); /* 扇區(qū)擦除 */
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 讀取flash */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 寫入flash */
#endif
norflash_ex.c
#include "./BSP/QSPI/qspi.h"
#include "./BSP/NORFLASH/norflash.h"
#include "./BSP/NORFLASH/norflash_ex.h"
extern uint8_t g_norflash_addrw; /* 表示當前是24bit/32bit數(shù)據(jù)位寬, 在norflash.c里面定義 */
/**
* @brief QSPI接口進入內存映射模式
* @note 調用該函數(shù)之前務必已經(jīng)初始化了QSPI接口
* sys_qspi_enable_memmapmode or norflash_init
* @param 無
* @retval 無
*/
static void norflash_ex_enter_mmap(void)
{
uint32_t tempreg = 0;
/* BY/W25QXX 寫使能(0X06指令) */
while (QUADSPI->SR & (1 << 5)); /* 等待BUSY位清零 */
QUADSPI->CCR = 0X00000106; /* 發(fā)送0X06指令,BY/W25QXX寫使能 */
while ((QUADSPI->SR & (1 << 1)) == 0); /* 等待指令發(fā)送完成 */
QUADSPI->FCR |= 1 << 1;
if (qspi_wait_flag(1 << 5, 0, 0XFFFF) == 0) /* 等待BUSY空閑 */
{
tempreg = 0XEB; /* INSTRUCTION[7:0]=0XEB,發(fā)送0XEB指令(Fast Read QUAD I/O) */
tempreg |= 1 << 8; /* IMODE[1:0]=1,單線傳輸指令 */
tempreg |= 3 << 10; /* ADDRESS[1:0]=3,四線傳輸?shù)刂?*/
tempreg |= (uint32_t)g_norflash_addrw << 12; /* ADSIZE[1:0]=2,24/32位地址長度 */
tempreg |= 3 << 14; /* ABMODE[1:0]=3,四線傳輸交替字節(jié) */
tempreg |= 0 << 16; /* ABSIZE[1:0]=0,8位交替字節(jié)(M0~M7) */
tempreg |= 4 << 18; /* DCYC[4:0]=4,4個dummy周期 */
tempreg |= 3 << 24; /* DMODE[1:0]=3,四線傳輸數(shù)據(jù) */
tempreg |= 3 << 26; /* FMODE[1:0]=3,內存映射模式 */
QUADSPI->CCR = tempreg; /* 設置CCR寄存器 */
}
sys_intx_enable(); /* 開啟中斷 */
}
/**
* @brief QSPI接口退出內存映射模式
* @note 調用該函數(shù)之前務必已經(jīng)初始化了QSPI接口
* sys_qspi_enable_memmapmode or norflash_init
* @param 無
* @retval 0, OK; 其他, 錯誤代碼
*/
static uint8_t norflash_ex_exit_mmap(void)
{
uint8_t res = 0;
sys_intx_disable(); /* 關閉中斷 */
SCB_InvalidateICache(); /* 清空I CACHE */
SCB_InvalidateDCache(); /* 清空D CACHE */
QUADSPI->CR &= ~(1 << 0); /* 關閉 QSPI 接口 */
QUADSPI->CR |= 1 << 1; /* 退出MEMMAPED模式 */
res = qspi_wait_flag(1 << 5, 0, 0XFFFF); /* 等待BUSY空閑 */
if (res == 0)
{
QUADSPI->CCR = 0; /* CCR寄存器清零 */
QUADSPI->CR |= 1 << 0; /* 使能 QSPI 接口 */
}
return res;
}
/**
* @brief 往 QSPI FLASH寫入數(shù)據(jù)
* @note 在指定地址開始寫入指定長度的數(shù)據(jù)
* 該函數(shù)帶擦除操作!
* @param pbuf : 數(shù)據(jù)存儲區(qū)
* @param addr : 開始寫入的地址(最大32bit)
* @param datalen : 要寫入的字節(jié)數(shù)(最大65535)
* @retval 0, OK; 其他, 錯誤代碼
*/
uint8_t norflash_ex_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint8_t res = 0;
res = norflash_ex_exit_mmap(); /* 退出內存映射模式 */
if (res == 0)
{
norflash_write(pbuf, addr, datalen);
}
norflash_ex_enter_mmap(); /* 進入內存映射模式 */
return res;
}
/**
* @brief 從 QSPI FLASH 讀取數(shù)據(jù)
* @note 在指定地址開始讀取指定長度的數(shù)據(jù)(必須處于內存映射模式下,才可以執(zhí)行)
*
* @param pbuf : 數(shù)據(jù)存儲區(qū)
* @param addr : 開始讀取的地址(最大32bit)
* @param datalen : 要讀取的字節(jié)數(shù)(最大65535)
* @retval 0, OK; 其他, 錯誤代碼
*/
void norflash_ex_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t i = 0;
addr += 0X90000000; /* 使用內存映射模式讀取,QSPI的基址是0X90000000,所以這里要加上基址 */
sys_intx_disable(); /* 關閉中斷 */
for (i = 0; i < datalen; i++)
{
pbuf[i] = *(volatile uint8_t *)(addr + i);
}
sys_intx_enable(); /* 開啟中斷 */
}
/**
* @brief 讀取QSPI FLASH的ID
* @param 無
* @retval NOR FLASH ID
*/
uint16_t norflash_ex_read_id(void)
{
uint8_t res = 0;
uint16_t id = 0;
res = norflash_ex_exit_mmap(); /* 退出內存映射模式 */
if (res == 0)
{
id = norflash_read_id();
}
norflash_ex_enter_mmap(); /* 進入內存映射模式 */
return id;
}
/**
* @brief 擦除QSPI FLASH的某個扇區(qū)
* @note 注意,這里是扇區(qū)地址,不是字節(jié)地址!!
* 擦除一個扇區(qū)的最少時間:150ms
*
* @param saddr: 扇區(qū)地址
* @retval 無
*/
void norflash_ex_erase_sector(uint32_t addr)
{
uint8_t res = 0;
res = norflash_ex_exit_mmap(); /* 退出內存映射模式 */
if (res == 0)
{
norflash_erase_sector(addr);
}
norflash_ex_enter_mmap(); /* 進入內存映射模式 */
}
/**
* @brief 擦除QSPI FLASH整個芯片
* @note 等待時間超長...
*
* @param 無
* @retval 無
*/
void norflash_ex_erase_chip(void)
{
uint8_t res = 0;
res = norflash_ex_exit_mmap(); /* 退出內存映射模式 */
if (res == 0)
{
norflash_erase_chip();
}
norflash_ex_enter_mmap(); /* 進入內存映射模式 */
}
norflash_ex.h
#ifndef __NORFLASH_EX_H
#define __NORFLASH_EX_H
#include "./SYSTEM/sys/sys.h"
void norflash_ex_erase_chip(void); /* NOR FLASH 全片擦除 */
uint16_t norflash_ex_read_id(void); /* NOR FLASH讀取ID */
void norflash_ex_erase_sector(uint32_t addr); /* NOR FLASH 擦除扇區(qū) */
uint8_t norflash_ex_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* NOR FLASH寫入數(shù)據(jù) */
void norflash_ex_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* NOR FLASH讀取數(shù)據(jù) */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/MPU/mpu.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/QSPI/qspi.h"
#include "./BSP/NORFLASH/norflash.h"
#include "./BSP/NORFLASH/norflash_ex.h"
#include "string.h"
/* 要寫入到FLASH的字符串數(shù)組 */
const uint8_t g_text_buf[] = {"MiniPRO H7 QSPI TEST"};
#define TEXT_SIZE sizeof(g_text_buf) /* TEXT字符串長度 */
int main(void)
{
uint8_t key;
uint16_t i = 0;
uint8_t datatemp[TEXT_SIZE + 2];
uint8_t rectemp[TEXT_SIZE + 2];
uint32_t flashsize;
uint16_t id = 0;
sys_cache_enable(); /* 打開L1-Cache */
HAL_Init(); /* 初始化HAL庫 */
sys_stm32_clock_init(240, 2, 2, 4); /* 設置時鐘, 480Mhz */
delay_init(480); /* 延時初始化 */
usart_init(115200); /* 串口初始化為115200 */
usmart_dev.init(240); /* 初始化USMART */
mpu_memory_protection(); /* 保護相關存儲區(qū)域 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按鍵 */
/*
* 不需要調用norflash_init函數(shù)了,因為sys.c里面的sys_qspi_enable_memmapmode函數(shù)已
* 經(jīng)初始化了QSPI接口,如果再調用,則內存映射模式的設置被破壞,導致QSPI代碼執(zhí)行異常!
* 除非不用分散加載,所有代碼放內部FLASH,才可以調用該函數(shù)!否則將導致異常!
*/
//norflash_init();
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "QSPI TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write KEY0:Read", RED); /* 顯示提示信息 */
id = norflash_ex_read_id(); /* 讀取FLASH ID */
while ((id == 0) || (id == 0XFFFF)) /* 檢測不到FLASH芯片 */
{
lcd_show_string(30, 130, 200, 16, 16, "FLASH Check Failed!", RED);
delay_ms(500);
lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);
delay_ms(500);
LED0_TOGGLE(); /* LED0閃爍 */
}
lcd_show_string(30, 130, 200, 16, 16, "QSPI FLASH Ready!", BLUE);
flashsize = 16 * 1024 * 1024; /* FLASH 大小為16M字節(jié) */
while (1)
{
key = key_scan(0);
if (key == KEY1_PRES) /* KEY1按下,寫入 */
{
lcd_fill(0, 150, 239, 319, WHITE); /* 清除半屏 */
lcd_show_string(30, 150, 200, 16, 16, "Start Write FLASH....", BLUE);
sprintf((char *)datatemp, "%s%d", (char *)g_text_buf, i);
norflash_ex_write((uint8_t *)datatemp, flashsize - 100, TEXT_SIZE + 2); /* 從倒數(shù)第100個地址處開始,寫入TEXT_SIZE + 2長度的數(shù)據(jù) */
lcd_show_string(30, 150, 200, 16, 16, "FLASH Write Finished!", BLUE); /* 提示傳送完成 */
}
if (key == KEY0_PRES) /* KEY0按下,讀取字符串并顯示 */
{
lcd_show_string(30, 150, 200, 16, 16, "Start Read FLASH... . ", BLUE);
norflash_ex_read(rectemp, flashsize - 100, TEXT_SIZE + 2); /* 從倒數(shù)第100個地址處開始,讀出TEXT_SIZE + 2個字節(jié) */
lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is: ", BLUE); /* 提示傳送完成 */
lcd_show_string(30, 170, 200, 16, 16, (char *)rectemp, BLUE); /* 顯示讀到的字符串 */
}
i++;
if (i == 20)
{
LED0_TOGGLE(); /* LED0閃爍 */
i = 0;
}
delay_ms(10);
}
}
文章來源:http://www.zghlxwxcb.cn/news/detail-855685.html
八、總結
文章來源地址http://www.zghlxwxcb.cn/news/detail-855685.html
到了這里,關于【正點原子STM32】QSPI四線SPI模式(Quad-SPI存儲器、間接模式、狀態(tài)輪詢模式、內存映射模式、命令序列、QSPI基本使用步驟、SPI FLASH基本使用步驟)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!