SPI协议介绍
spi是serial peripheral interface的缩写,即串行扩展总线。SPI是单主设备通信,总线中只有一个主设备发起通信,能发起通信的设备称为主设备。当SPI主设备想读写从设备时,首先拉低对应从设备的ss线(低电平有效)。然后发送工作麦种到时钟线上,在相应的脉冲时间上,主设备把信号发送到MOSI实现读写,同时又可以对MISO采样实现读。一般SPI通信涉及到一下术语:
SCLKserial clock (来自主设备)MOSIMaster Output Slave Input(来自主设备)MISOMaster Input Slave Output(来自从设备)SSSlave Select(低电平有效,来自主设备)
主设备和从设备的两种链接方式
一主一从
一主一从模式表明有一个主机和一个从机,如下所示:(其中还有SCK信号,由主机到从机)
一主多从
一主多从表示有一个主机和一个从机,唯一不同的点是要为每个从机配备一个选择信号。
SPI协议的工作模式
SPI有四种工作模式,主要由时钟极性CPOL(Clock Polarity),时钟相位CPHA(Clock Phase)的组合决定。
- CPOL为0,表示SCK在空闲状态为0,为1,则表示SCK在空闲状态为1
- CPHA为0,表示在SCK的第一个边沿时输出输出数据有效,CPHA为1时,表示在SCK的第二个边沿输入输出数据有效
CPOL = 0, CPHA = 0
CPOL = 0, CPHA = 1
CPOL = 1. CPHA = 0
CPOL = 1, CPHA = 1
SPI MASTER 的verilog设计思路
设计的引脚说明:
信号名方向 +解释clk输入,时钟信号rst_n输入,复位信号miso输入,从机输入到主机data_i输入,主机发送从机的数据(一定比特位宽)start输入,开始的使能信号mosi输出,主机到从机sclk输出,时钟信号ss_n输出,从机的选择信号finish一次传输完成信号
首先将需要的宏定义收录在一个文件defines.v中,其中的代码如下所示:
`define CPOL 0//clock polarity
`define CPHA 0//clock phase
`define CLK_FREQ 50_000_000 // input clk frequency
`define SCLK_FREQ 5_000_000 // sclk frequency
`define DATA_WIDTH 8// a word width
`define CLK_CYCLE 20
其次时SPI MASTER的设计思路,总体思路时采用一个状态机,首先状态机是在IDLE状态,然后接收开始信号后,会将寄存器data_I中数据一个一个的发送出去,当指定宽距的比特位发送完成后。此时有两种选择,一种是跳转到FINISH状态,另一种是跳转到EXTRA状态,在跳转到FINISH状态。主要是由于当数据发送完成之后要判断此时的SCLK状态是不是空闲下的默认状态,如果不是,则需要跳转到EXTRA状态。结束状态下一个状态回到IDLE状态,得到start命令。
IDLE空闲状态DATA发送数据状态EXTRA额外状态FINISH结束状态
verilog的代码如下:
`include "defines.v"
module SPI_MASTER(
input wire clk ,
input wire rst_n ,
input wire miso ,
input wire [`DATA_WIDTH-1:0] data_i ,
input wire start ,
output wire mosi ,
output reg sclk ,
output reg ss_n ,
output wire finish
);
parameter IDLE =5'b00001 ,//CHOOSE = 5'b00010 ,
DATA =5'b00100 ,
EXTRA =5'b01000 ,
FINISH =5'b10000 ;
parameter CNT_MAX = `CLK_FREQ / `SCLK_FREQ -1;
reg [31:0] cnt ;//sclk的时钟周期的计数器
reg [4:0] state ;
reg [4:0] nx_state ;
wire [3:0] cnt_data ;//输出的数据计数器
reg sclk_dly ;//sclk的打一拍信号
reg [3:0] cnt_sclk_pos ;//sclk的上升沿计数器信号
reg [3:0] cnt_sclk_neg ;//sclk的下降沿计数器信号
reg start_dly ;//start的打一拍信号
reg [3:0] cnt_data_dly ;
wire cnt_max_flag ;//计数器cnt达到最大值的信号
wire dec_pos_or_neg_sample ;//1 posedge sample, 0 negedge sample
wire sclk_posedge ;//sclk的上升沿
wire sclk_negedge ;//sclk的下降沿
assign dec_pos_or_neg_sample =(`CPOL == `CPHA)?1'b1 : 1'b0;
assign cnt_max_flag =(cnt == CNT_MAX )?1'b1 : 1'b0;
assign sclk_posedge =((sclk ==1'b1) && (sclk_dly == 1'b0))?1'b1 : 1'b0;
assign sclk_negedge =((sclk ==1'b0) && (sclk_dly == 1'b1))?1'b1 : 1'b0;
assign cnt_data = dec_pos_or_neg_sample ? cnt_sclk_pos : cnt_sclk_neg;
always @(posedge clk or negedge rst_n) begin
sclk_dly <= sclk;
start_dly <= start;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_pos <=4'd0;
end
elseif(state == FINISH) begin
cnt_sclk_pos <=4'd0;
end
//else if((sclk_posedge) && (cnt_sclk_pos == `DATA_WIDTH - 1)) begin// cnt_sclk_pos <= `DATA_WIDTH - 1;//endelseif(sclk_posedge) begin
cnt_sclk_pos <= cnt_sclk_pos +1'b1;
end
else begin
cnt_sclk_pos <= cnt_sclk_pos;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_neg <=4'd0;
end
elseif(state == FINISH) begin
cnt_sclk_neg <=4'd0;
end
//else if((sclk_negedge) && (cnt_sclk_neg == `DATA_WIDTH - 1)) begin// cnt_sclk_neg <= `DATA_WIDTH - 1;//endelseif(sclk_negedge) begin
cnt_sclk_neg <= cnt_sclk_neg +1'b1;
end
else begin
cnt_sclk_neg <= cnt_sclk_neg;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <= IDLE;
end
else begin
state <= nx_state;
end
end
always @(*) begin
nx_state <= IDLE;case(state)
IDLE: nx_state <= start_dly ? DATA : IDLE;
DATA: nx_state <=(cnt_data == `DATA_WIDTH)?(`CPHA ==0)? EXTRA : FINISH : DATA;
EXTRA: nx_state <= cnt_max_flag ? FINISH : EXTRA ;
FINISH: nx_state <= IDLE;default:nx_state <= IDLE;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt <= 'd0;
end
elseif((state == DATA)&&(nx_state == FINISH)&&(cnt == CNT_MAX)) begin
cnt <= 'd0;
end
elseif((state == DATA)&&(nx_state == EXTRA)&&(cnt == CNT_MAX)) begin
cnt <= 'd0;
end
elseif((state == DATA)&&(cnt == CNT_MAX)) begin
cnt <= 'd0;
end
elseif((state == EXTRA)&&(cnt == CNT_MAX)) begin
cnt <= 'd0;
end
elseif(state == DATA) begin
cnt <= cnt +1'b1;
end
elseif(state == EXTRA) begin
cnt <= cnt +1'b1;
end
else begin
cnt <= 'd0;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
sclk <=(`CPOL)?1'b1 : 1'b0;
end
elseif(start_dly) begin
sclk <=~sclk;
end
elseif((state == DATA)&&(cnt_max_flag)&&(cnt_data < `DATA_WIDTH)) begin
sclk <=~sclk;
end
elseif((state == DATA)&&(cnt_max_flag)&&(cnt_data == `DATA_WIDTH)&&(nx_state == EXTRA)) begin
sclk <=~sclk;
end
elseif((state == EXTRA)&&(cnt_max_flag)&&(nx_state == FINISH)) begin
sclk <=~sclk;
end
else begin
sclk <= sclk;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
ss_n <=1'b1;
end
elseif(start) begin
ss_n <=1'b0;
end
elseif(state == FINISH) begin
ss_n <=1'b1;
end
else begin
ss_n <= ss_n;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_data_dly <= 'd0;
end
else begin
cnt_data_dly <= cnt_data;
end
end
assign finish =(state == FINISH)?1'b1 : 1'b0;
assign mosi =(state == DATA)?((cnt_data_dly < `DATA_WIDTH)? data_i[cnt_data_dly]: data_i[`DATA_WIDTH-1]): data_i[0];
endmodule
仿真的testbench如下:
`timescale 1ns/1ns
`include "defines.v"
module tb_master_slave();
reg clk ;
reg rst_n ;//reg mosi ;
reg [`DATA_WIDTH-1:0] data_i ;
reg start ;
reg miso ;
wire mosi ;
wire sclk ;
wire finish ;
wire ss_n ;
wire [`DATA_WIDTH-1:0] data_o ;
wire r_finish ;
SPI_MASTER u_spi_master(.clk(clk),.rst_n(rst_n),.miso(miso),.data_i(data_i),.start(start),.mosi(mosi),.sclk(sclk),.finish(finish),.ss_n(ss_n));
SPI_SLAVE u_spi_slave(.clk(clk),.rst_n(rst_n),.mosi(mosi),.sclk(sclk),.tx_finish(finish),.start(start),.ss_n(ss_n),.data_o(data_o),.r_finish(r_finish));
initial begin
clk =1'b0;
rst_n =1'b0;
start =1'b0;
data_i =8'h35;
miso =1'b0;
#30
rst_n =1'b1;
#10;
@(posedge clk);
start <=1'b1;
@(posedge clk);
start <=1'b0;
@(negedge finish);
data_i =8'h44;repeat(2) @(posedge clk);
start =1'b1;
@(posedge clk);
start =1'b0;
end
always #(`CLK_CYCLE /2) clk =~clk;
endmodule
仿真波形如下,可以通过mosi成功发出了数据。
SPI SLAVE设计思路
SPI SPLAVE的设计思路大体如master类似,端口说明如下:
信号名方向 +解释clk输入,时钟信号rst_n输入,复位信号miso输入,从机输入到主机data_i输入,主机发送从机的数据(一定比特位宽)start输入,开始的使能信号mosi输出,主机到从机sclk输入,时钟信号ss_n输入,从机的选择信号r_finish一次传输完成信号data_o输出,收集到的数据
依然采用的是状态机思路,首先在IDLE状态,当开始信号使能之后,会跳转到RV_DATA接收到数据状态。RV_DATA数据接收完成,之后回跳转到FINISH状态,表明此次读取完成。
IDLE空闲状态RV_ DATA接收数据状态FINISH结束状态spi slave的代码如下所示:
`include "defines.v"
module SPI_SLAVE(
input wire clk ,
input wire rst_n ,
input wire mosi ,
input wire sclk ,
input wire tx_finish ,
input wire start ,
input wire ss_n ,
output wire [`DATA_WIDTH-1:0] data_o ,//output wire miso ,
output wire r_finish
);
parameter IDLE =4'b0001 ,
RV_DATA =4'b0010 ,
FINISH =4'b0100 ;
wire sclk_posedge ;
wire sclk_negedge ;
wire dec_pos_or_neg_sample;//wire sclk_posedge ;//wire sclk_negedge ;
reg sclk_dly ;
reg [`DATA_WIDTH-1:0] data_shift_pos ;
reg [`DATA_WIDTH-1:0] data_shift_neg ;
reg [3:0] state ;
reg [3:0] nx_state ;
reg [3:0] cnt_sclk_pos ;
reg [3:0] cnt_sclk_neg ;
wire [3:0] num_sample_data ;
assign sclk_posedge =((sclk ==1'b1) && (sclk_dly == 1'b0))?1'b1 : 1'b0;
assign sclk_negedge =((sclk ==1'b0) && (sclk_dly == 1'b1))?1'b1 : 1'b0;
assign dec_pos_or_neg_sample =(`CPOL == `CPHA)?1'b1 : 1'b0;//assign sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;//assign sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
assign num_sample_data =(dec_pos_or_neg_sample)? cnt_sclk_pos : cnt_sclk_neg;
always @(posedge clk or negedge rst_n) begin
sclk_dly <= sclk;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <= IDLE;
end
else begin
state <= nx_state;
end
end
always @(*) begin
nx_state <= IDLE;case(state)
IDLE: nx_state <= start ? RV_DATA :IDLE;
RV_DATA: begin
if((num_sample_data ==7)&&(dec_pos_or_neg_sample)&&(sclk_posedge)&&(!ss_n)) begin
nx_state <= FINISH;
end
elseif((num_sample_data ==7)&&(~dec_pos_or_neg_sample)&&(sclk_negedge)&&(!ss_n)) begin
nx_state <= FINISH;
end
else begin
nx_state <= RV_DATA;
end
end
FINISH: nx_state <= IDLE;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_pos <=4'd0;
end
elseif((state == FINISH)) begin
cnt_sclk_pos <=4'd0;
end
elseif(sclk_posedge) begin
cnt_sclk_pos <= cnt_sclk_pos +1'b1;
end
else begin
cnt_sclk_pos <= cnt_sclk_pos;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_neg <=4'd0;
end
elseif(state == FINISH) begin
cnt_sclk_neg <=4'd0;
end
elseif(sclk_negedge) begin
cnt_sclk_neg <= cnt_sclk_neg +1'b1;
end
else begin
cnt_sclk_neg <= cnt_sclk_neg;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_shift_pos <={`DATA_WIDTH{1'b0}};
end
elseif((state == RV_DATA)&&(sclk_posedge)) begin
data_shift_pos <={mosi, data_shift_pos[`DATA_WIDTH-1:1]};
end
elseif(state == FINISH) begin
data_shift_pos <={`DATA_WIDTH{1'b0}};
end
else begin
data_shift_pos <= data_shift_pos;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_shift_neg <={`DATA_WIDTH{1'b0}};
end
elseif((state == RV_DATA)&&(sclk_negedge)) begin
data_shift_neg <={mosi, data_shift_neg[`DATA_WIDTH-1:1]};
end
elseif(state == FINISH) begin
data_shift_neg <={`DATA_WIDTH{1'b0}};
end
else begin
data_shift_neg <= data_shift_neg;
end
end
//assign data_o = dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg;
assign data_o =(state == FINISH)?(dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg):{`DATA_WIDTH{1'b0}};
assign r_finish =(state == FINISH);
endmodule
SPI MASTRT 和 SLAVE联合仿真
testbench如下所示:
`timescale 1ns/1ns
`include "defines.v"
module tb_master_slave();
reg clk ;
reg rst_n ;//reg mosi ;
reg [`DATA_WIDTH-1:0] data_i ;
reg start ;
reg miso ;
wire mosi ;
wire sclk ;
wire finish ;
wire ss_n ;
wire [`DATA_WIDTH-1:0] data_o ;
wire r_finish ;
SPI_MASTER u_spi_master(.clk(clk),.rst_n(rst_n),.miso(miso),.data_i(data_i),.start(start),.mosi(mosi),.sclk(sclk),.finish(finish),.ss_n(ss_n));
SPI_SLAVE u_spi_slave(.clk(clk),.rst_n(rst_n),.mosi(mosi),.sclk(sclk),.tx_finish(finish),.start(start),.ss_n(ss_n),.data_o(data_o),.r_finish(r_finish));
initial begin
clk =1'b0;
rst_n =1'b0;
start =1'b0;
data_i =8'h35;
miso =1'b0;
#30
rst_n =1'b1;
#10;
@(posedge clk);
start <=1'b1;
@(posedge clk);
start <=1'b0;
@(negedge finish);
data_i =8'h44;repeat(2) @(posedge clk);
start =1'b1;
@(posedge clk);
start =1'b0;
end
always #(`CLK_CYCLE /2) clk =~clk;
endmodule
仿真的波形如下,可知成功的读取到了8’h35和8’h44。验证成功。
总结
通过这次spi的协议的编写,感觉自己对于状态机的掌握,以及脑子里可以浮现波形。加油加油!!!
版权归原作者 IC2ICU 所有, 如有侵权,请联系我们删除。