1 SPI接口簡介
SPI全稱:Serial Peripheral Interface(串行外設接口),是一種同步、高速、全雙工的通信總線。
同步:發送接收端要嚴格同步,一般有同步時鍾線。
高速:理論上,SPI模塊的最大時鍾頻率為外設總線時鍾頻率的1/2(實際情況低於此,需自行調試)。
全雙工:發送的同時可以接收。
1.1 SPI原理簡介
SPI是一種master-slave的總線通信,通常是“一master一slave”或“一master多slave”。
標准的SPI需要四根信號線:
SS(Slave Select):從設備選擇,也稱片選,master通過拉低slave的片選信號選擇slave
SCK(Serial Clock):傳輸時鍾的信號線,時鍾信號由master產生,類似於I2C的SCL
MOSI(Master Out Slave In):master輸出,slave輸入,由master向slave發送數據的通道
MISO(Master In Slave Out):master輸入,slave輸出,由slave向master發送數據的通道
圖中是1個master+3個slave的示意,同一時刻只有一個slave可以與master通信。
SPI的工作基於移位寄存器,工作過程就像一個環形傳送帶,由master逐位將數據放在傳送帶上,並驅動傳送帶將數據傳送到slave,同時slave也會同步地逐位將數據傳送給master。簡單的理解:可以認為是數據交換,master向slave發送多少bit數據,就可以由slave收到多少bit數據。
SPI是事實標准,沒有被任何的國際委員會承認,這樣導致協議在一些地方並沒有規定死,一方面使得協議比較靈活,可由廠商來自行定制;另一方面會造成一些混亂。如:
字長:不同的字長是很常見的,常見8bit或者16bit;
字節序:MSB優先還是LSB優先;
片選:片選高有效或者低有效(常見低有效),有時一master一slave不需要片選信號;
信號線:標准是4根信號線,可能無片選信號,只讀(無MOSI),只寫(無MISO)
時鍾:SCK信號idle時為高還是低
采樣: 第一個跳變沿采樣,還是第二個跳變沿采樣,因為SPI的數據輸入和輸出線獨立,所以允許同時完成數據的輸入和輸出,不同的SPI設備的實現方式不盡相同,主要是數據改變和采集的時間不同,在時鍾信號上沿或下沿采集有不同定義,具體請參考相關器件的datasheet,(常見的是在一個時鍾沿發送數據到MOSI線上,下個時鍾沿從MISO線接收數據)。
以下引用《在ARM Linux下使用GPIO模擬SPI時序詳解》的一段話,說得很好(見參考)
SPI是一個環形的總線結構,在SCLK的控制下,兩個雙向移位寄存器進行數據交換。那么主機和從機在進行交換數據的時候就設計到一個問題:即主機在什么時刻輸出到MOSI上而從機在什么時刻采樣這個數據,或者從機什么時刻輸出到MISO上而主機什么時刻采樣這個數據。同步通信的一個特點就是所有數據的變化和采樣都是伴隨着時鍾沿進行的,也就是說數據總是在時鍾的邊沿附近變化或被采樣,而一個完整的時鍾周期必定包含了一個上升沿和一個下降沿,只是這兩個沿的先后並無規定。又因為數據從產生到它穩定是需要一定的時間,那么如果主機在上升沿輸出數據到MOSI,從機就只能在下降沿去采樣這個數據了。反之,如果一方在下降沿輸出數據,那么另一方就必須在上升沿采樣這個數據。
1.2 SPI的CPOL與CPHA
CPOL:Clock Polarity 決定時鍾空閑狀態電平是高電平還是低電平
CPOL = 1 :時鍾空閑時為高,時鍾低電平有效
CPOL = 0 :時鍾空閑時為低,時鍾高電平有效
CPHA:Clock Phase 決定數據傳輸采樣和移位方式
CPHA = 0 :在時鍾信號SCK的第一個跳變沿采樣
CPHA = 1 :在時鍾信號SCK的第二個跳變沿采樣
這樣便形成了SPI的四種工作模式:
模式 | CPOL | CPHA |
---|---|---|
Mode 0 | 0 | 0 |
Mode 1 | 0 | 1 |
Mode 2 | 1 | 0 |
Mode 3 | 1 | 1 |
常用的模式是Mode0與Mode3。
直接看圖更清楚:
詳細一點描述如下(假設SS低有效):
Mode0 (CPOL = 0, CPHA = 0):
SS拉低,MISO引腳上的數據在第一個SCK沿跳變之前已經上線,SCK上升沿,(采樣MISO數據),准備好要發送的數據,SCK下降沿(發送MOSI),依次發送完一筆數據, SS拉高。 可見是Mode0是在第一個邊沿(上升沿)采樣的。
Mode1 (CPOL = 0, CPHA = 1):
SS拉低,准備好要發送的數據,SCK上升沿,(發送數據到MOSI),MISO准備好數據,SCK下降沿(采樣MISO數據),依次發送完一筆數據,SS拉高。 可見是Mode1是在第二個邊沿(下降沿)采樣的。
Mode2 (CPOL = 1, CPHA = 0):
SS拉低,MISO引腳上的數據在第一個SCK沿跳變之前已經上線,SCK下降沿,(采樣MISO數據),准備好要發送的數據,SCK上升沿(發送MOSI),依次發送完一筆數據, SS拉高。 可見是Mode2是在第一個邊沿(下降沿)采樣的。
Mode3 (CPOL = 1, CPHA = 1):
SS拉低,准備好要發送的數據,SCK下降沿,(發送數據到MOSI),MISO准備好數據,SCK上升沿(采樣MISO數據),依次發送完一筆數據,SS拉高。 可見是Mode3是在第二個邊沿(上升沿)采樣的。
根據datasheet獲取SPI模式:
大多數情況下,SPI從設備是硬件固定的,可以查看相關的硬件datasheet。
SPI slave設備SCL在空閑的時候是高電平還是低電平,決定了CPOL是0還是1;
設備是在上升沿還是下降沿去采樣數據,在定了CPOL的值的前提下,可以推算出CPHA是0還是1。
SCL空閑時電平 | 采樣 | CPHA | Mode |
---|---|---|---|
低電平 | 上升沿 | CPOL = 0, CPHA = 0 | Mode0 |
低電平 | 下降沿 | CPOL = 0, CPHA = 1 | Mode1 |
高電平 | 下降沿 | CPOL = 1, CPHA = 0 | Mode2 |
高電平 | 上升沿 | CPOL = 1, CPHA = 1 | Mode3 |
表述得很啰嗦,但是理解起來很簡單,比如說SCL空閑時電平為高,確定了CPOL = 1,如果
上升沿采樣的話,需要SCL由高到低,然后由低到高,即在SCL第二個邊沿采樣(CPHA=1)
SPI的4個模式,正是由於SCL的空閑狀態及采樣時機沒有規定死導致的,導致一些啰嗦,目前我還想不明白不固定死的原因,希望有知道原理的大佬告知。
1.3 SPI的優缺點
- 在點對點的通信中,SPI接口不需要進行尋址操作,且為全雙工通信,顯得簡單高效。但多個從設備系統中,每個從設備需要獨立的片選信號,硬件上比I2C系統要稍微復雜一些。
- SPI沒有指定的流控制,沒有應答機制確認是否收到數據。
2 軟件代碼
2.1 GPIO模擬SPI
CPOL = 1, CPHA = 1 模式示例代碼:
unsigned char SPI_Send_and_Recv_Byte(unsigned char val)
{
int i, j = 1;
unsigned char res = 0;
SPI_Set_CLK(1); // SCK = 1
delay_1us(10);
for (i = 0; i < 8; i++)
{
SPI_Set_DO(val & 0x01); // LSB優先,MOSI准備數據
val >>= 1;
delay_1us(10);
SPI_Set_CLK(0); // SCK = 0, SCL下降沿輸出數據
delay_1us(10);
if (SPI_Get_DI())
res += j; // MISO收數據
j = j << 1;
SPI_Set_CLK(1); // SCK = 1, SCL上升沿采樣
delay_1us(10);
}
DO_H;
return res;
}
用GD32F103的SPI標准庫實現,其初始化代碼為:
spi_struct_para_init(&spi_init_struct);
/* configure SPI0 parameters */
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 全雙工
spi_init_struct.device_mode = SPI_MASTER; // GD32F103作為master,提供SCLK
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; //8bit模式
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; // CPOL = 1, CPHA = 1
spi_init_struct.nss = SPI_NSS_SOFT;
spi_init_struct.prescale = SPI_PSC_64; // 分頻比
spi_init_struct.endian = SPI_ENDIAN_LSB; // LSB優先
spi_init(SPI0, &spi_init_struct);
spi_enable(SPI0);
上述代碼是等價的。