欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
第24章 SPI—讀寫(xiě)串行FLASH
本章參考資料:《STM32F4xx 中文參考手冊》、《STM32F4xx規格書(shū)》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》及《SPI總線(xiàn)協(xié)議介紹》。

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

關(guān)于FLASH存儲器,請參考"常用存儲器介紹"章節,實(shí)驗中FLASH芯片的具體參數,請參考其規格書(shū)《W25Q128》來(lái)了解。

24.1 SPI協(xié)議簡(jiǎn)介

SPI協(xié)議是由摩托羅拉公司提出的通訊協(xié)議(Serial Peripheral Interface),即串行外圍設備接口,是一種高速全雙工的通信總線(xiàn)。它被廣泛地使用在ADC、LCD等設備與MCU間,要求通訊速率較高的場(chǎng)合。

學(xué)習本章時(shí),可與I2C章節對比閱讀,體會(huì )兩種通訊總線(xiàn)的差異以及EEPROM存儲器與FLASH存儲器的區別。下面我們分別對SPI協(xié)議的物理層及協(xié)議層進(jìn)行講解。

24.1.1 SPI物理層

SPI通訊設備之間的常用連接方式見(jiàn)圖 241。

241 常見(jiàn)的SPI通訊系統

SPI通訊使用3條總線(xiàn)及片選線(xiàn),3條總線(xiàn)分別為SCK、MOSI、MISO,片選線(xiàn)為,它們的作用介紹如下:

(1)     ( Slave Select):從設備選擇信號線(xiàn),常稱(chēng)為片選信號線(xiàn),也稱(chēng)為NSS、CS,以下用NSS表示。當有多個(gè)SPI從設備與SPI主機相連時(shí),設備的其它信號線(xiàn)SCK、MOSI及MISO同時(shí)并聯(lián)到相同的SPI總線(xiàn)上,即無(wú)論有多少個(gè)從設備,都共同只使用這3條總線(xiàn);而每個(gè)從設備都有獨立的這一條NSS信號線(xiàn),本信號線(xiàn)獨占主機的一個(gè)引腳,即有多少個(gè)從設備,就有多少條片選信號線(xiàn)。I2C協(xié)議中通過(guò)設備地址來(lái)尋址、選中總線(xiàn)上的某個(gè)設備并與其進(jìn)行通訊;而SPI協(xié)議中沒(méi)有設備地址,它使用NSS信號線(xiàn)來(lái)尋址,當主機要選擇從設備時(shí),把該從設備的NSS信號線(xiàn)設置為低電平,該從設備即被選中,即片選有效,接著(zhù)主機開(kāi)始與被選中的從設備進(jìn)行SPI通訊。所以SPI通訊以NSS線(xiàn)置低電平為開(kāi)始信號,以NSS線(xiàn)被拉高作為結束信號。

(2)    SCK (Serial Clock):時(shí)鐘信號線(xiàn),用于通訊數據同步。它由通訊主機產(chǎn)生,決定了通訊的速率,不同的設備支持的最高時(shí)鐘頻率不一樣,如STM32的SPI時(shí)鐘頻率最大為fpclk/2,兩個(gè)設備之間通訊時(shí),通訊速率受限于低速設備。

(3)    MOSI (Master Output, Slave Input):主設備輸出/從設備輸入引腳。主機的數據從這條信號線(xiàn)輸出,從機由這條信號線(xiàn)讀入主機發(fā)送的數據,即這條線(xiàn)上數據的方向為主機到從機。

(4)    MISO(Master Input,,Slave Output):主設備輸入/從設備輸出引腳。主機從這條信號線(xiàn)讀入數據,從機的數據由這條信號線(xiàn)輸出到主機,即在這條線(xiàn)上數據的方向為從機到主機。

24.1.2 協(xié)議層

I2C的類(lèi)似,SPI協(xié)議定義了通訊的起始和停止信號、數據有效性、時(shí)鐘同步等環(huán)節。

1.    SPI基本通訊過(guò)程

先看看SPI通訊的通訊時(shí)序,見(jiàn)圖 242。

242 SPI通訊時(shí)序

這是一個(gè)主機的通訊時(shí)序。NSS、SCK、MOSI信號都由主機控制產(chǎn)生,而MISO的信號由從機產(chǎn)生,主機通過(guò)該信號線(xiàn)讀取從機的數據。MOSIMISO的信號只在NSS為低電平的時(shí)候才有效,在SCK的每個(gè)時(shí)鐘周期MOSIMISO傳輸一位數據。

以上通訊流程中包含的各個(gè)信號分解如下:

2.    通訊的起始和停止信號

在圖 242中的標號?處,NSS信號線(xiàn)由高變低,是SPI通訊的起始信號。NSS是每個(gè)從機各自獨占的信號線(xiàn),當從機檢在自己的NSS線(xiàn)檢測到起始信號后,就知道自己被主機選中了,開(kāi)始準備與主機通訊。在圖中的標號?處,NSS信號由低變高,是SPI通訊的停止信號,表示本次通訊結束,從機的選中狀態(tài)被取消。

3.    數據有效性

SPI使用MOSIMISO信號線(xiàn)來(lái)傳輸數據,使用SCK信號線(xiàn)進(jìn)行數據同步。MOSIMISO數據線(xiàn)在SCK的每個(gè)時(shí)鐘周期傳輸一位數據,且數據輸入輸出是同時(shí)進(jìn)行的。數據傳輸時(shí),MSB先行或LSB先行并沒(méi)有作硬性規定,但要保證兩個(gè)SPI通訊設備之間使用同樣的協(xié)定,一般都會(huì )采用圖 242中的MSB先行模式。

觀(guān)察圖中的????標號處,MOSIMISO的數據在SCK的上升沿期間變化輸出,在SCK的下降沿時(shí)被采樣。即在SCK的下降沿時(shí)刻,MOSIMISO的數據有效,高電平時(shí)表示數據"1",為低電平時(shí)表示數據"0"。在其它時(shí)刻,數據無(wú)效,MOSIMISO為下一次表示數據做準備。

SPI每次數據傳輸可以8位或16位為單位,每次傳輸的單位數不受限制。

4.    CPOL/CPHA及通訊模式

上面講述的圖 242中的時(shí)序只是SPI中的其中一種通訊模式,SPI一共有四種通訊模式,它們的主要區別是總線(xiàn)空閑時(shí)SCK的時(shí)鐘狀態(tài)以及數據采樣時(shí)刻。為方便說(shuō)明,在此引入"時(shí)鐘極性CPOL"和"時(shí)鐘相位CPHA"的概念。

時(shí)鐘極性CPOL是指SPI通訊設備處于空閑狀態(tài)時(shí),SCK信號線(xiàn)的電平信號(SPI通訊開(kāi)始前、 NSS線(xiàn)為高電平時(shí)SCK的狀態(tài))。CPOL=0時(shí), SCK在空閑狀態(tài)時(shí)為低電平,CPOL=1時(shí),則相反。

時(shí)鐘相位CPHA是指數據的采樣的時(shí)刻,當CPHA=0時(shí),MOSIMISO數據線(xiàn)上的信號將會(huì )在SCK時(shí)鐘線(xiàn)的"奇數邊沿"被采樣。當CPHA=1時(shí),數據線(xiàn)在SCK的"偶數邊沿"采樣。見(jiàn)圖 243及圖 244。

243 CPHA=0時(shí)的SPI通訊模式

我們來(lái)分析這個(gè)CPHA=0的時(shí)序圖。首先,根據SCK在空閑狀態(tài)時(shí)的電平,分為兩種情況。SCK信號線(xiàn)在空閑狀態(tài)為低電平時(shí),CPOL=0;空閑狀態(tài)為高電平時(shí),CPOL=1。

無(wú)論CPOL=0還是=1,因為我們配置的時(shí)鐘相位CPHA=0,在圖中可以看到,采樣時(shí)刻都是在SCK的奇數邊沿。注意當CPOL=0的時(shí)候,時(shí)鐘的奇數邊沿是上升沿,而CPOL=1的時(shí)候,時(shí)鐘的奇數邊沿是下降沿。所以SPI的采樣時(shí)刻不是由上升/下降沿決定的。MOSIMISO數據線(xiàn)的有效信號在SCK的奇數邊沿保持不變,數據信號將在SCK奇數邊沿時(shí)被采樣,在非采樣時(shí)刻,MOSIMISO的有效信號才發(fā)生切換。

