本節課我們將繼續學習下一個通信協議,SPI。SPI通信和我們剛剛學習過的I2C通信差不多。兩個協議的設計目的都一樣都是實現主控芯片和各種外掛芯片之間的數據交流,有了數據交流的能力,我們的主控芯片就可以掛載并操縱各式各樣的外部芯片,來實現一個功能更加強大的控制系統。
SPI(Serial Peripheral Interface),翻譯而來就是串行外設接口。是由Motorola公司開發的一種通用數據總線。我們知道,I2C可以在消耗最低硬件資源的情況下,實現最多的功能,但其存在的缺點就是由于I2C開漏外加上拉電阻的電路結構,使得通信線高電平的驅動能力比較弱,這就會導致通信線由低電平轉變到高電平的時候,上升沿耗時會比較長,限制了I2C的最大通信速度。所以I2C的標準模式只有100KHz的時鐘頻率,I2C的快速模式也只有400KHz的時鐘頻率。這個速度,相比于SPI而言還是慢很多的。了解完I2C的優勢和缺點,我們就來看一下SPI,在學習之前,簡單概括一下SPI相對于I2C的優缺點。
首先,SPI傳輸更快,SPI協議并沒有嚴格規定最大傳輸速度,這個最大傳輸速度取決于芯片廠商的設計需求;其次,SPI的設計比較簡單粗暴,實現的功能沒有I2C那么多,所以學習起來,SPI還是比I2C簡單很多的;最后,SPI的硬件開銷比較大,通信線的個數比較多,并且通信過程種,經常會有資源浪費的現象。
SPI有四根通信線:SCK(Serial Clock),意思是串行時鐘線;MOSI(Master Output Slave Input),意思是主機輸出從機輸入;MISO(Master Input Slave Output),意思是主機輸入從機輸出;SS(Slave Select),意思是從機選擇。
SPI的基本特征是同步全雙工,首先既然是同步時序,肯定就得有時鐘線了,所以這個SCK引腳就是用來提供時鐘信號的。數據位的輸出和輸入都是在SCK的上升沿或下降沿進行的,這樣,數據位的收發時刻就可以明確的確定,并且同步時序,時鐘快點慢點或者中途暫停一會都是沒問題的,這就是同步時序的好處,那對照I2C總線,這個SCK就相當于I2C的SCL,兩者作用相同。之后,SPI是全雙工的協議,就是數據發送和數據接收單獨各占一條線,發送用發送的線路,接收用接收的線路,兩者互不影響,所以這里MOSI和MISO就是分別用于主機發送和接收的兩條線路。
MOSI線是主機輸出從機輸入,如果是主機接在這條線上,那就是MO,主機輸出;如果是從機接在這條線上,那就是SI,從機輸入。意思就是一條通信線,如果主機接在上面配置為輸出,那從機肯定得配置為輸入,才能接收主機的數據,主機和從機不能同時配置為輸出或輸入,不然就沒辦法通信了。那同理,下面這條MISO,就是主機從從機接收數據的線路,這就是全雙工通信的兩根通信線,這兩根通信線加在一起,就相當于I2C總線的SDA。
SPI支持總線掛載多設備,使用的是一主多從的模型,SPI僅支持一主多從不支持多主機,這一點,SPI從功能上,沒有I2C強大。那I2C實現一主多從的方式是在起始條件之后,主機必須先發送一個字節進行尋址,用來指定我要跟哪個從機進行通信,所以I2C這里,要涉及分配地址和尋址的問題,但是SPI表示,你這太麻煩了,它直接大手一揮,再開辟一條通信線,專門用來指定我要跟哪個從機進行通信,所以這條專門用來指定從機的通信線就是這里的SS,從機選擇線。并且這個SS可能不止一條,SPI的主機表示,我有幾個從機,我就開幾條SS,所有從機一人一根,都別搶,我需要找你的時候,我就控制接到你那一根的SS線,給你低電平,就說明我要找你了,給你高電平,就說明我不跟你玩了。這就是SPI實現一主多從,指定從機的方式。
SPI沒有應答機制的設計,發送數據就發送,接收數據就接收,至于對面是不是存在,SPI是不管的。?
接下來我們來看一下SPI的硬件和軟件規定,首先是硬件電路,下圖就是SPI一個典型的應用電路。我們看一下,左邊這里,是SPI主機,主導整個SPI總線,主機一般都是控制器來做,比如STM32,下面這里SPI從機1、2、3,就是掛載在主機上的從設備了。因為有三個從機,所以SS線需要3根,再加SCK、MOSI、MISO,就是6根通信線。當然SPI的所有通信線都是單端信號,它們的高低電平都是相對GND的電壓差,所以單端信號,所有的設備還需要共地,這里GND的線沒畫出來,但是是必須要接的。然后如果從機沒有獨立供電的話,主機還需要再額外引出電源正極VCC給從機供電。所有SPI設備的SCK、MOSI、MISO分別連在一起;主機另外引出多條SS控制線,分別接到各從機的SS引腳;輸出引腳配置為推挽輸出,輸入引腳配置為浮空或上拉輸入,推挽輸出有很強的驅動能力,這將使得SPI引腳信號的下降沿非常迅速,上升沿也非常迅速,不像I2C那樣下降沿非常迅速但是上升沿比較緩慢了。SPI信號變化的快,那自然它就能達到更高的傳輸速度。
不過SPI還是有一個沖突點的,就是圖中的MISO,在這個引腳上,主機一個是輸入,但是三個從機全都是輸出,如果三個從機都始終是推挽輸出,勢必會導致沖突。所以在SPI協議里,有一條規定,就是當從機的SS引腳為高電平,也就是從機未被選中時,它的MISO引腳必須切換為高阻態,高阻態就相當于引腳斷開,不輸出任何電平,這樣就可以防止一條線有多個輸出,而導致的電平沖突的問題了,在SS為低電平時,MISO才允許變為推挽輸出。
接下來我們來看一下下面的移位示意圖,這個移位示意圖是SPI硬件電路設計的核心。SPI的基本收發電路,就是使用了這樣一個移位的模型。左邊是SPI主機,里面有一個8位的移位寄存器;右邊是SPI從機,里面也有一個8位的移位寄存器。這里移位寄存器有一個時鐘輸入端,因為SPI一般都是高位先行的,所以每來一個時鐘,移位寄存器都會向左進行移位,從機中的移位寄存器也是同理。然后移位寄存器的時鐘源是由主機提供的,這里叫做波特率發生器,它產生的時鐘驅動主機的移位寄存器進行移位,同時這個時鐘也通過SCK引腳進行輸出,接到從機的移位寄存器里。之后,上面移位寄存器的接法是:主機移位寄存器左邊移出去的數據通過MOSI引腳輸入到從機移位寄存器的右邊;從機移位寄存器左邊移出去的數據通過MISO引腳輸入到主機移位寄存器的右邊,形成一個圈。接下來演示一下這個電路如何工作,首先我們規定,波特率發生器時鐘的上升沿,所有移位寄存器向左移動一位,移出去的位放到引腳上,波特率發生器時鐘的下降沿,引腳上的位,采樣輸入到移位寄存器的最低位。接下來,假設主機有個數據10101010要發送到從機,同時,從機有個數據01010101要發送到主機,那我們就可以驅動時鐘,先產生一個上升沿,這時,所有的位都會往左移動一次,從最高位移出去的二進制數就會放到通信線上,實際上是放到了輸出數據寄存器,那么此時MOSI數據是1,所以MOSI的電平就是高電平;MISO的數據是0,所以MISO的電平就是低電平,這就是第一個時鐘上升沿執行的結果,就是把主機和從機中,移位寄存器的最高位,分別放到MOSI和MISO的通信線上,這就是數據的輸出。之后,時鐘繼續運行,上升沿之后,下一個邊沿就是下降沿,在下降沿時,主機和從機內,都會進行數據采樣輸入,也就是MOSI的1,會采樣輸入到從機的最低位,MISO的0,會采樣輸入到主機的最低位,這就是第一個時鐘結束后的現象。那時鐘繼續運行,下一個上升沿,同樣的操作,移位輸出,隨后下降沿,數據采樣輸入,如此循環。8個時鐘后,原來主機里的10101010跑到從機里了,原來從機里的01010101,跑到主機里了,這就實現了主機和從機一個字節的數據交換,實際上SPI的運行過程就是這樣,SPI的數據收發,都是基于字節交換,這個基本單元來進行的。當主機需要發送一個字節并且同時需要接收一個字節時,就可以執行一下字節交換的時序,這樣主機要發送的數據,跑到從機,主機要從從機接收的數據跑到主機,這就完成了發送同時接收的目的。
那你肯定會問,如果我主機只想發送不想接收怎么辦呢?其實很簡單,我們仍然調用交換字節的時序,只是主機接收到的數據我們不看它就行了;如果我主機只想接收不想發送怎么辦呢?同理,我們還是調用交換字節的時序,只是我們會隨便發送一個數據,只要能把從機的數據置換過來就行了,我們讀取置換過來的數據,不就是接收了嗎。這里我們隨便發過去的數據,從機也不會去看它當然這個隨便的數據我們不會真的隨便發,一般在接收的時候,我們會統一發送0x00或0xFF去跟從機換數據。以上就是SPI的基本原理。
總結一下就是,SPI通信的基礎是交換一個字節,有了交換一個字節,就可以實現發送一個字節、接收一個字節和發送同時接收一個字節,這三種功能。可以看出,SPI在只執行發送或者只執行接收的時候會存在一些資源浪費的現象,不過全雙工的通信,本來就會有浪費的情況發生。
接下來我們來看一下SPI的時序,首先是SPI的起始和終止。其中,起始條件是SS從高電平切換到低電平,也就是左邊的圖。SS是低電平有效的,那SS從高變到低,是不是就代表剛選中了某個從機,這就是通信的開始。然后終止條件是SS從低電平切換到高電平,也就是右邊的圖。SS從低電平變到高電平,就是結束了從機的選中狀態,就是通信的結束。在從機的整個選中狀態中,SS要始終保持位低電平。
接下來就是數據傳輸的基本單元了,這個基本單元,就是建立在我們剛才說的移位模型上的,并且這個基本單元什么時候開始移位,是上升沿移位還是下降沿移位,SPI并沒有限定死,給了我們可以配置的選擇,這樣的話,SPI就可以兼容更多的芯片。那在這里,SPI有兩個可以配置的位,分別叫做CPOL(Clock Polarity)時鐘極性和CPHA(Clock Phase)時鐘相位,每一位可以配置為1或0,總共組合起來就有模式0、1、2、3這四種模式。
先看模式1,這個時序的基本功能是交換一個字節,也就是我們剛開始介紹的那個循環。這里CPOL=0,表示空閑狀態時,SCK為低電平,下圖可以看到,在SS未被選中時,SCK默認是低電平的,然后CPHA=1,表示SCK第一個邊沿移出數據,第二個邊沿移入數據。圖中MISO中間的線表示高阻態,SS下降沿之后,從機的MISO被允許開啟輸出,SS上升沿之后,從機的MISO必須置回高阻態。由于CPHA=1,SCK第一個邊沿也就是上升沿時,主機和從機同時移出數據,主機通過MOSI移出最高位,此時MOSI的電平就表示了主機要發送數據的B7;從機通過MISO移出最高位,此時MISO表示從機要發松數據的B7,然后時鐘運行,產生下降沿,此時主機和從機同時移入數據,也就是進行數據采樣。這里主機移出的B7,進入從機移位寄存器的最低位,從機移出的B7,進入主機移位寄存器的最低位,這樣一個時鐘脈沖產生完畢,一個數據為傳輸完畢。接下來就是同樣的過程,上升沿,主機和從機同時輸出當前移位寄存器的最高位,第二次最高位,就是原始數據的B6,然后下降沿,主機和從機移入數據,B6傳輸完成,之后是時鐘繼續運行,數據依次移出、移入、移出、移入,最后一個下降沿,數據B0傳輸完成。至此,主機和從機就完成了一個字節的數據交換。如果主機只想交換一個字節,那這時就可以置SS為高電平,結束通信了。在SS的上升沿,MOSI還可以再變化一次,將MOSI置到一個默認的高電平或低電平,MISO從機必須置回高阻態。那如果主機還想繼續交換字節呢?在此時,主機就不必把SS置回高電平了,直接重復一下,從SCK的第一個上升沿到最后一個下降沿的時序,這樣就可以交換多個字節了。
接下來我們繼續看一下模式0,模式0和模式1的區別就是模式0的CPHA=0,模式1的CPHA=1。兩者在時序上的區別就是模式0的數據移出移入的時機會提前半個時鐘,也就是相位提前了。模式0,CPHA=0,表示SCK第一個邊沿移入數據,第二個邊沿移出數據,模式0在SCK第一個邊沿就要移入數據,但是數據總得先移出,才能移入。所以在模式0的配置下,SCK第一個邊沿之前,就要提前開始移出數據了。或者把它稱作是在第0個邊沿移出,第一個邊沿移入。
看一下時序,首先,SS下降沿開始通信,現在SCK還沒有變化,但是SCK一旦開始變化就要移入數據了,所以趁SCK還沒有變化,SS下降沿時,就要立刻觸發移位輸出,所以這里MOSI和MISO的輸出是對齊到SS的下降沿的。或者說,這里把SS的下降沿也當作時鐘的一部分了。
了解完模式0和模式1,下面的模式2和模式3就非常簡單了其中模式0和模式2的區別,就是模式0的CPOL=0,模式2的CPOL=1,兩者的波形就是SCK的極性取反一下,剩下流程上的東西完全一致
模式1和模式3的區別,也是模式1的CPOL=0,模式3的CPOL=1,兩者的波形,也是SCK的極性取反一下,其他地方沒有變化。這就是這四種模式。
最后提醒一下,這個CPHA表示的是時鐘相位,決定是第一個時鐘采樣移入還是第二個時鐘采樣移入,并不是規定上升沿采樣還是下降沿采樣的。當然,在CPOL確定的情況下,CPHA確實會改變采樣時刻的上升沿和下降沿。比如模式0的時候,是SCK上升沿采樣移入;模式1的時候,是SCK下降沿采樣移入。這個了解一下,CPHA決定是第幾個邊沿采樣,并不能單獨決定是上升沿還是下降沿。然后在這4種模式里,模式0和模式3,都是SCK上升沿采樣;模式1和模式2,都是SCK下降沿采樣。這就是時序基本單元,我們就講完了。
最后就是看幾個完整的SPI時序波形了,以W25Q64的時序為例進行時序波形講解。SPI對字節流功能的規定,不像I2C那樣,I2C的規定一般是有效數據流第一個字節是寄存器地址,之后依次是讀寫的數據,使用的是讀寫寄存器的模型;而在SPI中,通常采用的是指令碼加讀寫數據的模型,這個過程就是SPI起始后,第一個交換發送給從機的數據一般叫做指令碼,在從機中,對應的會定義一個指令集,當我們需要發送什么指令時,就可以在起始后第一個字節發送指令集里面的數據,這樣就能指導從機完成相應的功能了。不同的指令可以有不同的數據個數,有的指令,只需要一個字節的指令碼就可以完成,比如W25Q64的寫使能、寫失能等指令,而有的指令,后面就需要再跟讀寫的數據,比如W25Q64的寫數據、讀數據等。寫數據,指令后面就得跟上我要在哪里寫,我要寫什么;讀數據,指令后面就得跟上我要在哪里讀,我讀到的是什么。這就是指令碼加讀寫數據的模型,在SPI從機的芯片手冊里,都會定義好指令集,什么指令對應什么功能,什么指令后面得跟上什么數據。
首先是SPI發送指令的波形,這個指令的功能是向SS指定的設備,發送指令(0x06),在W25Q64芯片中,0x06代表的是寫使能。在這里,我們使用的是SPI模式0。在空閑狀態時,SS為高電平,SCK為低電平,MOSI和MISO的默認電平沒有嚴格規定。然后SS產生下降沿,時序開始,在SS的下降沿時刻,MOSI和MISO就要開始變換數據了。MOSI由于指令碼最高位仍然是0,所以這里保持低電平不變;MISO,從機現在沒有數據發送給主機,引腳電平沒有變換。實際上W25Q64不需要回傳數據時手冊里規定的是MISO仍然是高阻態,從機并沒有開啟輸出,不過這也沒問題,反正這個數據我們也不要看。這里因為STM32的MISO是上拉輸入,所以這里MISO呈現高電平。之后SCK第一個上升沿進行數據采樣,從機采樣輸入得到0,主機采樣輸入,得到1。之后繼續第二個時鐘,主機數據仍然是0,所以波形仍然沒有變化。然后這樣一位一位地發送、接收、發送、接收,到第6位數據才開始變化,主機要發送數據1,下降沿數據移出,主機將1移出到MOSI,MOSI變為高電平,然后SCK上升沿,數據采樣輸入。在最后一位下降沿,數據變化,MOSI變為0,上升沿,數據采樣,從機接收數據0。SCK低電平是變化的時期,高電平是讀取的時期。時序SCK最后一個上升沿結束,一個字節就交換完畢了。因為寫使能是單獨的指令,不需要跟隨數據,SPI只需要交換一個字節就完事了。所以最后,在SCK下降沿之后,SS置回高電平,結束通信。總結一下就是主機用0x06換來了從機的0xFF,當然實際上從機并沒有輸出,整個時序的功能就是發送指令,指令碼是0x06,從機一比對事先定義好的指令集,發現0x06是寫使能的指令,那從機就會控制硬件,進行寫使能,這樣一個指令從發送到執行就完成了,這就是發送單字節指令的時序。
之后看下一個,我們再看一條指令,下面這條指令是指定地址寫,功能是向SS指定的設備,先發送寫指令,寫指令在指令集中規定是0x02,隨后在指定地址下,寫入指定數據。因為W25Q64芯片有8M字節的存儲空間,一個字節的8位地址肯定不夠,所以這里地址是24位的,分3個字節傳輸。我們來看一下時序,首先SS下降沿,開始時序,這里,MOSI空閑時是高電平,所以在下降沿之后,SCK第一個時鐘之前,可以看到MOSI變換數據,由高電平變為低電平,然后SCK上升沿數據采樣輸入。后面還是一樣,下降沿變換數據,上升沿采樣數據。8個時鐘之后,一個字節交換完成,我們用0x02換來了0xFF,其中發送的0x02是一條指令,代表這是一個寫數據的時序。既然是寫數據的時序,后面必然還要跟著寫的地址和數據了,所以在最后一個下降沿時刻,因為我們后續還要繼續交換字節,所以在數據交換完成后的下一個SCK下降沿,我們要把下一個字節的最高位放到MOSI上,當然下一個字節的最高位仍然是0,所以這里數據沒有變化,之后還是同樣的流程,交換一個字節,第二個字節,我們用0x12,換來了0xFF,根據W25Q64芯片的規定,寫指令之后的字節,定義為地址高位,所以這個0x12就表示發送地址的23-16位,之后發送的是0x34,這個就表示發送地址的15-8位,之后還是交換一個字節,發送的是0x56,這個表示發送地址的7-0位,通過3個字節的交換,24位的地址就發送完畢了。從機收到的24位地址是0x123456。那三位地址結束后,就要發送寫入指定地址的內容了,我們繼續調用交換一個字節,發送數據,這里的波形是0x55,這就表示我要在0x123456地址下,寫入0x55這個數據。最后,如果只想寫入一個數據的話,就可以SS置高電平,結束通信了。當然這里也可以繼續發送數據,SPI里也會有和I2C一樣的地址指針,每讀寫一個字節,地址指針自動加1。如果發送一個字節之后不終止,繼續發送的字節就會依次寫入到后續的存儲空間里,這樣就可以實現從指定地址開始,寫入多個字節了,這就是SPI寫入的時序。由于SPI沒有應答機制,所以交換一個字節后,就立刻交換下一個字節就行了。由于整個流程,我們只需要發送的功能,并沒有接收的需求,所以MISO這條接收的線路就始終處于掛機的狀態,我們并沒有用到。
接著下一個時序,下面這幅圖是指定地址讀的時序,功能是向SS指定的設備,先發送讀指令,這里芯片定義0x03為讀指令,隨后在指定地址下讀取從機數據。我們看一下時序,起始之后第一個字節,主機發送指令0x03,代表我要讀數據了,之后還是一樣,主機再依次交換三個字節,分別是0x12、0x34、0x56,組合到一起就是0x123456,代表24位地址。最后這個地方就是關鍵點了,因為我們是讀取數據,指定地址之后,顯然我們要開始接收數據,所以這里3個字節地址交換完后,我們要把從機的數據搞過來。我們隨便給主機一個數據,當然一般給FF就行了,從機就會把0x123456地址下的數據通過MISO發給主機,可以看到,這樣的波形就表示指定地址下的數據是0x55,這樣主機就實現了指定地址讀一個字節的目的,如果繼續進行數據交換,那么從機內部的地址指針自動加1,從機就會繼續把指定地址下一個位置的數據發過來,這樣依次進行,就可以實現指定地址接收多個字節的目的了。然后最后,數據傳輸完畢,SS置回高電平,時序結束。