娇小w搡bbbb搡bbb,《第一次の人妻》,中国成熟妇女毛茸茸,边啃奶头边躁狠狠躁视频免费观看

歷史上的今天

今天是:2024年10月21日(星期一)

正在發(fā)生

2018年10月21日 | STM32學習之:SPI讀寫串行Flash

發(fā)布者:oplkjjj 來源: eefocus關鍵字:STM32  SPI讀寫串  行Flash 手機看文章 掃描二維碼
隨時隨地手機看文章

24.1 SPI協議簡介


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


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


24.1.1 SPI物理層


SPI通訊設備之間的常用連接方式見圖 241。

圖 241 常見的SPI通訊系統(tǒng)


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


(1)     ( 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):主設備輸出/從設備輸入引腳。主機的數據從這條信號線輸出,從機由這條信號線讀入主機發(fā)送的數據,即這條線上數據的方向為主機到從機。


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


24.1.2 協議層


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


1.    SPI基本通訊過程


先看看SPI通訊的通訊時序,見圖 242。

圖 242 SPI通訊時序


這是一個主機的通訊時序。NSS、SCK、MOSI信號都由主機控制產生,而MISO的信號由從機產生,主機通過該信號線讀取從機的數據。MOSI與MISO的信號只在NSS為低電平的時候才有效,在SCK的每個時鐘周期MOSI和MISO傳輸一位數據。


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


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


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


3.    數據有效性


SPI使用MOSI及MISO信號線來傳輸數據,使用SCK信號線進行數據同步。MOSI及MISO數據線在SCK的每個時鐘周期傳輸一位數據,且數據輸入輸出是同時進行的。數據傳輸時,MSB先行或LSB先行并沒有作硬性規(guī)定,但要保證兩個SPI通訊設備之間使用同樣的協定,一般都會采用圖 242中的MSB先行模式。


觀察圖中的標號處,MOSI及MISO的數據在SCK的上升沿期間變化輸出,在SCK的下降沿時被采樣。即在SCK的下降沿時刻,MOSI及MISO的數據有效,高電平時表示數據"1",為低電平時表示數據"0"。在其它時刻,數據無效,MOSI及MISO為下一次表示數據做準備。


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


4.    CPOL/CPHA及通訊模式


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


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


時鐘相位CPHA是指數據的采樣的時刻,當CPHA=0時,MOSI或MISO數據線上的信號將會在SCK時鐘線的"奇數邊沿"被采樣。當CPHA=1時,數據線在SCK的"偶數邊沿"采樣。見圖 243及圖 244。

圖 243 CPHA=0時的SPI通訊模式


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


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


類似地,當CPHA=1時,不受CPOL的影響,數據信號在SCK的偶數邊沿被采樣,見圖 244。

圖 244 CPHA=1時的SPI通訊模式


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


表 241 SPI的四種模式


SPI模式

CPOL

CPHA

空閑時SCK時鐘

采樣時刻

0

0

0

低電平

奇數邊沿

1

0

1

低電平

偶數邊沿

2

1

0

高電平

奇數邊沿

3

1

1

高電平

偶數邊沿

24.2 STM32的SPI特性及架構


與I2C外設一樣,STM32芯片也集成了專門用于SPI協議通訊的外設。


24.2.1 STM32的SPI外設簡介


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


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


24.2.2 STM32的SPI架構剖析

圖 245 SPI架構圖


1.    通訊引腳


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


其中SPI1、SPI4、SPI5、SPI6是APB2上的設備,最高通信速率達45Mbtis/s,SPI2、SPI3是APB1上的設備,最高通信速率為22.5Mbits/s。除了通訊速率,在其它功能上沒有差異。


2.    時鐘控制邏輯


SCK線的時鐘信號,由波特率發(fā)生器根據"控制寄存器CR1"中的BR[0:2]位控制,該位是對fpclk時鐘的分頻因子,對fpclk的分頻結果就是SCK引腳的輸出時鐘頻率,計算方法見表 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總線頻率,APB1為fpclk1,APB2為fpckl2。


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


3.    數據控制邏輯


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


4.    整體控制邏輯


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


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


24.2.3 通訊過程


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


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

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


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


(1)    控制NSS信號線,產生起始信號(圖中沒有畫出);


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


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


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


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


假如我們使能了TXE或RXNE中斷,TXE或RXNE置1時會產生SPI中斷信號,進入同一個中斷服務函數,到SPI中斷服務程序后,可通過檢查寄存器位來了解是哪一個事件,再分別進行處理。也可以使用DMA方式來收發(fā)"數據寄存器DR"中的數據。


24.3 SPI初始化結構體詳解


跟其它外設一樣,STM32標準庫提供了SPI初始化結構體及初始化函數來配置SPI外設。初始化結構體及函數定義在庫文件"stm32f4xx_spi.h"及"stm32f4xx_spi.c"中,編程時我們可以結合這兩個文件內的注釋使用或參考庫幫助文檔。了解初始化結構體后我們就能對SPI外設運用自如了,見代碼清單 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的數據幀長度,可選8/16位 */


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


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


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


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


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


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


12 } SPI_InitTypeDef;


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


(1)    SPI_Direction


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


(2)    SPI_Mode


本成員設置SPI工作在主機模式(SPI_Mode_Master)或從機模式(SPI_Mode_Slave ),這兩個模式的最大區(qū)別為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的說明參考前面"通訊模式"小節(jié)。


時鐘極性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外設。


24.4 SPI—讀寫串行FLASH實驗


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


本小節(jié)以一種使用SPI通訊的串行FLASH存儲芯片的讀寫實驗為大家講解STM32的SPI使用方法。實驗中STM32的SPI外設采用主模式,通過查詢事件的方式來確保正常通訊。


24.4.1 硬件設計

圖 247 SPI串行FLASH硬件連接圖


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


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


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


24.4.2 軟件設計


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


1.    編程要點


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


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


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


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


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


(12)    編寫測試程序,對讀寫數據進行校驗。


2.    代碼分析


SPI硬件相關宏定義


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


代碼清單 242 SPI硬件配置相關的宏


1 //SPI號及時鐘初始化函數


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號、引腳號、引腳源以及復用功能映射都以宏封裝起來,并且定義了控制CS(NSS)引腳輸出電平的宏,以便配置產生起始和停止信號時使用。


初始化SPI的 GPIO


利用上面的宏,編寫SPI的初始化函數,見代碼清單 243。


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


 1 

2 /**


3 * @brief SPI_FLASH初始化


4 * @param 無


5 * @retval 無


6 */


7 void SPI_FLASH_Init(void)