類(lèi)似地,當CPHA=1時(shí),不受CPOL的影響,數據信號在SCK的偶數邊沿被采樣,見(jiàn)圖 244。

244 CPHA=1時(shí)的SPI通訊模式

CPOLCPHA的不同狀態(tài),SPI分成了四種模式,見(jiàn)表 241,主機與從機需要工作在相同的模式下才可以正常通訊,實(shí)際中采用較多的是"模式0"與"模式3"。

241 SPI的四種模式

SPI模式

CPOL

CPHA

空閑時(shí)SCK時(shí)鐘

采樣時(shí)刻

0

0

0

低電平

奇數邊沿

1

0

1

低電平

偶數邊沿

2

1

0

高電平

奇數邊沿

3

1

1

高電平

偶數邊沿

24.2 STM32的SPI特性及架構

I2C外設一樣,STM32芯片也集成了專(zhuān)門(mén)用于SPI協(xié)議通訊的外設。

24.2.1 STM32的SPI外設簡(jiǎn)介

STM32SPI外設可用作通訊的主機及從機,支持最高的SCK時(shí)鐘頻率為fpclk/2 (STM32F429型號的芯片默認fpclk190MHz,fpclk245MHz),完全支持SPI協(xié)議的4種模式,數據幀長(cháng)度可設置為8位或16位,可設置數據MSB先行或LSB先行。它還支持雙線(xiàn)全雙工(前面小節說(shuō)明的都是這種模式)、雙線(xiàn)單向以及單線(xiàn)模式。其中雙線(xiàn)單向模式可以同時(shí)使用MOSIMISO數據線(xiàn)向一個(gè)方向傳輸數據,可以加快一倍的傳輸速度。而單線(xiàn)模式則可以減少硬件接線(xiàn),當然這樣速率會(huì )受到影響。我們只講解雙線(xiàn)全雙工模式。

STM32SPI外設還支持I2S功能,I2S功能是一種音頻串行通訊協(xié)議,在我們以后講解MP3播放器的章節中會(huì )進(jìn)行介紹。

24.2.2 STM32的SPI架構剖析

245 SPI架構圖

1.    通訊引腳

SPI的所有硬件架構都從圖 245中左側MOSI、MISO、SCKNSS線(xiàn)展開(kāi)的。STM32芯片有多個(gè)SPI外設,它們的SPI通訊信號引出到不同的GPIO引腳上,使用時(shí)必須配置到這些指定的引腳,見(jiàn)表 242。關(guān)于GPIO引腳的復用功能,可查閱《STM32F4xx規格書(shū)》,以它為準。

242 STM32F4xxSPI引腳(整理自《STM32F4xx規格書(shū)》)

引腳

SPI編號

SPI1

SPI2

SPI3

SPI4

SPI5

SPI6

MOSI

PA7/PB5

PB15/PC3/PI3

PB5/PC12/PD6

PE6/PE14

PF9/PF11

PG14

MISO

PA6/PB4

PB14/PC2/PI2

PB4/PC11

PE5/PE13

PF8/PH7

PG12

SCK

PA5/PB3

PB10/PB13/PD3

PB3/PC10

PE2/PE12

PF7/PH6

PG13

NSS

PA4/PA15

PB9/PB12/PI0

PA4/PA15

PE4/PE11

PF6/PH5

PG8

其中SPI1、SPI4、SPI5、SPI6APB2上的設備,最高通信速率達45Mbtis/s,SPI2、SPI3APB1上的設備,最高通信速率為22.5Mbits/s。除了通訊速率,在其它功能上沒(méi)有差異。

2.    時(shí)鐘控制邏輯

SCK線(xiàn)的時(shí)鐘信號,由波特率發(fā)生器根據"控制寄存器CR1"中的BR[0:2]位控制,該位是對fpclk時(shí)鐘的分頻因子,對fpclk的分頻結果就是SCK引腳的輸出時(shí)鐘頻率,計算方法見(jiàn)表 243。

243 BR位對fpclk的分頻

BR[0:2]

分頻結果(SCK頻率)

 

BR[0:2]

分頻結果(SCK頻率)

000

fpclk/2

 

100

fpclk/32

001

fpclk/4

 

101

fpclk/64

010

fpclk/8

 

110

fpclk/128

011

fpclk/16

 

111

fpclk/256

其中的fpclk頻率是指SPI所在的APB總線(xiàn)頻率,APB1fpclk1,APB2fpckl2。

通過(guò)配置"控制寄存器CR"的"CPOL位"及"CPHA"位可以把SPI設置成前面分析的4SPI模式。

3.    數據控制邏輯

SPIMOSIMISO都連接到數據移位寄存器上,數據移位寄存器的內容來(lái)源于接收緩沖區及發(fā)送緩沖區以及MISO、MOSI線(xiàn)。當向外發(fā)送數據的時(shí)候,數據移位寄存器以"發(fā)送緩沖區"為數據源,把數據一位一位地通過(guò)數據線(xiàn)發(fā)送出去;當從外部接收數據的時(shí)候,數據移位寄存器把數據線(xiàn)采樣到的數據一位一位地存儲到"接收緩沖區"中。通過(guò)寫(xiě)SPI的"數據寄存器DR"把數據填充到發(fā)送緩沖區中,通過(guò)"數據寄存器DR",可以獲取接收緩沖區中的內容。其中數據幀長(cháng)度可以通過(guò)"控制寄存器CR1"的"DFF位"配置成8位及16位模式;配置"LSBFIRST位"可選擇MSB先行還是LSB先行。

4.    整體控制邏輯

整體控制邏輯負責協(xié)調整個(gè)SPI外設,控制邏輯的工作模式根據我們配置的"控制寄存器(CR1/CR2)"的參數而改變,基本的控制參數包括前面提到的SPI模式、波特率、LSB先行、主從模式、單雙向模式等等。在外設工作時(shí),控制邏輯會(huì )根據外設的工作狀態(tài)修改"狀態(tài)寄存器(SR)",我們只要讀取狀態(tài)寄存器相關(guān)的寄存器位,就可以了解SPI的工作狀態(tài)了。除此之外,控制邏輯還根據要求,負責控制產(chǎn)生SPI中斷信號、DMA請求及控制NSS信號線(xiàn)。

實(shí)際應用中,我們一般不使用STM32 SPI外設的標準NSS信號線(xiàn),而是更簡(jiǎn)單地使用普通的GPIO,軟件控制它的電平輸出,從而產(chǎn)生通訊起始和停止信號。

24.2.3 通訊過(guò)程

STM32使用SPI外設通訊時(shí),在通訊的不同階段它會(huì )對"狀態(tài)寄存器SR"的不同數據位寫(xiě)入參數,我們通過(guò)讀取這些寄存器標志來(lái)了解通訊狀態(tài)。

246中的是"主模式"流程,即STM32作為SPI通訊的主機端時(shí)的數據收發(fā)過(guò)程。

246 主發(fā)送器通訊過(guò)程

主模式收發(fā)流程及事件說(shuō)明如下:

(1)    控制NSS信號線(xiàn),產(chǎn)生起始信號(圖中沒(méi)有畫(huà)出);

(2)    把要發(fā)送的數據寫(xiě)入到"數據寄存器DR"中,該數據會(huì )被存儲到發(fā)送緩沖區;

(3)    通訊開(kāi)始,SCK時(shí)鐘開(kāi)始運行。MOSI把發(fā)送緩沖區中的數據一位一位地傳輸出去;MISO則把數據一位一位地存儲進(jìn)接收緩沖區中;

(4)    當發(fā)送完一幀數據的時(shí)候,"狀態(tài)寄存器SR"中的"TXE標志位"會(huì )被置1,表示傳輸完一幀,發(fā)送緩沖區已空;類(lèi)似地,當接收完一幀數據的時(shí)候,"RXNE標志位"會(huì )被置1,表示傳輸完一幀,接收緩沖區非空;

(5)    等待到"TXE標志位"為1時(shí),若還要繼續發(fā)送數據,則再次往"數據寄存器DR"寫(xiě)入數據即可;等待到"RXNE標志位"為1時(shí),通過(guò)讀取"數據寄存器DR"可以獲取接收緩沖區中的內容。

假如我們使能了TXERXNE中斷,TXERXNE1時(shí)會(huì )產(chǎn)生SPI中斷信號,進(jìn)入同一個(gè)中斷服務(wù)函數,到SPI中斷服務(wù)程序后,可通過(guò)檢查寄存器位來(lái)了解是哪一個(gè)事件,再分別進(jìn)行處理。也可以使用DMA方式來(lái)收發(fā)"數據寄存器DR"中的數據。

24.3 SPI初始化結構體詳解

跟其它外設一樣,STM32標準庫提供了SPI初始化結構體及初始化函數來(lái)配置SPI外設。初始化結構體及函數定義在庫文件"stm32f4xx_spi.h"及"stm32f4xx_spi.c"中,編程時(shí)我們可以結合這兩個(gè)文件內的注釋使用或參考庫幫助文檔。了解初始化結構體后我們就能對SPI外設運用自如了,見(jiàn)代碼清單 241。

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

1 typedef struct

2 {

3 uint16_t SPI_Direction; /*設置SPI的單雙向模式 */

4 uint16_t SPI_Mode; /*設置SPI的主/從機端模式 */

5 uint16_t SPI_DataSize; /*設置SPI的數據幀長(cháng)度,可選8/16 */

6 uint16_t SPI_CPOL; /*設置時(shí)鐘極性CPOL,可選高/低電平*/

7 uint16_t SPI_CPHA; /*設置時(shí)鐘相位,可選奇/偶數邊沿采樣 */

8 uint16_t SPI_NSS; /*設置NSS引腳由SPI硬件控制還是軟件控制*/

9 uint16_t SPI_BaudRatePrescaler; /*設置時(shí)鐘分頻因子,fpclk/分頻數=fSCK */

10 uint16_t SPI_FirstBit; /*設置MSB/LSB先行 */

11 uint16_t SPI_CRCPolynomial; /*設置CRC校驗的表達式 */

12 } SPI_InitTypeDef;

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

(1)    SPI_Direction

本成員設置SPI的通訊方向,可設置為雙線(xiàn)全雙工(SPI_Direction_2Lines_FullDuplex),雙線(xiàn)只接收(SPI_Direction_2Lines_RxOnly),單線(xiàn)只接收(SPI_Direction_1Line_Rx)、單線(xiàn)只發(fā)送模式(SPI_Direction_1Line_Tx)。

(2)    SPI_Mode

本成員設置SPI工作在主機模式(SPI_Mode_Master)或從機模式(SPI_Mode_Slave ),這兩個(gè)模式的最大區別為SPISCK信號線(xiàn)的時(shí)序,SCK的時(shí)序是由通訊中的主機產(chǎn)生的。若被配置為從機模式,STM32SPI外設將接受外來(lái)的SCK信號。

(3)    SPI_DataSize

本成員可以選擇SPI通訊的數據幀大小是為8(SPI_DataSize_8b)還是16(SPI_DataSize_16b)。

(4)    SPI_CPOLSPI_CPHA

這兩個(gè)成員配置SPI的時(shí)鐘極性CPOL和時(shí)鐘相位CPHA,這兩個(gè)配置影響到SPI的通訊模式,關(guān)于CPOLCPHA的說(shuō)明參考前面"通訊模式"小節。

時(shí)鐘極性CPOL成員,可設置為高電平(SPI_CPOL_High)或低電平(SPI_CPOL_Low )。

時(shí)鐘相位CPHA 則可以設置為SPI_CPHA_1Edge(SCK的奇數邊沿采集數據SPI_CPHA_2Edge (SCK的偶數邊沿采集數據。

(5)    SPI_NSS

本成員配置NSS引腳的使用模式,可以選擇為硬件模式(SPI_NSS_Hard )與軟件模式(SPI_NSS_Soft  ),在硬件模式中的SPI片選信號由SPI硬件自動(dòng)產(chǎn)生,而軟件模式則需要我們親自把相應的GPIO端口拉高或置低產(chǎn)生非片選和片選信號。實(shí)際中軟件模式應用比較多。

(6)    SPI_BaudRatePrescaler

本成員設置波特率分頻因子,分頻后的時(shí)鐘即為SPISCK信號線(xiàn)的時(shí)鐘頻率。這個(gè)成員參數可設置為fpclk2、4、6、8、16、32、64、128、256分頻。

(7)    SPI_FirstBit

所有串行的通訊協(xié)議都會(huì )有MSB先行(高位數據在前)還是LSB先行(低位數據在前)的問(wèn)題,而STM32SPI模塊可以通過(guò)這個(gè)結構體成員,對這個(gè)特性編程控制。

(8)    SPI_CRCPolynomial

這是SPICRC校驗中的多項式,若我們使用CRC校驗時(shí),就使用這個(gè)成員的參數(多項式),來(lái)計算CRC的值。

配置完這些結構體成員后,我們要調用SPI_Init函數把這些參數寫(xiě)入到寄存器中,實(shí)現SPI的初始化,然后調用SPI_Cmd來(lái)使能SPI外設。

24.4 SPI—讀寫(xiě)串行FLASH實(shí)驗

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

本小節以一種使用SPI通訊的串行FLASH存儲芯片的讀寫(xiě)實(shí)驗為大家講解STM32SPI使用方法。實(shí)驗中STM32SPI外設采用主模式,通過(guò)查詢(xún)事件的方式來(lái)確保正常通訊。

24.4.1 硬件設計

247 SPI串行FLASH硬件連接圖

本實(shí)驗板中的FLASH芯片(型號:W25Q128)是一種使用SPI通訊協(xié)議的NOR FLASH存儲器,它的CS/CLK/DIO/DO引腳分別連接到了STM32對應的SDI引腳NSS/SCK/MOSI/MISO上,其中STM32NSS引腳是一個(gè)普通的GPIO,不是SPI的專(zhuān)用NSS引腳,所以程序中我們要使用軟件控制的方式。

FLASH芯片中還有WPHOLD引腳。WP引腳可控制寫(xiě)保護功能,當該引腳為低電平時(shí),禁止寫(xiě)入數據。我們直接接電源,不使用寫(xiě)保護功能。HOLD引腳可用于暫停通訊,該引腳為低電平時(shí),通訊暫停,數據輸出引腳輸出高阻抗狀態(tài),時(shí)鐘和數據輸入引腳無(wú)效。我們直接接電源,不使用通訊暫停功能。

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

24.4.2 軟件設計

為了使工程更加有條理,我們把讀寫(xiě)FLASH相關(guān)的代碼獨立分開(kāi)存儲,方便以后移植。在"工程模板"之上新建"bsp_spi_flash.c"及"bsp_spi_ flash.h"文件,這些文件也可根據您的喜好命名,它們不屬于STM32標準庫的內容,是由我們自己根據應用需要編寫(xiě)的。

1.    編程要點(diǎn)

(7)    初始化通訊使用的目標引腳及端口時(shí)鐘;

(8)    使能SPI外設的時(shí)鐘;

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

(10)    編寫(xiě)基本SPI按字節收發(fā)的函數;

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

(12)    編寫(xiě)測試程序,對讀寫(xiě)數據進(jìn)行校驗。

2.    代碼分析
SPI硬件相關(guān)宏定義

我們把SPI硬件相關(guān)的配置都以宏的形式定義到"bsp_spi_ flash.h"文件中,見(jiàn)代碼清單 242。

代碼清單 242 SPI硬件配置相關(guān)的宏

1 //SPI及時(shí)鐘初始化函數

2 #define FLASH_SPI SPI3

3 #define FLASH_SPI_CLK RCC_APB1Periph_SPI3

4 #define FLASH_SPI_CLK_INIT RCC_APB1PeriphClockCmd

5 //SCK引腳

6 #define FLASH_SPI_SCK_PIN GPIO_Pin_3

7 #define FLASH_SPI_SCK_GPIO_PORT GPIOB

8 #define FLASH_SPI_SCK_GPIO_CLK RCC_AHB1Periph_GPIOB

9 #define FLASH_SPI_SCK_PINSOURCE GPIO_PinSource3

10 #define FLASH_SPI_SCK_AF GPIO_AF_SPI3

11 //MISO引腳

12 #define FLASH_SPI_MISO_PIN GPIO_Pin_4

13 #define FLASH_SPI_MISO_GPIO_PORT GPIOB

14 #define FLASH_SPI_MISO_GPIO_CLK RCC_AHB1Periph_GPIOB

15 #define FLASH_SPI_MISO_PINSOURCE GPIO_PinSource4

16 #define FLASH_SPI_MISO_AF GPIO_AF_SPI3

17 //MOSI引腳

18 #define FLASH_SPI_MOSI_PIN GPIO_Pin_5

19 #define FLASH_SPI_MOSI_GPIO_PORT GPIOB

20 #define FLASH_SPI_MOSI_GPIO_CLK RCC_AHB1Periph_GPIOB

21 #define FLASH_SPI_MOSI_PINSOURCE GPIO_PinSource5

22 #define FLASH_SPI_MOSI_AF GPIO_AF_SPI3

23 //CS(NSS)引腳

24 #define FLASH_CS_PIN GPIO_Pin_8

25 #define FLASH_CS_GPIO_PORT GPIOI

26 #define FLASH_CS_GPIO_CLK RCC_AHB1Periph_GPIOI

27

28 //控制CS(NSS)引腳輸出低電平

29 #define SPI_FLASH_CS_LOW() {FLASH_CS_GPIO_PORT->BSRRH=FLASH_CS_PIN;}

30 //控制CS(NSS)引腳輸出高電平

31 #define SPI_FLASH_CS_HIGH() {FLASH_CS_GPIO_PORT->BSRRL=FLASH_CS_PIN;}

以上代碼根據硬件連接,把與FLASH通訊使用的SPI、引腳號、引腳源以及復用功能映射都以宏封裝起來(lái),并且定義了控制CS(NSS)引腳輸出電平的宏,以便配置產(chǎn)生起始和停止信號時(shí)使用。

初始化SPI的 GPIO

利用上面的宏,編寫(xiě)SPI的初始化函數,見(jiàn)代碼清單 243。

代碼清單 243 SPI的初始化函數(GPIO初始化部分)

 1 			

2 /**

3 * @brief SPI_FLASH初始化

4 * @param 無(wú)

5 * @retval 無(wú)

6 */

7 void SPI_FLASH_Init(void)

8 {

9 GPIO_InitTypeDef GPIO_InitStructure;

10

11 /* 使能 FLASH_SPI GPIO 時(shí)鐘 */

12 /*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO,

13 SPI_FLASH_SPI_MISO_GPIO SPI_FLASH_SPI_SCK_GPIO 時(shí)鐘使能 */

14 RCC_AHB1PeriphClockCmd (FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MISO_GPIO_CLK|

15 FLASH_SPI_MOSI_GPIO_CLK|FLASH_CS_GPIO_CLK, ENABLE);

16

17 /*!< SPI_FLASH_SPI 時(shí)鐘使能 */

18 FLASH_SPI_CLK_INIT(FLASH_SPI_CLK, ENABLE);

19

20 //設置引腳復用

21 GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT,FLASH_SPI_SCK_PINSOURCE,

22 FLASH_SPI_SCK_AF);

23 GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT,FLASH_SPI_MISO_PINSOURCE,

24 FLASH_SPI_MISO_AF);

25 GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT,FLASH_SPI_MOSI_PINSOURCE,

26 FLASH_SPI_MOSI_AF);

