1. 程式人生 > 其它 >stm32學習總結)—SPI-FLASH 實驗 _

stm32學習總結)—SPI-FLASH 實驗 _

SPI匯流排

SPI 簡介

SPI 的全稱是"Serial Peripheral Interface",意為序列外圍介面,是Motorola 首先在其 MC68HCXX 系列處理器上定義的。SPI 介面主要應用在 EEPROM、 FLASH、實時時鐘、AD 轉換器,還有數字訊號處理器和數字訊號解碼器之間。SPI是一種高速的,全雙工,同步的通訊匯流排,並且在晶片的管腳上只佔用四根線,節約了晶片的管腳,同時為 PCB 的佈局上節省空間,提供方便,正是出於這種簡單易用的特性,如今越來越多的晶片集成了這種通訊協議,比如 STM32 系列晶片。下面我們看下 SPI 內部結構簡易圖,如圖 39.1.1.1 所示:

SPI 物理層

SPI 通訊裝置之間的常用連線方式見圖 25-1。   SPI 通訊使用 3 條匯流排及片選線,3 條匯流排分別為 SCK、MOSI、MISO,片選線為SS,它們的作用介紹如下: (1) SS( Slave Select):從裝置選擇訊號線,常稱為片選訊號線,也稱為 NSS、CS,以下用 NSS 表示。當有多個 SPI 從裝置與 SPI 主機相連時,裝置的其它訊號線 SCK、MOSI 及 MISO 同時並聯到相同的 SPI 總線上,即無論有多少個從裝置,都共同只使用這 3 條匯流排;而每個從裝置都有獨立的這一條 NSS 訊號線,本訊號線獨佔主機的一個引腳,即有多少個從裝置,就有多少條片選訊號線。I2C 協議中通過裝置地址來定址、選中總線上的某個裝置並與其進行通訊;而 SPI 協議中沒有裝置地址,它使用 NSS 訊號線來定址,當主機要選擇從裝置時,把該從裝置的 NSS 訊號線設定為低電平,該從裝置即被選中,即片選有效,接著主機開始與被選中的從裝置進行 SPI 通訊。所以SPI 通訊以 NSS 線置低電平為開始訊號,以 NSS 線被拉高作為結束訊號。  (2) SCK (Serial Clock)
:時鐘訊號線,用於通訊資料同步。它由通訊主機產生,決定了通訊的速率,不同的裝置支援的最高時鐘頻率不一樣,如 STM32 的 SPI 時鐘頻率最大為fpclk/2,兩個裝置之間通訊時,通訊速率受限於低速裝置。  (3) MOSI (Master Output, Slave Input):主裝置輸出/從裝置輸入引腳。主機的資料從這條訊號線輸出,從機由這條訊號線讀入主機發送的資料,即這條線上資料的方向為主機到從機。 (4) MISO(Master Input,,Slave Output):主裝置輸入/從裝置輸出引腳。主機從這條訊號線 讀入資料,從機的資料由這條訊號線輸出到主機,即在這條線上資料的方向為從機到主機。

協議層

