提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
SPI总线的定义
SPI总线工作方式
SPI驱动Verilog实现
总结
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做初步实现,如有错误欢迎指正。