提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
SPI(Serial Peripheral Interface)是一种同步串行通信协议,用于在微控制器、传感器、存储器等外设之间进行数据交换。SPI总线是一种全双工的通信方式,它由一个主设备(Master)和一个或多个从设备(Slave)组成。
spi总线的特点:
1、传输方式:SPI总线使用同步的时钟信号进行通信,主设备控制时钟信号的频率和极性。数据在时钟的边沿上升或下降时转移。SPI总线支持全双工通信,意味着主设备和从设备可以同时发送和接收数据。
2、线路结构:SPI总线具有四条线路:时钟线(SCLK),主设备输出的数据线(MOSI),主设备接收的数据线(MISO)和片选线(CS)。
时钟线(SCLK):时钟信号线,由主机发出,不同的主机可能具有不同的时钟速率,也就是通信频率。
主设备输出的数据线(MOSI):主机输出数据,从机接收数据。(M代表Master,S代表Slave)
主设备接收的数据线(MISO):主机接收数据,从机输出数据。
片选线(CS):由主机选择与某一从机进行通讯。常为低电平有效
一主一从模式:
一主多从模式:
(PS:借用一下大佬孤独的单刀的图,侵权请联系我,会立刻删除,原文章链接:FPGA实现的SPI协议(一)----SPI驱动_fpga spi_孤独的单刀的博客-CSDN博客)
SPI的工作模式通常由两个因素决定:一是时钟极性(CPOL,Clock Polarity),二是时钟相位(Clock Phase)。其中时钟极性决定了SCLK在空闲时是低电平还是高电平,时钟相位决定了在什么沿采集数据。
(PS:借用一下大佬孤独的单刀的图,侵权请联系我,会立刻删除,原文章链接::)
通过象限可以将这四种模式进行简要概括:
SPI总线的优缺点:
优点:
1、灵活:SPI可以使用不同的工作模式,以满足不同场景的需要。SPI主设备可以配置不同的时钟频率,灵活可变。
2、高速:相比与串口(UART)、IIC接口,SPI具有很高的通信频率
3、简单的线路结构,只有SCLK、MOSI、MISO、CS 4条线路
4、不限于8位,一次可以传输任意大小的字
缺点:
1、仅支持一个主设备,从设备被动响应,不支持从设备主动传输数据。
2、虽然SPI总线线路相对简单,但每个外设都需要至少四条线路,因此在连接多个外设时,线路数量会增加。(每增加一个从机都会增加一个片选信号)
3、主/从机没有应答信号
--------------------------------------------------------------------------------------
说明:模式0,一主一从模式,使用一段式状态机实现。
(PS:不使用状态机的SPI驱动程序请阅读大佬孤独的单刀的文章:
FPGA实现的SPI协议(一)----SPI驱动_fpga spi_孤独的单刀的博客-CSDN博客)
主机代码:
- `timescale 1ns / 1ps
- //
- // Company:
- // Engineer:
- //
- // Create Date: 2023/09/01 17:04:04
- // Design Name:
- // Module Name: SPI_derive
- // Project Name:
- // Target Devices:
- // Tool Versions:
- // Description:
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- // SPI总线主机驱动程序
- //
-
-
- module SPI_derive #(
- parameter integer DW = 8
- )
- (
- input sys_clk , //系统时钟50M
- input sys_rst , //复位信号低电平有效
-
- input[DW-1:0] din , //要发送的数据
- input spi_start , //SPI驱动程序启动信号,一个高电平
- input spi_end , //SPI驱动程序结束信号
- output reg [DW-1:0] dout , //接收的数据
- output reg rec_over , //单个字节接收完毕信号
- output reg send_over , //单个字节发送结束信号
-
- output reg sclk , //SPI程序的SCLK 本程序是对系统时钟的2分频
- output reg cs_n , //SPI片选信号
- output reg mosi , //SPI输出,给从机发送数据
- input miso //SPI输入,从机发送过来的数据
-
- );
-
- localparam idle = 3'b001 ;
- localparam send_recv = 3'b010 ;
- localparam over = 3'b100 ;
- reg[2:0] state ;
-
- reg[DW-1:0] recv_data = 'd0 ;
- reg clk_2d = 'd0 ;
- reg[2:0] send_cnt = 'd0 ;
- reg[2:0] recv_cnt = 'd0 ;
- reg[1:0] cs_n_cnt = 'd0 ;
-
-
-
- always@(posedge sys_clk,negedge sys_rst)begin
- if(!sys_rst == 1)begin
- state <= idle;
- cs_n <= 1'b1;
- sclk <= 1'b0;
- mosi <= 1'b1;
- send_cnt<= 'd0 ;
- recv_cnt<= 'd0 ;
- dout <= 'd0 ;
- send_over <= 1'b0;
- rec_over <= 1'b0;
- cs_n_cnt <= 2'b0;
- end
- else begin
- case(state)
- idle:
- begin
- if(spi_start == 1)begin
- state <= send_recv;
- end
- else begin
- state <= state;
- end
- end
- send_recv: //send and receive
- begin
- if(spi_end == 1)begin
- state <= over;
- end
- else begin
- state <= state;
- end
-
- if(cs_n_cnt == 2 || sclk == 1)begin //发送 相对于CS的下降沿,发送延迟1个SCLK周期
- mosi <= din[DW-1-send_cnt];
- if(send_cnt < 3'b111)begin
- send_cnt <= send_cnt + 1 ;
- end
- else begin
- send_cnt <= 3'b0;
- end
- end
- else begin
- mosi <= mosi;
- send_cnt <= send_cnt;
- end
-
- if(send_cnt == 3'b111 && sclk == 1)begin
- send_over <= 1'b1;
- end
- else begin
- send_over <= 1'b0;
- end
- ///
- if(cs_n_cnt > 2 && sclk == 0)begin //接收
- recv_data[DW-1-recv_cnt] <= miso;
- if(recv_cnt < 3'b111)begin
- recv_cnt <= recv_cnt + 1;
- rec_over <= 1'b0;
- end
- else begin
- rec_over <= 1'b1;
- recv_cnt <= 3'b0;
- end
- end
- else begin
- recv_data<= recv_data;
- recv_cnt <= recv_cnt;
- rec_over <= rec_over;
- end
-
- if(recv_cnt == 3'b000 && sclk == 1)begin
- rec_over <= 1'b1;
- dout <= recv_data;
- end
- else begin
- rec_over <= 1'b0;
- dout <= 'd0;
- end
-
-
-
- cs_n <= 1'b0;
- if(cs_n_cnt < 3)begin
- cs_n_cnt <= cs_n_cnt + 1;
- end
- else begin
- cs_n_cnt <= cs_n_cnt;
- end
-
-
- if(cs_n_cnt < 3)begin //空余出1个的SCLK周期,使得主从机收发的数据对其
- sclk <= sclk;
- end
- else begin
- sclk <= !sclk;
- end
- end
- over:
- begin
- state <= idle;
- cs_n <= 1'b1;
- sclk <= 1'b0;
- mosi <= 1'b1;
- send_cnt <= 'd0 ;
- recv_cnt<= 'd0 ;
- dout <= 'd0 ;
- send_over <= 1'b0;
- rec_over <= 1'b0;
- cs_n_cnt <= 2'b0;
- end
- endcase
- end
- end
-
-
-
-
- endmodule
从机代码:
- `timescale 1ns / 1ps
- //
- // Company:
- // Engineer:
- //
- // Create Date: 2023/09/01 17:04:04
- // Design Name:
- // Module Name: SPI_derive
- // Project Name:
- // Target Devices:
- // Tool Versions:
- // Description:
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- // SPI总线从机驱动程序
- //
-
-
- module spi_slave #(
- parameter integer DW = 8
- )
- (
- input sys_clk , //系统时钟50M
- input sys_rst , //复位信号低电平有效
-
- input[DW-1:0] din , //要发送的数据
- output reg spi_start , //SPI驱动程序启动信号,通知后级SPI程序已经启动
- output reg spi_end , //SPI驱动程序结束信号,通知后级SPI程序已经结束
- output reg [DW-1:0] dout , //接收的数据
- output reg rec_over , //单个字节接收完毕信号
- output reg send_over , //单个字节发送结束信号
-
- input sclk , //SPI程序的SCLK 本程序是对系统时钟的2分频
- input cs_n , //SPI片选信号
- input mosi , //SPI输入,主机发送过来的数据
- output reg miso //SPI输出,从机发送的数据
-
- );
-
- localparam idle = 3'b001 ;
- localparam send_recv = 3'b010 ;
- localparam over = 3'b100 ;
- reg[2:0] state ;
-
- reg[DW-1:0] recv_data = 'd0 ;
- reg clk_2d = 'd0 ;
- reg[2:0] send_cnt = 'd0 ;
- reg[2:0] recv_cnt = 'd0 ;
- reg sd_rc_flag = 'd0 ;
-
-
-
-
-
- always@(posedge sys_clk,negedge sys_rst)begin
- if(!sys_rst == 1)begin
- state <= idle;
- send_cnt<= 'd0 ;
- recv_cnt<= 'd0 ;
- dout <= 'd0 ;
- send_over <= 1'b0;
- rec_over <= 1'b0;
- sd_rc_flag <= 1'b0;
- spi_start<= 1'b0;
- spi_end <= 1'b0;
- end
- else begin
- case(state)
- idle:
- begin
- send_cnt<= 'd0 ;
- recv_cnt<= 'd0 ;
- dout <= 'd0 ;
- send_over <= 1'b0;
- rec_over <= 1'b0;
- sd_rc_flag <= 1'b0;
- spi_end <= 1'b0;
- if(cs_n == 0)begin
- state <= send_recv;
- spi_start<= 1'b1;
- end
- else begin
- state <= state;
- spi_start<= spi_start;
- end
- end
- send_recv: //send and receive
- begin
- if(cs_n == 1)begin
- state <= over;
- end
- else begin
- state <= state;
- end
-
-
- if(sd_rc_flag == 0 || sclk == 1)begin //发送
- miso <= din[DW-1-send_cnt];
- if(send_cnt < 3'b111)begin
- send_cnt <= send_cnt + 1 ;
- end
- else begin
- send_cnt <= 3'b000;
- end
- end
- else begin
- send_cnt <= send_cnt;
- end
-
- if(send_cnt == 3'b111 && sclk == 1)begin
- send_over <= 1'b1;
- end
- else begin
- send_over <= 1'b0;
- end
- ///
- if(sd_rc_flag == 1 && sclk == 0)begin //接收
- recv_data[DW-1-recv_cnt] <= mosi;
- if(recv_cnt < 3'b111)begin
- recv_cnt <= recv_cnt + 1;
- rec_over <= 1'b0;
- end
- else begin
- rec_over <= 1'b1;
- recv_cnt <= 3'b000;
- end
- end
- else begin
- recv_data<= recv_data;
- recv_cnt <= recv_cnt;
- rec_over <= rec_over;
- end
-
- if(recv_cnt == 3'b000 && sclk == 1)begin
- rec_over <= 1'b1;
- dout <= recv_data;
- end
- else begin
- rec_over <= 1'b0;
- dout <= 'd0;
- end
-
- spi_start<= 1'b0;
- spi_end <= 1'b0;
- sd_rc_flag <= 1'b1;
- end
- over:
- begin
- state <= idle;
- send_cnt <= 'd0 ;
- recv_cnt<= 'd0 ;
- dout <= 'd0 ;
- send_over <= 1'b0;
- rec_over <= 1'b0;
- sd_rc_flag <= 1'b0;
- spi_start<= 1'b0;
- spi_end <= 1'b1;
-
- end
- endcase
- end
- end
-
-
-
-
- endmodule
主机仿真波形
从机仿真波形
Testbench源代码:
- `timescale 1ns / 1ps
- //
- // Company:
- // Engineer:
- //
- // Create Date: 2023/09/02 10:38:32
- // Design Name:
- // Module Name: tb_spi_derive
- // Project Name:
- // Target Devices:
- // Tool Versions:
- // Description:
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- //
- //
-
-
- module tb_spi_derive();
-
- reg sys_clk ;
- reg sys_rst ;
- reg[7:0] din ;
- reg dval_i ;
- reg spi_start ;
- reg spi_end ;
-
- wire[7:0] dout ;
- wire dval_o ;
- wire rec_over ;
- wire send_over ;
-
- wire sclk ;
- wire cs_n ;
- wire mosi ;
- wire miso ;
-
-
- reg[7:0] din_s ;
- wire spi_start_s ;
- wire spi_end_s ;
-
- wire[7:0] dout_s ;
- wire rec_over_s ;
- wire send_over_s ;
-
-
-
-
-
-
- reg[3:0] send_cnt ;
- reg[3:0] send_cnt_s ;
-
-
-
-
-
- SPI_derive #(
- .DW (8 )
- )
- u_SPI_derive(
- .sys_clk (sys_clk ) ,
- .sys_rst (sys_rst ) ,
- .din (din ),
- .spi_start (spi_start),
- .spi_end (spi_end ),
- .dout (dout ),
- .rec_over (rec_over ),
- .send_over (send_over),
- .sclk (sclk ),
- .cs_n (cs_n ),
- .mosi (mosi ),
- .miso (miso )
- );
-
-
- spi_slave #(
- .DW (8 )
- )
- u_spi_slave(
- .sys_clk (sys_clk ) ,
- .sys_rst (sys_rst ) ,
- .din (din_s ),
- .spi_start (spi_start_s),
- .spi_end (spi_end_s ),
- .dout (dout_s ),
- .rec_over (rec_over_s ),
- .send_over (send_over_s),
- .sclk (sclk ),
- .cs_n (cs_n ),
- .mosi (mosi ),
- .miso (miso )
- );
-
-
-
- initial begin
- sys_clk = 1;
- sys_rst = 0;
- spi_start = 1'b0;
- din = 8'd0;
- dval_i = 1'b0;
- spi_end = 1'b0;
-
-
- #80
- sys_rst = 1;
- din_s = 8'h51;
- #30
- spi_start = 1'b1; din = 8'b00110011;
- #20
- spi_start = 1'b0;
-
- end
-
- always@(posedge sys_clk,negedge sys_rst)
- begin
- if(!sys_rst)begin
- din <= 8'd0;
- spi_end <= 1'b0;
- send_cnt <= 4'b0;
- end
- else if(send_over == 1) begin
- din <= din + 8'b01;
- if(send_cnt < 10)begin
- send_cnt <= send_cnt + 1;
- spi_end <= 1'b0;
- end
- else begin
- send_cnt <= 4'b0;
- spi_end <= 1'b1;
- end
- end
- else begin
- spi_end <= 1'b0;
- send_cnt <= send_cnt;
- din <= din;
- end
- end
-
- always@(posedge sys_clk,negedge sys_rst)
- begin
- if(!sys_rst)begin
- din_s <= 8'b0;
- send_cnt_s<= 4'b0;
- end
- else if(spi_start_s == 1|| send_over_s ==1) begin
- din_s <= din_s + 8'b01;
- if(send_cnt_s < 10)begin
- send_cnt_s <= send_cnt_s + 1;
- end
- else begin
- send_cnt_s <= 4'b0;
- end
- end
- else begin
- send_cnt_s <= send_cnt_s;
- din_s <= din_s;
- end
- end
-
-
-
-
- initial begin
- forever #10 sys_clk = !sys_clk;
- end
-
-
-
-
- endmodule
本文只是对SPI总线的模式0做初步实现,如有错误欢迎指正。