8 {


9 GPIO_InitTypeDef GPIO_InitStructure;


10


11 /* 使能 FLASH_SPI 及 GPIO 時鐘 */


12 /*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO,


13 SPI_FLASH_SPI_MISO_GPIO和 SPI_FLASH_SPI_SCK_GPIO 時鐘使能 */


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 時鐘使能 */


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來使能SPI引腳使用的GPIO端口時鐘,調用時使用"|"操作同時配置多個引腳。調用宏FLASH_SPI_CLK_INIT使能SPI外設時鐘(該宏封裝了APB時鐘使能的庫函數)。


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


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


配置SPI的模式


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


代碼清單 244 配置SPI模式


1 /**


2 * @brief SPI_FLASH引腳初始化


3 * @param 無


4 * @retval 無


5 */


6 void SPI_FLASH_Init(void)


7 {


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


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 }


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


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


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


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


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


1 #define Dummy_Byte 0xFF


2 /**


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


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


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


6 */


7 u8 SPI_FLASH_SendByte(u8 byte)


8 {


9 SPITimeout = SPIT_FLAG_TIMEOUT;


10


11 /* 等待發(fā)送緩沖區(qū)為空,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 /* 寫入數據寄存器,把要寫入的數據寫入發(fā)送緩沖區(qū) */


18 SPI_I2S_SendData(FLASH_SPI, byte);


19


20 SPITimeout = SPIT_FLAG_TIMEOUT;


21


22 /* 等待接收緩沖區(qū)非空,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 /* 讀取數據寄存器,獲取接收緩沖區(qū)數據 */


29 return SPI_I2S_ReceiveData(FLASH_SPI);


30 }


31


32 /**


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


34 * @param 無


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


36 */


37 u8 SPI_FLASH_ReadByte(void)


38 {


39 return (SPI_FLASH_SendByte(Dummy_Byte));


40 }


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


SPI_FLASH_SendByte函數實現了前面講解的"SPI通訊過程":


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


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


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


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


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


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


控制FLASH的指令


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


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


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


在FLSAH芯片內部,存儲有固定的廠商編號(M7-M0)和不同類型FLASH芯片獨有的編號(ID15-ID0),見表 245。


表 245 FLASH數據手冊的設備ID說明


FLASH型號

廠商號(M7-M0)

FLASH型號(ID15-ID0)

W25Q64

EF h

4017 h

W25Q128

EF h

4018 h


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


此處我們以該指令為例,配合其指令時序圖進行講解,見圖 248。



圖 248 FLASH讀ID指令"JEDEC ID"的時序(摘自規(guī)格書《W25Q128》)


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


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


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


定義FLASH指令編碼表


為了方便使用,我們把FLASH芯片的常用指令編碼使用宏來封裝起來,后面需要發(fā)送指令編碼的時候我們直接使用這些宏即可,見代碼清單 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"指令的時序,我們把讀取FLASH ID的過程編寫成一個函數,見代碼清單 247。


代碼清單 247 讀取FLASH芯片ID


1 /**


2 * @brief 讀取FLASH ID


3 * @param 無


4 * @retval FLASH ID


5 */


6 u32 SPI_FLASH_ReadID(void)


7 {


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


9


10 /* 開始通訊:CS低電平 */


11 SPI_FLASH_CS_LOW();


12


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


14 SPI_FLASH_SendByte(W25X_JedecDeviceID);


15


16 /* 讀取一個字節(jié)數據 */


17 Temp0 = SPI_FLASH_SendByte(Dummy_Byte);


18


19 /* 讀取一個字節(jié)數據 */


20 Temp1 = SPI_FLASH_SendByte(Dummy_Byte);


21


22 /* 讀取一個字節(jié)數據 */


23 Temp2 = SPI_FLASH_SendByte(Dummy_Byte);


24


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


26 SPI_FLASH_CS_HIGH();


27


28 /*把數據組合起來,作為函數的返回值*/


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


30


31 return Temp;


32 }


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


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


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


代碼清單 248 寫使能命令


1 /**


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


3 * @param none


4 * @retval none


5 */


6 void SPI_FLASH_WriteEnable(void)


7 {


8 /* 通訊開始:CS低 */


9 SPI_FLASH_CS_LOW();


10


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


12 SPI_FLASH_SendByte(W25X_WriteEnable);


13


14 /*通訊結束:CS高 */


15 SPI_FLASH_CS_HIGH();


16 }


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

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


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


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

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


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


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


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


2 #define WIP_Flag 0x01


3


4 /**


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


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); /* 正在寫入標志 */


31


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


33 SPI_FLASH_CS_HIGH();


34 }


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


FLASH扇區(qū)擦除


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


通常,對存儲矩陣擦除的基本操作單位都是多個字節(jié)進行,如本例子中的FLASH芯片支持"扇區(qū)擦除"、"塊擦除"以及"整片擦除"。


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


圖 2411 FLASH芯片的存儲矩陣


使用扇區(qū)擦除指令"Sector Erase"可控制FLASH芯片開始擦寫,其指令時序見圖 2414。

圖 2412 扇區(qū)擦除時序


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


代碼清單 2410 擦除扇區(qū)


1 /**


2 * @brief 擦除FLASH扇區(qū)


3 * @param SectorAddr:要擦除的扇區(qū)地址


4 * @retval 無


5 */


6 void SPI_FLASH_SectorErase(u32 SectorAddr)


7 {


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


9 SPI_FLASH_WriteEnable();


10 SPI_FLASH_WaitForWriteEnd();


11 /* 擦除扇區(qū) */


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


13 SPI_FLASH_CS_LOW();


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


15 SPI_FLASH_SendByte(W25X_SectorErase);


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


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


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


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


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


21 SPI_FLASH_SendByte(SectorAddr & 0xFF);


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


23 SPI_FLASH_CS_HIGH();


24 /* 等待擦除完畢*/


25 SPI_FLASH_WaitForWriteEnd();


26 }


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


FLASH的頁寫入


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

圖 2413 FLASH芯片頁寫入


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


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


把頁寫入時序封裝成函數,其實現見代碼清單 2411。


代碼清單 2411 FLASH的頁寫入


1 /**


2 * @brief 對FLASH按頁寫入數據,調用本函數寫入數據前需要先擦除扇區(qū)


3 * @param pBuffer,要寫入數據的指針


4 * @param WriteAddr,寫入地址


5 * @param NumByteToWrite,寫入數據長度,必須小于等于頁大小


6 * @retval 無


7 */


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


9 {


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


11 SPI_FLASH_WriteEnable();


12


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


14 SPI_FLASH_CS_LOW();


15 /* 寫送寫指令*/


16 SPI_FLASH_SendByte(W25X_PageProgram);


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


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


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


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


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


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 /* 寫入數據*/


31 while (NumByteToWrite--)


32 {


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


34 SPI_FLASH_SendByte(*pBuffer);


35 /* 指向下一字節(jié)數據 */


36 pBuffer++;


37 }


38


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


40 SPI_FLASH_CS_HIGH();


41


42 /* 等待寫入完畢*/


43 SPI_FLASH_WaitForWriteEnd();


44 }


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


不定量數據寫入


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


代碼清單 2412不定量數據寫入


1 /**


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


3 * @param pBuffer,要寫入數據的指針


4 * @param WriteAddr,寫入地址


5 * @param NumByteToWrite,寫入數據長度


6 * @retval 無


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運算求余,若writeAddr是SPI_FLASH_PageSize整數倍,運算結果Addr值為0*/


13 Addr = WriteAddr % SPI_FLASH_PageSize;


14


15 /*差count個數據值,剛好可以對齊到頁地址*/


16 count = SPI_FLASH_PageSize - Addr;


17 /*計算出要寫多少整數頁*/


18 NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;


19 /*mod運算求余,計算出剩余不滿一頁的字節(jié)數*/


20 NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;


21


22 /* Addr=0,則WriteAddr 剛好按頁對齊 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 /*先把整數頁都寫了*/


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 /*若有多余的不滿一頁的數據,把它寫完*/


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 /*當前頁剩余的count個位置比NumOfSingle小,寫不完*/


51 if (NumOfSingle > count)


52 {


53 temp = NumOfSingle - count;


54


55 /*先寫滿當前頁*/


56 SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);


57 WriteAddr += count;


58 pBuffer += count;


59


60 /*再寫剩余的數據*/


61 SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);


62 }


63 else /*當前頁剩余的count個位置能寫完NumOfSingle個數據*/


64 {


65 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);


66 }


67 }


68 else /* NumByteToWrite > SPI_FLASH_PageSize */


69 {


70 /*地址不對齊多出的count分開處理,不加入這個運算*/


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 /*把整數頁都寫了*/


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 /*若有多余的不滿一頁的數據,把它寫完*/


87 if (NumOfSingle != 0)


88 {


89 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);


90 }


91 }


92 }


93 }


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


從FLASH讀取數據


相對于寫入,FLASH芯片的數據讀取要簡單得多,使用讀取指令"Read Data"即可,其指令時序見圖 2414。

圖 2414 SPI FLASH讀取數據時序


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


代碼清單 2413 從FLASH讀取數據


