打算写几篇专题,系统总结下常用的片上总线、现场总线,就先从最常用的SPI开始吧。
1. SPI是干什么的?除了SPI还有那些其它电路板及的通讯总线?有何差别?
相信接触过MCU的同学对SPI都不陌生,详细定义就不罗嗦了。SPI常用的场合包括ADC读写、存储芯片读写、MCU间通讯等等。可以一主多从(通过片选来选择Slave),也可以做成菊花链等等形式的拓扑。与SPI类似的总线还有IIC、UART等,甚至还有很多单根线的总线,原理都是基于简单的串行通信,区别在于收发时序和连接拓扑。要熟练使用这些总线,关键在于理解其时序图,在此基础上创造各种变种的总线形式也不是难事(当然为了设计的通用性不建议这么做)。
以下维基百科的SPI词条介绍非常全面,推荐阅读。
https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Data_transmission
2. SPI是什么样的?
在此借用一张维基百科上的图,SPI通常有4根线,SS用于选定当前通信的slave,SCLK为通信的基准时钟,采样/发送都在时钟边沿执行,MOSI、MISO为串行的数据线。
以下是一个典型的SPI时序图,Master和Slave均在时钟上升沿采样,下降沿发送数据。数据从最高位(MSB)开始发送。
需要注意图中所有的时序关系都要被满足,包括CS下降沿到第1个时钟上升沿间隔(tsclk_su)、数据的建立时间(tSU)、保持时间(tHD)等等。通常这些参数由具体的器件决定,如果不满足则有通信失败的风险。
3. 如何使用SPI?SPI有哪几种配置模式(相位、极性)?
根据SPI时钟信号的空闲状态、是上升沿采样还是下降沿采样,SPI有四种模式。CPOL=0表示时钟空闲时为低电平,反之为高电平;CPHA=0表示时钟信号第一个边沿是采样边沿,反之表示第2个边沿是采样边沿。对于带SPI接口的MCU而言,通常可由软件配置CPOL(Clock Polarity)、CPHA(Clock Phase),以适应和不同类型器件的通信。
4. 如何用verilog 编写SPI协议?
以下是使用verilog写的SPI主从通信代码,经过实测通信OK,可供参考。主从都在下降沿置数,同时在下降沿采样上一次发送的数据。为了尽量在主时钟较慢的情况下提高通信速度,采用的是非同步采样方式(即直接用SCLK边沿触发采样/发送,而不用主时钟对SCLK进行同步)。
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
1 module Serial2Parallel_Slave ( 2 3 input rst_n, 4 input clk, 5 6 input dataCS, 7 input dataSclk, 8 9 input sDataRd, 10 input [15:0] pDataWr, 11 12 output sDataWr, 13 output [15:0] pDataRd 14 ); 15 16 // - SPI read and write 17 reg sDataWr_reg; 18 reg [15:0] pDataRd_reg; 19 reg rxDone_reg; 20 reg [5:0] j;//operation steps 21 always @(negedge dataSclk or negedge rst_n) 22 if(!rst_n) begin 23 j <= 6'd0; 24 sDataWr_reg <= 1'b0; 25 pDataRd_reg <= 16'd0; 26 rxDone_reg <= 1'b0; 27 end 28 // - CS high,clear j & AD data 29 else if(dataCS) begin 30 j <= 6'd0; 31 sDataWr_reg <= 1'b0; 32 pDataRd_reg <= 16'd0; 33 rxDone_reg <= 1'b0; 34 end 35 else begin 36 // - first falling of Sclk, send MSB of send data 37 if(j == 6'd0) begin 38 j <= j + 1'b1; 39 sDataWr_reg <= pDataWr[15];//send data 40 pDataRd_reg <= 16'd0;//receive data clear 41 rxDone_reg <= 1'b0; 42 end 43 44 // - 2nd to 16th falling of Sclk 45 else if(j <= 6'd15) begin 46 j <= j + 1'b1; 47 sDataWr_reg <= pDataWr[15-j];//send data 48 pDataRd_reg[16-j] <= sDataRd;//receive data 49 rxDone_reg <= 1'b0; 50 end 51 52 // - at 17th falling of sclk_fbk, CS is still low, receive LSB of receive data 53 else if(j == 6'd16) begin 54 j <= j + 1'b1; 55 sDataWr_reg <= 1'b0;//send data clear 56 pDataRd_reg[0] <= sDataRd;//receive data 57 rxDone_reg <= 1'b1;//receive done 58 end 59 else begin 60 j <= j; 61 sDataWr_reg <= sDataWr_reg; 62 pDataRd_reg <= pDataRd_reg; 63 rxDone_reg <= rxDone_reg; 64 end 65 end 66 67 // - data latch for pDataRd 68 reg [15:0] pDataRd_l; 69 always @(posedge dataCS or negedge rst_n) 70 if(!rst_n) 71 pDataRd_l <= 16'd0; 72 else if(rxDone_reg) begin 73 pDataRd_l <= pDataRd_reg; 74 end 75 else begin 76 pDataRd_l <= pDataRd_l; 77 end 78 79 // - output assignment 80 assign sDataWr = sDataWr_reg; 81 assign pDataRd = pDataRd_l; 82 83 84 endmodule