SPI通信的基礎知識


1 SPI物理層
SPI通信設備之間常用物理連接方式如下圖

 SPI通訊使用3條總線及片選線,3條總線分別為SCK、MOSI、MISO,片選線為CS。
 CS:從設備選擇信號線,常稱為片選信號線,也稱為NSS。當有多個SPI從設備與SPI主機相連時,設備的其它信號線SCK、MOSI及MISO同時並聯到相同的SPI總線上,即無論有多少個從設備,都共同只使用這3條總線;而每個從設備都有獨立的這一條CS信號線,本信號線獨占主機的一個引腳,即有多少個從設備,就有多少條片選信號線。SPI協議中沒有設備地址,它使用CS信號線來尋址,當主機要選擇從設備時,把該從設備的CS信號線設置為低電平,該從設備即被選中,即片選有效,接着主機開始與被選中的從設備進行SPI通訊。所以SPI通訊以CS線置低電平為開始信號,以NSS線被拉高作為結束信號。
 SCK:時鍾信號線,用於通訊數據同步。它由通訊主機產生,決定了通訊的速率,不同的設備支持的最高時鍾頻率不一樣,如STM32的SPI時鍾頻率最大為fpclk/2,兩個設備之間通訊時,通訊速率受限於低速設備。
在NRF52832中

typedef enum
{
    NRF_DRV_SPI_FREQ_125K = NRF_SPI_FREQ_125K, ///< 125 kbps.
    NRF_DRV_SPI_FREQ_250K = NRF_SPI_FREQ_250K, ///< 250 kbps.
    NRF_DRV_SPI_FREQ_500K = NRF_SPI_FREQ_500K, ///< 500 kbps.
    NRF_DRV_SPI_FREQ_1M   = NRF_SPI_FREQ_1M,   ///< 1 Mbps.
    NRF_DRV_SPI_FREQ_2M   = NRF_SPI_FREQ_2M,   ///< 2 Mbps.
    NRF_DRV_SPI_FREQ_4M   = NRF_SPI_FREQ_4M,   ///< 4 Mbps.
    NRF_DRV_SPI_FREQ_8M   = NRF_SPI_FREQ_8M    ///< 8 Mbps.
} nrf_drv_spi_frequency_t;

nrf_drv_spi_frequency_t可以用來配置SPI的通信速率。
 MOSI (Master Output, Slave Input)主設備輸出/從設備輸入引腳。主機的數據從這條信號線輸出,從機由這條信號線讀入主機發送的數據,即這條線上數據的方向為主機到從機。
 MISO(Master Input,,Slave Output):主設備輸入/從設備輸出引腳。主機從這條信號線讀入數據,從機的數據由這條信號線輸出到主機,即在這條線上數據的方向為從機到主機。
2 協議層
2.1 SPI基本通信過程

 這是一個主機的通訊時序。NSS、SCK、MOSI信號都由主機控制產生,而MISO的信號由從機產生,主機通過該信號線讀取從機的數據。MOSI與MISO的信號只在NSS為低電平的時候才有效,在SCK的每個時鍾周期MOSI和MISO傳輸一位數據。
 在上圖中的標號1處,NSS信號線由高變低,是SPI通訊的起始信號。NSS是每個從機各自獨占的信號線,當從機檢在自己的NSS線檢測到起始信號后,就知道自己被主機選中了,開始准備與主機通訊。在圖中的標號處,NSS信號由低變高,是SPI通訊的停止信號,表示本次通訊結束,從機的選中狀態被取消。
 SPI使用MOSI及MISO信號線來傳輸數據,使用SCK信號線進行數據同步。MOSI及MISO數據線在SCK的每個時鍾周期傳輸一位數據,且數據輸入輸出是同時進行的。數據傳輸時,MSB先行或LSB先行並沒有作硬性規定,但要保證兩個SPI通訊設備之間使用同樣的協定,一般都會采用上圖中的MSB先行模式。
 觀察圖中的2,3,4,5標號處,MOSI及MISO的數據在SCK的上升沿期間變化輸出,在SCK的下降沿時被采樣。即在SCK的下降沿時刻,MOSI及MISO的數據有效,高電平時表示數據“1”,為低電平時表示數據“0”。在其它時刻,數據無效,MOSI及MISO為下一次表示數據做准備。
 SPI每次數據傳輸可以8位或16位為單位,每次傳輸的單位數不受限制。
3 SPI的模式
 上面講述的圖中的時序只是SPI中的其中一種通訊模式,SPI一共有四種通訊模式,它們的主要區別是總線空閑時SCK的時鍾狀態以及數據采樣時刻。為方便說明,在此引入“時鍾極性CPOL”和“時鍾相位CPHA”的概念。
