国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【正點原子STM32】QSPI四線SPI模式(Quad-SPI存儲器、間接模式、狀態(tài)輪詢模式、內存映射模式、命令序列、QSPI基本使用步驟、SPI FLASH基本使用步驟)

這篇具有很好參考價值的文章主要介紹了【正點原子STM32】QSPI四線SPI模式(Quad-SPI存儲器、間接模式、狀態(tài)輪詢模式、內存映射模式、命令序列、QSPI基本使用步驟、SPI FLASH基本使用步驟)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

一、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)
八、總結


qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
SPI(Serial Peripheral Interface)根據(jù)其數(shù)據(jù)傳輸能力和硬件接口的不同,可以分為以下幾個類別:

  1. 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ù)安全和暫停通信。
  2. Dual SPI(雙線SPI)

    • 雙線SPI是對標準SPI的一種改進,它將原本的MOSI和MISO引腳合并成一對雙向數(shù)據(jù)線,通常稱為IO0和IO1。
    • 在一個時鐘周期內,通過這兩條數(shù)據(jù)線可以同時傳輸兩位數(shù)據(jù),這樣理論上可以將數(shù)據(jù)傳輸速率提高一倍,但這時SPI工作在半雙工模式下,即在同一時刻只能進行讀或寫操作。
  3. 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中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟


一、QSPI介紹

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
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中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
QSPI(Quad Serial Peripheral Interface)在功能結構上相較于標準SPI增加了更多的數(shù)據(jù)線,用于實現(xiàn)更高的數(shù)據(jù)傳輸速率。在雙閃存模式禁止的情況下,其功能結構主要包括:

  1. 時鐘線CLK

    • 時鐘線是QSPI通信的同步信號,所有數(shù)據(jù)的傳輸都在時鐘信號的上升沿或下降沿進行。
  2. 片選線BK1_nCS

    • 片選線用于選擇與QSPI接口相連的特定閃存設備。當BK1_nCS信號為低電平時,選定的閃存設備被激活并開始進行數(shù)據(jù)傳輸。
  3. 數(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 時鐘源

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
在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、間接模式

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
間接模式是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、內存映射模式

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
內存映射模式是STM32H7系列微控制器中QSPI接口的另一種工作模式,主要適用于以下場景:

  1. 讀取操作

    • 在內存映射模式下,外部Quad-SPI Flash存儲器可以直接映射到STM32H7的內存地址空間中,處理器可以通過訪問特定的內存地址來讀取存儲器中的數(shù)據(jù),就像訪問內部RAM一樣。
  2. 擴展內部存儲器

    • 外部Quad-SPI Flash存儲器被當作內部存儲器的一部分使用,其他主機(例如處理器內核或DMA控制器)可以直接讀取這些地址上的數(shù)據(jù),無需通過QSPI接口的特殊函數(shù)調用。
  3. 執(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、命令序列(間接模式 或 內存映射模式)

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
在STM32H7系列微控制器的QSPI接口中,無論是間接模式還是內存映射模式,對SPI Flash進行數(shù)據(jù)讀寫操作時,都需要構建和發(fā)送一個命令序列。這個命令序列通常由五個可配置階段構成:

  1. 指令階段

    • 發(fā)送一個或多個字節(jié)的命令代碼,以指示SPI Flash執(zhí)行特定的操作,如讀取、寫入、擦除等。
  2. 地址階段

    • 發(fā)送用于定位數(shù)據(jù)在SPI Flash中的地址信息,地址的長度可配置,取決于具體的應用需求。
  3. 交替字節(jié)階段(Optional)

    • 用于在某些特殊操作中傳遞額外的信息或控制字節(jié),不是所有操作都需要這個階段。
  4. 空周期階段(Dummy Cycle Phase,Optional)

    • 在某些讀取操作中,為了滿足SPI Flash的時序要求,可能需要插入一定數(shù)量的空時鐘周期。這個階段的長度也可配置。
  5. 數(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ù)各階段

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟

1.7、QSPI FLASH設置

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI 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 中斷類型

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
在STM32H7系列微控制器的QSPI接口中,支持多種類型的中斷,這些中斷在不同操作模式下有不同的觸發(fā)條件:

  1. 超時中斷

    • 當QSPI操作超過預設的時間限制仍未完成時,超時中斷被觸發(fā)。這有助于及時發(fā)現(xiàn)和處理潛在的通信故障。
  2. 狀態(tài)匹配中斷(狀態(tài)輪詢模式下)

    • 在狀態(tài)輪詢模式下,QSPI監(jiān)視外部SPI Flash的狀態(tài)寄存器。當Flash狀態(tài)寄存器中的特定位匹配預設值時,狀態(tài)匹配中斷發(fā)生,例如擦除或寫入操作完成。
  3. FIFO達到閾值中斷(間接模式下)

    • 在間接模式下,QSPI內部的FIFO(First-In-First-Out緩沖區(qū))設有閾值。當FIFO中的數(shù)據(jù)到達預設的滿載或空載閾值時,會觸發(fā)相應的中斷,以提示CPU進行數(shù)據(jù)的讀取或寫入。
  4. 傳輸完成中斷(間接模式下DLR指定字節(jié)數(shù)的數(shù)據(jù)已經(jīng)發(fā)送完成)

    • 在間接模式下,當QSPI完成了通過QUADSPI_DLR寄存器設定的字節(jié)數(shù)的數(shù)據(jù)傳輸后,會觸發(fā)傳輸完成中斷(TCF,Transfer Complete Flag)。
  5. 傳輸錯誤中斷(間接模式下地址越界或其他錯誤)

    • 當QSPI在執(zhí)行間接模式操作時遇到錯誤,例如試圖訪問超出外部SPI Flash地址范圍(地址越界),或者發(fā)生其他傳輸錯誤時,會觸發(fā)傳輸錯誤中斷。這有助于及時捕獲并處理這類異常情況,保障系統(tǒng)的穩(wěn)定性與安全性。

這些中斷可以通過配置QSPI相關的中斷使能寄存器和狀態(tài)寄存器來管理和響應。在實際應用中,合理利用中斷能夠顯著提高系統(tǒng)實時性和任務處理效率。

二、QSPI相關寄存器介紹

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
以下是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ù)。
      qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
      qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
      qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
      qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
      qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
      qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
      qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟

三、QSPI相關HAL庫驅動介紹

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
在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_ReceiveHAL_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中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
      qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟

四、QSPI基本使用步驟

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
QSPI(Quad Serial Peripheral Interface)在STM32上的基本使用步驟可以總結為:

  1. 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
    
  2. 設置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);
    
  3. 使能QSPI中斷及設置MPU(Memory Protection Unit,內存保護單元)(可選)

    • 如果需要使用中斷功能,可以通過設置QSPI的中斷標志位并調用HAL_NVIC_EnableIRQ()函數(shù)來使能QSPI中斷。
    • 若需要使用內存映射模式,可能需要配置MPU,以確保對映射到內存空間的QSPI Flash進行合理的權限和訪問控制。
  4. 編寫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, ..., ...)