1 /**


2 * @brief 讀取FLASH數據


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


4 * @param ReadAddr,讀取地址


5 * @param NumByteToRead,讀取數據長度


6 * @retval 無


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 /* 讀取一個字節(jié)*/


27 *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);


28 /* 指向下一個字節(jié)緩沖區(qū) */


29 pBuffer++;


30 }


31


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


33 SPI_FLASH_CS_HIGH();


34 }


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


3.    main函數


最后我們來編寫main函數,進行FLASH芯片讀寫校驗,見代碼清單 2414。


代碼清單 2414 main函數


1 /* 獲取緩沖區(qū)的長度 */


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ā)送緩沖區(qū)初始化 */


13 uint8_t Tx_Buffer[] = "感謝您選用秉火stm32開發(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 * 輸入:無


28 * 輸出:無


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這是一個16M串行flash(W25Q128)實驗 \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 /* 擦除將要寫入的 SPI FLASH 扇區(qū),FLASH寫入前要先擦除 */


54 SPI_FLASH_SectorErase(FLASH_SectorToErase);


55


56 /* 將發(fā)送緩沖區(qū)的數據寫到flash中 */


57 SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);


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


59


60 /* 將剛剛寫入的數據讀出來放到接收緩沖區(qū)中 */


61 SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);


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


63


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


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進行校驗,若ID校驗通過則向FLASH的特定地址寫入測試數據,然后再從該地址讀取數據,測試讀寫是否正常。


注意:


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


24.4.3 下載驗證


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


24.5 每課一問


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


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


3.    如果扇區(qū)未經擦除就寫入,會有什么后果?請做實驗驗證。


4.    簡述FLASH存儲器與EEPROM存儲器的區(qū)別。



關鍵字:STM32  SPI讀寫串  行Flash 引用地址:STM32學習之:SPI讀寫串行Flash

上一篇:STM32學習之:DMA詳解
下一篇:STM32學習之:IAR中確認某段代碼的執(zhí)行時間

推薦閱讀

對于ARM的應用開發(fā)主要有兩種方式:一種是直接在ARM芯片上進行應用開發(fā),不采用操作系統(tǒng),也稱為裸機編程,這種開發(fā)方式主要應用于一些低端的ARM芯片上,其開發(fā)過程非常類似單片機,這里不多敘述。還有一種是在ARM芯片上運行操作系統(tǒng),對于硬件的操作需要編寫相應的驅動程序,應用開發(fā)則是基于操作系統(tǒng)的,這種方式的嵌入式應用開發(fā)與單片機開發(fā)差異...
工信部將《道路機動車輛生產企業(yè)及產品公告》擬發(fā)布的新增車輛生產企業(yè)及已準入企業(yè)變更信息情況予以公示,特斯拉(上海)有限公司算是正式在程序上進入預備狀態(tài)。根據特斯拉的規(guī)劃,上海的新工廠今年運營以后,最初的生產能力約為每年 15 萬臺,經過擴大投資以后產能可以擴大到每年 50 萬臺。 出自《從特斯拉、LG 化學中國工廠看供應鏈確定性 》P6...
開關穩(wěn)壓器使用占空比來實現電壓或電流反饋控制。占空比是指導通時間(TON)與整個周期時長(關斷時間(TOFF)加上導通時間)之比,定義了輸入電壓和輸出電壓之間的簡單關系。更準確的計算可能還需要考慮其他因素,但在以下這些說明中,這些并不是決定性因素。開關穩(wěn)壓器的占空比由各自的開關穩(wěn)壓器拓撲決定。降壓型(降壓)轉換器具有占空比D,D = 輸出電壓...
廣告摘要聲明廣告【文/石頭】10月20日消息,智能固廢分選裝備提供商弓葉科技連續(xù)完成天使輪和Pre-A輪億元級融資。其中,天使輪由華創(chuàng)資本投資,Pre-A輪由經緯創(chuàng)投獨家投資。弓葉科技表示,本輪融資后,公司將會研發(fā)生產新一代性價比更高的智能固廢分選裝備。據悉,弓葉科技在工業(yè)機器人和智能裝備行業(yè)已深耕十五年,積累了豐富的智能裝備研發(fā)生產經驗。在...

