FPGA——SPI从机通信实现与仿真
一、设计思路
发送数据计数器
接收数据计数器
从机的时钟SCK是由主机支持的,所以不是一个时钟域,接收时钟SCK需要防止亚稳态接两级触发器
因为边沿检测接两级触发器延后一拍,所以接收的数据要再接一级触发器,与接收数据的边沿对齐
二、参数化设计
从机代码参数说明
DATA_W:为接收、发送数据的个数
工作方式设置:
- 模式0:spi_sync复位时为0,接收计数器加一条件为上升沿(pedge),发送计数器加一条件为下降沿(nedge)
- 模式1:spi_sync复位时为0,接收计数器加一条件为下降沿(nedge),发送计数器加一条件为上升沿(pedge)
- 模式2:spi_sync复位时为1,接收计数器加一条件为下降沿(nedge),发送计数器加一条件为上升沿(pedge)
- 模式3:spi_sync复位时为0,接收计数器加一条件为上升沿(pedge),发送计数器加一条件为下降沿(nedge)
参考资料:
模式0:CPOL= 0,CPHA=0。SCK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
模式1:CPOL= 0,CPHA=1。SCK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
模式2:CPOL= 1,CPHA=0。SCK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
模式3:CPOL= 1,CPHA=1。SCK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
三、SPI从机代码(工作模式3)
module spi_slave(
clk , //50MHz时钟
rst_n , //复位
data_in , //要发送的数据
data_out , //接收到的数据
spi_sck , //主机时钟
spi_miso , //主收从发(从机)
spi_mosi , //主发从收(从机)
spi_cs , //主机片选,低有效(从机)
tx_en , //发送使能
tx_done , //发送完成标志位
rx_done //接收完成标志位
);
//改DATA_W的参数即可实现任意字节的发送和接收,现在是两字节发送和接收
parameter DATA_W = 16;
parameter SYNC_W = 2;
//计数器参数
parameter CNT_W = 4;
parameter CNT_N = DATA_W;
input clk;
input rst_n;
input [DATA_W-1:0] data_in;
input spi_sck;
input spi_mosi;
input spi_cs;
input tx_en;
output [DATA_W-1:0] data_out;
output spi_miso;
output tx_done;
output rx_done;
reg [DATA_W-1:0] data_out;
reg spi_miso;
reg tx_done;
reg rx_done;
//中间变量
reg [SYNC_W-1:0] spi_sync;
wire nedge;
wire pedge;
reg spi_mosi_reg;
//计数器变量
reg [CNT_W-1:0] cnt_rxbit;
wire add_cnt_rxbit;
wire end_cnt_rxbit;
reg [CNT_W-1:0] cnt_txbit;
wire add_cnt_txbit;
wire end_cnt_txbit;
reg tx_flag;
//边沿检测
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
//SCK时钟空闲状态位高电平,工作模式3
spi_sync <= 2'b11;
else
spi_sync <= {spi_sync[0],spi_sck};
end
assign nedge = spi_sync[1:0] == 2'b10;
assign pedge = spi_sync[1:0] == 2'b01;
//上升沿接收,工作模式三
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_rxbit <= 0;
else if(add_cnt_rxbit)begin
if(end_cnt_rxbit)
cnt_rxbit <= 0;
else
cnt_rxbit <= cnt_rxbit + 1'b1;
end
end
assign add_cnt_rxbit = pedge;
assign end_cnt_rxbit = add_cnt_rxbit && cnt_rxbit == CNT_N - 1;
//下降沿发送,工作模式三
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_txbit <= 0;
else if(add_cnt_txbit)begin
if(end_cnt_txbit)
cnt_txbit <= 0;
else
cnt_txbit <= cnt_txbit + 1'b1;
end
end
assign add_cnt_txbit = nedge && tx_flag;
assign end_cnt_txbit = add_cnt_txbit && cnt_txbit == CNT_N - 1;
//因为异步信号同步化的原因,为了与延后的下降沿对齐,多打一拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
spi_mosi_reg <= 0;
else
spi_mosi_reg <= spi_mosi;
end
//下降沿接收
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
data_out <= 0;
else if(!spi_cs)
data_out[CNT_N - 1 - cnt_rxbit ] <= spi_mosi_reg;
end
//上升沿发送数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
spi_miso <= 0;
else if(!spi_cs && tx_flag)
spi_miso <= data_in[CNT_N - 1 - cnt_txbit];
else
spi_miso <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rx_done <= 0;
else if(end_cnt_rxbit)
rx_done <= 1;
else
rx_done <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_done <= 0;
else if(end_cnt_txbit)
tx_done <= 1;
else
tx_done <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_flag <= 0;
else if(tx_en)
tx_flag <= 1;
else if(end_cnt_txbit)
tx_flag <= 0;
end
endmodule
四、SPI从机 2字节仿真验证代码
`timescale 1ns / 1ns
module spi_slave_tb();
parameter DATA_W = 16;
parameter CYCLE = 20;
parameter CYCLE_SPI = 40;
reg clk;
reg rst_n;
reg [DATA_W-1:0] data_in;
reg spi_sck;
reg spi_mosi;
reg spi_cs;
wire [DATA_W-1:0] data_out;
wire spi_miso;
wire rx_done;
wire tx_done;
reg [DATA_W-1:0] data;
reg tx_en;
spi_slave spi_slave(
.clk (clk), //50MHz时钟
.rst_n (rst_n), //复位
.data_in (data_in), //要发送的数据
.data_out (data_out), //接收到的数据
.spi_sck (spi_sck), //主机时钟
.spi_miso (spi_miso), //主收从发(从机)
.spi_mosi (spi_mosi), //主发从收(从机)
.spi_cs (1'b0), //主机片选,低有效(从机)
.tx_done (tx_done), //发送完成标志位
.tx_en (tx_en),
.rx_done (rx_done) //接收完成标志位
);
reg [3:0]state;
initial begin
rst_n = 1;
#3;
rst_n = 0;
#(3*CYCLE)
rst_n = 1;
end
initial clk = 1;
always #(CYCLE/2) clk = ~clk;
//SCK空闲时为1
initial spi_sck = 1;
always #(CYCLE_SPI/2) spi_sck = ~spi_sck;
initial begin
data <= 16'hAAA1;
repeat(5)begin
@ (posedge rx_done)
data <= data + 1'b1;
end
#1000;
$stop;
end
initial begin
tx_en = 0;
data_in = 0;
@ (posedge rst_n)
#(10*CYCLE)
data_in = 16'hAAA9;
tx_en = 1;
#(CYCLE)
tx_en = 0;
end
//下降沿发送数据
always @(negedge spi_sck,negedge rst_n)
begin
if(!rst_n)begin
spi_mosi <= 0;
state <= 4'b0000;
end
else begin
case(state)
4'd0:
begin
state <= state + 1;
spi_mosi <= data[15];
end
4'd1:
begin
state <= state + 1;
spi_mosi <= data[14];
end
4'd2:
begin
state <= state + 1;
spi_mosi <= data[13];
end
4'd3:
begin
state <= state + 1;
spi_mosi <= data[12];
end
4'd4:
begin
state <= state + 1;
spi_mosi <= data[11];
end
4'd5:
begin
state <= state + 1;
spi_mosi <= data[10];
end
4'd6:
begin
state <= state + 1;
spi_mosi <= data[9];
end
4'd7:
begin
state <= state + 1;
spi_mosi <= data[8];
end
4'd8:
begin
state <= state + 1;
spi_mosi <= data[7];
end
4'd9:
begin
state <= state + 1;
spi_mosi <= data[6];
end
4'd10:
begin
state <= state + 1;
spi_mosi <= data[5];
end
4'd11:
begin
state <= state + 1;
spi_mosi <= data[4];
end
4'd12:
begin
state <= state + 1;
spi_mosi <= data[3];
end
4'd13:
begin
state <= state + 1;
spi_mosi <= data[2];
end
4'd14:
begin
state <= state + 1;
spi_mosi <= data[1];
end
4'd15:
begin
state <= state + 1;
spi_mosi <= data[0];
end
default:state <= 4'b0000;
endcase
end
end
endmodule