一、SPI是一種常用的串行通信接口,與UART不同的地方在於。SPI可以同時掛多個從機,但是UART只能點對點的傳輸數據,此外SPI有四條線實現數據的傳輸,而UART采用的是2條實現串行數據的傳輸
1.SPI的主從機的接口模型
(master和slave在時鍾的上升沿采樣,下降沿發送數據。數據從最高位(MSB)開始發送。)
用3條通訊總線和1條片選線。
- MOSI:Master Output Slave Input,顧名思義,即主設備輸出/從設備輸入。數據從主機輸出到從機,主機發送數據。
- MISO:Master Iutput Slave Onput,主設備輸入/從設備輸出,數據由從機輸出到主機,主機接收數據。
- SCK:即時鍾信號線,用於通訊同步。該信號由主機產生,其支持的最高通訊速率為fpclk/2,即所掛載總線速率的一半。如SPI2掛載在APB1總線上,則其最高速率為36MHz / 2 = 18MHz。類似木桶效應,兩個設備之間通訊時,通訊速率受限於較低速的設備。
- NSS:即片選信號線,用於選擇通訊的從設備,也可用CS表示。每個從設備都有一條獨立的NSS信號線,主機通過將某個設備的NSS線置低電平來選擇與之通訊的從設備。所以SPI通訊以NSS線電平置低為起始信號,以NSS線電平被拉高為停止信號。

2.SPI如何使用,以及對應的有幾種配置模式(相位、極性)
SPI配置模式分類根據的是時鍾信號空閑狀態。、以及上升沿采樣還是下降沿采樣,
CPOL=0表示的是時鍾空閑的時候為低電平,反之是高電平
CPHA=0表示的是時鍾信號的第一個邊沿是采樣沿
CPHA=1表示的是時鍾信號的第二個邊沿是采樣沿
對應的時序圖如下:

CPOL、CPHA
- CPOL:即在沒有數據傳輸時,時鍾的空閑狀態的電平。
- CPHA:即數據的采樣時刻。
有一點需要注意的是,主機和從機需要工作在相同的模式下才能正常通訊。
3.起始、停止信號(轉於知乎)
如上圖,編號1和6即為起始和停止信號的發生區域。NSS電平由高變低,則產生起始信號;NSS電平由低變高,則產生停止信號。從機檢測到自己的NSS線電平被置低,則開始與主機進行通訊;反之,檢測到NSS電平被拉高,則停止通訊。
4.數據有效性
MOSI和MISO線在SCK的每個時鍾周期傳輸一位數據,開發者可以自行設置MSB或LSB先行,不過需要保證兩個通訊設備都使用同樣的協定。從圖16-1看到,在SCK的上升沿和下降沿時進行觸發和采樣。
SPI有四種通訊模式,在SCK上升沿觸發,下降沿采樣只是其中一種模式。四種模式的主要區別便是總線空閑時SCK的狀態及數據采樣時刻。這涉及到“時鍾極性CPOL”和“時鍾相位CPHA”,由CPOL和CPHA的組合而產生了四種的通訊模式。

5.SPI的verilog實現:結合實際的應用場景對該通信協議進行分析:在一個網絡通信模型中,可以將基帶部分作為主控,RF部分作為受控部分,把SPI接口作兩者之間傳輸數據的接口,它完成的主要工作是
(1)將從base band接收到的16位的並行數據,轉換為RF所能接收的串行數據,並將該數據根據SPI協議送給RF。
(2)產生RF所需的時鍾信號SCLK,使能信號CSB。
(3)接收從RF傳回的串行數據,並將其轉換為並行數據。
(4)將base band發送的數據,與RF返回的數據進行比較,並把比較結果傳給base band。

