Spi从机实现
SPI协议很常见,跟UART,I2C一块算是嵌入式,FPGA这些入门必学的协议。要了解从机,必然要知道主机的工作原理。
SPI(serial peripheral interface)是一种同步串行通信协议,由一个主设备和一个或多个从设备组成,主设备启动与从设备的同步通信,从而完成数据的交换。SPI是一种高速全双工同步通信总线,标准的SPI使用4个引脚。也有少数的SPI的数据引脚输入输出是共用的,不过这种情况相对比较少见。一个SPI主机可以同时跟随多个从机设备,这就用到CS引脚,所以如果是一个SPI主机带多个SPI从设备的,主机SPI会有多根CS引脚,而每次只能使能一个CS引脚,使能的CS便是当前可进行主机通信的SPI设备。
SPI的速率比UART,I2C快,在很多ADC,DAC芯片采用的接口大都是SPI协议或者SPI协议的变形。四线的SPI引脚定义如下:
SCLK: 时钟信号,时钟频率即SPI速率,和SPI模式有关。
MOSI:主机输出,从机输入。
MISO:主机输入,从机输出。
1 //************************************************************************** 2 // *** file name : SPI_slave_if.v 3 // *** version : 1.0 4 // *** Description : SPI_slave_if 5 // *** Blogs : https://www.cnblogs.com/WenGalois123/ 6 // *** Author : Galois_V 7 // *** Date : 2022.5.29 8 // *** Changes : Initial 9 //************************************************************************** 10 `timescale 1ns/1ps 11 module SPI_slave_if 12 #( 13 parameter CPOL = 1'b0 , 14 parameter CPHA = 1'b0 15 ) 16 ( 17 input i_sys_clk , 18 input i_sys_rstn , 19 input i_spi_cs , 20 input i_spi_sclk , 21 input i_spi_mosi , 22 output o_spi_miso , 23 output [6:0] o_ctrl_wr_addr , 24 output o_ctrl_wr_en , 25 output [7:0] o_ctrl_wr_data , 26 output [6:0] o_ctrl_rd_addr , 27 output o_ctrl_addr_en , 28 input [7:0] i_ctrl_rd_data 29 ); 30 31 reg [1:0] r_spi_s_cs ; 32 reg [1:0] r_spi_s_sclk ; 33 reg [1:0] r_spi_s_mosi ; 34 reg [7:0] r_spi_s_rxd ; 35 reg [7:0] r_bit_cnt ; 36 reg [7:0] r_spi_txd ; 37 reg r_spi_rx_valid ; 38 reg r_first_byte ; 39 reg r_rx_byte_valid ; 40 reg [7:0] r_spi_addr ; 41 reg [7:0] r_spi_din ; 42 43 wire w_spi_rx_edge ; 44 wire w_spi_tx_edge ; 45 wire w_spi_cs_en ; 46 wire w_spi_sclk_pos ; 47 wire w_spi_sclk_neg ; 48 wire w_spi_reset ; 49 wire w_byte_end ; 50 51 always@(posedge i_sys_clk) 52 begin 53 if(~i_sys_rstn) 54 begin 55 r_spi_s_cs <= 2'b11; 56 r_spi_s_sclk <= 2'b11; 57 r_spi_s_mosi <= 2'b11; 58 end 59 else 60 begin 61 r_spi_s_cs <= {r_spi_s_cs[0],i_spi_cs}; 62 r_spi_s_sclk <= {r_spi_s_sclk[0],i_spi_sclk}; 63 r_spi_s_mosi <= {r_spi_s_mosi[0],i_spi_mosi}; 64 end 65 end 66 67 assign w_spi_cs_en = &r_spi_s_cs; 68 assign w_spi_sclk_pos = ~r_spi_s_sclk[1] & r_spi_s_sclk[0]; 69 assign w_spi_sclk_neg = ~r_spi_s_sclk[0] & r_spi_s_sclk[1]; 70 assign w_spi_reset = ~i_sys_rstn | w_spi_cs_en; 71 assign w_spi_rx_edge = (CPOL^CPHA) ? w_spi_sclk_neg : w_spi_sclk_pos; 72 assign w_spi_tx_edge = (CPOL^CPHA) ? w_spi_sclk_pos : w_spi_sclk_neg; 73 /******************************************************************************\ 74 SCLK sample edge count 75 \******************************************************************************/ 76 always@(posedge i_sys_clk) 77 begin 78 if(w_spi_reset) 79 begin 80 r_spi_s_rxd <= 'd0; 81 end 82 else if(w_spi_rx_edge) 83 begin 84 r_spi_s_rxd <= {r_spi_s_rxd[6:0],r_spi_s_mosi[0]}; 85 end 86 end 87 always@(posedge i_sys_clk) 88 begin 89 if(w_spi_reset | w_byte_end) 90 begin 91 r_bit_cnt <= 'd1; 92 end 93 else if(w_spi_rx_edge) 94 begin 95 r_bit_cnt <= r_bit_cnt + 1'b1; 96 end 97 end 98 assign w_byte_end = (r_bit_cnt == 4'd8) & w_spi_rx_edge; 99 100 wire w_tx_end; 101 wire w_tx_refresh; 102 reg [7:0] r_tx_bit_cnt; 103 always@(posedge i_sys_clk) 104 begin 105 if(w_spi_reset | w_tx_end) 106 begin 107 r_tx_bit_cnt <= 'd1; 108 end 109 else if(w_spi_tx_edge) 110 begin 111 r_tx_bit_cnt <= r_tx_bit_cnt + 1'b1; 112 end 113 end 114 assign w_tx_end = (r_tx_bit_cnt == 4'd8) & w_spi_tx_edge; 115 assign w_tx_refresh = (CPHA )? (r_tx_bit_cnt == 4'd1) & w_spi_tx_edge : (r_tx_bit_cnt == 4'd8) & w_spi_tx_edge; 116 117 /******************************************************************************\ 118 SPI slave tx data 119 \******************************************************************************/ 120 reg [7:0] r_spi_rd_data; 121 always@(posedge i_sys_clk) 122 begin 123 if(w_spi_reset) 124 begin 125 r_spi_rd_data <= 'd0; 126 end 127 else if(o_ctrl_addr_en) 128 begin 129 r_spi_rd_data <= i_ctrl_rd_data; 130 end 131 end 132 133 always@(posedge i_sys_clk) 134 begin 135 if(w_spi_reset) 136 begin 137 r_spi_txd <= 'd0; 138 end 139 else if(w_tx_refresh) 140 begin 141 r_spi_txd <= r_spi_rd_data; 142 end 143 else if(w_spi_tx_edge) 144 begin 145 r_spi_txd <= {r_spi_txd[6:0],1'b0}; 146 end 147 end 148 149 assign o_spi_miso = r_spi_txd[7]; 150 /******************************************************************************\ 151 SPI addr and data operation 152 \******************************************************************************/ 153 always@(posedge i_sys_clk) 154 begin 155 if(w_spi_reset) 156 begin 157 r_first_byte <= 1'b1; 158 end 159 else if(r_spi_rx_valid) 160 begin 161 r_first_byte <= 1'b0; 162 end 163 end 164 165 always@(posedge i_sys_clk) 166 begin 167 if(w_spi_reset) 168 begin 169 r_rx_byte_valid <= 'd0; 170 end 171 else if((r_bit_cnt == 4'd8) & w_spi_rx_edge) 172 begin 173 r_rx_byte_valid <= 1'b1; 174 end 175 else 176 begin 177 r_rx_byte_valid <= 1'b0; 178 end 179 end 180 181 always@(posedge i_sys_clk) 182 begin 183 if(w_spi_reset) 184 begin 185 r_spi_addr <= 'd0; 186 r_spi_din <= 'd0; 187 r_spi_rx_valid <= 'd0; 188 end 189 else if(r_rx_byte_valid) 190 begin 191 r_spi_rx_valid <= 1'b1; 192 if(r_first_byte) 193 begin 194 r_spi_addr <= r_spi_s_rxd; 195 end 196 else 197 begin 198 r_spi_addr[6:0] <= r_spi_addr[6:0] + 1'b1;//Continuous read/write operation 199 r_spi_din <= r_spi_s_rxd; 200 end 201 end 202 else 203 begin 204 r_spi_rx_valid <= 'd0; 205 end 206 end 207 208 assign o_ctrl_addr_en = r_spi_rx_valid; 209 assign o_ctrl_wr_en = ~r_first_byte & r_spi_rx_valid & r_spi_addr[7]; 210 assign o_ctrl_rd_addr = r_spi_addr[6:0]; 211 assign o_ctrl_wr_data = r_spi_din; 212 assign o_ctrl_wr_addr = r_first_byte ? r_spi_addr[6:0] : r_spi_addr[6:0] - 1'b1; 213 214 endmodule
以上便是SPI从机的主要代码,可根据实际需求做修改,笔者做了个SPI从机IO扩展的工程。IO扩展模块是根据前面提到的GPIO模块进行修改,不做展示。下面的仿真文件参考了https://www.cnblogs.com/fhyfhy/p/4429302.html,该博主的SPI从机讲得也挺好,可供参考。
SPI_slave_tb.v
1 `timescale 1 ns/1 ns 2 `define TEST3 3 `ifdef TEST0 4 `define CPOL 1'b0 5 `define CPHA 1'b0 6 `endif 7 `ifdef TEST1 8 `define CPOL 1'b0 9 `define CPHA 1'b1 10 `endif 11 `ifdef TEST2 12 `define CPOL 1'b1 13 `define CPHA 1'b0 14 `endif 15 `ifdef TEST3 16 `define CPOL 1'b1 17 `define CPHA 1'b1 18 `endif 19 20 module SPI_slave_tb ; 21 reg clk ; 22 reg rst_n ; 23 24 reg spi_cs ; 25 reg spi_sck ; 26 wire spi_miso ; 27 reg spi_mosi ; 28 29 30 SPI_slave 31 #( 32 .GPIO_NUM (32 ), 33 .CPOL (`CPOL ), 34 .CPHA (`CPHA ) 35 ) 36 u_SPI_slave 37 ( 38 .i_sys_clk (clk ), 39 .i_sys_rstn (rst_n ), 40 .i_spi_cs (spi_cs ), 41 .i_spi_sclk (spi_sck ), 42 .i_spi_mosi (spi_mosi ), 43 .o_spi_miso (spi_miso ), 44 .i_gpio_i (32'ha56d57f3 ), 45 .o_gpio_o ( ), 46 .o_gpio_t ( ) 47 48 ); 49 50 51 52 parameter tck = 100; 53 parameter t = 1000/tck; 54 55 always 56 #(t/2) clk = ~clk; 57 58 59 //------------------------------- 60 61 task spi_sd; 62 input [7:0] data_in; 63 begin 64 65 /* //00 66 spi_sck = 0; 67 #(5*t); 68 spi_sck = 0; spi_mosi= data_in[7]; #(5*t); spi_sck = 1; #(5*t); //send bit[7] 69 spi_sck = 0; spi_mosi= data_in[6]; #(5*t); spi_sck = 1; #(5*t); //send bit[6] 70 spi_sck = 0; spi_mosi= data_in[5]; #(5*t); spi_sck = 1; #(5*t); //send bit[5] 71 spi_sck = 0; spi_mosi= data_in[4]; #(5*t); spi_sck = 1; #(5*t); //send bit[4] 72 spi_sck = 0; spi_mosi= data_in[3]; #(5*t); spi_sck = 1; #(5*t); //send bit[3] 73 spi_sck = 0; spi_mosi= data_in[2]; #(5*t); spi_sck = 1; #(5*t); //send bit[2] 74 spi_sck = 0; spi_mosi= data_in[1]; #(5*t); spi_sck = 1; #(5*t); //send bit[1] 75 spi_sck = 0; spi_mosi= data_in[0]; #(5*t); spi_sck = 1; #(5*t); //send bit[0] 76 spi_sck = 0; */ 77 78 79 /* //01 80 spi_sck = 0; 81 #(5*t); 82 spi_sck = 0; #(5*t); spi_mosi= data_in[7]; spi_sck = 1; #(5*t); //send bit[7] 83 spi_sck = 0; #(5*t); spi_mosi= data_in[6]; spi_sck = 1; #(5*t); //send bit[6] 84 spi_sck = 0; #(5*t); spi_mosi= data_in[5]; spi_sck = 1; #(5*t); //send bit[5] 85 spi_sck = 0; #(5*t); spi_mosi= data_in[4]; spi_sck = 1; #(5*t); //send bit[4] 86 spi_sck = 0; #(5*t); spi_mosi= data_in[3]; spi_sck = 1; #(5*t); //send bit[3] 87 spi_sck = 0; #(5*t); spi_mosi= data_in[2]; spi_sck = 1; #(5*t); //send bit[2] 88 spi_sck = 0; #(5*t); spi_mosi= data_in[1]; spi_sck = 1; #(5*t); //send bit[1] 89 spi_sck = 0; #(5*t); spi_mosi= data_in[0]; spi_sck = 1; #(5*t); //send bit[0] 90 spi_sck = 0; */ 91 92 /* //10 93 spi_sck = 1; 94 #(5*t); 95 spi_sck = 1; spi_mosi= data_in[7]; #(5*t); spi_sck = 0; #(5*t); //send bit[7] 96 spi_sck = 1; spi_mosi= data_in[6]; #(5*t); spi_sck = 0; #(5*t); //send bit[6] 97 spi_sck = 1; spi_mosi= data_in[5]; #(5*t); spi_sck = 0; #(5*t); //send bit[5] 98 spi_sck = 1; spi_mosi= data_in[4]; #(5*t); spi_sck = 0; #(5*t); //send bit[4] 99 spi_sck = 1; spi_mosi= data_in[3]; #(5*t); spi_sck = 0; #(5*t); //send bit[3] 100 spi_sck = 1; spi_mosi= data_in[2]; #(5*t); spi_sck = 0; #(5*t); //send bit[2] 101 spi_sck = 1; spi_mosi= data_in[1]; #(5*t); spi_sck = 0; #(5*t); //send bit[1] 102 spi_sck = 1; spi_mosi= data_in[0]; #(5*t); spi_sck = 0; #(5*t); //send bit[0] 103 spi_sck = 1; */ 104 105 //11 106 spi_sck = 0; 107 #(5*t); 108 spi_sck = 0; spi_mosi= data_in[7]; #(5*t); spi_sck = 1; #(5*t); //send bit[7] 109 spi_sck = 0; spi_mosi= data_in[6]; #(5*t); spi_sck = 1; #(5*t); //send bit[6] 110 spi_sck = 0; spi_mosi= data_in[5]; #(5*t); spi_sck = 1; #(5*t); //send bit[5] 111 spi_sck = 0; spi_mosi= data_in[4]; #(5*t); spi_sck = 1; #(5*t); //send bit[4] 112 spi_sck = 0; spi_mosi= data_in[3]; #(5*t); spi_sck = 1; #(5*t); //send bit[3] 113 spi_sck = 0; spi_mosi= data_in[2]; #(5*t); spi_sck = 1; #(5*t); //send bit[2] 114 spi_sck = 0; spi_mosi= data_in[1]; #(5*t); spi_sck = 1; #(5*t); //send bit[1] 115 spi_sck = 0; spi_mosi= data_in[0]; #(5*t); spi_sck = 1; #(5*t); //send bit[0] 116 spi_sck = 1; 117 end 118 endtask 119 120 121 initial 122 begin 123 clk = 0; 124 rst_n = 0; 125 spi_cs = 1; 126 127 //spi_sck = 0; //CPOL=0 128 129 spi_sck = 1;//CPOL=1 130 131 132 spi_mosi = 1; 133 #(20*t) rst_n = 1; 134 repeat(1) 135 begin 136 #(10*t); 137 spi_cs = 0; 138 spi_sd(8'h80); 139 #(50*t); 140 spi_sd(8'h58); 141 #(50*t); 142 spi_sd(8'h43); 143 #(50*t); 144 spi_sd(8'h8f); 145 #(50*t); 146 spi_sd(8'h7e); 147 #(50*t); 148 spi_cs = 1; 149 end 150 151 repeat(1) 152 begin 153 #(10*t); 154 spi_cs = 0; 155 spi_sd(8'h00); 156 #(50*t); 157 spi_sd(8'h58); 158 #(50*t); 159 spi_sd(8'h43); 160 #(50*t); 161 spi_sd(8'h8f); 162 #(50*t); 163 spi_sd(8'h7e); 164 #(50*t); 165 spi_cs = 1; 166 end 167 168 169 #(20*t); 170 spi_cs = 0; 171 spi_sd(8'h00); 172 #(50*t); 173 spi_sd(8'ha5); 174 #(50*t); 175 spi_cs = 1; 176 177 #(20*t); 178 spi_cs = 0; 179 spi_sd(8'h85); 180 #(50*t); 181 spi_sd(8'ha5); 182 #(50*t); 183 spi_cs = 1; 184 185 #(20*t); 186 spi_cs = 0; 187 spi_sd(8'h03); 188 #(50*t); 189 spi_sd(8'hf4); 190 #(50*t); 191 spi_cs = 1; 192 193 #(20*t); 194 spi_cs = 0; 195 spi_sd(8'hff); 196 #(50*t); 197 spi_sd(8'hff); 198 #(50*t); 199 spi_cs = 1; 200 201 #(100*t); 202 spi_cs = 0; 203 spi_sd(8'hba); 204 #(50*t); 205 spi_sd(8'hae); 206 #(50*t); 207 spi_cs = 1; 208 209 #(20*t); 210 spi_cs = 0; 211 spi_sd(8'h5a); 212 #(50*t); 213 spi_sd(8'hee); 214 #(50*t); 215 spi_cs = 1; 216 end 217 218 219 220 221 endmodule
以下是根据tb文件仿真的结果,上述SPI接口代码定义了第一Byte数据最高位为读写位,剩下7位作为地址。bit[7] = 0为读操作,bit[7] = 1为写操作。以下是CPOL = 1,CPHA = 1模式。
写操作:
读操作: