1. 程式人生 > >【STM32】SPI的基本原理、庫函式(SPI一般步驟)

【STM32】SPI的基本原理、庫函式(SPI一般步驟)

《STM32中文參考手冊V10》-第23章 序列外設介面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,具體的描述見下表:

SPI各根線的描述
名稱 描述
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(時鐘相位)來控制我們主裝置的通訊模式,具體如下:

SPI通訊模式
模式 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最近接收的資料