27

28 /*!< 配置 SPI_FLASH_SPI 引腳: SCK */

29 GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;

30 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

31 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

32 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

33 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

35

36 GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);

37

38 /*!< 配置 SPI_FLASH_SPI 引腳: MISO */

39 GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;

40 GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);

41

42 /*!< 配置 SPI_FLASH_SPI 引腳: MOSI */

43 GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;

44 GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);

45

46 /*!< 配置 SPI_FLASH_SPI 引腳: CS */

47 GPIO_InitStructure.GPIO_Pin = FLASH_CS_PIN;

48 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;

49 GPIO_Init(FLASH_CS_GPIO_PORT, &GPIO_InitStructure);

50

51 /* 停止信號 FLASH: CS引腳高電平*/

52 SPI_FLASH_CS_HIGH();

53 /*為方便講解,以下省略SPI模式初始化部分*/

54 //......

55 }

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

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

(2)    調用庫函數RCC_AHB1PeriphClockCmd來(lái)使能SPI引腳使用的GPIO端口時(shí)鐘,調用時(shí)使用"|"操作同時(shí)配置多個(gè)引腳。調用宏FLASH_SPI_CLK_INIT使能SPI外設時(shí)鐘(該宏封裝了APB時(shí)鐘使能的庫函數)。

(3)    GPIO初始化結構體賦值,把SCK/MOSI/MISO引腳初始化成復用推挽模式。而CS(NSS)引腳由于使用軟件控制,我們把它配置為普通的推挽輸出模式。

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

配置SPI的模式

以上只是配置了SPI使用的引腳,對SPI外設模式的配置。在配置STM32SPI模式前,我們要先了解從機端的SPI模式。本例子中可通過(guò)查閱FLASH數據手冊《W25Q128》獲取。根據FLASH芯片的說(shuō)明,它支持SPI模式0及模式3,支持雙線(xiàn)全雙工,使用MSB先行模式,支持最高通訊時(shí)鐘為104MHz,數據幀長(cháng)度為8位。我們要把STM32SPI外設中的這些參數配置一致。見(jiàn)代碼清單 244。

代碼清單 244 配置SPI模式

1 /**

2 * @brief SPI_FLASH引腳初始化

3 * @param 無(wú)

4 * @retval 無(wú)

5 */

6 void SPI_FLASH_Init(void)

7 {

8 /*為方便講解,省略了SPIGPIO初始化部分*/

9 //......

10

11 SPI_InitTypeDef SPI_InitStructure;

12 /* FLASH_SPI 模式配置 */

13 // FLASH芯片支持SPI模式0及模式3,據此設置CPOL CPHA

14 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

15 SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

16 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

17 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;

18 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

19 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;

20 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;

21 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;

22 SPI_InitStructure.SPI_CRCPolynomial = 7;

23 SPI_Init(FLASH_SPI, &SPI_InitStructure);

24

25 /* 使能 FLASH_SPI */

26 SPI_Cmd(FLASH_SPI, ENABLE);

27 }

這段代碼中,把STM32SPI外設配置為主機端,雙線(xiàn)全雙工模式,數據幀長(cháng)度為8位,使用SPI模式3(CPOL=1,CPHA=1),NSS引腳由軟件控制以及MSB先行模式。最后一個(gè)成員為CRC計算式,由于我們與FLASH芯片通訊不需要CRC校驗,并沒(méi)有使能SPICRC功能,這時(shí)CRC計算式的成員值是無(wú)效的。

賦值結束后調用庫函數SPI_Init把這些配置寫(xiě)入寄存器,并調用SPI_Cmd函數使能外設。

使用SPI發(fā)送和接收一個(gè)字節的數據

初始化好SPI外設后,就可以使用SPI通訊了,復雜的數據通訊都是由單個(gè)字節數據收發(fā)組成的,我們看看它的代碼實(shí)現,見(jiàn)代碼清單 245。

代碼清單 245 使用SPI發(fā)送和接收一個(gè)字節的數據

1 #define Dummy_Byte 0xFF

2 /**

3 * @brief 使用SPI發(fā)送一個(gè)字節的數據

4 * @param byte:要發(fā)送的數據

5 * @retval 返回接收到的數據

6 */

7 u8 SPI_FLASH_SendByte(u8 byte)

8 {

9 SPITimeout = SPIT_FLAG_TIMEOUT;

10

11 /* 等待發(fā)送緩沖區為空,TXE事件 */

12 while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET)