與 I2C 的類似,SPI 協議定義了通訊的起始和停止訊號、資料有效性、時鐘同步等環節。 1. SPI 基本通訊過程   先看看 SPI 通訊的通訊時序,見圖 25-2。 這是一個主機的通訊時序。NSS、SCK、MOSI 訊號都由主機控制產生,而 MISO 的訊號由從機產生,主機通過該訊號線讀取從機的資料。MOSI 與 MISO 的訊號只在 NSS 為低電平的時候才有效,在 SCK 的每個時鐘週期 MOSI 和 MISO 傳輸一位資料。以上通訊流程中包含的各個訊號分解如下:  2. 通訊的起始和停止訊號   在圖 25-2 中的標號1處,NSS 訊號線由高變低,是 SPI 通訊的起始訊號。NSS 是每個從機各自獨佔的訊號線,當從機在自己的 NSS 線檢測到起始訊號後,就知道自己被主機選中了,開始準備與主機通訊。在圖中的標號6處,NSS 訊號由低變高,是 SPI 通訊的停止訊號,表示本次通訊結束,從機的選中狀態被取消。  3. 資料有效性   SPI 使用 MOSI 及 MISO 訊號線來傳輸資料,使用 SCK 訊號線進行資料同步。MOSI及 MISO 資料線在 SCK 的每個時鐘週期傳輸一位資料,且資料輸入輸出是同時進行的。資料傳輸時,MSB 先行或 LSB 先行並沒有作硬性規定,但要保證兩個 SPI 通訊裝置之間使用同樣的協定,一般都會採用圖 25-2 中的 MSB 先行模式。觀察圖中的2、3、4、5標號處,MOSI 及 MISO 的資料在 SCK 的上升沿期間變化輸出,在 SCK 的下降沿時被取樣即在 SCK 的下降沿時刻,MOSI 及 MISO 的資料有效,高電平時表示資料“1”,為低電平時表示資料“0”。在其它時刻,資料無效,MOSI 及 MISO為下一次表示資料做準備。SPI 每次資料傳輸可以 8 位或 16 位為單位,每次傳輸的單位數不受限制。  4. CPOL/CPHA 及通訊模式   上面講述的圖 25-2 中的時序只是 SPI 中的其中一種通訊模式,SPI 一共有四種通訊模式,它們的主要區別是匯流排空閒時 SCK 的時鐘狀態以及資料取樣時刻。為方便說明,在此引入“時鐘極性 CPOL”和“時鐘相位 CPHA”的概念。時鐘極性 CPOL 是指 SPI 通訊裝置處於空閒狀態時,SCK 訊號線的電平訊號(即 SPI 通訊開始前、 NSS 線為高電平時 SCK 的狀態)。CPOL=0 時, SCK 在空閒狀態時為低電平,CPOL=1 時,則相反。時鐘相位 CPHA 是指資料的取樣的時刻,當 CPHA=0 時,MOSI 或 MISO 資料線上的訊號將會在 SCK 時鐘線的“奇數邊沿”被取樣。當 CPHA=1 時,資料線在 SCK 的“偶數邊沿”取樣。見圖 25-3 及圖 25-4。   我們來分析這個 CPHA=0 的時序圖。首先,根據 SCK 在空閒狀態時的電平,分為兩種情況。SCK 訊號線在空閒狀態為低電平時,CPOL=0;空閒狀態為高電平時,CPOL=1。無論 CPOL=0 還是=1,因為我們配置的時鐘相位 CPHA=0,在圖中可以看到,取樣時刻都是在 SCK 的奇數邊沿。注意當 CPOL=0 的時候,時鐘的奇數邊沿是上升沿,而CPOL=1 的時候,時鐘的奇數邊沿是下降沿。所以 SPI 的取樣時刻不是由上升/下降沿決定的。MOSI 和 MISO 資料線的有效訊號在 SCK 的奇數邊沿保持不變,資料訊號將在 SCK 奇數邊沿時被取樣,在非取樣時刻,MOSI 和 MISO 的有效訊號才發生切換。      由 CPOL 及 CPHA 的不同狀態,SPI 分成了四種模式,見表 25-1,主機與從機需要工作在相同的模式下才可以正常通訊,實際中採用較多的是“模式 0”與“模式 3”。 STM32 的 SPI 特性及架構   與 I2C 外設一樣,STM32 晶片也集成了專門用於 SPI 協議通訊的外設。  STM32 的 SPI 外設簡介   STM32 的 SPI 外設可用作通訊的主機及從機,支援最高的 SCK 時鐘頻率為 fpclk/2(STM32F103 型號的晶片預設 fpclk1為 72MHz,fpclk2為 36MHz),完全支援 SPI 協議的 4 種模式,資料幀長度可設定為 8 位或 16 位,可設定資料 MSB 先行或 LSB 先行。它還支援雙線全雙工(前面小節說明的都是這種模式)、雙線單向以及單線模式。其中雙線單向模式可以同時使用 MOSI 及 MISO 資料線向一個方向傳輸資料,可以加快一倍的傳輸速度。而單線模式則可以減少硬體接線,當然這樣速率會受到影響。我們只講解雙線全雙工模式。  STM32 的 SPI 架構剖析 1.通訊引腳   SPI 的所有硬體架構都從圖 25-5 中左側 MOSI、MISO、SCK 及 NSS 線展開的。STM32 晶片有多個 SPI 外設,它們的 SPI 通訊訊號引出到不同的 GPIO 引腳上,使用時必須配置到這些指定的引腳,見表 25-2。   其中 SPI1 是 APB2 上的裝置,最高通訊速率達 36Mbtis/s,SPI2、SPI3 是 APB1 上的裝置,最高通訊速率為 18Mbits/s。除了通訊速率,在其它功能上沒有差異。其中 SPI3 用到了下載介面的引腳,這幾個引腳預設功能是下載,第二功能才是 IO 口,如果想使用 SPI3介面,則程式上必須先禁用掉這幾個 IO 口的下載功能。一般在資源不是十分緊張的情況下,這幾個 IO 口是專門用於下載和除錯程式,不會複用為 SPI3。  2. 時鐘控制邏輯   SCK 線的時鐘訊號,由波特率發生器根據“控制暫存器 CR1”中的 BR[0:2]位控制,該位是對 fpclk時鐘的分頻因子,對 fpclk的分頻結果就是 SCK 引腳的輸出時鐘頻率,計算方法見表 25-3。   其中的 fpclk頻率是指 SPI 所在的 APB 匯流排頻率,APB1 為 fpclk1,APB2 為 fpckl2。通過配置“控制暫存器 CR”的“CPOL 位”及“CPHA”位可以把 SPI 設定成前面分析的 4 種 SPI 模式。 3. 資料控制邏輯   SPI 的 MOSI 及 MISO 都連線到資料移位暫存器上,資料移位暫存器的資料來源及目標接收、傳送緩衝區以及 MISO、MOSI 線。 當向外傳送資料的時候,資料移位暫存器以“傳送緩衝區”為資料來源,把資料一位一位地通過資料線傳送出去; 當從外部接收資料的時候,資料移位暫存器把資料線取樣到的資料一位一位地儲存到“接收緩衝區”中。 通過寫 SPI的“資料暫存器 DR”把資料填充到傳送 F 緩衝區中,通訊讀“資料暫存器 DR”,可以獲取接收緩衝區中的內容。其中資料幀長度可以通過“控制暫存器 CR1”的“DFF 位”配置成 8 位及 16 位模式;配置“LSBFIRST 位”可選擇 MSB 先行還是 LSB 先行。 4. 整體控制邏輯   整體控制邏輯負責協調整個 SPI 外設,控制邏輯的工作模式根據我們配置的“控制暫存器(CR1/CR2)”的引數而改變,基本的控制引數包括前面提到的 SPI 模式、波特率、LSB先行、主從模式、單雙向模式等等。   在外設工作時,控制邏輯會根據外設的工作狀態修改“狀態暫存器(SR)”,我們只要讀取狀態暫存器相關的暫存器位,就可以瞭解 SPI 的工作狀態了。除此之外,控制邏輯還根據要求,負責控制產生 SPI 中斷訊號、DMA 請求及控制NSS 訊號線。   實際應用中,我們一般不使用 STM32 SPI 外設的標準 NSS 訊號線,而是更簡單地使用普通的 GPIO,軟體控制它的電平輸出,從而產生通訊起始和停止訊號。

通訊過程

