參考:spi詳解 spi協議
SPI的基本介紹
SPI的簡介
SPI,是英語Serial Peripheral interface的縮寫,顧名思義就是串行外圍設備接口,是Motorola首先在其MC68HCXX系列處理器上定義的。
SPI接口主要應用在EEPROM、FLASH、實時時鍾、AD轉換器,還有數字信號處理器和數字信號解碼器之間。SPI是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只占用四根線,節約了芯片的管腳,同時為PCB的布局上節省空間,提供方便,正是出於這種簡單易用的特性,現在越來越多的芯片集成了這種通信協議,比如AT91RM9200。
SPI分為主、從兩種模式,一個SPI通訊系統需要包含一個(且只能是一個)主設備,一個或多個從設備。SPI接口的讀寫操作,都是由主設備發起。當存在多個從設備時,通過各自的片選信號進行管理。
- 優點:支持全雙工通信、通信簡單、數據傳輸速率快;
- 缺點:沒有指定的流控制,沒有應答機制確認是否接收到數據,所以跟IIC總線協議比較在數據的可靠性上有一定的缺陷。
STM32中SPI接口的特點
- 3線全雙工同步傳輸;
- 8或16位傳輸幀格式選擇;
- 主或從操作,支持多主模式;
- 主模式和從模式下均可以由軟件或硬件進行NSS管理:主/從操作模式的動態改變;
- 可編程的時鍾極性和相位;
- 可編程的數據順序,MSB在前或LSB在前;
- 可觸發中斷的專用發送和接收標志;
- SPI總線忙狀態標志;
- 支持可靠通信的硬件CRC;
- 可觸發中斷的主模式故障、過載以及CRC錯誤標志;
- 支持DMA功能的1字節發送和接收緩沖器:產生發送和接受請求。
SPI協議
SPI引腳說明
SPI的通信原理很簡單,它以主從方式工作,這種模式通常有一個主設備和一個或多個從設備,需要至少4根線,事實上3根也可以(單向傳輸時)。這四根線分別是MISO、MOSI、SCLK、CS,具體的描述見下表:
名稱 | 描述 |
MISO | 主設備數據輸出,從設備數據輸入 |
MOSI | 主設備數據輸出,從設備數據輸入 |
SCLK | 時鍾信號,主設備產生 |
CS | 片選信號,主設備控制 |
CS:控制芯片是否被選中的,也就是說只有片選信號為預先規定的使能信號時(一般默認為低電位),對此芯片的操作才有效,這就允許在同一總線上連接多個SPI設備成為可能。
也就是說:當有多個從設備的時候,因為每個從設備上都有一個片選引腳接入到主設備機中,當我們的主設備和某個從設備通信時將需要將從設備對應的片選引腳電平拉低。
MISO/MOSI/SCLK:通訊是通過數據交換完成的,這里先要知道SPI是串行通訊協議,也就是說數據是一位一位的傳輸的。這就是SCLK時鍾線存在的原因,由SCLK提供時鍾脈沖,MISO,MOSI則基於此脈沖完成數據傳輸。數據輸出通過MOSI線,數據在時鍾上升沿或下降沿時采樣,同時也會有返回數據用於接受。完成一位數據傳輸,輸入也使用同樣原理。這樣,在至少8次時鍾信號的改變(上沿和下沿為一次),就可以完成8位數據的傳輸。
要注意的是:
- SCLK信號線只由主設備控制,從設備不能控制信號線。同樣,在一個基於SPI的設備中,至少有一個主控設備;
- 在點對點的通信中,SPI接口不需要進行尋址操作,且為全雙工通信,顯得簡單高效。在多個從設備的系統中,每個從設備需要獨立的使能信號,硬件上比I2C系統要稍微復雜一些。
SPI通訊模式
SPI通信有4種不同的模式,不同的從設備可能在出廠是就是配置為某種模式,這是不能改變的;但我們的通信雙方必須是工作在同一模式下,所以我們可以對我們的主設備的SPI模式進行配置,通過CPOL(時鍾極性)和CPHA(時鍾相位)來控制我們主設備的通信模式,具體如下:
模式 | CPOL(時鍾極性) | CPHA(時鍾相位) |
MODE0 | 0 | 0 |
MODE1 | 0 | 1 |
MODE2 | 1 | 0 |
MODE3 | 1 | 1 |
時鍾極性CPOL是用來配置SCLK的電平出於哪種狀態時是空閑態或者有效態,時鍾相位CPHA是用來配置數據采樣是在第幾個邊沿:
- CPOL=0,表示當SCLK=0時處於空閑態,所以有效狀態就是SCLK處於高電平時;
- CPOL=1,表示當SCLK=1時處於空閑態,所以有效狀態就是SCLK處於低電平時;
- CPHA=0,表示數據采樣是在第1個邊沿,數據發送在第2個邊沿;
- CPHA=1,表示數據采樣是在第2個邊沿,數據發送在第1個邊沿。
具體四種模式的時序圖如下:
對於SPI的四種通訊模式,總結起來,就是:
- CPOL=0,CPHA=0:此時空閑態時,SCLK處於低電平,數據采樣是在第1個邊沿,也就是SCLK由低電平到高電平的跳變,所以數據采樣是在上升沿;
- CPOL=0,CPHA=1:此時空閑態時,SCLK處於低電平,數據發送是在第1個邊沿,也就是SCLK由低電平到高電平的跳變,所以數據采樣是在下降沿;
- CPOL=1,CPHA=0:此時空閑態時,SCLK處於高電平,數據采集是在第1個邊沿,也就是SCLK由高電平到低電平的跳變,所以數據采集是在下降沿;
- CPOL=1,CPHA=1:此時空閑態時,SCLK處於高電平,數據發送是在第1個邊沿,也就是SCLK由高電平到低電平的跳變,所以數據采集是在上升沿。
SPI內部工作機制
下面對照一個SPI單主機與單從機連接圖,理解其內部工作機制:
- 硬件上為4根線;
- 主機和從機都有一個串行移位寄存器,主機通過向它的SPI串行寄存器寫入一個字節來發起一次傳輸;
- 串行移位寄存器通過MOSI信號線將字節傳送給從機,同時從機也將自己的串行移位寄存器中的內容通過MISO信號線返回給主機。這樣,兩個移位寄存器中的內容就被交換;
- 外設的寫操作和讀操作是同步完成的。如果只進行寫操作,主機只需忽略接收到的字節;反之,若主機要讀取從機的一個字節,就必須發送一個空字節來引發從機的傳輸。
也就是說:SPI是一個環形總線結構,由CS、SCLK、MISO、MOSI構成,其時序其實很簡單,主要是在SCLK的控制下,數據按照從高位到低位的方式依次移出主機寄存器和從機寄存器,並且依次移入從機寄存器和主機寄存器。當寄存器中的內容全部移出時,相當於完成了兩個寄存器內容的交換。
假設主機的8位寄存器裝的是待發送的數據10101010,上升沿發送、下降沿接收、高位先發送。那么第一個上升沿來的時候,主機將會通過MOSI信號線傳輸給從機最高位1,自身寄存器變成0101010x。同時,MISO信號線會從從機處返回一個數據給主機,那么這時寄存器為0101010MISO,這樣在 8個時鍾脈沖以后,兩個寄存器的內容互相交換一次。這樣就完成里一個SPI時序。
這個時候就會有一個疑問,或者說產生一個必然了:
為什么主機發送一個數據給從機,從機就同時通過MISO返回的一個數據給主機呢?
解釋:主機和從機的發送數據是同時完成的,兩者的接收數據也是同時完成的。也就是說,當上升沿主機發送數據的時候,從機也發送了數據。
所以為了保證主從機正確通信,應使得它們的SPI具有相同的時鍾極性和時鍾相位。
STM32的SPI接口
SPI可分為主、從兩種模式,並且支持全雙工模式,所以這也就導致STM32的SPI接口比較復雜。比如:配置SPI為主模式、配置SPI為從模式、配置SPI為單工通信、配置SPI為雙工通信等等。這里的內容就非常龐大,涉及到的寄存器的位也比較多,所以就不介紹太多,想要了解更多可以去查看STM32F1xx官方資料的第23章節。
SPI接口的框圖
SPI引腳
STM32的SPI接口通過4個引腳與外部器件相連,與標准的SPI協議是一致的:
- MISO:主設備輸入/從設備輸出引腳。該引腳在從模式下發送數據,在主模式下接收數據;
- MOSI:主設備輸出/從設備輸入引腳。該引腳在主模式下發送數據,在從模式下接收數據;
- SCK:串口時鍾,作為主設備的輸入,從設備的輸入;
- NSS:從設備選擇。這是一個可選的引腳,用來選擇主/從設備。它的功能是用來作為“片選引腳”,讓主設備可以單獨地與特定從設備通訊,避免數據線上的沖突。
從選擇(NSS)腳管理
有2種NSS模式:
- 軟件NSS模式:可以通過設置SPI_CR1寄存器的SSM位來使能這種模式。在這種模式下NSS引腳可以用作它用,而內部NSS信號電平可以通過寫SPI_CR1的SSI位來驅動;
- 硬件NSS模式,分兩種情況:
- NSS輸出被使能:當STM32F10xxx工作為主SPI,並且NSS輸出已經通過SPI_CR2寄存器的SSOE位使能,這時NSS引腳被拉低,所有NSS引腳與這個主SPI的NSS引腳相連並配置為硬件NSS的SPI設備,將自動變成從SPI設備。 當一個SPI設備需要發送廣播數據,它必須拉低NSS信號,以通知所有其它的設備它是主設備;如果它不能拉低NSS,這意味着總線上有另外一個主設備在通信,這時將產生一個硬件失敗錯誤;
- NSS輸出被關閉:允許操作於多主環境。
數據幀格式
- 根據SPI_CR1寄存器中的LSBFIRST位,輸出數據位時可以左對齊(MSB對齊標准)也可以右對齊(LSB對齊標准)。
- 根據SPI_CR1寄存器的DFF位,每個數據幀可以是8位或是16位。所選擇的數據幀格式對發送和/或接收都有效。
狀態標志
應用程序通過3個狀態標志可以完全監控SPI總線的狀態:
- 發送緩沖器空閑標志(TXE)
此標志為1時表明發送緩沖器為空,可以寫下一個待發送的數據進入緩沖器中。當寫入SPI_DR時,TXE標志被清除。
- 接收緩沖器非空(RXNE)
此標志為1時表明在接收緩沖器中包含有效的接收數據。讀SPI數據寄存器可以清除此標志。
- 忙(Busy)標志
BSY標志由硬件設置與清除(寫入此位無效果),此標志表明SPI通信層的狀態。
當它被設置為1時,表明SPI正忙於通信,但有一個例外:在主模式的雙向接收模式下(MSTR=1、BDM=1並且BDOE=0),在接收期間BSY標志保持為低。
在軟件要關閉SPI模塊並進入停機模式(或關閉設備時鍾)之前,可以使用BSY標志檢測傳輸是否結束,這樣可以避免破壞最后一次傳輸,因此需要嚴格按照下述過程執行。
SPI中斷
STM32的SPI引腳
SPI引腳位置
外設的GPIO配置
SPI相關配置庫函數
- 1個初始化函數
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
作用:初始化SPI的相關參數,比如方向(全雙工)、主從模式、數據大小、CPOL、CPHA、片選軟件模式、預分頻系數等。
- 3個使能函數
-
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
-
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
-
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
作用:使能SPI接口;使能SPI中斷;使能SPI的DMA功能。
- 2個數據傳輸函數
-
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
-
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
作用:分別用於SPI傳輸數據、接收數據。
- 4個狀態位函數
-
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
-
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
-
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
-
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
作用:前兩者用於獲得和清除SPI的各種狀態位;后兩者則針對SPI的中斷標志位。
SPI一般步驟
實驗目標:利用SPI2進行初始化等操作。
- 配置相關引腳的復用功能,使能SPIx時鍾。調用函數:void GPIO_Init();
- 初始化SPIx,設置SPIx工作模式。調用函數:void SPI_Init();
- 使能SPIx。調用函數:void SPI_Cmd();
- SPI傳輸數據。調用函數:void SPI_I2S_SendData();uint16_t SPI_I2S_ReceiveData();
- 查看SPI傳輸狀態。調用函數:SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)。
下面按照這個一般步驟來進行一個簡單的SPI程序:
-
void SPI2_Init(void)
-
{
-
GPIO_InitTypeDef GPIO_InitStructure;
-
SPI_InitTypeDef SPI_InitStructure;
-
-
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB時鍾使能
-
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2時鍾使能
-
-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15復用推挽輸出
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
-
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
-
-
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
-
-
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工
-
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //設置SPI工作模式:設置為主SPI
-
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //設置SPI的數據大小:SPI發送接收8位幀結構
-
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步時鍾的空閑狀態為高電平
-
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步時鍾的第二個跳變沿(上升或下降)數據被采樣
-
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信號由硬件(NSS管腳)還是軟件(使用SSI位)管理:內部NSS信號有SSI位控制
-
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定義波特率預分頻的值:波特率預分頻值為256
-
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始
-
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值計算的多項式
-
SPI_Init(SPI2, &SPI_InitStructure); //根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器
-
-
SPI_Cmd(SPI2, ENABLE); //使能SPI外設
-
-
SPI2_ReadWriteByte(0xff);//啟動傳輸
-
-
-
}
-
//SPI 速度設置函數
-
//SpeedSet:
-
//SPI_BaudRatePrescaler_2 2分頻
-
//SPI_BaudRatePrescaler_8 8分頻
-
//SPI_BaudRatePrescaler_16 16分頻
-
//SPI_BaudRatePrescaler_256 256分頻
-
-
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
-
{
-
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
-
SPI2->CR1&=0XFFC7;
-
SPI2->CR1|=SPI_BaudRatePrescaler; //設置SPI2速度
-
SPI_Cmd(SPI2,ENABLE);
-
-
}
-
-
//SPIx 讀寫一個字節
-
//TxData:要寫入的字節
-
//返回值:讀取到的字節
-
u8 SPI2_ReadWriteByte(u8 TxData)
-
{
-
u8 retry=0;
-
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //檢查指定的SPI標志位設置與否:發送緩存空標志位
-
{
-
retry++;
-
if(retry>200)return 0;
-
}
-
SPI_I2S_SendData(SPI2, TxData); //通過外設SPIx發送一個數據
-
retry=0;
-
-
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //檢查指定的SPI標志位設置與否:接受緩存非空標志位
-
{
-
retry++;
-
if(retry>200)return 0;
-
}
-
return SPI_I2S_ReceiveData(SPI2); //返回通過SPIx最近接收的數據
-
}