SPI通讯–MASTER && SLAVE
背景
一转眼到了三月,前段时间出了段时间的差,终于趁这个周末有空对SPI通讯进行了复现。自己在着手编写代码前,也看了很多的博客以及多次查看了SPI的通讯时序图,始终觉得还是很多地方无法理解,其实主要就是纠结于SCLK的产生。最终,通过参考了 jgliu的博客完成了此次代码的编写与验证。但目前代码还处于功能仿真完成阶段,后续还需在板上进行验证(flash读写)。
jgliu博客地址
https://www.cnblogs.com/liujinggang/p/9609739.html
该博客对SPI的介绍可以说是目前为止我看到比较详细、易懂且全面的了,感谢这位大神的分享。
备注:本文章代码为参考博客代码的更改版
代码功能介绍
1、MASTER端SPI代码:配置为模式0,一次传输8bit数据,可实现数据的收发;sclk,cs信号均由MASTER端产生。
2、SLAVE端SPI代码:配置为模式0,一次传输8bit数据。
模式0时序图
具体代码
SPI通讯之MASTER端代码
`timescale 1ns / 1ps
//
//
// SPI通讯--8位数据--模式0--上升沿采样、下降沿数据切换
//
//
module spi_master(
input clk,
input rst_n,
input [7:0] tx_data,
input spi_en,
input miso,
output reg mosi,
output reg cs,
output reg sclk,
output reg tx_done,
output reg rx_done,
output reg [7:0] TEM_rx_data
);
//
//
// 传输开始
//
//
reg spi_start_flag;
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
spi_start_flag <= 1'b0;
end else begin
if(spi_en) begin
spi_start_flag <= 1'b1;
end else begin
if(rx_done) begin
spi_start_flag <= 1'b0;
end
end
end
end
//
//
// 兼容低速率从设备
//
//
parameter SLAVE_FREQUENCY = 5_000_000, //假设从设备的最大频率为5Mhz
MASTER_FREQUENCY = 50_000_000, //主机的时钟频率为50MHZ
RATE_NUM = MASTER_FREQUENCY / SLAVE_FREQUENCY;
reg [3:0] rate_cnt;
reg rate_flag;
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
rate_cnt <= 'd0;
rate_flag <= 'd0;
end else begin
if(spi_start_flag) begin
if(rate_cnt <= RATE_NUM - 1) begin
rate_cnt <= rate_cnt + 1'b1;
rate_flag <= 'd0;
end else begin
rate_flag <= 'd1;
rate_cnt <= 'd0;
end
end else begin
rate_flag <= 'd0;
rate_cnt <= 'd0;
end
end
end
//
//
// sclk计数器 0-16
//
//
reg [4:0] sclk_cnt;
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
sclk_cnt <= 'd0;
end else begin
if(rate_flag) begin
if(sclk_cnt <= 16 - 1) begin
sclk_cnt <= sclk_cnt + 1'b1;
end else begin
sclk_cnt <= 'd0;
end
end else begin
if(!spi_start_flag) begin
sclk_cnt <= 'd0;
end
end
end
end
//
//
// SPI发送--上升沿采样、下降沿数据切换
//
//
reg [7:0]rx_data;
always @(posedge clk, negedge rst_n) begin
if(~rst_n) begin
mosi <= 0;
tx_done <= 'd0;
sclk <= 'd0;
rx_data <= 'd0;
rx_done <= 'd0;
cs <= 'b1;
TEM_rx_data <= 'b0;
end else begin
if (spi_start_flag) begin
case(sclk_cnt)
'd0: begin
cs <= 1'b0;
rx_data <= 'd0;
mosi <= tx_data[7];
sclk <= 'd0;
tx_done <= 1'b0;
rx_done <= 1'b0;
end
'd1: begin
mosi <= mosi;
sclk <= 'd1;
rx_data[7] <= miso;
end
'd2: begin
mosi <= tx_data[6];
sclk <= 'd0;
end
'd3: begin
mosi <= mosi;
tx_done <= 'd0;
sclk <= 'd1;
rx_data[6] <= miso;
end
'd4: begin
mosi <= tx_data[5];
sclk <= 'd0;
end
'd5: begin
mosi <= mosi;
tx_done <= 'd0;
sclk <= 'd1;
rx_data[5] <= miso;
end
'd6: begin
mosi <= tx_data[4];
sclk <= 'd0;
end
'd7: begin
mosi <= mosi;
tx_done <= 'd0;
sclk <= 'd1;
rx_data[4] <= miso;
end
'd8: begin
mosi <= tx_data[3];
sclk <= 'd0;
end
'd9: begin
mosi <= mosi;
tx_done <= 'd0;
sclk <= 'd1;
rx_data[3] <= miso;
end
'd10: begin
mosi <= tx_data[2];
sclk <= 'd0;
end
'd11: begin
mosi <= mosi;
tx_done <= 'd0;
sclk <= 'd1;
rx_data[2] <= miso;
end
'd12: begin
mosi <= tx_data[1];
sclk <= 'd0;
end
'd13: begin
mosi <= mosi;
tx_done <= 'd0;
sclk <= 'd1;
rx_data[1] <= miso;
end
'd14: begin
mosi <= tx_data[0];
sclk <= 'd0;
end
'd15: begin
mosi <= mosi;
sclk <= 'd1;
rx_data[0] <= miso;
//rx_done <= 1'b1;
end
'd16: begin
sclk <= 1'b0;
TEM_rx_data <= rx_data;
if(rate_cnt >= RATE_NUM-1) begin
rx_done <= 1'b1;
tx_done <= 1'b1;
cs <= 1'b1;
end
end
default: sclk_cnt <= 'd0;
endcase
end else begin
mosi <= 0;
tx_done <= 'd0;
sclk <= 'd0;
rx_data <= 'd0;
rx_done <= 'd0;
cs <= 'b1;
end
end
end
endmodule
SPI通讯之SLAVE端代码
SLAVE_TOP
`timescale 1ns / 1ps
//
//
// SPI从机TOP模块--模式0--8位数据
//
//
module spi_slave_top(
input clk,
input rst_n,
input [7:0]need_tx_data,
input cs,
input sclk,
input mosi,
output miso,
output tx_done,
output rx_done,
output [7:0]rx_data
);
spi_slave_send slave_send_inst(
.clk(clk),
.rst_n(rst_n),
.need_tx_data(need_tx_data),
.sclk(sclk),
.cs(cs),
.miso(miso),
.tx_done(tx_done)
);
spi_slave_receive slave_rece_inst(
.clk (clk),
.rst_n (rst_n),
.sclk (sclk),
.cs (cs),
.mosi (mosi),
.rx_done (rx_done),
.TEM_rx_data (rx_data)
);
endmodule
SLAVE_RECEIVE
`timescale 1ns / 1ps
//
//
// spi从端接收数据
//
//
module spi_slave_receive(
input clk,
input rst_n,
input sclk,
input cs,
input mosi,
output reg rx_done,
output reg [7:0]TEM_rx_data
);
//
//
// 片选拉低检测--下降沿选通
//
//
reg cs_0;
reg cs_1;
wire cs_neg;
assign cs_neg = !cs_0 && cs_1;
reg sclk_0;
reg sclk_1;
wire sclk_pos;
assign sclk_pos = sclk_0 && !sclk_1;
always @(posedge clk, negedge rst_n) begin
if(~rst_n) begin
cs_0 <= 1'b0;
cs_1 <= 1'b0;
sclk_0 <= 1'b0;
sclk_1 <= 1'b0;
end else begin
cs_0 <= cs;
cs_1 <= cs_0;
sclk_0 <= sclk;
sclk_1 <= sclk_0;
end
end
//
//
// 数据接收状态机--数据上升沿被采样、下降沿数据切换--SPI模式0
//
//
reg [3:0]current_state;
reg [3:0]rx_cnt;
parameter IDLE = 4'd0,
RECEIVE_STATE = 4'd1;
always @(posedge clk, negedge rst_n) begin
if(~rst_n) begin
current_state <= IDLE;
end else begin
case(current_state)
IDLE: begin
if(cs_neg) begin
current_state <= RECEIVE_STATE;
end
end
RECEIVE_STATE: begin
if(rx_cnt > 8 - 1 && cs_neg) begin
current_state <= IDLE;
end
end
endcase
end
end
reg [7:0]rx_data;
always @(posedge clk, negedge rst_n) begin
if(~rst_n) begin
rx_data <= 8'b0;
rx_cnt <= 4'b0;
rx_done <= 1'b0;
rx_data <= 'd0;
end else begin
case(current_state)
IDLE: begin
rx_cnt <= 4'b0;
rx_done <= 1'b0;
rx_data <= 'd0;
end
RECEIVE_STATE: begin
if(!cs) begin
if(rx_cnt <= 8 - 1) begin
if(sclk_pos) begin
rx_data[7-rx_cnt] <= mosi;
rx_cnt <= rx_cnt + 1'b1;
end
end else begin
rx_done <= 1'b1;
end
end else begin
rx_cnt <= 4'b0;
rx_done <= 1'b0;
end
end
endcase
end
end
//
//
// 最终接收数据
//
//
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
TEM_rx_data <= 'b0;
end else begin
if(rx_done) begin
TEM_rx_data <= rx_data;
end
end
end
endmodule
SLAVE_SEND
`timescale 1ns / 1ps
//
//
// SPI从端--数据发送--发送由高到低
//
//
module spi_slave_send(
input clk,
input rst_n,
input [7:0]need_tx_data,
input sclk,
input cs,
output reg miso,
output reg tx_done
);
//
//
// 片选拉低检测--下降沿选通
//
//
reg cs_0;
reg cs_1;
wire cs_neg;
wire cs_pos;
assign cs_neg = !cs_0 && cs_1;
assign cs_pos = !cs_1 && cs_0;
reg sclk_0;
reg sclk_1;
wire sclk_neg;
assign sclk_neg = !sclk_0 && sclk_1;
always @(posedge clk, negedge rst_n) begin
if(~rst_n) begin
cs_0 <= 1'b0;
cs_1 <= 1'b0;
sclk_0 <= 1'b0;
sclk_1 <= 1'b0;
end else begin
cs_0 <= cs;
cs_1 <= cs_0;
sclk_0 <= sclk;
sclk_1 <= sclk_0;
end
end
//
//
// 数据发送状态机--数据上升沿被采样、下降沿数据切换--SPI模式0
//
//
reg [3:0]current_state;
parameter IDLE = 4'd0,
SEND_FIRST = 4'd1,
SEND_STATE = 4'd2;
reg [3:0] send_cnt;
always @(posedge clk, negedge rst_n) begin
if(~rst_n) begin
current_state <= IDLE;
end else begin
case(current_state)
IDLE: begin
if(!cs && sclk_neg) begin
current_state <= SEND_FIRST;
end
end
SEND_FIRST: begin
current_state <= SEND_STATE;
end
SEND_STATE: begin
if(send_cnt > 7) begin
current_state <= IDLE;
end
end
default: current_state <= IDLE;
endcase
end
end
always @(posedge clk, negedge rst_n) begin
if(~rst_n) begin
miso <= 1'b0;
tx_done <= 1'b0;
send_cnt <= 4'd0;
end else begin
case(current_state)
IDLE: begin
miso <= need_tx_data[7];
tx_done <= 1'b0;
send_cnt <= 4'd0;
end
SEND_FIRST: begin
send_cnt <= send_cnt + 1'b1;
miso <= need_tx_data[6];
end
SEND_STATE: begin
if(!cs) begin
if(send_cnt <= 8 - 1) begin
if(sclk_neg) begin
send_cnt <= send_cnt + 1'b1;
miso <= need_tx_data[6 - send_cnt];
end
end else begin
tx_done <= 1'b1;
end
end else begin
send_cnt <= 4'd0;
tx_done <= 1'b0;
end
end
endcase
end
end
endmodule
代码功能仿真
仿真波形图中,tx_data为SPI主端以及从端互相发送的数据,slave_rx_data以及master_rx_data为接收到的数据;从时序图中的sclk,cs以及mosi、miso波形可以看出,与SPI波形原理图基本一致。
问题总结
1、由于参考的博客中,主端的发送以及接收是分别使能进行的,而我的代码是将数据的收发合到一起的,这也导致原博客中的线性序列机('d0-'d15)在我这并不能直接使用;在本代码中,额外增加了状态’d16以满足片选拉高时间的需求。
2、本次调试的主要问题就在于需发送数据切换时,切换间隔会发生误读数据,必须等到下一次完整读取才会更新为正确的数据。
3、在理解SPI通讯后,实际上个人感觉比串口通讯还更为简单方便,而且在这一次的代码复现中,自己也首次学习了线性状态机的使用,实际上在UART、SPI通讯中使用线性状态机更为方便、快捷。
后续安排
后续会看看手里的开发板资源是否有用SPI通讯驱动的芯片,从而完成代码的板级验证。