13 {

14 if ((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);

15 }

16

17 /* 寫(xiě)入數據寄存器,把要寫(xiě)入的數據寫(xiě)入發(fā)送緩沖區 */

18 SPI_I2S_SendData(FLASH_SPI, byte);

19

20 SPITimeout = SPIT_FLAG_TIMEOUT;

21

22 /* 等待接收緩沖區非空,RXNE事件 */

23 while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET)

24 {

25 if ((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);

26 }

27

28 /* 讀取數據寄存器,獲取接收緩沖區數據 */

29 return SPI_I2S_ReceiveData(FLASH_SPI);

30 }

31

32 /**

33 * @brief 使用SPI讀取一個(gè)字節的數據

34 * @param 無(wú)

35 * @retval 返回接收到的數據

36 */

37 u8 SPI_FLASH_ReadByte(void)

38 {

39 return (SPI_FLASH_SendByte(Dummy_Byte));

40 }

SPI_FLASH_SendByte發(fā)送單字節函數中包含了等待事件的超時(shí)處理,這部分原理跟I2C中的一樣,在此不再贅述。

SPI_FLASH_SendByte函數實(shí)現了前面講解的"SPI通訊過(guò)程":

(1)    本函數中不包含SPI起始和停止信號,只是收發(fā)的主要過(guò)程,所以在調用本函數前后要做好起始和停止信號的操作;

(2)    SPITimeout變量賦值為宏SPIT_FLAG_TIMEOUT。這個(gè)SPITimeout變量在下面的while循環(huán)中每次循環(huán)減1,該循環(huán)通過(guò)調用庫函數SPI_I2S_GetFlagStatus檢測事件,若檢測到事件,則進(jìn)入通訊的下一階段,若未檢測到事件則停留在此處一直檢測,當檢測SPIT_FLAG_TIMEOUT次都還沒(méi)等待到事件則認為通訊失敗,調用的SPI_TIMEOUT_UserCallback輸出調試信息,并退出通訊;

(3)    通過(guò)檢測TXE標志,獲取發(fā)送緩沖區的狀態(tài),若發(fā)送緩沖區為空,則表示可能存在的上一個(gè)數據已經(jīng)發(fā)送完畢;

(4)    等待至發(fā)送緩沖區為空后,調用庫函數SPI_I2S_SendData把要發(fā)送的數據"byte"寫(xiě)入到SPI的數據寄存器DR,寫(xiě)入SPI數據寄存器的數據會(huì )存儲到發(fā)送緩沖區,由SPI外設發(fā)送出去;

(5)    寫(xiě)入完畢后等待RXNE事件,即接收緩沖區非空事件。由于SPI雙線(xiàn)全雙工模式下MOSIMISO數據傳輸是同步的(請對比"SPI通訊過(guò)程"閱讀),當接收緩沖區非空時(shí),表示上面的數據發(fā)送完畢,且接收緩沖區也收到新的數據;

(6)    等待至接收緩沖區非空時(shí),通過(guò)調用庫函數SPI_I2S_ReceiveData讀取SPI的數據寄存器DR,就可以獲取接收緩沖區中的新數據了。代碼中使用關(guān)鍵字"return"把接收到的這個(gè)數據作為SPI_FLASH_SendByte函數的返回值,所以我們可以看到在下面定義的SPI接收數據函數SPI_FLASH_ReadByte,它只是簡(jiǎn)單地調用了SPI_FLASH_SendByte函數發(fā)送數據"Dummy_Byte",然后獲取其返回值(因為不關(guān)注發(fā)送的數據,所以此時(shí)的輸入參數"Dummy_Byte"可以為任意值)??梢赃@樣做的原因是SPI的接收過(guò)程和發(fā)送過(guò)程實(shí)質(zhì)是一樣的,收發(fā)同步進(jìn)行,關(guān)鍵在于我們的上層應用中,關(guān)注的是發(fā)送還是接收的數據。

控制FLASH的指令

搞定SPI的基本收發(fā)單元后,還需要了解如何對FLASH芯片進(jìn)行讀寫(xiě)。FLASH芯片自定義了很多指令,我們通過(guò)控制STM32利用SPI總線(xiàn)向FLASH芯片發(fā)送指令,FLASH芯片收到后就會(huì )執行相應的操作。

而這些指令,對主機端(STM32)來(lái)說(shuō),只是它遵守最基本的SPI通訊協(xié)議發(fā)送出的數據,但在設備端(FLASH芯片)把這些數據解釋成不同的意義,所以才成為指令。查看FLASH芯片的數據手冊《W25Q128》,可了解各種它定義的各種指令的功能及指令格式,見(jiàn)表 244。

244 FLASH常用芯片指令表(摘自規格書(shū)《W25Q128)

指令

第一字節(指令編碼)

第二字節

第三字節

第四字節

第五字節

第六字節

第七-N字節

Write Enable

06h

  

  

  

  

  

  

Write Disable

04h

  

  

  

  

  

  

Read Status Register

05h

(S7–S0)

  

  

  

  

 

Write Status Register

01h

(S7–S0)

  

  

  

  

  

Read Data

03h

A23–A16

A15–A8

A7–A0

(D7–D0)

(Next byte)

continuous

Fast Read

0Bh

A23–A16

A15–A8

A7–A0

dummy

(D7–D0)

(Next Byte) continuous

Fast Read Dual Output

3Bh

A23–A16

A15–A8

A7–A0

dummy

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

(one byte per 4 clocks, continuous)

Page Program

02h

A23–A16

A15–A8

A7–A0

D7–D0

Next byte

Up to 256 bytes

Block Erase(64KB)

D8h

A23–A16

A15–A8

A7–A0

  

  

  

Sector Erase(4KB)

20h

A23–A16

A15–A8

A7–A0

  

  

  

Chip Erase

C7h

  

  

  

  

  

  

Power-down

B9h

  

  

  

  

  

  

Release Power- down / Device ID

ABh

dummy

dummy

dummy

(ID7-ID0)

  

  

Manufacturer/ Device ID

90h

dummy

dummy

00h

(M7-M0)

(ID7-ID0)

  

JEDEC ID

9Fh

(M7-M0)

生產(chǎn)廠(chǎng)商

(ID15-ID8)

存儲器類(lèi)型

(ID7-ID0) 容量

  

  

  

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

FLSAH芯片內部,存儲有固定的廠(chǎng)商編號(M7-M0)和不同類(lèi)型FLASH芯片獨有的編號(ID15-ID0),見(jiàn)表 245。

245 FLASH數據手冊的設備ID說(shuō)明

FLASH型號

廠(chǎng)商號(M7-M0)

FLASH型號(ID15-ID0)

W25Q64

EF h

4017 h

W25Q128

EF h

4018 h

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

此處我們以該指令為例,配合其指令時(shí)序圖進(jìn)行講解,見(jiàn)圖 248。

248 FLASHID指令"JEDEC ID"的時(shí)序(摘自規格書(shū)《W25Q128)

主機首先通過(guò)MOSI線(xiàn)向FLASH芯片發(fā)送第一個(gè)字節數據為"9F h",當FLASH芯片收到該數據后,它會(huì )解讀成主機向它發(fā)送了"JEDEC指令",然后它就作出該命令的響應:通過(guò)MISO線(xiàn)把它的廠(chǎng)商ID(M7-M0)及芯片類(lèi)型(ID15-0)發(fā)送給主機,主機接收到指令響應后可進(jìn)行校驗。常見(jiàn)的應用是主機端通過(guò)讀取設備ID來(lái)測試硬件是否連接正常,或用于識別設備。

對于FLASH芯片的其它指令,都是類(lèi)似的,只是有的指令包含多個(gè)字節,或者響應包含更多的數據。

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

定義FLASH指令編碼表

為了方便使用,我們把FLASH芯片的常用指令編碼使用宏來(lái)封裝起來(lái),后面需要發(fā)送指令編碼的時(shí)候我們直接使用這些宏即可,見(jiàn)代碼清單 246。

代碼清單 246 FLASH指令編碼表

1 /*FLASH常用命令*/

2 #define W25X_WriteEnable 0x06

3 #define W25X_WriteDisable 0x04

4 #define W25X_ReadStatusReg 0x05

5 #define W25X_WriteStatusReg 0x01

6 #define W25X_ReadData 0x03

7 #define W25X_FastReadData 0x0B