史海拾趣

問答坊 | AI 解惑

求一個AC200V轉DC24V的開關電源(直流24V有正、負)

求一個AC200V轉DC24V的開關電源, 即:輸入:L         輸出:+24V                  N              ...…

查看全部問答∨

有關NandFlash分區(qū)問題?--求助!

剛剛自學嵌入式,很多問題沒搞清楚,請路過的嵌入式前輩幫幫忙!       在內核移植中,為什么要對NandFlash分區(qū),不是在Vivi移植中已分區(qū),在Vivi階段不是把內核映像和文件系統(tǒng)映像都加載到RAM中?內核在啟動完后應該沒有對Nan ...…

查看全部問答∨

STM32F10x系列MCU

本公司代理銷售ST的STM32系列MCU 公司地址:廣東深圳市福田區(qū)南園路70號上田大廈4D 公司官網:www.upsd8051.com 聯系方式:MSN:lg_0755@hotmail.com,QQ:45293156;           TEL:0755-83248843,E-Mail:hubin@u ...…

查看全部問答∨

用處圖像處理的DSP有哪些

大家知道一般做圖像處理時用的DSP芯片有哪些嗎?希望大家不吝賜教哦,呵呵…

查看全部問答∨

WinCE6 下驅動問題

WinCE5.0下的流設備驅動就是一個用戶態(tài)的DLL,我們寫驅動只需要寫一個動態(tài)庫,輸出他需要的函數就可以了。   CE6.0的體系結構據說變化了不少,請問CE6下的驅動也可以這樣寫嗎? …

查看全部問答∨

請教高手過采樣的問題?

    我要用到兩個個16位的ADC,對速度沒有要求,但是要求保證精度。看了stm32的手冊,上邊講是應用過采樣技術將提高ADC的精度。感覺很不錯,這樣我就能省兩個ADC的錢了,降低了不少成本。    我現在 ...…

查看全部問答∨

請問用IAR好還是用KEIL好?

                                 一直以來都是用KEIL,看到壇子里很多人都是用IAR開發(fā)的,故產生這個疑問…

查看全部問答∨

【TI原創(chuàng)】軟件解碼正交編碼器

正交編碼器,旋轉編碼器的一種(增量式編碼器),用來確定機械行程的位移量與方向。通過監(jiān)控脈沖數目和兩個脈沖的相對相位,可以跟蹤旋轉的位置,旋轉 和速度。比如電機轉速。LM3S8962包含兩個正交編碼器接口,這對于電機驅動與監(jiān)控足夠,不過編碼 ...…

查看全部問答∨
小廣播
設計資源 培訓 開發(fā)板 精華推薦

最新單片機文章
何立民專欄 單片機及嵌入式寶典

北京航空航天大學教授,20余年來致力于單片機與嵌入式系統(tǒng)推廣工作。

 
EEWorld訂閱號

 
EEWorld服務號

 
汽車開發(fā)圈

 
機器人開發(fā)圈

電子工程世界版權所有 京ICP證060456號 京ICP備10001474號-1 電信業(yè)務審批[2006]字第258號函 京公網安備 11010802033920號 Copyright ? 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
主站蜘蛛池模板: 长阳| 田林县| 房产| 静乐县| 峨眉山市| 东源县| 鹰潭市| 海林市| 当涂县| 正定县| 临沧市| 施甸县| 体育| 宜兴市| 辰溪县| 吉首市| 清丰县| 思南县| 新绛县| 固镇县| 高安市| 通城县| 梅州市| 越西县| 泸定县| 伊吾县| 公安县| 沂水县| 辉县市| 胶南市| 红桥区| 金湖县| 玛沁县| 宾川县| 新平| 库车县| 白河县| 呈贡县| 金川县| 常德市| 金华市|