在實際應用中,還需要結合具體的應用場景和閃存芯片的數(shù)據(jù)手冊進行詳細配置和操作。例如,在進行讀寫操作前,可能需要先發(fā)送特定的讀寫命令,并根據(jù)需要擦除或寫入扇區(qū)地址。在完成操作后,可能需要輪詢狀態(tài)寄存器或等待中斷來確認操作完成。

五、SPI FLASH簡介

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
W25Q128是一款16MB(16,777,216字節(jié))容量的SPI(Serial Peripheral Interface)接口的NOR型閃存芯片,具備高速讀寫性能和出色的耐用性,支持多次重復擦寫且在斷電后仍能保持數(shù)據(jù)完整性,數(shù)據(jù)保存期限長達20年。

在基本操作方面,W25Q128支持以下操作:

  1. 擦除:W25Q128的最小擦除單位是一個扇區(qū),也就是4KB(4096字節(jié))。這意味著用戶無法單獨擦除某個字節(jié)或字節(jié)組,而必須按照扇區(qū)為單位進行擦除操作。

  2. 寫入:寫入操作通常以頁為單位進行,每個扇區(qū)包含16個頁,每個頁大小為256字節(jié)。不過在寫入之前,所寫的扇區(qū)必須先被擦除。

  3. 讀取:支持隨機讀取任意位置的數(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ù)操作效率和延長閃存壽命。
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
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ī)定的范圍內進行設置。
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
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ù)手冊以獲取準確信息。
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟

六、SPI FLASH基本使用步驟

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,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);
    }
}

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟

八、總結

qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟
qspi中tl信號,STM32,Quad-SPI存儲器,間接模式、內存映射模式,狀態(tài)輪詢模式,命令序列,QSPI寄存器和HAL庫驅動,QSPI基本使用步驟,SPI FLASH基本使用步驟文章來源地址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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包