8 #define W25X_FastReadDual 0x3B

9 #define W25X_PageProgram 0x02

10 #define W25X_BlockErase 0xD8

11 #define W25X_SectorErase 0x20

12 #define W25X_ChipErase 0xC7

13 #define W25X_PowerDown 0xB9

14 #define W25X_ReleasePowerDown 0xAB

15 #define W25X_DeviceID 0xAB

16 #define W25X_ManufactDeviceID 0x90

17 #define W25X_JedecDeviceID 0x9F

18 /*其它*/

19 #define sFLASH_ID 0XEF4018

20 #define Dummy_Byte 0xFF

讀取FLASH芯片ID

根據"JEDEC"指令的時(shí)序,我們把讀取FLASH ID的過(guò)程編寫(xiě)成一個(gè)函數,見(jiàn)代碼清單 247。

代碼清單 247 讀取FLASH芯片ID

1 /**

2 * @brief 讀取FLASH ID

3 * @param 無(wú)

4 * @retval FLASH ID

5 */

6 u32 SPI_FLASH_ReadID(void)

7 {

8 u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;

9

10 /* 開(kāi)始通訊:CS低電平 */

11 SPI_FLASH_CS_LOW();

12

13 /* 發(fā)送JEDEC指令,讀取ID */

14 SPI_FLASH_SendByte(W25X_JedecDeviceID);

15

16 /* 讀取一個(gè)字節數據 */

17 Temp0 = SPI_FLASH_SendByte(Dummy_Byte);

18

19 /* 讀取一個(gè)字節數據 */

20 Temp1 = SPI_FLASH_SendByte(Dummy_Byte);

21

22 /* 讀取一個(gè)字節數據 */

23 Temp2 = SPI_FLASH_SendByte(Dummy_Byte);

24

25 /* 停止通訊:CS高電平 */

26 SPI_FLASH_CS_HIGH();

27

28 /*把數據組合起來(lái),作為函數的返回值*/

29 Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;

30

31 return Temp;

32 }

這段代碼利用控制CS引腳電平的宏"SPI_FLASH_CS_LOW/HIGH"以及前面編寫(xiě)的單字節收發(fā)函數SPI_FLASH_SendByte,很清晰地實(shí)現了"JEDEC ID"指令的時(shí)序:發(fā)送一個(gè)字節的指令編碼"W25X_JedecDeviceID",然后讀取3個(gè)字節,獲取FLASH芯片對該指令的響應,最后把讀取到的這3個(gè)數據合并到一個(gè)變量Temp中,然后作為函數返回值,把該返回值與我們定義的宏"sFLASH_ID"對比,即可知道FLASH芯片是否正常。

FLASH寫(xiě)使能以及讀取當前狀態(tài)

在向FLASH芯片存儲矩陣寫(xiě)入數據前,首先要使能寫(xiě)操作,通過(guò)"Write Enable"命令即可寫(xiě)使能,見(jiàn)代碼清單 248。

代碼清單 248 寫(xiě)使能命令

1 /**

2 * @brief FLASH發(fā)送寫(xiě)使能命令

3 * @param none

4 * @retval none

5 */

6 void SPI_FLASH_WriteEnable(void)

7 {

8 /* 通訊開(kāi)始:CS */

9 SPI_FLASH_CS_LOW();

10

11 /* 發(fā)送寫(xiě)使能命令*/

12 SPI_FLASH_SendByte(W25X_WriteEnable);

13

14 /*通訊結束:CS */

15 SPI_FLASH_CS_HIGH();

16 }

EEPROM一樣,由于FLASH芯片向內部存儲矩陣寫(xiě)入數據需要消耗一定的時(shí)間,并不是在總線(xiàn)通訊結束的一瞬間完成的,所以在寫(xiě)操作后需要確認FLASH芯片"空閑"時(shí)才能進(jìn)行再次寫(xiě)入。為了表示自己的工作狀態(tài),FLASH芯片定義了一個(gè)狀態(tài)寄存器,見(jiàn)圖 249。

249 FLASH芯片的狀態(tài)寄存器

我們只關(guān)注這個(gè)狀態(tài)寄存器的第0位"BUSY",當這個(gè)位為"1"時(shí),表明FLASH芯片處于忙碌狀態(tài),它可能正在對內部的存儲矩陣進(jìn)行"擦除"或"數據寫(xiě)入"的操作。

利用指令表中的"Read Status Register"指令可以獲取FLASH芯片狀態(tài)寄存器的內容,其時(shí)序見(jiàn)圖 2410。

2410 讀取狀態(tài)寄存器的時(shí)序

只要向FLASH芯片發(fā)送了讀狀態(tài)寄存器的指令,FLASH芯片就會(huì )持續向主機返回最新的狀態(tài)寄存器內容,直到收到SPI通訊的停止信號。據此我們編寫(xiě)了具有等待FLASH芯片寫(xiě)入結束功能的函數,見(jiàn)代碼清單 249。

代碼清單 249 通過(guò)讀狀態(tài)寄存器等待FLASH芯片空閑

1 /*WIP(BUSY)標志:FLASH內部正在寫(xiě)入*/

2 #define WIP_Flag 0x01

3

4 /**

5 * @brief 等待WIP(BUSY)標志被置0,即等待到FLASH內部數據寫(xiě)入完畢

6 * @param none

7 * @retval none

8 */

9 void SPI_FLASH_WaitForWriteEnd(void)

10 {

11 u8 FLASH_Status = 0;

12 /* 選擇 FLASH: CS */

13 SPI_FLASH_CS_LOW();

14

15 /* 發(fā)送讀狀態(tài)寄存器命令 */

16 SPI_FLASH_SendByte(W25X_ReadStatusReg);

17

18 SPITimeout = SPIT_FLAG_TIMEOUT;

19 /* FLASH忙碌,則等待 */

20 do

21 {

22 /* 讀取FLASH芯片的狀態(tài)寄存器 */

23 FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);

24 if ((SPITimeout--) == 0)

25 {

26 SPI_TIMEOUT_UserCallback(4);

27 return;

28 }

29 }

30 while ((FLASH_Status & WIP_Flag) == SET); /* 正在寫(xiě)入標志 */

31

32 /* 停止信號 FLASH: CS */

33 SPI_FLASH_CS_HIGH();

34 }

這段代碼發(fā)送讀狀態(tài)寄存器的指令編碼"W25X_ReadStatusReg"后,在while循環(huán)里持續獲取寄存器的內容并檢驗它的"WIP_Flag標志"(BUSY),一直等待到該標志表示寫(xiě)入結束時(shí)才退出本函數,以便繼續后面與FLASH芯片的數據通訊。

FLASH扇區擦除

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

通常,對存儲矩陣擦除的基本操作單位都是多個(gè)字節進(jìn)行,如本例子中的FLASH芯片支持"扇區擦除"、"塊擦除"以及"整片擦除",見(jiàn)表 246。

246 本實(shí)驗FLASH芯片的擦除單位

擦除單位

大小

扇區擦除Sector Erase

4KB

塊擦除Block Erase

64KB

整片擦除Chip Erase

整個(gè)芯片完全擦除

FLASH芯片的最小擦除單位為扇區(Sector),而一個(gè)塊(Block)包含16個(gè)扇區,其內部存儲矩陣分布見(jiàn)圖 2411。。

2411 FLASH芯片的存儲矩陣

使用扇區擦除指令"Sector Erase"可控制FLASH芯片開(kāi)始擦寫(xiě),其指令時(shí)序見(jiàn)圖 2414。

2412 扇區擦除時(shí)序

扇區擦除指令的第一個(gè)字節為指令編碼,緊接著(zhù)發(fā)送的3個(gè)字節用于表示要擦除的存儲矩陣地址。要注意的是在扇區擦除指令前,還需要先發(fā)送"寫(xiě)使能"指令,發(fā)送扇區擦除指令后,通過(guò)讀取寄存器狀態(tài)等待扇區擦除操作完畢,代碼實(shí)現見(jiàn)代碼清單 2410。

代碼清單 2410 擦除扇區

1 /**

2 * @brief 擦除FLASH扇區

3 * @param SectorAddr:要擦除的扇區地址

4 * @retval 無(wú)

5 */

