前言
现在大多数的器件都是采用SPI进行通信,但是有的是标准的SPI,由的却不是标准的SPI通信(不遵循CPOL CPHA),为了兼容标准与不标准SPI,特意编写了灵活性较强的SPI。
一、SPI
在Stm32 手册中规定了 SPI的四种形式,我称之为标准的SPI,可以从图中发现在数据发生变化的时间有所不同,这主要是由CPHA决定的若CPHA=1则意味着数据数据是在时钟变化之后变化的,若CPHA=0则意味着数据数据是在时钟变化之前变化的;而CPOL代表的是CLK在闲时的电平。
标准和不标准之说,则是在数据接收时的异同,不标准的SPI在数据接收时,会指定在时钟的上升沿/下降沿返回数据,此类的SPI大多数是某些公司定制的。标准的SPI则依旧按照上图返回数据。
二、代码说明
端口介绍:
module spi_master_v3 #(
parameter TX_DATA_WIDTH = 16,
parameter RX_DATA_WIDTH = 16
)(
input i_sys_clk ,
input i_reset_n ,
input i_start , //发送触发信号,上升沿有效
input i_load_again,
input i_CPOL , //时钟极性 闲时为CPOL
input i_CPHA , //时钟相位 1:在数据来之前变化 0:在是数据来之后发生变化
input i_CS_nVAl , //片选信号有效电平
input i_MSB , //1:大端模式 0:小端模式
input i_RX_nDown , //1:上升沿收数据 0:下降沿收数据
input i_RX_ndirc_L, //1:先接收高位 0:先接收低位
input [7:0]i_div_num ,
input [7:0]i_tx_bit_num ,
input [7:0]i_rx_bit_num ,
input [3:0]i_delay_cycle , //CS延迟的节拍数
input [TX_DATA_WIDTH-1:0]i_tx_data,
output [TX_DATA_WIDTH-1:0]o_rx_data,
output reg o_axi_vali ,
input i_axi_rdy ,
output o_clk ,
output o_cs ,
output o_mosi ,
input i_miso ,
output o_busy
);
load_again 的原理及使用方法:
在i_start 的上升沿时,采集第一次 i_load_again 的值,若i_load_again信号为高,那么
表示主机方,想发送突发数据(可以指定每次突发数据的长度),主机方想发送突发数据时,
只需要将 i_axi_rdy 信号拉高,通知模块准备接收突发数据流,并指定是否还有突发数据,若想
结束突发数据发送,只需将i_load_again拉低即可,模块在接收完成突发数据时,会将o_axi_vali
信号拉高;若在第一次发送数据时,把 i_load_again 拉低则表明只接收帧数据,此时拉高
i_axi_rdy 无效。
1.程序源码
`timescale 1ns / 100ps
//
// Company:
// Engineer:
//
// Create Date: 22:41:44 2020年10月26日
// Design Name:
// Module Name: spi_master_v3
// Project Name:
// Target Devices:
// Tool versions:
// Description:
/*
此版本的SPi总的来说能都应对绝大部分 SPI时序,其中在版本中加入了load_again功能,以及
i_RX_nDown,i_RX_ndirc_L,具体的使用见端口注释
load_again 的原理及使用方法:
在i_start 的上升沿时,采集第一次 i_load_again 的值,若i_load_again信号为高,那么
表示主机方,想发送突发数据(可以指定每次突发数据的长度),主机方想发送突发数据时,
只需要将 i_axi_rdy 信号拉高,通知模块准备接收突发数据流,并指定是否还有突发数据,若想
结束突发数据发送,只需将i_load_again拉低即可,模块在接收完成突发数据时,会将o_axi_vali
信号拉高;若在第一次发送数据时,把 i_load_again 拉低则表明只接收帧数据,此时拉高
i_axi_rdy 无效。
*/
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module spi_master_v3 #(
parameter TX_DATA_WIDTH = 16,
parameter RX_DATA_WIDTH = 16
)(
input i_sys_clk ,
input i_reset_n ,
input i_start ,
input i_load_again,
input i_CPOL , //时钟极性 闲时为CPOL
input i_CPHA , //时钟相位 1:在数据来之前变化 0:在是数据来之后发生变化
input i_CS_nVAl , //片选信号低电平有效
input i_MSB , //1:大端模式 0:小端模式
input i_RX_nDown , //1:上升沿收数据 0:下降沿收数据
input i_RX_ndirc_L, //1:先接收高位 0:先接收低位
input [7:0]i_div_num ,
input [7:0]i_tx_bit_num ,
input [7:0]i_rx_bit_num ,
input [3:0]i_delay_cycle , //CS延迟的节拍数
input [TX_DATA_WIDTH-1:0]i_tx_data,
output [TX_DATA_WIDTH-1:0]o_rx_data,
output reg o_axi_vali ,
input i_axi_rdy ,
output o_clk ,
output o_cs ,
output o_mosi ,
input i_miso ,
output o_busy
);
/*********************************************************/
reg [1:0]start_buff;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)
start_buff <= 0;
else
start_buff <= {start_buff[0] , i_start};
end
wire start_flag = (start_buff == 2'b01);
/*********************************************************/
reg [7:0]div_num ;
reg [7:0]tx_bit_cnt_f;
reg [7:0]rx_bit_cnt_f;
reg [7:0]tx_bit_cnt_load;
reg [3:0]delay_cycle;
reg [TX_DATA_WIDTH-1:0]tx_load_again_data;
reg load_again;
reg flash_data;
reg load_again_data_done;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n) begin
div_num <= 0;
delay_cycle <= 0;
load_again <= 0;
flash_data <= 0;
tx_bit_cnt_load <= 0;
tx_load_again_data <= 0;
end
else if(start_flag)begin
div_num <= i_div_num;
tx_bit_cnt_f <= i_tx_bit_num;
rx_bit_cnt_f <= i_rx_bit_num;
delay_cycle <= i_delay_cycle;
load_again <= i_load_again;
o_axi_vali <= 0;
flash_data <= 0;
end
else if(load_again_data_done) begin
flash_data <= 0;
o_axi_vali <= 0;
end
else if(load_again && i_axi_rdy ) begin //采集下一次的load_again信号
tx_bit_cnt_load <= i_tx_bit_num;
load_again <= i_load_again;
tx_load_again_data <= i_tx_data; //接收数据已经饱和
o_axi_vali <= 1;
flash_data <= 1;
end
else begin
o_axi_vali <= 0;
end
end
/*********************************************************/
localparam idle = 1;
localparam s1 = idle << 1;
localparam s2 = idle << 2;
localparam s3 = idle << 3;
localparam s4 = idle << 4;
(* fsm_coding = "One-HOt" *)reg [4:0]curr_sta;
(* fsm_coding = "One-HOt" *)reg [4:0]next_sta;
//fsm_1
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)
curr_sta <= idle;
else
curr_sta <= next_sta;
end
//fsm_2
reg tx_start;
reg delay_clr;
wire delay_cs_ok;
reg [7:0]tx_bit_cnt;
always @(*) begin
next_sta = idle;
if(!i_reset_n) begin
next_sta = idle;
tx_start = 0;
delay_clr = 1;
end
else begin
case (curr_sta)
idle :begin
if(start_flag)
next_sta = s1;
else
next_sta = idle;
tx_start = 0;
delay_clr = 1;
end
s1:begin
if(delay_cs_ok) begin
next_sta = s2;
delay_clr = 1;
end
else begin
next_sta = s1;
delay_clr = 0;
end
tx_start = 0;
end
s2:begin
if(tx_bit_cnt == tx_bit_cnt_f) //传输完成一次
next_sta = flash_data ? s4 : s3;
else
next_sta = s2;
tx_start = 1;
delay_clr = 1;
end
s3:begin
if(delay_cs_ok) begin
next_sta = idle;
delay_clr = 1;
end
else begin
next_sta = s3;
delay_clr = 0;
end
tx_start = 0;
end
s4:begin
if(load_again_data_done)
next_sta = s2;
else
next_sta = s4;
end
default:next_sta = idle;
endcase
end
end
//fsm_3
reg cs;
reg clk;
reg [TX_DATA_WIDTH-1:0]tx_data;
wire clk_half;
wire clk_full;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)begin
cs <= ~i_CS_nVAl;
clk <= i_CPOL;
tx_bit_cnt <= 0;
load_again_data_done <= 0;
end
else begin
case(curr_sta)
idle:begin
cs <= ~i_CS_nVAl;
clk <= i_CPOL;
load_again_data_done <= 0;
end
s1:begin
cs <= i_CS_nVAl;
clk <= i_CPOL;
tx_bit_cnt <= 0;
tx_data <= i_tx_data;
if(delay_cs_ok && i_CPHA)
clk <= ~i_CPOL;
else
clk <= i_CPOL;
end
s2:begin
if(tx_bit_cnt == tx_bit_cnt_f - 1 && clk_full && i_CPHA && !flash_data )
clk <= i_CPOL;
else if(clk_half || clk_full)
clk <= ~clk;
if(clk_full) begin
tx_bit_cnt <= tx_bit_cnt + 1'b1;
tx_data <= i_MSB ? tx_data << 1 : tx_data >> 1;
end
load_again_data_done <= 0;
end
s3:begin
//cs <= ~i_CS_nVAl;
end
s4:begin //装载loag_again_data
tx_data <= tx_load_again_data;
tx_bit_cnt_f <= tx_bit_cnt_load;
tx_bit_cnt <= 0;
load_again_data_done <= 1;
end
default:;
endcase
end
end
assign o_clk = clk;
assign o_mosi= i_MSB ? tx_data[TX_DATA_WIDTH - 1]:tx_data[0];
assign o_cs = cs;
assign o_busy = (curr_sta !=idle);
/******************************************************************************/
//rx data
wire [7:0]delay_bit = tx_bit_cnt_f - rx_bit_cnt_f;
reg [7:0]rx_bit_cnt;
reg [RX_DATA_WIDTH-1:0]rx_data;
reg clk_f;
always @(posedge i_sys_clk )
clk_f <= clk;
wire clk_up = (!clk_f) & clk;
wire clk_dn = (!clk) & clk_f;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n) begin
rx_bit_cnt <= 0;
rx_data <= 0;
end
else if(start_flag) begin
rx_bit_cnt <= 0;
rx_data <= 0;
end
else if(((!i_RX_nDown && clk_dn) || (i_RX_nDown && clk_up))&& rx_bit_cnt >= delay_bit) begin
if(i_RX_ndirc_L) //先收低位数据
rx_data <= {rx_data[RX_DATA_WIDTH-2:0],i_miso};
else
rx_data <= {i_miso,rx_data[RX_DATA_WIDTH-1:1]};
rx_bit_cnt <= rx_bit_cnt + 1;
end
else if((!i_RX_nDown && clk_dn) || (i_RX_nDown && clk_up))begin
rx_bit_cnt <= rx_bit_cnt + 1;
end
end
assign o_rx_data = i_RX_ndirc_L ? rx_data : rx_data >>(RX_DATA_WIDTH - delay_bit) ;
/******************************************************************************/
reg [7:0]div_num_cnt;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)
div_num_cnt <= 0;
else if(tx_start)begin
if(div_num_cnt == div_num)
div_num_cnt <= 0;
else
div_num_cnt <= div_num_cnt + 1;
end
else
div_num_cnt <= 0;
end
assign clk_half = (div_num_cnt == div_num >> 1);
assign clk_full = (div_num_cnt == div_num >> 0);
/******************************************************************************/
reg [3:0]delay_cnt;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)
delay_cnt <= 0;
else if(delay_clr)
delay_cnt <= 0;
else
delay_cnt <= delay_cnt + 1;
end
assign delay_cs_ok = (delay_cnt == delay_cycle);
/******************************************************************************/
function integer calc_width(input integer num);
begin
calc_width = 0;
while(num > 0) begin
num = num >> 1 ;
calc_width = calc_width + 1;
end
end
endfunction
/******************************************************************************/
endmodule
2.仿真源码
`timescale 1ns / 100ps
//
// Company:
// Engineer: Deam
//
// Create Date: 21:39:35 2020年10月30日
// Design Name:
// Module Name: tb_spi_master_v3
// Project Name:
// Target Devices:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_spi_master_v3 ;
reg i_sys_clk;
reg i_reset_n;
always #0.5 i_sys_clk =~i_sys_clk;
initial begin
i_sys_clk = 0;
i_reset_n = 0;
#2
i_reset_n = 1;
end
/******************************************************************************/
parameter TX_DATA_WIDTH = 16;
parameter RX_DATA_WIDTH = 16;
reg i_start;
reg i_miso;
reg i_load_again;
reg i_CS_nVAl;
reg i_CPOL;
reg i_CPHA;
reg i_MSB;
reg i_RX_ndirc_L;
reg i_axi_rdy;
reg i_RX_nDown;
reg [7:0]i_div_num;
reg [7:0]i_tx_bit_num;
reg [7:0]i_rx_bit_num;
reg [3:0]i_delay_cycle;
reg [TX_DATA_WIDTH-1:0]i_tx_data;
wire [RX_DATA_WIDTH-1:0]o_rx_data;
wire o_clk;
wire o_cs;
wire o_mosi;
wire o_busy;
spi_master_v3 #(
.TX_DATA_WIDTH (TX_DATA_WIDTH ),
.RX_DATA_WIDTH (RX_DATA_WIDTH )
)uut(
.i_sys_clk (i_sys_clk ),
.i_reset_n (i_reset_n ),
.i_start (i_start ),
.i_tx_data (i_tx_data ),
.i_tx_bit_num (i_tx_bit_num),
.i_rx_bit_num (i_rx_bit_num),
.i_div_num (i_div_num ),
.i_CPOL (i_CPOL ),
.i_CS_nVAl (i_CS_nVAl ),
.i_CPHA (i_CPHA ),
.i_MSB (i_MSB ),
.i_RX_nDown (i_RX_nDown ),
.i_RX_ndirc_L (i_RX_ndirc_L),
.i_load_again (i_load_again),
.o_rx_data (o_rx_data ),
.i_delay_cycle (i_delay_cycle),
.i_axi_rdy (i_axi_rdy ),
.o_axi_vali (o_axi_vali ),
.o_clk (o_clk ),
.o_cs (o_cs ),
.o_mosi (o_mosi ),
.i_miso (i_miso ),
.o_busy (o_busy )
);
/******************************************************************************/
always @(negedge o_clk)
i_miso <= $random;
initial begin
i_start = 0;
i_miso = 0;
i_load_again = 0;
i_CS_nVAl = 0;
i_CPOL = 0;
i_CPHA = 0;
i_MSB = 1;
i_RX_nDown = 0;
i_RX_ndirc_L = 0;
i_delay_cycle = 1;
i_axi_rdy = 0;
i_div_num = 20;
i_tx_bit_num = 8;
i_rx_bit_num = 4;
#10;
/* repeat (3) begin
wait(!o_busy);
#10;
send_data();
end */
send_data();
#50 i_axi_rdy = 1;
//send_data();
#100 i_load_again = 0;
end
/******************************************************************************/
task send_data;
begin
#1;
i_start = 1;
i_tx_data = $random;
i_div_num = 10;
#1;
i_start = 0;
end
endtask
/******************************************************************************/
endmodule
3. do 文件源码
# ./当前路径
# ../上一级目录
#退出当前仿真
quit -sim
#清空命令行
.main clear
#创建库
#vlib lib
#vib ./lib/work
#将创建库映射至物理逻辑库
#vmap work .lib/work
#编译文件
vlog -work work ./../spi_master_v3.v
vlog -work work ./../tb_spi_master_v3.v
#启动仿真
vsim -voptargs=+acc work.tb_spi_master_v3
#添加波形
do tb_master_v3_wave.do
#运行仿真
run 500ns
################################################################################3
#10进制显示波形
#add wave -radix unsigned /tb_usart_rx/o_data
#模拟显示
#add wave -format Analog-Step -height 74 -max 255.0 /tb_usart_rx/o_data
三、仿真结果
在这里列出了使用loag_again 和不适用load_again的两种情况,更多情况请自行仿真,若出现错误,欢迎指出。内容为原创,原创不易。