STM32 使用 SPI 外設通訊時,在通訊的不同階段它會對“狀態暫存器 SR”的不同資料位寫入引數,我們通過讀取這些暫存器標誌來了解通訊狀態。  圖 25-6 中的是“主模式”流程,即 STM32 作為 SPI 通訊的主機端時的資料收發過程。 主模式收發流程及事件說明如下: (1) 控制 NSS 訊號線,產生起始訊號(圖中沒有畫出); (2) 把要傳送的資料寫入到“資料暫存器 DR”中,該資料會被儲存到傳送緩衝區; (3) 通訊開始,SCK 時鐘開始執行。MOSI 把傳送緩衝區中的資料一位一位地傳輸出去;MISO 則把資料一位一位地儲存進接收緩衝區中; (4) 當傳送完一幀資料的時候,“狀態暫存器 SR”中的“TXE 標誌位”會被置 1,表示傳輸完一幀,傳送緩衝區已空;類似地,當接收完一幀資料的時候,“RXNE標誌位”會被置 1,表示傳輸完一幀,接收緩衝區非空; (5) 等待到“TXE 標誌位”為 1 時,若還要繼續傳送資料,則再次往“資料暫存器DR”寫入資料即可;等待到“RXNE 標誌位”為 1 時,通過讀取“資料暫存器DR”可以獲取接收緩衝區中的內容。 假如我們使能了 TXE 或 RXNE 中斷,TXE 或 RXNE 置 1 時會產生 SPI 中斷訊號,進入同一個中斷服務函式,到 SPI 中斷服務程式後,可通過檢查暫存器位來了解是哪一個事件,再分別進行處理。也可以使用 DMA 方式來收發“資料暫存器 DR”中的資料。 

SPI 初始化結構體詳解

跟其它外設一樣,STM32 標準庫提供了 SPI 初始化結構體及初始化函式來配置 SPI 外設。初始化結構體及函式定義在庫檔案“stm32f4xx_spi.h”及“stm32f4xx_spi.c”中,程式設計時我們可以結合這兩個檔案內的註釋使用或參考庫幫助文件。瞭解初始化結構體後我們就能對 SPI 外設運用自如了,程式碼如下。 這些結構體成員說明如下,其中括號內的文字是對應引數在 STM32 標準庫中定義的巨集: (1) SPI_Direction 本成員設定 SPI 的通訊方向,可設定為雙線全雙工(SPI_Direction_2Lines_FullDuplex),雙線只接收(SPI_Direction_2Lines_RxOnly),單線只接收(SPI_Direction_1Line_Rx)、單線只發送模式(SPI_Direction_1Line_Tx)。 (2) SPI_Mode 本成員設定 SPI 工作在主機模式(SPI_Mode_Master)或從機模式(SPI_Mode_Slave ),這兩個模式的最大區別為 SPI 的 SCK 訊號線的時序,SCK 的時序是由通訊中的主機產生的。若被配置為從機模式,STM32 的 SPI 外設將接受外來的 SCK 訊號。 (3) SPI_DataSize 本成員可以選擇 SPI 通訊的資料幀大小是為 8 位(SPI_DataSize_8b)還是 16 位(SPI_DataSize_16b)。 (4) SPI_CPOL 和 SPI_CPHA 這兩個成員配置 SPI 的時鐘極性 CPOL 和時鐘相位 CPHA,這兩個配置影響到 SPI 的通訊模式,關於 CPOL 和 CPHA 的說明參考前面“通訊模式”小節。 時鐘極性 CPOL 成員,可設定為高電平(SPI_CPOL_High)或低電平(SPI_CPOL_Low )。 時鐘相位 CPHA 則可以設定為 SPI_CPHA_1Edge(在 SCK 的奇數邊沿採集資料) 或SPI_CPHA_2Edge (在 SCK 的偶數邊沿採集資料) 。  (5) SPI_NSS 本成員配置 NSS 引腳的使用模式,可以選擇為硬體模式(SPI_NSS_Hard )與軟體模式(SPI_NSS_Soft ),在硬體模式中的 SPI 片選訊號由 SPI 硬體自動產生,而軟體模式則需要我們親自把相應的 GPIO 埠拉高或置低產生非片選和片選訊號。實際中軟體模式應用比較多。 (6) SPI_BaudRatePrescaler 本成員設定波特率分頻因子,分頻後的時鐘即為 SPI 的 SCK 訊號線的時鐘頻率。這個成員引數可設定為 fpclk 的 2、4、6、8、16、32、64、128、256 分頻。 (7) SPI_FirstBit 所有序列的通訊協議都會有 MSB 先行(高位資料在前)還是 LSB 先行(低位資料在前)的問題,而 STM32 的 SPI 模組可以通過這個結構體成員,對這個特性程式設計控制。 (8) SPI_CRCPolynomial 這是 SPI 的 CRC 校驗中的多項式,若我們使用 CRC 校驗時,就使用這個成員的引數(多項式),來計算 CRC 的值。   配置完這些結構體成員後,我們要呼叫 SPI_Init 函式把這些引數寫入到暫存器中,實現 SPI 的初始化,然後呼叫 SPI_Cmd 來使能 SPI 外設。

SPI 配置步驟 

(1)使能 SPI 及對應 GPIO 埠時鐘並配置引腳的複用功能   要使用 SPI 就必須使能它的時鐘,前面介紹框圖時,我們知道 SPI1 是掛接在 APB2 總線上,而 SPI2 和 SPI3 掛接在 APB1 總線上。而且 SPI 匯流排介面對應不同的 STM32 引腳,所以還需使能對應引腳的埠時鐘,同時配置為複用功能。 (2)初始化 SPI,包括資料幀長度、傳輸模式、MSB 和 LSB 順序等 (3)使能(開啟)SPI (4)SPI 資料傳輸   通過上面幾個步驟的配置,SPI 已經可以開始通訊了,在通訊的過程中肯定會有資料的傳送和接收,韌體庫也提供了 SPI 的傳送和接收函式。 SPI 傳送資料函式原型為:   void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);這個函式很好理解,往 SPIx 資料暫存器寫入資料 Data,從而實現傳送。 SPI 接收資料函式原型為:   uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);此函式非常簡單,從 SPIx 資料暫存器中讀取接收到的資料。 (5)檢視 SPI 傳輸狀態   在 SPI 傳輸過程中,我們經常要判斷資料是否傳輸完成,傳送區是否為空等狀態,這是通過函式 SPI_I2S_GetFlagStatus 實現的,此函式原型為: FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG); 此函式非常簡單,第二個引數是用來選擇 SPI 傳輸過程中判斷的標誌,對應的標誌可在 stm32f10x_spi.h 檔案中查詢到,使用較多的是傳送完成標誌(SPI_I2S_FLAG_TXE)和接收完成標誌(SPI_I2S_FLAG_RXNE)。 判斷髮送是否完成的方法是: SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE); 將以上幾步配置好後,我們就可以使用 STM32F1 的 SPI 和外部 FLASH(EN25QXX)通訊了。  