6 void SPI_FLASH_SectorErase(u32 SectorAddr)

7 {

8 /* 發(fā)送FLASH寫(xiě)使能命令 */

9 SPI_FLASH_WriteEnable();

10 SPI_FLASH_WaitForWriteEnd();

11 /* 擦除扇區 */

12 /* 選擇FLASH: CS低電平 */

13 SPI_FLASH_CS_LOW();

14 /* 發(fā)送扇區擦除指令*/

15 SPI_FLASH_SendByte(W25X_SectorErase);

16 /*發(fā)送擦除扇區地址的高位*/

17 SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);

18 /* 發(fā)送擦除扇區地址的中位 */

19 SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);

20 /* 發(fā)送擦除扇區地址的低位 */

21 SPI_FLASH_SendByte(SectorAddr & 0xFF);

22 /* 停止信號 FLASH: CS 高電平 */

23 SPI_FLASH_CS_HIGH();

24 /* 等待擦除完畢*/

25 SPI_FLASH_WaitForWriteEnd();

26 }

這段代碼調用的函數在前面都已講解,只要注意發(fā)送擦除地址時(shí)高位在前即可。調用扇區擦除指令時(shí)注意輸入的地址要對齊到4KB。

FLASH的頁(yè)寫(xiě)入

目標扇區被擦除完畢后,就可以向它寫(xiě)入數據了。與EEPROM類(lèi)似,FLASH芯片也有頁(yè)寫(xiě)入命令,使用頁(yè)寫(xiě)入命令最多可以一次向FLASH傳輸256個(gè)字節的數據,我們把這個(gè)單位為頁(yè)大小。FLASH頁(yè)寫(xiě)入的時(shí)序見(jiàn)圖 2413。

2413 FLASH芯片頁(yè)寫(xiě)入

從時(shí)序圖可知,第1個(gè)字節為"頁(yè)寫(xiě)入指令"編碼,2-4字節為要寫(xiě)入的"地址A",接著(zhù)的是要寫(xiě)入的內容,最多個(gè)可以發(fā)送256字節數據,這些數據將會(huì )從"地址A"開(kāi)始,按順序寫(xiě)入到FLASH的存儲矩陣。若發(fā)送的數據超出256個(gè),則會(huì )覆蓋前面發(fā)送的數據。

與擦除指令不一樣,頁(yè)寫(xiě)入指令的地址并不要求按256字節對齊,只要確認目標存儲單元是擦除狀態(tài)即可(即被擦除后沒(méi)有被寫(xiě)入過(guò))。所以,若對"地址x"執行頁(yè)寫(xiě)入指令后,發(fā)送了200個(gè)字節數據后終止通訊,下一次再執行頁(yè)寫(xiě)入指令,從"地址(x+200)"開(kāi)始寫(xiě)入200個(gè)字節也是沒(méi)有問(wèn)題的(小于256均可)。只是在實(shí)際應用中由于基本擦除單元是4KB,一般都以扇區為單位進(jìn)行讀寫(xiě),想深入了解,可學(xué)習我們的"FLASH文件系統"相關(guān)的例子。

把頁(yè)寫(xiě)入時(shí)序封裝成函數,其實(shí)現見(jiàn)代碼清單 2411。

代碼清單 2411 FLASH的頁(yè)寫(xiě)入

1 /**

2 * @brief FLASH按頁(yè)寫(xiě)入數據,調用本函數寫(xiě)入數據前需要先擦除扇區

3 * @param pBuffer,要寫(xiě)入數據的指針

4 * @param WriteAddr,寫(xiě)入地址

5 * @param NumByteToWrite,寫(xiě)入數據長(cháng)度,必須小于等于頁(yè)大小

6 * @retval 無(wú)

7 */

8 void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)

9 {

10 /* 發(fā)送FLASH寫(xiě)使能命令 */

11 SPI_FLASH_WriteEnable();

12

13 /* 選擇FLASH: CS低電平 */

14 SPI_FLASH_CS_LOW();

15 /* 寫(xiě)送寫(xiě)指令*/

16 SPI_FLASH_SendByte(W25X_PageProgram);

17 /*發(fā)送寫(xiě)地址的高位*/

18 SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);

19 /*發(fā)送寫(xiě)地址的中位*/

20 SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);

21 /*發(fā)送寫(xiě)地址的低位*/

22 SPI_FLASH_SendByte(WriteAddr & 0xFF);

23

24 if (NumByteToWrite > SPI_FLASH_PerWritePageSize)

25 {

26 NumByteToWrite = SPI_FLASH_PerWritePageSize;

27 FLASH_ERROR("SPI_FLASH_PageWrite too large!");

28 }

29

30 /* 寫(xiě)入數據*/

31 while (NumByteToWrite--)

32 {

33 /* 發(fā)送當前要寫(xiě)入的字節數據 */

34 SPI_FLASH_SendByte(*pBuffer);

35 /* 指向下一字節數據 */

36 pBuffer++;

37 }

38

39 /* 停止信號 FLASH: CS 高電平 */

40 SPI_FLASH_CS_HIGH();

41

42 /* 等待寫(xiě)入完畢*/

43 SPI_FLASH_WaitForWriteEnd();

44 }

這段代碼的內容為:先發(fā)送"寫(xiě)使能"命令,接著(zhù)才開(kāi)始頁(yè)寫(xiě)入時(shí)序,然后發(fā)送指令編碼、地址,再把要寫(xiě)入的數據一個(gè)接一個(gè)地發(fā)送出去,發(fā)送完后結束通訊,檢查FLASH狀態(tài)寄存器,等待FLASH內部寫(xiě)入結束。

不定量數據寫(xiě)入

應用的時(shí)候我們常常要寫(xiě)入不定量的數據,直接調用"頁(yè)寫(xiě)入"函數并不是特別方便,所以我們在它的基礎上編寫(xiě)了"不定量數據寫(xiě)入"的函數,基實(shí)現見(jiàn)代碼清單 2412。

代碼清單 2412不定量數據寫(xiě)入

1 /**

2 * @brief FLASH寫(xiě)入數據,調用本函數寫(xiě)入數據前需要先擦除扇區

3 * @param pBuffer,要寫(xiě)入數據的指針

4 * @param WriteAddr,寫(xiě)入地址

5 * @param NumByteToWrite,寫(xiě)入數據長(cháng)度

6 * @retval 無(wú)

7 */

8 void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)

9 {

10 u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

11

12 /*mod運算求余,若writeAddrSPI_FLASH_PageSize整數倍,運算結果Addr值為0*/

13 Addr = WriteAddr % SPI_FLASH_PageSize;

14

15 /*count個(gè)數據值,剛好可以對齊到頁(yè)地址*/

16 count = SPI_FLASH_PageSize - Addr;

17 /*計算出要寫(xiě)多少整數頁(yè)*/

18 NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;

19 /*mod運算求余,計算出剩余不滿(mǎn)一頁(yè)的字節數*/

20 NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

21

22 /* Addr=0,WriteAddr 剛好按頁(yè)對齊 aligned */

23 if (Addr == 0)

24 {

25 /* NumByteToWrite < SPI_FLASH_PageSize */

26 if (NumOfPage == 0)

27 {

28 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);

29 }

30 else /* NumByteToWrite > SPI_FLASH_PageSize */

31 {

32 /*先把整數頁(yè)都寫(xiě)了*/

33 while (NumOfPage--)

34 {

35 SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);

36 WriteAddr += SPI_FLASH_PageSize;

37 pBuffer += SPI_FLASH_PageSize;

38 }

39

40 /*若有多余的不滿(mǎn)一頁(yè)的數據,把它寫(xiě)完*/

41 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);

42 }

43 }

44 /* 若地址與 SPI_FLASH_PageSize 不對齊 */

45 else

46 {

47 /* NumByteToWrite < SPI_FLASH_PageSize */

48 if (NumOfPage == 0)

49 {

50 /*當前頁(yè)剩余的count個(gè)位置比NumOfSingle小,寫(xiě)不完*/

51 if (NumOfSingle > count)

52 {

53 temp = NumOfSingle - count;

54

55 /*先寫(xiě)滿(mǎn)當前頁(yè)*/

56 SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);

57 WriteAddr += count;

58 pBuffer += count;

59

60 /*再寫(xiě)剩余的數據*/

61 SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);

62 }

63 else /*當前頁(yè)剩余的count個(gè)位置能寫(xiě)完NumOfSingle個(gè)數據*/

64 {

65 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);

66 }

67 }

68 else /* NumByteToWrite > SPI_FLASH_PageSize */

69 {

70 /*地址不對齊多出的count分開(kāi)處理,不加入這個(gè)運算*/

71 NumByteToWrite -= count;

72 NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;

73 NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

74

75 SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);

76 WriteAddr += count;

77 pBuffer += count;

78

79 /*把整數頁(yè)都寫(xiě)了*/

80 while (NumOfPage--)

81 {

82 SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);

83 WriteAddr += SPI_FLASH_PageSize;

84 pBuffer += SPI_FLASH_PageSize;

85 }

86 /*若有多余的不滿(mǎn)一頁(yè)的數據,把它寫(xiě)完*/

87 if (NumOfSingle != 0)

88 {

89 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);

90 }

91 }

92 }

93 }

這段代碼與EEPROM章節中的"快速寫(xiě)入多字節"函數原理是一樣的,運算過(guò)程在此不再贅述。區別是頁(yè)的大小以及實(shí)際數據寫(xiě)入的時(shí)候,使用的是針對FLASH芯片的頁(yè)寫(xiě)入函數,且在實(shí)際調用這個(gè)"不定量數據寫(xiě)入"函數時(shí),還要注意確保目標扇區處于擦除狀態(tài)。

從FLASH讀取數據

相對于寫(xiě)入,FLASH芯片的數據讀取要簡(jiǎn)單得多,使用讀取指令"Read Data"即可,其指令時(shí)序見(jiàn)圖 2414。

2414 SPI FLASH讀取數據時(shí)序

發(fā)送了指令編碼及要讀的起始地址后,FLASH芯片就會(huì )按地址遞增的方式返回存儲矩陣的內容,讀取的數據量沒(méi)有限制,只要沒(méi)有停止通訊,FLASH芯片就會(huì )一直返回數據。代碼實(shí)現見(jiàn)代碼清單 2413。

代碼清單 2413 FLASH讀取數據

1 /**

2 * @brief 讀取FLASH數據

3 * @param pBuffer,存儲讀出數據的指針

4 * @param ReadAddr,讀取地址

5 * @param NumByteToRead,讀取數據長(cháng)度

6 * @retval 無(wú)

7 */

8 void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)

9 {

10 /* 選擇FLASH: CS低電平 */

11 SPI_FLASH_CS_LOW();

12

13 /* 發(fā)送指令 */

14 SPI_FLASH_SendByte(W25X_ReadData);

15

16 /* 發(fā)送地址高位 */

17 SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);

18 /* 發(fā)送地址中位 */

19 SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);

20 /* 發(fā)送地址低位 */

21 SPI_FLASH_SendByte(ReadAddr & 0xFF);

22

23 /* 讀取數據 */

24 while (NumByteToRead--)

25 {

26 /* 讀取一個(gè)字節*/

27 *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);

28 /* 指向下一個(gè)字節緩沖區 */

29 pBuffer++;

30 }

31

32 /* 停止信號 FLASH: CS 高電平 */

33 SPI_FLASH_CS_HIGH();

34 }

由于讀取的數據量沒(méi)有限制,所以發(fā)送讀命令后一直接收NumByteToRead個(gè)數據到結束即可。

3.    main函數

最后我們來(lái)編寫(xiě)main函數,進(jìn)行FLASH芯片讀寫(xiě)校驗,見(jiàn)代碼清單 2414。

代碼清單 2414 main函數

1 /* 獲取緩沖區的長(cháng)度 */

2 #define TxBufferSize1 (countof(TxBuffer1) - 1)

3 #define RxBufferSize1 (countof(TxBuffer1) - 1)

4 #define countof(a) (sizeof(a) / sizeof(*(a)))

5 #define BufferSize (countof(Tx_Buffer)-1)

6

7 #define FLASH_WriteAddress 0x00000

8 #define FLASH_ReadAddress FLASH_WriteAddress

9 #define FLASH_SectorToErase FLASH_WriteAddress

10

11

12 /* 發(fā)送緩沖區初始化 */

13 uint8_t Tx_Buffer[] = "感謝您選用秉火stm32開(kāi)發(fā)板\r\n";

14 uint8_t Rx_Buffer[BufferSize];

15

16 //讀取的ID存儲位置

17 __IO uint32_t DeviceID = 0;

18 __IO uint32_t FlashID = 0;

19 __IO TestStatus TransferStatus1 = FAILED;

20

21 // 函數原型聲明

22 void Delay(__IO uint32_t nCount);

23

24 /*

25 * 函數名:main

26 * 描述:主函數

27 * 輸入:無(wú)

28 * 輸出:無(wú)

29 */

30 int main(void)

31 {

32 LED_GPIO_Config();

33 LED_BLUE;

34

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

36 Debug_USART_Config();

37

38 printf("\r\n這是一個(gè)16M串行flash(W25Q128)實(shí)驗 \r\n");

39

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

41 SPI_FLASH_Init();

42

43 Delay( 200 );

44

45 /* 獲取 SPI Flash ID */

46 FlashID = SPI_FLASH_ReadID();

47

48 /* 檢驗 SPI Flash ID */

49 if (FlashID == sFLASH_ID)

50 {

51 printf("\r\n檢測到SPI FLASH W25Q128 !\r\n");

52

53 /* 擦除將要寫(xiě)入的 SPI FLASH 扇區,FLASH寫(xiě)入前要先擦除 */

54 SPI_FLASH_SectorErase(FLASH_SectorToErase);

55

56 /* 將發(fā)送緩沖區的數據寫(xiě)到flash */

57 SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);

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

59

60 /* 將剛剛寫(xiě)入的數據讀出來(lái)放到接收緩沖區中 */

61 SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);

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

63

64 /* 檢查寫(xiě)入的數據與讀出的數據是否相等 */

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

66

67 if ( PASSED == TransferStatus1 )

68 {

69 LED_GREEN;

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

71 }

72 else

73 {

74 LED_RED;

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

76 }

77 }// if (FlashID == sFLASH_ID)

78 else

79 {

80 LED_RED;

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

82 }

83

84 SPI_Flash_PowerDown();

85 while (1);

86 }

函數中初始化了LED、串口、SPI外設,然后讀取FLASH芯片的ID進(jìn)行校驗,若ID校驗通過(guò)則向FLASH的特定地址寫(xiě)入測試數據,然后再從該地址讀取數據,測試讀寫(xiě)是否正常。

注意:

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

24.4.3 下載驗證

用USB線(xiàn)連接開(kāi)發(fā)板"USB TO UART"接口跟電腦,在電腦端打開(kāi)串口調試助手,把編譯好的程序下載到開(kāi)發(fā)板。在串口調試助手可看到FLASH測試的調試信息。

24.5 每課一問(wèn)

1.    在SPI外設初始化部分,MISO引腳可以設置為輸入模式嗎?為什么?實(shí)際測試現象如何?

2.    嘗試使用FLASH芯片存儲int整型變量,float型浮點(diǎn)變量,編寫(xiě)程序寫(xiě)入數據,并讀出校驗。

3.    如果扇區未經(jīng)擦除就寫(xiě)入,會(huì )有什么后果?請做實(shí)驗驗證。

4.    簡(jiǎn)述FLASH存儲器與EEPROM存儲器的區別。

 

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
st7735s SPI驅動(dòng)顯示圖標
Linux下驅動(dòng)2.4G無(wú)線(xiàn)模塊(NRF24L01) - jammy_lee的日志 - 網(wǎng)...
軟件模擬SPI接口程序代碼(4種模式)
基于A(yíng)T89S51/AT89S52單片機ISP技術(shù)原理及在線(xiàn)編程器的實(shí)現
SPI定義 - 千年一嘆的日志 - 網(wǎng)易博客
【接口時(shí)序】4、SPI總線(xiàn)的原理與Verilog實(shí)現
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久