module Serial2Parallel_Master #( parameter SCLK_DIVIDER = 8'd0 //Sclk Freq = Clk/2 / (SCLK_DIVIDER + 1) )( input rst_n, input clk, input sDataRd, input [15:0] pDataWr, output dataCS, output dataSclk, output sDataWr, output [15:0] pDataRd ); // counter,used to generate dataSclk signal reg dataCS_reg; reg dataSclk_reg; reg[7:0] Count1; always @(posedge clk or negedge rst_n) if(!rst_n) Count1 <= 8'd0; else if(Count1 == SCLK_DIVIDER) Count1 <= 8'd0; else Count1 <= Count1 + 1'b1; // generate CS and Sclk sequence reg [5:0] i;//Step number always @(posedge clk or negedge rst_n) if(!rst_n)begin i <= 6'd0; dataSclk_reg <= 1'b1;//Sclk high at reset dataCS_reg <= 1'b1; //CS high at reset end else begin case(i) //pull down CS at the beginning 6'd0: if(Count1 == SCLK_DIVIDER) begin i <= i + 1'b1; dataSclk_reg <= 1'b1; dataCS_reg <= 1'b0; end else; //generate 1st to 17th Sclk falling edge 6'd1,6'd3,6'd5,6'd7,6'd9,6'd11,6'd13,6'd15,6'd17,6'd19,6'd21,6'd23,6'd25,5'd27,6'd29,6'd31,6'd33: if(Count1 == SCLK_DIVIDER) begin dataSclk_reg <= 1'b0; dataCS_reg <= 1'b0; i <= i + 1'b1; end else; //generate 1st to 16th Sclk rising edge 6'd2,6'd4,6'd6,6'd8,6'd10,6'd12,6'd14,6'd16,6'd18,6'd20,6'd22,6'd24,6'd26,6'd28,6'd30,6'd32: if(Count1 == SCLK_DIVIDER) begin dataSclk_reg <= 1'b1; dataCS_reg <= 1'b0; i <= i + 1'b1; end else; 6'd34://CS and Sclk go high if(Count1 == SCLK_DIVIDER) begin dataSclk_reg <= 1'b1; dataCS_reg <= 1'b1; i <= i + 1'b1; end else; 6'd35://CS keep high, Sclk go low if(Count1 == SCLK_DIVIDER) begin dataSclk_reg <= 1'b0; dataCS_reg <= 1'b1; i <= 6'd0; end else ; default ; endcase end ; // - receive and send SPI data reg sDataWr_reg; reg [15:0] pDataRd_reg; reg rxDone_reg; reg [5:0] j; always @(negedge dataSclk or negedge rst_n) if(!rst_n) begin j <= 6'd0; sDataWr_reg <= 1'b0; pDataRd_reg <= 16'd0; rxDone_reg <= 1'b0; end // - CS high,clear j & AD data else if(dataCS) begin j <= 6'd0; sDataWr_reg <= 1'b0; pDataRd_reg <= 16'd0; rxDone_reg <= 1'b0; end else begin // - first falling of Sclk, send MSB of send data if(j == 6'd0) begin j <= j + 1'b1; sDataWr_reg <= pDataWr[15];//send data pDataRd_reg <= 16'd0;//receive data clear rxDone_reg <= 1'b0; end // - 2nd to 16th falling of Sclk else if(j <= 6'd15) begin j <= j + 1'b1; sDataWr_reg <= pDataWr[15-j];//send data pDataRd_reg[16-j] <= sDataRd;//receive data rxDone_reg <= 1'b0; end // - at 17th falling of sclk_fbk, CS is still low, receive LSB of receive data else if(j == 6'd16) begin j <= j + 1'b1; sDataWr_reg <= 1'b0;//send data clear pDataRd_reg[0] <= sDataRd;//receive data rxDone_reg <= 1'b1;//receive done end else begin j <= j; sDataWr_reg <= sDataWr_reg; pDataRd_reg <= pDataRd_reg; rxDone_reg <= rxDone_reg; end end // - data latch for pDataRd reg [15:0] pDataRd_l; always @(posedge clk or negedge rst_n) if(!rst_n) pDataRd_l <= 16'd0; else if(rxDone_reg) begin pDataRd_l <= pDataRd_reg; end else begin pDataRd_l <= pDataRd_l; end // - delay sDataWr for 1 main clk(10ns) reg sDataWr_dly; always @(posedge clk or negedge rst_n) if(!rst_n) sDataWr_dly <= 1'b0; else if(sDataWr_reg) begin sDataWr_dly <= 1'b1; end else begin sDataWr_dly <= 1'b0; end // - output assignment assign dataCS = dataCS_reg; assign dataSclk = dataSclk_reg; assign sDataWr = sDataWr_dly; assign pDataRd = pDataRd_l; endmodule

module Serial2Parallel_Slave ( input rst_n, input clk, input dataCS, input dataSclk, input sDataRd, input [15:0] pDataWr, output sDataWr, output [15:0] pDataRd ); // - SPI read and write reg sDataWr_reg; reg [15:0] pDataRd_reg; reg rxDone_reg; reg [5:0] j;//operation steps always @(negedge dataSclk or negedge rst_n) if(!rst_n) begin j <= 6'd0; sDataWr_reg <= 1'b0; pDataRd_reg <= 16'd0; rxDone_reg <= 1'b0; end // - CS high,clear j & AD data else if(dataCS) begin j <= 6'd0; sDataWr_reg <= 1'b0; pDataRd_reg <= 16'd0; rxDone_reg <= 1'b0; end else begin // - first falling of Sclk, send MSB of send data if(j == 6'd0) begin j <= j + 1'b1; sDataWr_reg <= pDataWr[15];//send data pDataRd_reg <= 16'd0;//receive data clear rxDone_reg <= 1'b0; end // - 2nd to 16th falling of Sclk else if(j <= 6'd15) begin j <= j + 1'b1; sDataWr_reg <= pDataWr[15-j];//send data pDataRd_reg[16-j] <= sDataRd;//receive data rxDone_reg <= 1'b0; end // - at 17th falling of sclk_fbk, CS is still low, receive LSB of receive data else if(j == 6'd16) begin j <= j + 1'b1; sDataWr_reg <= 1'b0;//send data clear pDataRd_reg[0] <= sDataRd;//receive data rxDone_reg <= 1'b1;//receive done end else begin j <= j; sDataWr_reg <= sDataWr_reg; pDataRd_reg <= pDataRd_reg; rxDone_reg <= rxDone_reg; end end // - data latch for pDataRd reg [15:0] pDataRd_l; always @(posedge dataCS or negedge rst_n) if(!rst_n) pDataRd_l <= 16'd0; else if(rxDone_reg) begin pDataRd_l <= pDataRd_reg; end else begin pDataRd_l <= pDataRd_l; end // - output assignment assign sDataWr = sDataWr_reg; assign pDataRd = pDataRd_l; endmodule
二、IIC通信
IIC的通信模式示意圖:
IIC的verilog實現

`timescale 1ns / 1ps module IIC_AD( clk,rst_n, scl,sda); input clk; // 50MHz input rst_n; //��λ�źţ�����Ч output scl; // 24C02��ʱ�Ӷ˿� inout sda; // 24C02�����ݶ˿� reg[2:0] cnt; // cnt=0:scl上升沿,cnt=1:scl高電平中間,cnt=2:scl下降沿,cnt=3:scl低電平中間 reg[9:0] cnt_delay; //500循環計數,產生iic所需要的時鍾 reg scl_r=1; //時鍾脈沖寄存器 always @ (posedge clk or negedge rst_n) if(!rst_n) cnt_delay <= 9'd0; else if(cnt_delay == 9'd499) cnt_delay <= 9'd0; //計數到10us為scl的周期,即100KHz else cnt_delay <= cnt_delay+1'b1; always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt <= 3'd5; else begin case (cnt_delay) 9'd124: cnt <= 3'd1; //cnt=1:scl高電平中間,用於數據采樣 9'd249: cnt <= 3'd2; //cnt=2:scl下降沿 9'd280: cnt <= 3'd3; //cnt=3:scl低電平中間,用於數據變化 9'd499: cnt <= 3'd0; //cnt=0:scl上升沿 default: cnt <= 3'd5; endcase end end `define SCL_POS (cnt==3'd0) `define SCL_HIG (cnt==3'd1) `define SCL_NEG (cnt==3'd2) `define SCL_LOW (cnt==3'd3) always @ (posedge clk or negedge rst_n) if(!rst_n) scl_r <= 1'b1; else if(cnt==3'd0) scl_r <= 1'b1; //scl上升沿 else if(cnt==3'd2) scl_r <= 1'b0; //scl下降沿 assign scl = scl_r; //產生iic所需要的時鍾 //--------------------------------------------- reg[3:0] num; reg [7:0] db_r; //在IIC上傳送的數據寄存器 reg sda_link; //輸出數據sda信號inout方向控制 0z-c,1c-z reg sda_r=1; //輸出數據寄存器 //需要寫入24C02的地址和數據 parameter DEVICE_WRITE=8'b0101_1000;//被尋址器件地址(寫操作) parameter BYTE_ADDR=8'b0000_0011;//寫入/讀出EEPROM的地址寄存器 parameter WRITE_DATA=8'b0001_1011;//寫入EEPROM的數據 reg [3:0] cstate;//狀態寄存器 parameter START = 4'd0;//狀態機的進行步驟編號 parameter ADD1 = 4'd1; parameter ACK1 = 4'd2; parameter ADD2 = 4'd3; parameter ACK2 = 4'd4; parameter DATA = 4'd5; parameter ACK3 = 4'd6; parameter STOP1 = 4'd7; //--------------------------------------------- always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin num <= 3'd0; sda_link <= 1'b1; sda_r <= 1'b1; cstate <= START; end else begin case (cstate) START: begin if(`SCL_HIG) begin //scl為高電平期間 sda_link <= 1'b1; //確定數據傳輸方向,數據線sda為output db_r <= DEVICE_WRITE; sda_r <= 1'b0; //拉低數據線sda,產生起始位信號 cstate <= ADD1; num <= 4'd0; //num計數清零 end else cstate <= START; //等待scl高電平中間位置到來 等待數據開始傳輸 end ADD1: begin if(`SCL_LOW) begin if(num == 4'd8) begin num <= 4'd0; //num清零 sda_r <= 1'b1; //提高數據線sda,開始數據變化 sda_link <= 1'b0; //確定數據傳輸方向,數據線sda為input cstate <= ACK1; end else begin cstate <= ADD1; num <= num+1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0]; default: ; endcase end end else cstate <= ADD1; end ACK1: begin if(/*!sda*/`SCL_NEG) //注:24C01/02/04/08/16器件可以不考慮應答位 begin cstate <= ADD2; //從機響應信號 db_r <= BYTE_ADDR; // 1地址 end else cstate <= ACK1; //等待從機響應 end ADD2: begin if(`SCL_LOW) begin if(num == 4'd8) begin sda_link <= 1'b0; sda_r <= 1'b1; num <= 4'd0; cstate <= ACK2; end else begin sda_link <= 1'b1;//sda作為output cstate <= ADD2; num <= num+1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0]; default: ; endcase end end else cstate <= ADD2; end ACK2: begin if(/*!sda*/`SCL_NEG) begin //從機響應操作 cstate <= DATA; //寫操作 db_r <= WRITE_DATA; //寫入數據 end else cstate <= ACK2; //等待從機響應 end DATA: begin if(`SCL_LOW) begin if(num == 4'd8) begin sda_link <= 1'b0; sda_r <= 1'b1; num <= 4'd0; cstate <= ACK3; end else begin sda_link <= 1'b1; cstate <= DATA; num <= num+1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0]; default: ; endcase end end else cstate <= DATA; end ACK3: begin if(/*!sda*/`SCL_NEG) begin sda_r <= 1'b0; //拉低數據線sda,產生停止信號 sda_link <= 1'b1;//sda作為output cstate <= STOP1; end else cstate <= ACK3; end STOP1: begin if(`SCL_HIG) begin sda_link <= 1'b1; sda_r <= 1'b1; //拉低數據線sda,產生停止信號 cstate <= START; end else cstate <= STOP1; end default: cstate <= START; endcase end end assign sda = sda_link ? sda_r:1'bz; endmodule
IIC的關鍵詞:兩線和低速,該總線采用開漏的結構可以很好地實現數據的雙向傳輸,也就是說在要用到sda或者scl線的時候,可以通過內部的NMOS下拉為零,否則上拉為高電平
還需要注意的就是開始和結束條件,以及IIC協議的要求:
scl為高的器件,sda必須保持穩定,sda變化相對於scl變高有建立時間的要求,而sda變化相對於scl變低有保持時間的要求。scl低電平期間,數據sda才可以發生變化。而這里的建立保持時間是微秒級別的,所以IIC的速度慢,1MHz左右。從器件不適合高速數字邏輯單路。
然后具體的執行順序為:
s1:Start+器件地址+應答信號+要發送的數據+應答信號。
通過scl和sda兩條線的控制來實現數據的傳輸,其中的sda信號線是inout,因為作為串行數據傳輸線,它不僅要傳輸上位機的數據到下位機,此外還需要下位機發送一個響應到上位機去,所以實際上需要它是inout。
IIC通過器件的地址來區分從機,而SPI主要是通過作為主機的CS來區分從機的編碼。
三、關於兩者的對比:
首先是SPI的優點在於:
1.利於硬件實現,不需要多個器件地址,只用到4根數據線,封裝簡單
2.全雙工傳輸,可以同時發送和接收數據
3.三態輸出端口,相對IIC的開漏輸出,抗干擾能力強,傳輸穩定
4.傳輸數據的速度在幾百MHz遠遠高於IIC的幾十Mhz
5.輸入輸出的比特數沒有限制
缺點
1.信號線多,而且隨着從器件的個數增加,芯片選擇線會增加
2.傳輸過程沒有確認信號,不知道從器件的接收情況。
3.沒有校驗機制,傳輸錯誤不會有提示。
IIC的優點:
控制線少,結構簡單
IIC的缺點:
傳輸速度慢而且麻煩。