前面的話
前天一位讀者私信說我分享的文章誤導了他,然後就取消關注了,以至於我想回復他是哪裏寫的不對或是哪些寫的不好時,也無法回覆了,雖然有些許收到打擊,但還是會堅持學習,總結和分享的,道阻且長,也難免會有錯誤和紕漏;
目錄
前面的話
目錄
先說串口
SPI通訊協議
SPI特性
模式編號
多從機模式
優缺點
編程實現
先說串口
之前寫過一篇UART,通用串行異步通訊協議,感興趣可以參考一下《我打賭!你還不會UART》;因爲UART沒有時鐘信號,無法控制何時發送數據,也無法保證雙發按照完全相同的速度接收數據。因此,雙方以不同的速度進行數據接收和發送,就會出現問題。
如果要解決這個問題,UART爲每個字節添加額外的起始位和停止位,以幫助接收器在數據到達時進行同步;
雙方還必須事先就傳輸速度達成共識(設置相同的波特率,例如每秒9600位)。
傳輸速率如果有微小差異不是問題,因爲接收器會在每個字節的開頭重新同步。相應的協議如下圖所示;
串口傳輸的過程如果您注意到上圖中的
11001010
不等於0x53,這是一個細節。串口協議通常會首先發送最低有效位,因此最小位在最左邊LSB
。低四位字節實際上是0011 = 0x3
,高四位字節是0101 = 0x5
。
異步串行工作得很好,但是在每個字節發送的時候都需要額外的起始位和停止位以及在發送和接收數據所需的複雜硬件方面都有很多開銷。
不難發現,如果接收端和發送端設置的速度都不一致,那麼接收到的數據將是垃圾(亂碼)。
下面開始講一下SPI協議,會有哪些優點。
SPI通訊協議
於是我們想有沒有更好一點的串行通訊方式;相比較於UART
,SPI
的工作方式略有不同。
SPI
是一個同步的數據總線,也就是說它是用單獨的數據線和一個單獨的時鐘信號來保證發送端和接收端的完美同步。
時鐘是一個振盪信號,它告訴接收端在確切的時機對數據線上的信號進行採樣。
產生時鐘的一側稱爲主機,另一側稱爲從機。總是只有一個主機(一般來說可以是微控制器/MCU),但是可以有多個從機(後面詳細介紹);
數據的採集時機可能是時鐘信號的上升沿(從低到高)或下降沿(從高到低)。
具體要看對SPI的配置;
整體的傳輸大概可以分爲以下幾個過程:
主機先將
NSS
信號拉低,這樣保證開始接收數據;當接收端檢測到時鐘的邊沿信號時,它將立即讀取數據線上的信號,這樣就得到了一位數據(1
bit
);由於時鐘是隨數據一起發送的,因此指定數據的傳輸速度並不重要,儘管設備將具有可以運行的最高速度(稍後我們將討論選擇合適的時鐘邊沿和速度)。
主機發送到從機時:主機產生相應的時鐘信號,然後數據一位一位地將從
MOSI
信號線上進行發送到從機;主機接收從機數據:如果從機需要將數據發送回主機,則主機將繼續生成預定數量的時鐘信號,並且從機會將數據通過
MISO
信號線發送;
具體如下圖所示;
SPI的時序注意,SPI是“全雙工”(具有單獨的發送和接收線路),因此可以在同一時間發送和接收數據,另外SPI的接收硬件可以是一個簡單的移位寄存器。這比異步串行通信所需的完整UART要簡單得多,並且更加便宜;
SPI特性
SPI總線包括4條邏輯線,定義如下:
MISO:
Master input slave output
主機輸入,從機輸出(數據來自從機);MOSI:
Master output slave input
主機輸出,從機輸入(數據來自主機);SCLK :
Serial Clock
串行時鐘信號,由主機產生髮送給從機;SS:
Slave Select
片選信號,由主機發送,以控制與哪個從機通信,通常是低電平有效信號。
其他製造商可能會遵循其他命名規則,但是最終他們指的相同的含義。以下是一些常用術語;
MISO也可以是
SIMO
,DOUT
,DO
,SDO
或SO
(在主機端);MOSI也可以是
SOMI
,DIN
,DI
,SDI
或SI
(在主機端);NSS也可以是
CE
,CS
或SSEL
;SCLK也可以是
SCK
;
本文將按照以下命名進行講解[MISO, MOSI, SCK,NSS]
下圖顯示了單個主機和單個從機之間的典型SPI連接。
主從連接時鐘頻率
SPI總線上的主機必須在通信開始時候配置並生成相應的時鐘信號。在每個SPI時鐘週期內,都會發生全雙工數據傳輸。
主機在MOSI
線上發送一位數據,從機讀取它,而從機在MISO
線上發送一位數據,主機讀取它。
就算只進行單向的數據傳輸,也要保持這樣的順序。這就意味着無論接收任何數據,必須實際發送一些東西!在這種情況下,我們稱其爲虛擬數據;
從理論上講,只要實際可行,時鐘速率就可以是您想要的任何速率,當然這個速率受限於每個系統能提供多大的系統時鐘頻率,以及最大的SPI傳輸速率。
時鐘極性 CKP/Clock Polarity
除了配置串行時鐘速率(頻率)外,SPI主設備還需要配置時鐘極性。
根據硬件製造商的命名規則不同,時鐘極性通常寫爲CKP或CPOL。時鐘極性和相位共同決定讀取數據的方式,比如信號上升沿讀取數據還是信號下降沿讀取數據;
CKP可以配置爲1或0。這意味着您可以根據需要將時鐘的默認狀態(IDLE)設置爲高或低。極性反轉可以通過簡單的邏輯逆變器實現。您必須參考設備的數據手冊才能正確設置CKP和CKE。
CKP = 0
:時鐘空閒IDLE
爲低電平0
;CKP = 1
:時鐘空閒IDLE
爲高電平1
;
時鐘相位 CKE /Clock Phase (Edge)
除配置串行時鐘速率和極性外,SPI主設備還應配置時鐘相位(或邊沿)。根據硬件製造商的不同,時鐘相位通常寫爲CKE或CPHA;
顧名思義,時鐘相位/邊沿,也就是採集數據時是在時鐘信號的具體相位或者邊沿;
CKE = 0
:在時鐘信號SCK
的第一個跳變沿採樣;CKE = 1
:在時鐘信號SCK
的第二個跳變沿採樣;
時鐘配置總結
綜上幾種情況,下圖總結了所有時鐘配置組合,並突出顯示了實際採樣數據的時刻;
其中黑色線爲採樣數據的時刻;
藍色線爲SCK時鐘信號;
具體如下圖所示;
模式編號
SPI的時鐘極性和相位的配置通常稱爲 SPI模式,所有可能的模式都遵循以下約定;具體如下表所示;
SPI Mode | CPOL | CPHA |
---|---|---|
0 [00] | 0 | 0 |
1 [01] | 0 | 1 |
2 [10] | 1 | 0 |
3 [11] | 1 | 1 |
除此之外,我們還應該仔細檢查微控制器數據手冊中包含的模式表,以確保一切正常。
多從機模式
前面說到SPI總線必須有一個主機,可以有多個從機,那麼具體連接到SPI總線的方法有以下兩種:
第一種方法:多NSS
通常,每個從機都需要一條單獨的SS線。
如果要和特定的從機進行通訊,可以將相應的
NSS
信號線拉低,並保持其他NSS
信號線的狀態爲高電平;如果同時將兩個NSS
信號線拉低,則可能會出現亂碼,因爲從機可能都試圖在同一條MISO
線上傳輸數據,最終導致接收數據亂碼。
具體連接方式如下圖所示;
多NSS連接第二種方法:菊花鏈
在數字通信世界中,在設備信號(總線信號或中斷信號)以串行的方式從一 個設備依次傳到下一個設備,不斷循環直到數據到達目標設備的方式被稱爲菊花鏈。
菊花鏈的最大缺點是因爲是信號串行傳輸,所以一旦數據鏈路中的某設備發生故障的時候,它下面優先級較低的設備就不可能得到服務了;
另一方面,距離主機越遠的從機,獲得服務的優先級越低,所以需要安排好從機的優先級,並且設置總線檢測器,如果某個從機超時,則對該從機進行短路,防止單個從機損壞造成整個鏈路崩潰的情況;
具體的連接如下圖所示;
菊花鏈連接其中紅線加粗爲數據的流向;
所以最終的數據流向圖可以表示爲:
數據流圖SCK爲時鐘信號,8clks表示8個邊沿信號;
其中D爲數據,X爲無效數據;
所以不難發現,菊花鏈模式充分使用了SPI其移位寄存器的功能,整個鏈充當通信移位寄存器,每個從機在下一個時鐘週期將輸入數據複製到輸出。
優缺點
SPI通訊的優勢
使SPI作爲串行通信接口脫穎而出的原因很多;
全雙工串行通信;
高速數據傳輸速率。
簡單的軟件配置;
極其靈活的數據傳輸,不限於8位,它可以是任意大小的字;
非常簡單的硬件結構。從站不需要唯一地址(與I2C不同)。從機使用主機時鐘,不需要精密時鐘振盪器/晶振(與UART不同)。不需要收發器(與CAN不同)。
SPI的缺點
沒有硬件從機應答信號(主機可能在不知情的情況下無處發送);
通常僅支持一個主設備;
需要更多的引腳(與I2C不同);
沒有定義硬件級別的錯誤檢查協議;
與RS-232和CAN總線相比,只能支持非常短的距離;
編程實現
下面是通過STM32的cubemx自動生成的HAL庫代碼,比較簡單,截取了其中一部分,具體如下;
static void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; //主機模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; //全雙工
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; //數據位爲8位
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; //CPOL=0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; //CPHA爲數據線的第一個變化沿
hspi1.Init.NSS = SPI_NSS_SOFT; //軟件控制NSS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分頻,32M/2=16MHz
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; //最高位先發送
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; //TIMODE模式關閉
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//CRC關閉
hspi1.Init.CRCPolynomial = 10; //默認值,無效
if (HAL_SPI_Init(&hspi1) != HAL_OK) //初始化
{
_Error_Handler(__FILE__, __LINE__);
}
}
//發送數據
HAL_StatusTypeDef
HAL_SPI_Transmit(SPI_HandleTypeDef *hspi,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
//接收數據
HAL_StatusTypeDef
HAL_SPI_Receive(SPI_HandleTypeDef *hspi,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
免責聲明:本文系網絡轉載,版權歸原作者所有。如涉及作品版權問題,請與我們聯繫,我們將根據您提供的版權證明材料確認版權並支付稿酬或者刪除內容。