spi配置程式碼(只配置了spi1)

   
 1 #ifndef _spi_H
 2 #define _spi_H
 3 
 4 #include "system.h"
 5 
 6 void SPI1_Init(void);             //初始化SPI1口
 7 void SPI1_SetSpeed(u8 SpeedSet); //設定SPI1速度   
 8 u8 SPI1_ReadWriteByte(u8 TxData);//SPI1匯流排讀寫一個位元組
 9 
10 //void SPI2_Init(void);             //初始化SPI2口
11 //void SPI2_SetSpeed(u8 SpeedSet); //設定SPI2速度   
12 //u8 SPI2_ReadWriteByte(u8 TxData);//SPI2匯流排讀寫一個位元組
13 
14 #endif
 

 

   
 1 #include "spi.h"
 2 
 3 //以下是SPI模組的初始化程式碼,配置成主機模式                           
 4 //SPI口初始化
 5 //這裡針是對SPI1的初始化
 6 void SPI1_Init(void)
 7 {
 8     GPIO_InitTypeDef  GPIO_InitStructure;
 9     SPI_InitTypeDef  SPI_InitStructure;
10     
11     /* SPI的IO口和SPI外設開啟時鐘 */
12     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
13     RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
14     
15     /* SPI的IO口設定 */
16     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
17     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   //複用推輓輸出
18     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
19     GPIO_Init(GPIOA, &GPIO_InitStructure);
20 
21     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //設定SPI單向或者雙向的資料模式:SPI設定為雙線雙向全雙工
22     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;        //設定SPI工作模式:設定為主SPI
23     SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;        //設定SPI的資料大小:SPI傳送接收8位幀結構
24     SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;        //串行同步時鐘的空閒狀態為高電平
25     SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;    //串行同步時鐘的第二個跳變沿(上升或下降)資料被取樣
26     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;        //NSS訊號由硬體(NSS管腳)還是軟體(使用SSI位)管理:內部NSS訊號有SSI位控制
27     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;        //定義波特率預分頻的值:波特率預分頻值為256
28     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    //指定資料傳輸從MSB位還是LSB位開始:資料傳輸從MSB位開始
29     SPI_InitStructure.SPI_CRCPolynomial = 7;    //CRC值計算的多項式
30     SPI_Init(SPI1, &SPI_InitStructure);  //根據SPI_InitStruct中指定的引數初始化外設SPIx暫存器
31     
32     SPI_Cmd(SPI1, ENABLE); //使能SPI外設
33     
34     SPI1_ReadWriteByte(0xff);//啟動傳輸    
35 }
36 
37 //SPI1速度設定函式
38 //SPI速度=fAPB2/分頻係數
39 //@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256  
40 //fAPB2時鐘一般為84Mhz:
41 void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
42 {
43     SPI1->CR1&=0XFFC7;//位3-5清零,用來設定波特率
44     SPI1->CR1|=SPI_BaudRatePrescaler;    //設定SPI1速度 
45     SPI_Cmd(SPI1,ENABLE); //使能SPI1
46 } 
47 
48 //SPI1 讀寫一個位元組
49 //TxData:要寫入的位元組
50 //返回值:讀取到的位元組
51 u8 SPI1_ReadWriteByte(u8 TxData)
52 {                      
53  
54     while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);//等待發送區空  
55     
56     SPI_I2S_SendData(SPI1, TxData); //通過外設SPIx傳送一個byte  資料
57         
58     while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); //等待接收完一個byte  
59  
60     return SPI_I2S_ReceiveData(SPI1); //返回通過SPIx最近接收的資料    
61              
62 }
 

上述程式中的一個奇怪的地方

在複用SPI匯流排時,必須先設定匯流排埠。讀取其他ARM晶片(如NXP)一般很容易看出晶片的設定是否正確。不過對於STM32就容易讓人迷惑了。就像上述程式中,我們在使用SPI匯流排進行通訊時,可以這樣設定:

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     // 複用的推輓輸出

     其他埠如時鐘埠以及MOSI埠都是stm32向外輸出,引腳設定成推輓輸出沒問題, 但是大家對MISO埠的設定就會產生疑惑了,MISO不是應該設定成為輸入埠(GPIO_Mode_IN_FLOATING)才行的嗎?

      答題是肯定的,對於STM32的這一類管腳來說(如USART_RX)即可以設定成為輸入模式,也可以設定成為複用的推輓輸出。其工作都是正常的,不過建議大家還是設定成為輸入埠的好,容易理解。

      具體產生這一問題的原因是:從功能上來說,MISO應該配置為輸入模式才對,但為什麼也可以配置為GPIO_Mode_AF_PP?請看下面的GPIO複用功能配置框圖。當一個GPIO埠配置為GPIO_Mode_AF_PP是,這個埠的內部結構框圖如下:

  圖中可以看到,片上外設的複用功能輸出訊號會連線到輸出控制電路,然後在埠上產生輸出訊號。但是在晶片內部,MISO是SPI模組的輸入引腳,而不是輸出引腳,也就是說圖中的"複用功能輸出訊號"根本不存在(MISO不會產生輸出訊號),因此"輸出控制電路"不能對外產生輸出訊號。

  而另一方面看,即使在GPIO_Mode_AF_PP模式下,複用功能輸入訊號卻與外部引腳之間相互連線,既MISO得到了外部訊號的電平,實現了輸入的功能(可以4-5-6-7路線輸入資料,複用的情況下就是4-5-複用功能路線)。

FLASH介紹

