SPI總線

1、概述.

     SPI, Serial Perripheral Interface, 串行外圍設備接口, 是 Motorola 公司推出的一種同步串行接口技術. SPI 總線在物理上是經過接在外圍設備微控制器(PICmicro) 上面的微處理控制單元 (MCU) 上叫做同步串行端口(Synchronous Serial Port) 的模塊(Module)來實現的, 它容許 MCU 以全雙工的同步串行方式, 與各類外圍設備進行高速數據通訊.

     SPI 主要應用在 EEPROM, Flash, 實時時鐘(RTC), 數模轉換器(ADC), 數字信號處理器(DSP) 以及數字信號解碼器之間. 它在芯片中只佔用四根管腳 (Pin) 用來控制以及數據傳輸, 節約了芯片的 pin 數目, 同時爲 PCB 在佈局上節省了空間. 正是出於這種簡單易用的特性, 如今愈來愈多的芯片上都集成了 SPI技術.git

2、 特色

     1. 採用主-從模式(Master-Slave) 的控制方式

       SPI 規定了兩個 SPI 設備之間通訊必須由主設備 (Master) 來控制次設備 (Slave). 一個 Master 設備能夠經過提供 Clock 以及對 Slave 設備進行片選 (Slave Select) 來控制多個 Slave 設備, SPI 協議還規定 Slave 設備的 Clock 由 Master 設備經過 SCK 管腳提供給 Slave 設備, Slave 設備自己不能產生或控制 Clock, 沒有 Clock 則 Slave 設備不能正常工做.

     2. 採用同步方式(Synchronous)傳輸數據

       Master 設備會根據將要交換的數據來產生相應的時鐘脈衝(Clock Pulse), 時鐘脈衝組成了時鐘信號(Clock Signal) , 時鐘信號經過時鐘極性 (CPOL) 和 時鐘相位 (CPHA) 控制着兩個 SPI 設備間什麼時候數據交換以及什麼時候對接收到的數據進行採樣, 來保證數據在兩個設備之間是同步傳輸的.

     3. 數據交換(Data Exchanges)

       SPI 設備間的數據傳輸之因此又被稱爲數據交換, 是由於 SPI 協議規定一個 SPI 設備不能在數據通訊過程當中僅僅只充當一個 "發送者(Transmitter)" 或者 "接收者(Receiver)". 在每一個 Clock 週期內, SPI 設備都會發送並接收一個 bit 大小的數據, 至關於該設備有一個 bit 大小的數據被交換了.

       一個 Slave 設備要想可以接收到 Master 發過來的控制信號, 必須在此以前可以被 Master 設備進行訪問 (Access). 因此, Master 設備必須首先經過 SS/CS pin 對 Slave 設備進行片選, 把想要訪問的 Slave 設備選上.

       在數據傳輸的過程當中,  每次接收到的數據必須在下一次數據傳輸以前被採樣. 若是以前接收到的數據沒有被讀取, 那麼這些已經接收完成的數據將有可能會被丟棄,  致使 SPI 物理模塊最終失效. 所以, 在程序中通常都會在 SPI 傳輸完數據後, 去讀取 SPI 設備裏的數據, 即便這些數據(Dummy Data)在咱們的程序裏是無用的.編程

 

 3、 工做機制

     1. 概述函數

   

   上圖只是對 SPI 設備間通訊的一個簡單的描述, 下面就來解釋一下圖中所示的幾個組件(Module):
  
       SSPBUF, Synchronous Serial Port Buffer, 泛指 SPI 設備裏面的內部緩衝區, 通常在物理上是以 FIFO 的形式, 保存傳輸過程當中的臨時數據;

       SSPSR, Synchronous Serial Port Register, 泛指 SPI 設備裏面的移位寄存器(Shift Regitser), 它的做用是根據設置好的數據位寬(bit-width) 把數據移入或者移出 SSPBUF;

       Controller, 泛指 SPI 設備裏面的控制寄存器, 能夠經過配置它們來設置 SPI 總線的傳輸模式.

        一般狀況下, 咱們只須要對上圖所描述的四個管腳(pin) 進行編程便可控制整個 SPI 設備之間的數據通訊:

        SCK, Serial Clock, 主要的做用是 Master 設備往 Slave 設備傳輸時鐘信號, 控制數據交換的時機以及速率;

        SS/CS, Slave Select/Chip Select, 用於 Master 設備片選 Slave 設備, 使被選中的 Slave 設備可以被 Master 設備所訪問;

        SDO/MOSI, Serial Data Output/Master Out Slave In, 在 Master 上面也被稱爲 Tx-Channel, 做爲數據的出口, 主要用於 SPI 設備發送數據;

        SDI/MISO, Serial Data Input/Master In Slave Out, 在 Master 上面也被稱爲 Rx-Channel, 做爲數據的入口, 主要用於SPI 設備接收數據;

        SPI 設備在進行通訊的過程當中, Master 設備和 Slave 設備之間會產生一個數據鏈路迴環(Data Loop), 就像上圖所畫的那樣, 經過 SDO 和 SDI 管腳, SSPSR 控制數據移入移出 SSPBUF, Controller 肯定 SPI 總線的通訊模式, SCK 傳輸時鐘信號.


      2. Timing.oop

   

   上圖經過 Master 設備與 Slave 設備之間交換1 Byte 數據來講明 SPI 協議的工做機制.

        首先,  在這裏解釋一下兩個概念:
        CPOL: 時鐘極性, 表示 SPI 在空閒時, 時鐘信號是高電平仍是低電平. 若 CPOL 被設爲 1, 那麼該設備在空閒時 SCK 管腳下的時鐘信號爲高電平. 當 CPOL 被設爲 0 時則正好相反.

        CPOL = 0: SCK idle phase is low; 
        CPOL = 1: SCK idle phase is high;


        CPHA: 時鐘相位, 表示 SPI 設備是在 SCK 管腳上的時鐘信號變爲上升沿時觸發數據採樣, 仍是在時鐘信號變爲降低沿時觸發數據採樣. 若 CPHA 被設置爲 1, 則 SPI 設備在時鐘信號變爲降低沿時觸發數據採樣, 在上升沿時發送數據. 當 CPHA 被設爲 0 時也正好相反.

        CPHA = 0: Output data at negedge of clock while receiving data at posedge of clock;
        CPHA = 1: Output data at posedge of clock while receiving data at negedge of clock;

        上圖裏的 "Mode 1, 1" 說明了本例所使用的 SPI 數據傳輸模式被設置成 CPOL = 1, CPHA = 1. 這樣, 在一個 Clock 週期內, 每一個單獨的 SPI 設備都能以全雙工(Full-Duplex) 的方式, 同時發送和接收 1 bit 數據, 即至關於交換了 1 bit 大小的數據. 若是 SPI 總線的 Channel-Width 被設置成 Byte, 表示 SPI 總線上每次數據傳輸的最小單位爲 Byte, 那麼掛載在該 SPI 總線的設備每次數據傳輸的過程至少須要 8 個 Clock 週期(忽略設備的物理延遲). 所以, SPI 總線的頻率越快, Clock 週期越短, 則 SPI 設備間數據交換的速率就越快.


     3. SSPSR.佈局

   

     SSPSR 是 SPI 設備內部的移位寄存器(Shift Register). 它的主要做用是根據 SPI 時鐘信號狀態, 往 SSPBUF 裏移入或者移出數據, 每次移動的數據大小由 Bus-Width 以及 Channel-Width 所決定.

        Bus-Width 的做用是指定地址總線到 Master 設備之間數據傳輸的單位.
        例如, 咱們想要往 Master 設備裏面的 SSPBUF 寫入 16 Byte 大小的數據: 首先, 給 Master 設備的配置寄存器設置 Bus-Width 爲 Byte; 而後往 Master 設備的 Tx-Data 移位寄存器在地址總線的入口寫入數據, 每次寫入 1 Byte 大小的數據(使用 writeb 函數); 寫完 1 Byte 數據以後, Master 設備裏面的 Tx-Data 移位寄存器會自動把從地址總線傳來的1 Byte 數據移入 SSPBUF 裏; 上述動做一共須要重複執行 16 次.

        Channel-Width 的做用是指定 Master 設備與 Slave 設備之間數據傳輸的單位. 與 Bus-Width 類似,  Master 設備內部的移位寄存器會依據 Channel-Width 自動地把數據從 Master-SSPBUF 裏經過 Master-SDO 管腳搬運到 Slave 設備裏的 Slave-SDI 引腳, Slave-SSPSR 再把每次接收的數據移入 Slave-SSPBUF裏.

        一般狀況下, Bus-Width 老是會大於或等於 Channel-Width, 這樣能保證不會出現因 Master 與 Slave 之間數據交換的頻率比地址總線與 Master 之間的數據交換頻率要快, 致使 SSPBUF 裏面存放的數據爲無效數據這樣的狀況.


        4. SSPBUF.spa

  

     咱們知道, 在每一個時鐘週期內, Master 與 Slave 之間交換的數據其實都是 SPI 內部移位寄存器從 SSPBUF 裏面拷貝的. 咱們能夠經過往 SSPBUF 對應的寄存器 (Tx-Data / Rx-Data register) 裏讀寫數據, 間接地操控 SPI 設備內部的 SSPBUF.

          例如, 在發送數據以前, 咱們應該先往 Master 的 Tx-Data 寄存器寫入將要發送出去的數據, 這些數據會被 Master-SSPSR 移位寄存器根據 Bus-Width 自動移入 Master-SSPBUF 裏, 而後這些數據又會被 Master-SSPSR 根據 Channel-Width 從 Master-SSPBUF 中移出, 經過 Master-SDO  管腳傳給 Slave-SDI 管腳,  Slave-SSPSR 則把從  Slave-SDI 接收到的數據移入 Slave-SSPBUF 裏.  與此同時, Slave-SSPBUF 裏面的數據根據每次接收數據的大小(Channel-Width), 經過 Slave-SDO 發往 Master-SDI, Master-SSPSR 再把從 Master-SDI 接收的數據移入 Master-SSPBUF.在單次數據傳輸完成以後, 用戶程序能夠經過從 Master 設備的 Rx-Data 寄存器讀取 Master 設備數據交換獲得的數據.


         5. Controller.code

   

   Master 設備裏面的 Controller 主要經過時鐘信號(Clock Signal)以及片選信號(Slave Select Signal)來控制 Slave 設備. Slave 設備會一直等待, 直到接收到 Master 設備發過來的片選信號, 而後根據時鐘信號來工做.

       Master 設備的片選操做必須由程序所實現. 例如: 由程序把 SS/CS 管腳的時鐘信號拉低電平, 完成 SPI 設備數據通訊的前期工做; 當程序想讓 SPI 設備結束數據通訊時, 再把 SS/CS 管腳上的時鐘信號拉高電平.blog

 

 

 

 

 附:模擬SPI底層驅動接口

 

/*
=================================================================================
SPI_ExchangeByte( );
Function : Exchange a byte via the SPI bus
INTPUT   : input, The input byte
OUTPUT   : The output byte from SPI bus
=================================================================================
*/
unsigned char SPI_ExchangeByte(unsigned char input)
{
        unsigned char i;
        unsigned char Temp_byte,RxByte=0x00;  
        Temp_byte = input;    
      for(i = 0; i < 8; i++)
    {            
            if(Temp_byte & 0x80)            // 把最高位輸出
                    MOSI_Set;            // SDI = 1;
            else
                    MOSI_Clr;            // SDI = 0;
 
            RxByte=RxByte<<1;
            
            SCK_Clr;            // SCK = 0;
            SCK_Set;            // SCK = 1; 
            
            Temp_byte <<= 0x01;
            
            RxByte|=(char)MISO_STA; 
    }
    return(RxByte);
}