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:主机输入,从机输出。

         CS/SS:从机设备选择,低电平有效。
  
  SPI有四种常见的模式,主要是根据SCLK的初始电平及采样边沿来定。4种模式分别由CPOL(Clock Polarity)时钟极性和CPHA(Clock Phase)时钟相位的不同组合确定的。
  
  对应的时序图如下:
  
  根据SPI协议的时序,写相关代码。笔者现在写代码尽可能考虑代码的通用性,一次SPI从机代码就包含了4种模式。另外,笔者这里使用高速时钟去采低速SPI时钟的方式是为了避免毛刺对信号的影响,由于会产生系统时钟的延时,这里大概是4~5个系统时钟延时,所以此代码只能对较高的系统时钟,SPI时钟较低的情况使用。例如100MHz系统时钟下,该SPI从机最高大概能支持20MHz的SPI主机速率,当然这只是理想情况下的,实际可能要考虑延时问题等,可能最大能支持的SPI时钟低于15MHz。代码比较粗糙,仅供参考。
  SPI_slave_if.v:
  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模式。

  写操作:

  读操作:

posted on 2022-05-29 18:45  Galois_V  阅读(1180)  评论(0编辑  收藏  举报