控制 FLASH 的指令

  搞定 SPI 的基本收發單元后,還需要了解如何對 FLASH 晶片進行讀寫。FLASH 晶片自定義了很多指令,我們通過控制 STM32 利用 SPI 匯流排向 FLASH 晶片傳送指令,FLASH晶片收到後就會執行相應的操作。而這些指令,對主機端(STM32)來說,只是它遵守最基本的 SPI 通訊協議傳送出的資料,但在裝置端(FLASH 晶片)把這些資料解釋成不同的意義,所以才成為指令。檢視FLASH 晶片的資料手冊《W25Q64》,可瞭解各種它定義的各種指令的功能及指令格式,見表 25-4。      該表中的第一列為指令名,第二列為指令編碼,第三至第 N 列的具體內容根據指令的不同而有不同的含義。其中帶括號的位元組引數,方向為 FLASH 向主機傳輸,即命令響應,不帶括號的則為主機向 FLASH 傳輸。表中“A0~A23”指 FLASH 晶片內部儲存器組織的地址;“M0~M7”為廠商號(MANUFACTURER ID);“ID0-ID15”為 FLASH 晶片的ID;“dummy”指該處可為任意資料;“D0~D7”為 FLASH 內部儲存矩陣的內容。    在 FLSAH 晶片內部,儲存有固定的廠商編號(M7-M0)和不同型別 FLASH 晶片獨有的編號(ID15-ID0),見表 25-5。   通過指令表中的讀 ID 指令“JEDEC ID”可以獲取這兩個編號,該指令編碼為“9Fh”,其中“9F h”是指 16 進位制數“9F” (相當於 C 語言中的 0x9F)。緊跟指令編碼的三個位元組分別為 FLASH 晶片輸出的“(M7-M0)”、“(ID15-ID8)”及“(ID7-ID0)” 。此處我們以該指令為例,配合其指令時序圖進行講解,見圖 25-8。 

 

  主機首先通過 MOSI 線向 FLASH 晶片傳送第一個位元組資料為“9F h”,當 FLASH 晶片收到該資料後,它會解讀成主機向它傳送了“JEDEC 指令”,然後它就作出該命令的響應:通過 MISO 線把它的廠商 ID(M7-M0)及晶片型別(ID15-0)傳送給主機,主機接收到指令響應後可進行校驗。常見的應用是主機端通過讀取裝置 ID 來測試硬體是否連線正常,或用於識別裝置。對於 FLASH 晶片的其它指令,都是類似的,只是有的指令包含多個位元組,或者響應包含更多的資料。   實際上,編寫裝置驅動都是有一定的規律可循的。首先我們要確定裝置使用的是什麼通訊協議。如上一章的 EEPROM 使用的是 I2C,本章的 FLASH 使用的是 SPI。那麼我們就先根據它的通訊協議,選擇好 STM32 的硬體模組,並進行相應的 I2C 或 SPI 模組初始化。接著,我們要了解目標裝置的相關指令,因為不同的裝置,都會有相應的不同的指令。如EEPROM 中會把第一個資料解釋為內部儲存矩陣的地址(實質就是指令)。而 FLASH 則定義了更多的指令,有寫指令,讀指令,讀 ID 指令等等。最後,我們根據這些指令的格式要求,使用通訊協議向裝置傳送指令,達到控制裝置的目標

定義 FLASH 指令編碼表

為了方便使用,我們把 FLASH 晶片的常用指令編碼使用巨集來封裝起來,後面需要傳送指令編碼的時候我們直接使用這些巨集即可。    
 1 //指令表
 2 #define EN25X_WriteEnable        0x06 
 3 #define EN25X_WriteDisable        0x04 
 4 #define EN25X_ReadStatusReg        0x05 
 5 #define EN25X_WriteStatusReg    0x01 
 6 #define EN25X_ReadData            0x03 
 7 #define EN25X_FastReadData        0x0B 
 8 #define EN25X_FastReadDual        0x3B 
 9 #define EN25X_PageProgram        0x02 
10 #define EN25X_BlockErase        0xD8 
11 #define EN25X_SectorErase        0x20 
12 #define EN25X_ChipErase            0xC7 
13 #define EN25X_PowerDown            0xB9 
14 #define EN25X_ReleasePowerDown    0xAB 
15 #define EN25X_DeviceID            0xAB 
16 #define EN25X_ManufactDeviceID    0x90 
17 #define EN25X_JedecDeviceID        0x9F 
 

讀取 FLASH 晶片 ID

根據“JEDEC”指令的時序,我們把讀取 FLASH ID 的過程編寫成一個函式。    
 1 //讀取晶片ID
 2 //返回值如下:                   
 3 //0XEF13,表示晶片型號為EN25Q80  
 4 //0XEF14,表示晶片型號為EN25Q16    
 5 //0XEF15,表示晶片型號為EN25Q32  
 6 //0XEF16,表示晶片型號為EN25Q64 
 7 //0XEF17,表示晶片型號為EN25Q128       
 8 u16 EN25QXX_ReadID(void)
 9 {
10     u16 Temp = 0;      
11     EN25QXX_CS=0;                    
12     SPI2_ReadWriteByte(0x9F);//傳送讀取ID命令        
13     SPI2_ReadWriteByte(0x00);         
14     SPI2_ReadWriteByte(0x00);         
15     SPI2_ReadWriteByte(0x00);                     
16     Temp|=SPI2_ReadWriteByte(0xFF)<<8;  
17     Temp|=SPI2_ReadWriteByte(0xFF);     
18     //EN25QXX_CS=1;                    
19     return Temp;
20 }  
    這段程式碼利用控制 CS 引腳電平的巨集“SPI_FLASH_CS_LOW/HIGH”以及前面編寫的單位元組收發函式 SPI_FLASH_SendByte,很清晰地實現了“JEDEC ID”指令的時序:傳送一個位元組的指令編碼“W25X_JedecDeviceID”,然後讀取 3 個位元組,獲取 FLASH 晶片對該指令的響應,最後把讀取到的這 3 個數據合併到一個變數 Temp 中,然後作為函式返回值,把該返回值與我們定義的巨集“sFLASH_ID”對比,即可知道 FLASH 晶片是否正常。   

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

在向 FLASH 晶片儲存矩陣寫入資料前,首先要使能寫操作,通過“Write Enable”命令即可寫使能。    
1 //EN25QXX寫使能    
2 //將WEL置位   
3 void EN25QXX_Write_Enable(void)   
4 {
5     EN25QXX_CS=0;                            //使能器件   
6     SPI2_ReadWriteByte(EN25X_WriteEnable);      //傳送寫使能  
7     EN25QXX_CS=1;                            //取消片選               
8 } 
       與 EEPROM 一樣,由於 FLASH 晶片向內部儲存矩陣寫入資料需要消耗一定的時間,並不是在匯流排通訊結束的一瞬間完成的,所以在寫操作後需要確認 FLASH 晶片“空閒”時才能進行再次寫入。為了表示自己的工作狀態,FLASH 晶片定義了一個狀態暫存器,見圖 25-9

我們只關注這個狀態暫存器的第 0 位“BUSY”,當這個位為“1”時,表明 FLASH晶片處於忙碌狀態,它可能正在對內部的儲存矩陣進行“擦除”或“資料寫入”的操作。利用指令表中的“Read Status Register”指令可以獲取 FLASH 晶片狀態暫存器的內容,其時序見圖 25-10。 

 

  只要向 FLASH 晶片傳送了讀狀態暫存器的指令,FLASH 晶片就會持續向主機返回最新的狀態暫存器內容,直到收到 SPI 通訊的停止訊號。據此我們編寫了具有等待 FLASH 晶片寫入結束功能的函式,見下面程式碼。     
 1 //讀取EN25QXX的狀態暫存器
 2 //BIT7  6   5   4   3   2   1   0
 3 //SPR   RV  TB BP2 BP1 BP0 WEL BUSY
 4 //SPR:預設0,狀態暫存器保護位,配合WP使用
 5 //TB,BP2,BP1,BP0:FLASH區域防寫設定
 6 //WEL:寫使能鎖定
 7 //BUSY:忙標記位(1,忙;0,空閒)
 8 //預設:0x00
 9 u8 EN25QXX_ReadSR(void)   
10 {  
11     u8 byte=0;   
12     EN25QXX_CS=0;                            //使能器件   
13     SPI2_ReadWriteByte(EN25X_ReadStatusReg);    //傳送讀取狀態暫存器命令    
14     byte=SPI2_ReadWriteByte(0Xff);             //讀取一個位元組  
15     EN25QXX_CS=1;                            //取消片選     
16     return byte;   
17 } 
     
1 //等待空閒
2 void EN25QXX_Wait_Busy(void)   
3 {   
4     while((EN25QXX_ReadSR()&0x01)==0x01);   // 等待BUSY位清空
5 }
 

FLASH 扇區擦除

由於 FLASH 儲存器的特性決定了它只能把原來為“1”的資料位改寫成“0”,而原來為“0”的資料位不能直接改寫為“1”。所以這裡涉及到資料“擦除”的概念,在寫入前,必須要對目標儲存矩陣進行擦除操作,把矩陣中的資料位擦除為“1”,在資料寫入的時候,如果要儲存資料“1”,那就不修改儲存矩陣 ,在要儲存資料“0”時,才更改該位。通常,對儲存矩陣擦除的基本操作單位都是多個位元組進行,如本例子中的 FLASH 芯 片支援“扇區擦除”、“塊擦除”以及“整片擦除”,見表 25-6。  FLASH 晶片的最小擦除單位為扇區(Sector),而一個塊(Block)包含 16 個扇區,其內部儲存矩陣分佈見圖 25-11。 使用扇區擦除指令“Sector Erase”可控制 FLASH 晶片開始擦寫,其指令時序見圖25-14。  扇區擦除指令的第一個位元組為指令編碼,緊接著傳送的 3 個位元組用於表示要擦除的儲存矩陣地址。要注意的是在扇區擦除指令前,還需要先發送“寫使能”指令,傳送扇區擦除指令後,通過讀取暫存器狀態等待扇區擦除操作完畢,程式碼如下。    
 1 //擦除一個扇區
 2 //Dst_Addr:扇區地址 根據實際容量設定
 3 //擦除一個山區的最少時間:150ms
 4 void EN25QXX_Erase_Sector(u32 Dst_Addr)   
 5 {  
 6     //監視falsh擦除情況,測試用   
 7      printf("fe:%x\r\n",Dst_Addr);      
 8      Dst_Addr*=4096;
 9     EN25QXX_Write_Enable();                  //SET WEL      
10     EN25QXX_Wait_Busy();   
11       EN25QXX_CS=0;                            //使能器件   
12     SPI2_ReadWriteByte(EN25X_SectorErase);      //傳送扇區擦除指令 
13     SPI2_ReadWriteByte((u8)((Dst_Addr)>>16));  //傳送24bit地址    
14     SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));   
15     SPI2_ReadWriteByte((u8)Dst_Addr);  
16     EN25QXX_CS=1;                            //取消片選               
17     EN25QXX_Wait_Busy();                      //等待擦除完成
18 }
  這段程式碼呼叫的函式在前面都已講解,只要注意傳送擦除地址時高位在前即可。呼叫扇區擦除指令時注意輸入的地址要對齊到 4KB。  

FLASH 的頁寫入

目標扇區被擦除完畢後,就可以向它寫入資料了。與 EEPROM 類似,FLASH 晶片也有頁寫入命令,使用頁寫入命令最多可以一次向 FLASH 傳輸 256 個位元組的資料,我們把這個單位為頁大小。FLASH 頁寫入的時序見圖 25-13。    從時序圖可知,第 1 個位元組為“頁寫入指令”編碼,2-4 位元組為要寫入的“地址 A”,接著的是要寫入的內容,最多個可以傳送 256 位元組資料,這些資料將會從“地址 A”開始,按順序寫入到 FLASH 的儲存矩陣。若傳送的資料超出 256 個,則會覆蓋前面傳送的資料。與擦除指令不一樣,頁寫入指令的地址並不要求按 256 位元組對齊,只要確認目標儲存單元是擦除狀態即可(即被擦除後沒有被寫入過)。所以,若對“地址 x”執行頁寫入指令後,傳送了 200 個位元組資料後終止通訊,下一次再執行頁寫入指令,從“地址(x+200)”開始寫入 200 個位元組也是沒有問題的(小於 256 均可)。 只是在實際應用中由於基本擦除單元是4KB,一般都以扇區為單位進行讀寫,想深入瞭解,可學習我們的“FLASH 檔案系統”相關的例子。把頁寫入時序封裝成函式,其實現見下列程式碼。     
 1 //寫SPI FLASH  
 2 //在指定地址開始寫入指定長度的資料
 3 //該函式帶擦除操作!
 4 //pBuffer:資料儲存區
 5 //WriteAddr:開始寫入的地址(24bit)                        
 6 //NumByteToWrite:要寫入的位元組數(最大65535)   
 7 u8 EN25QXX_BUFFER[4096];         
 8 void EN25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
 9 { 
10     u32 secpos;
11     u16 secoff;
12     u16 secremain;       
13      u16 i;    
14     u8 * EN25QXX_BUF;      
15        EN25QXX_BUF=EN25QXX_BUFFER;         
16      secpos=WriteAddr/4096;//扇區地址  
17     secoff=WriteAddr%4096;//在扇區內的偏移
18     secremain=4096-secoff;//扇區剩餘空間大小   
19      //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//測試用
20      if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大於4096個位元組
21     while(1) 
22     {    
23         EN25QXX_Read(EN25QXX_BUF,secpos*4096,4096);//讀出整個扇區的內容
24         for(i=0;i<secremain;i++)//校驗資料
25         {
26             if(EN25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除        
27         }
28         if(i<secremain)//需要擦除
29         {
30             EN25QXX_Erase_Sector(secpos);//擦除這個扇區
31             for(i=0;i<secremain;i++)       //複製
32             {
33                 EN25QXX_BUF[i+secoff]=pBuffer[i];      
34             }
35             EN25QXX_Write_NoCheck(EN25QXX_BUF,secpos*4096,4096);//寫入整個扇區  
36 
37         }else EN25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//寫已經擦除了的,直接寫入扇區剩餘區間.                    
38         if(NumByteToWrite==secremain)break;//寫入結束了
39         else//寫入未結束
40         {
41             secpos++;//扇區地址增1
42             secoff=0;//偏移位置為0      
43 
44                pBuffer+=secremain;  //指標偏移
45             WriteAddr+=secremain;//寫地址偏移       
46                NumByteToWrite-=secremain;                //位元組數遞減
47             if(NumByteToWrite>4096)secremain=4096;    //下一個扇區還是寫不完
48             else secremain=NumByteToWrite;            //下一個扇區可以寫完了
49         }     
50     }     
51 }
    這段程式碼的內容為:先發送“寫使能”命令,接著才開始頁寫入時序,然後傳送指令編碼、地址,再把要寫入的資料一個接一個地傳送出去,傳送完後結束通訊,檢查 FLASH狀態暫存器,等待 FLASH 內部寫入結束。  

從 FLASH 讀取資料

相對於寫入,FLASH 晶片的資料讀取要簡單得多,使用讀取指令“Read Data”即可,其指令時序見圖 25-14。   傳送了指令編碼及要讀的起始地址後,FLASH 晶片就會按地址遞增的方式返回儲存矩陣的內容,讀取的資料量沒有限制,只要沒有停止通訊,FLASH 晶片就會一直返回資料。程式碼如下。    
 1 //讀取SPI FLASH  
 2 //在指定地址開始讀取指定長度的資料
 3 //pBuffer:資料儲存區
 4 //ReadAddr:開始讀取的地址(24bit)
 5 //NumByteToRead:要讀取的位元組數(最大65535)
 6 void EN25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
 7 { 
 8      u16 i;                                               
 9     EN25QXX_CS=0;                            //使能器件   
10     SPI2_ReadWriteByte(EN25X_ReadData);         //傳送讀取命令   
11     SPI2_ReadWriteByte((u8)((ReadAddr)>>16));  //傳送24bit地址    
12     SPI2_ReadWriteByte((u8)((ReadAddr)>>8));   
13     SPI2_ReadWriteByte((u8)ReadAddr);   
14     for(i=0;i<NumByteToRead;i++)
15     { 
16         pBuffer[i]=SPI2_ReadWriteByte(0XFF);   //迴圈讀數  
17     }
18     EN25QXX_CS=1;                                
19 }  
  由於讀取的資料量沒有限制,所以傳送讀命令後一直接收 NumByteToRead 個數據到結束即可。 

3. main 函式  

最後我們來編寫 main 函式,進行 FLASH 晶片讀寫校驗,程式碼如下。    
 1 #include "system.h"
 2 #include "SysTick.h"
 3 #include "led.h"
 4 #include "usart.h"
 5 #include "tftlcd.h"
 6 #include "key.h"
 7 #include "spi.h"
 8 #include "flash.h"
 9 
10 
11 //要寫入到25Q64的字串陣列
12 const u8 text_buf[]="www.prechin.net";
13 #define TEXT_LEN sizeof(text_buf)
14 //u16 key3;
15 
16 int main()
17 {
18     u8 i=0;
19     u8 key;
20     u8 buf[30];
21     
22     SysTick_Init(72);
23     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中斷優先順序分組 分2組
24     LED_Init();
25     USART1_Init(9600);
26     TFTLCD_Init();            //LCD初始化
27     KEY_Init();
28     EN25QXX_Init();
29     
30     FRONT_COLOR=BLACK;
31     LCD_ShowString(10,10,tftlcd_data.width,tftlcd_data.height,16,"PRECHIN STM32F1");
32     LCD_ShowString(10,30,tftlcd_data.width,tftlcd_data.height,16,"www.prechin.net");
33     LCD_ShowString(10,50,tftlcd_data.width,tftlcd_data.height,16,"FLASH-SPI Test");
34     LCD_ShowString(10,70,tftlcd_data.width,tftlcd_data.height,16,"K_UP:Write   K_DOWN:Read");
35     FRONT_COLOR=RED;
36     
37     while(EN25QXX_ReadID()!=EN25Q64)            //檢測不到EN25Q64
38     //while(1)
39     {
40         //key3 = EN25QXX_ReadID();
41         printf("EN25Q64 Check Failed! \r\n");
42         LCD_ShowString(10,150,tftlcd_data.width,tftlcd_data.height,16,"EN25Q64 Check Failed!  ");
43     }
44     printf("EN25Q64 Check Success!\r\n");
45     LCD_ShowString(10,150,tftlcd_data.width,tftlcd_data.height,16,"EN25Q64 Check Success!");
46         
47     LCD_ShowString(10,170,tftlcd_data.width,tftlcd_data.height,16,"Write Data:");
48     LCD_ShowString(10,190,tftlcd_data.width,tftlcd_data.height,16,"Read Data :");
49     
50     while(1)
51     {
52         key=KEY_Scan(0);
53         if(key==KEY_UP)
54         {
55             EN25QXX_Write((u8 *)text_buf,0,TEXT_LEN);
56             printf("傳送的資料:%s\r\n",text_buf);
57             LCD_ShowString(10+11*8,170,tftlcd_data.width,tftlcd_data.height,16,"www.prechin.net");
58         }
59         if(key==KEY_DOWN)
60         {
61             EN25QXX_Read(buf,0,TEXT_LEN);
62             printf("接收的資料:%s\r\n",buf);
63             LCD_ShowString(10+11*8,190,tftlcd_data.width,tftlcd_data.height,16,buf);
64         }
65         
66         i++;
67         if(i%20==0)
68         {
69             led1=!led1;
70         }
71         
72         delay_ms(10);
73             
74     }
75 }
  注意: 由於實驗板上的 FLASH 晶片預設已經儲存了特定用途的資料,如擦除了這些資料會影響到某些程式的執行。所以我們預留了 FLASH 晶片的“第 0 扇區(0-4096 地址)”專用於本實驗,如非必要,請勿擦除其它地址的內容。如已擦除,可在配套資料裡找到“刷外部 FLASH 內容”程式,根據其說明給 FLASH 重新寫入出廠內容。            
 1 //無檢驗寫SPI FLASH 
 2 //必須確保所寫的地址範圍內的資料全部為0XFF,否則在非0XFF處寫入的資料將失敗!
 3 //具有自動換頁功能 
 4 //在指定地址開始寫入指定長度的資料,但是要確保地址不越界!
 5 //pBuffer:資料儲存區
 6 //WriteAddr:開始寫入的地址(24bit)
 7 //NumByteToWrite:要寫入的位元組數(最大65535)
 8 //CHECK OK
 9 void EN25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
10 {                       
11     u16 pageremain;       
12     pageremain=256-WriteAddr%256; //單頁剩餘的位元組數                 
13     if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大於256個位元組
14     while(1)
15     {       
16         EN25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
17         if(NumByteToWrite==pageremain)break;//寫入結束了
18          else //NumByteToWrite>pageremain
19         {
20             pBuffer+=pageremain;
21             WriteAddr+=pageremain;    
22 
23             NumByteToWrite-=pageremain;              //減去已經寫入了的位元組數
24             if(NumByteToWrite>256)pageremain=256; //一次可以寫入256個位元組
25             else pageremain=NumByteToWrite;       //不夠256個位元組了
26         }
27     }        
28 } 
 

 

     
 1 #ifndef _flash_H
 2 #define _flash_H
 3 
 4 #include "system.h"
 5 
 6 
 7 //EN25X系列/Q系列晶片列表       
 8 //EN25Q80  ID  0XEF13
 9 //EN25Q16  ID  0XEF14
10 //EN25Q32  ID  0XEF15
11 //EN25Q64  ID  0XEF16    
12 //EN25Q128 ID  0XEF17    
13 #define EN25Q80     0XEF13     
14 #define EN25Q16     0XEF14
15 #define EN25Q32     0XEF15
16 //#define EN25Q64     0XEF16
17 //#define EN25Q128    0XEF17
18 //#define EN25Q64     0XC816
19 //#define EN25Q64     0X1C16        //GD25QXX
20 //#define EN25Q64     0X2016        //XM25QHXX
21 #define EN25Q64     0Xb16        //MXIC C216
22 #define EN25Q128    0XC817
23 
24 extern u16 EN25QXX_TYPE;                    //定義EN25QXX晶片型號           
25 
26 #define    EN25QXX_CS         PGout(13)          //EN25QXX的片選訊號
27 
28 
29 //指令表
30 #define EN25X_WriteEnable         0x06 
31 #define EN25X_WriteDisable        0x04 
32 #define EN25X_ReadStatusReg       0x05 
33 #define EN25X_WriteStatusReg      0x01 
34 #define EN25X_ReadData            0x03 
35 #define EN25X_FastReadData        0x0B 
36 #define EN25X_FastReadDual        0x3B 
37 #define EN25X_PageProgram         0x02 
38 #define EN25X_BlockErase          0xD8 
39 #define EN25X_SectorErase         0x20 
40 #define EN25X_ChipErase           0xC7 
41 #define EN25X_PowerDown           0xB9 
42 #define EN25X_ReleasePowerDown    0xAB 
43 #define EN25X_DeviceID            0xAB 
44 #define EN25X_ManufactDeviceID    0x90 
45 #define EN25X_JedecDeviceID       0x9F 
46 
47 void EN25QXX_Init(void);
48 u16  EN25QXX_ReadID(void);                  //讀取FLASH ID
49 u8     EN25QXX_ReadSR(void);                //讀取狀態暫存器 
50 void EN25QXX_Write_SR(u8 sr);              //寫狀態暫存器
51 void EN25QXX_Write_Enable(void);          //寫使能 
52 void EN25QXX_Write_Disable(void);        //防寫
53 void EN25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
54 void EN25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);   //讀取flash
55 void EN25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//寫入flash
56 void EN25QXX_Erase_Chip(void);              //整片擦除
57 void EN25QXX_Erase_Sector(u32 Dst_Addr);    //扇區擦除
58 void EN25QXX_Wait_Busy(void);               //等待空閒
59 void EN25QXX_PowerDown(void);            //進入掉電模式
60 void EN25QXX_WAKEUP(void);                //喚醒
61 
62 
63 #endif