時鍾極性CPOL是指SPI通訊設備處於空閑狀態時,SCK信號線的電平信號(即SPI通訊開始前、 NSS線為高電平時SCK的狀態)。CPOL=0時, SCK在空閑狀態時為低電平,CPOL=1時,則相反。
時鍾相位CPHA是指數據的采樣的時刻,當CPHA=0時,MOSI或MISO數據線上的信號將會在SCK時鍾線的“奇數邊沿”被采樣。當CPHA=1時,數據線在SCK的“偶數邊沿”采樣。

 我們來分析這個CPHA=0的時序圖。首先,根據SCK在空閑狀態時的電平,分為兩種情況。SCK信號線在空閑狀態為低電平時,CPOL=0;空閑狀態為高電平時,CPOL=1。
 無論CPOL=0還是=1,因為我們配置的時鍾相位CPHA=0,在圖中可以看到,采樣時刻都是在SCK的奇數邊沿。注意當CPOL=0的時候,時鍾的奇數邊沿是上升沿,而CPOL=1的時候,時鍾的奇數邊沿是下降沿。所以SPI的采樣時刻不是由上升/下降沿決定的。MOSI和MISO數據線的有效信號在SCK的奇數邊沿保持不變,數據信號將在SCK奇數邊沿時被采樣,在非采樣時刻,MOSI和MISO的有效信號才發生切換。

 類似地,當CPHA=1時,不受CPOL的影響,數據信號在SCK的偶數邊沿被采樣,見上圖。
 由CPOL及CPHA的不同狀態,SPI分成了四種模式,見下表,主機與從機需要工作在相同的模式下才可以正常通訊,實際中采用較多的是“模式0”與“模式3”。

3 軟件模擬
模式0

void SPI_write_read_mode1(unsigned int n_Data)
{
  unsigned char x = 0x00;
  unsigned int data;
  SPI_CS = 0;  
  delay_us(10);          
  for(x = 0x00;x < 8;x ++)
  {
    SPI_SCK = 0;                //數據發送
    if((n_Data & 0x80) == 0x80)  
       SPI_MOSI = 1;          
    else SPI_MOSI = 0;          
    n_Data = n_Data << 1;  
          
     SPI_SCK = 1;              //數據接收
     data<<=1;  
     if(SPI_MISO)  
     {data++;}
     SPI_SCK = 0;
  }
  
}

模式1

void SPI_write_read_mode2(unsigned int n_Data)
{
  unsigned char x = 0x00;
  unsigned int data;
  SPI_CS = 0;  
  delay_us(10);          
  for(x = 0x00;x < 8;x ++)
  {
    SPI_SCK = 1;                //數據發送
    if((n_Data & 0x80) == 0x80)  
       SPI_MOSI = 1;          
    else SPI_MOSI = 0;        
    n_Data = n_Data << 1;  
          
    SPI_SCK = 0;              //數據接收
    data<<=1;  
    if(SPI_MISO)  
    {data++;}
    SPI_SCK = 0;
  }
  
}

模式2

void SPI_write_read_mode3(unsigned int n_Data)
{
  unsigned char x = 0x00;
  unsigned int data;
  SPI_CS = 0;  
  delay_us(10);          
  for(x = 0x00;x < 8;x ++)
  {
    SPI_SCK = 1;                  //數據發送
    if((n_Data & 0x80) == 0x80)  
       SPI_MOSI = 1;          
    else SPI_MOSI = 0;          
    n_Data = n_Data << 1;  
          
    SPI_SCK = 0;                //數據接收 
    data<<=1;  
    if(SPI_MISO)  
    {data++;}
    SPI_SCK = 0;
  }
  SPI_SCK = 1;    
}

模式3

void SPI_write_read_mode4(unsigned int n_Data)
{
  unsigned char x = 0x00;
  unsigned int data;
  SPI_CS = 0;  
  delay_us(10);        
  for(x = 0x00;x < 8;x ++)
  {
    SPI_SCK = 0;                  //數據發送
    if((n_Data & 0x80) == 0x80)  
       SPI_MOSI = 1;          
    else SPI_MOSI = 0;          
    n_Data = n_Data << 1;  
          
    SPI_SCK = 1;                //數據接收
    data<<=1;  
    if(SPI_MISO)  
    {data++;}
    SPI_SCK = 0;
  }
  SPI_SCK = 1;  
}

參考資料:
1《STM32庫開發實戰指南-基於STM32F4》 劉火良,楊森編著 機械工業出版社
2 軟件模擬SPI部分的程序參考了公眾號“ Bernice堅果丁” https://mp.weixin.qq.com/s/SqKvReXRXCrqIj-G78I9CA


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM