摘要:1、本文介绍SPI物理层面连接(通过哪几条线通信),2、本文介绍SPI时序(通过哪种方式进行器件之间交流)。3、提供主机和从机verilog代码。4、仅供自己参考
一、SPI物理层连接
(1)有四根线连接:CS_N(片选信号--主机发出)、miso(从机发出,主机接收)、mosi(主机发出,从机接收)、SCLK(时钟信号,主机发出,作为数据传输的参考时钟)。
(2)结构图:
(3)总结:
1、SPI是一种全双工,同步通信总线,需要四根信号线(可用于FLASH器件的控制);
2、SPI通信有主从之分,可以实现一主多从或者是一主多从,但是不能实现多主多从,因为从器件只有一根cs_n片选信号线,没办法知道是哪个主机发出的控制信号;
3、SCLK是主机产生的,用来同步数据;
4、SPI是以MSB方式传播(高位先发);
二、SPI协议层时序
(1)SPI要记住以下几个知识(必须死记)
1、miso是从机发送,主机接收的信号线;
2、mosi是主机发送,从机接收的信号线;
3、mosi和mosi可以同时工作;
4、SCLK是主机产生,用来同步数据;且!!!:主机接收和发送数据都是在SCLK的跳变沿
从机接收和发送数据也是在SCLK的跳变沿。
5、根据4可知,数据的接收和发送是在SCLK的上升沿,究竟是在上升沿还是下降沿是由SPI的通信模式决定。注!!!:这个通信模式是双发同时遵守的!!,不是通过什么信号线配置的!!。
6、cs_n片选信号是由主机产生的,控制从机是否开始工作。
(2)通信模式:
解释:什么叫通信模式,就是决定数据的采集是在SCLK的上升沿还是下降沿,并且还决定SCLK在空闲时候是什么状态。
1、由CPOL和CPHA决定(这两个就是两个名称,没有什么用,相当于你的名字,没有实际的用处),所以可以有四种通信模式,00 、01、 10、 11。
2、CPOL :clock polarity 时钟极性
CPHA :clock phase 时钟相位,表示奇偶边沿采样。
CPHA = 0 :表示在时钟(SCLK)奇数边沿对数据采样,CPHA = 1表示在SCLK时钟偶数边沿采样。
CPOL = 0 :表示SCLK空闲时低电平,CPOL = 1表示SCLK时钟空闲时是高电平。
总结表格:
时序图示意(提供11模式时序图):
注!!!:以上的CPOL和CPHA是没有任何物理意义,仅仅用他们来表示这种通信模式,也可以用其他名字来代替CPOL和CPHA,是要双方用同一个模式就可以了。
(3)怎么通信:
1、首先要知道:miso、mosi、cs_n在空闲时是高电平,sclk空闲状态由通信模式决定。
2、由起始信号和停止信号表示传输开始和停止:
3、sclk怎么产生:用计数器记录一段时间实现SCLK的翻转,就产生了sclk脉冲信号;
4、mosi怎么产生:mosi的产生根据通信模式产生,比如在11模式:sclk空闲为高,在上升沿采样。那么在sclk下降沿的时候更具数据改变mosi信号线的高低;
5、miso怎么接收:比如在11模式。在sclk上升沿来临的时候采集miso,把串行数据变成并行数据;
6、定位数据哪一位接收或者发送:用一个计数器记录这一次接受了好多bit数据,并且放在数据对应位上,实现串并转换。
如上图:当bit为0的时候发送data的第7位(MSB),cnt_bit的变化是当sclk记录完一个周期之后加一。
主机verilog代码:文章来源:https://www.toymoban.com/news/detail-623342.html
module spi_master(
input clk , //system clock 50MHz
input rst_n , //reset, low valid
input start , //按键控制数据发送
/* input [7:00] send_data , //要发送的数据 */
input spi_miso,//从机发送,主机接收
output send_done , //这一次发送结束
output reg [7:0] rx_data,
output reg sclk, //
output reg spi_mosi,//主发
output reg spi_cs //片选信号
);
//Parameter Declarations
localparam
IDLE = 4'b0001,
START = 4'b0010,
WR_RD_DATA = 4'b0100,
STOP = 4'b1000;
//Internal wire/reg declarations
reg [3:00] state_c, state_n; //
//跳转条件定义
wire idle2start;
wire start2wr_rd_data;
wire wr_rd_data2stop;
wire stop2dile;
reg [7:0] data_tmp;//输入数据暂存
reg [7:0] data_tmp_rx;
reg [5:00] cnt_sclk ; //Counter
wire add_cnt_sclk ; //Counter Enable
wire end_cnt_sclk ; //Counter Reset
reg [3:00] cnt_bit ; //Counter
wire add_cnt_bit ; //Counter Enable
wire end_cnt_bit ; //Counter Reset
//Module instantiations , self-build module
//缓存数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_tmp <= 8'd20;
end
else if(start)begin
data_tmp <= data_tmp + 3;
end
else begin
data_tmp <= data_tmp;
end
end //always end
//Logic Description
//第一段设置状态转移空间
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end //always end
//第二段、组合逻辑定义状态转移
always@(*)begin
case(state_c)
IDLE:begin
if(idle2start)begin
state_n = START;
end
else begin
state_n = state_c;
end
end
START:begin
if(start2wr_rd_data)begin
state_n = WR_RD_DATA;
end
else begin
state_n = state_c;
end
end
WR_RD_DATA:begin
if(wr_rd_data2stop)begin
state_n = STOP;
end
else begin
state_n = state_c;
end
end
STOP:begin
if(stop2dile)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default: begin
state_n = IDLE;
end
endcase
end //always end
assign idle2start = state_c == IDLE && start;
assign start2wr_rd_data = state_c == START && spi_cs == 0;
assign wr_rd_data2stop = state_c == WR_RD_DATA && end_cnt_bit ;
assign stop2dile = state_c == STOP && spi_cs == 1;
//计数器
//cnt_sclk[5:00]
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_sclk <= 6'd0;
end
else if(add_cnt_sclk)begin
if(end_cnt_sclk)begin
cnt_sclk <= 6'd0;
end
else begin
cnt_sclk <= cnt_sclk + 1'b1;
end
end
else begin
cnt_sclk <= 6'd0;
end
end
assign add_cnt_sclk = state_c == WR_RD_DATA;
assign end_cnt_sclk = add_cnt_sclk && cnt_sclk >= 9; //周期是10个时钟周期,当cnt_sclk加到5的时候翻转
//cnt_bit
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 4'd0;
end
else if(wr_rd_data2stop)begin
cnt_bit <= 4'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 4'd0;
end
else begin
cnt_bit <= cnt_bit + 1'b1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = state_c == WR_RD_DATA && end_cnt_sclk; //在计数器为0的时候加一,表示数据变化是在下降沿,采集是在上升沿
assign end_cnt_bit = add_cnt_bit && cnt_bit >= 7;
//第三段,定义状态机输出情况,可以时序逻辑,也可以组合逻辑
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sclk <= 1'b1;
spi_cs <= 1'b1;
spi_mosi <= 1'b1;
rx_data <= 8'd0;
end
else begin
case(state_c)
IDLE :begin//全高
sclk <= 1'b1;
spi_cs <= 1'b1;
spi_mosi <= 1'b1;
end
START :begin
spi_cs <= 1'b0;
sclk <= 1'b1;
spi_mosi <= 1'b1;
end
WR_RD_DATA :begin
//发送
spi_cs <= 1'b0;
if(cnt_sclk < 5)begin
sclk <= 1'b0;
end
else begin
sclk <= 1'b1;
end
spi_mosi <= data_tmp[7-cnt_bit];
//接收
if(sclk == 1'b1)begin
data_tmp_rx[7-cnt_bit] <= spi_miso;
end
end
STOP :begin
rx_data <= data_tmp_rx;
sclk <= 1'b1;
spi_cs <= 1'b1;
spi_mosi <= 1'b1;
end
default:;
endcase
end
end //always end
assign send_done = stop2dile;
endmodule
从机verilog代码:文章来源地址https://www.toymoban.com/news/detail-623342.html
module spi_slave(
input sclk , //
input spi_cs , //
input spi_mosi,//接收的数据
output reg spi_miso, //输出的无数据
output [7:0] rx_data,
output data_vld//信号有效
);
//Parameter Declarations
reg [7:0] rx_data_tmp;
reg [2:0] cnt_bit;
reg [7:0] tx_data = 8'd0;
//Internal wire/reg declarations
//Module instantiations , self-build module
//Logic Description
always @(posedge sclk)begin
if(spi_cs == 1'b1)begin
rx_data_tmp <= 8'd0;
spi_miso <= 1'b1;
cnt_bit <= 3'b111;
end
else begin
cnt_bit = cnt_bit + 1;
if(sclk)begin
rx_data_tmp[7-cnt_bit] <= spi_mosi;
end
else ;
if(cnt_bit == 1'b1)begin
tx_data <= tx_data + 1;
end
else ;
end
end
always @(*)begin
if(spi_cs == 1'b1)begin
spi_miso = 1'b1;
end
else begin
if(sclk == 1'b0)begin
spi_miso = tx_data[7 - cnt_bit];
end
else begin
spi_miso = spi_miso;
end
end
end
assign rx_data = rx_data_tmp;
assign data_vld = cnt_bit == 3'd7;
endmodule
到了这里,关于FPGA:三大协议(UART、IIC、SPI)之SPI的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!