基于FPGA的SPI协议接口的verilog设计
Posted fpga&matlab
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于FPGA的SPI协议接口的verilog设计相关的知识,希望对你有一定的参考价值。
1.简介与仿真结论
SPI是一种三线同步接口,分别为同步时钟信号、数据输入信号和数据输出信号。另外每个扩展芯片还需要一个片选信号,主器件通过片选信号选通与其通信的从器件。它允许处理器与各种外围设备之间以串行方式(如8位数据同时、同步地被发送和接收)进行通信。
系统的功能仿真,MODELSIM。得到如下的结果:
SPI-MASTER仿真结果图
2.理论分析
在SPI接口中,数据的传输需要1个时钟信号线和两条数据线,共由四根线组成,如下表。由此可见,SPI的结构简单,具有配置灵活等优点。
表1:SPI端口信号
信号 | 描述 |
SCK | 串行时钟线,由SPI主设备产生 |
MOSI | 主设备输出/从设备输入数据线 |
MISO | 主设备输入/从设备输出数据线 |
SS | 从设备选择线,低电平有效 |
其基本的管脚结构图如下:
图1 SPI结构图
SPI有主从两种工作模式。在主模式,每一位数据的发送接收需要1次时钟作用,并且只有主机才能初始化数据的传输。数据的传输开始于对主机数据寄存器的写操作。如果移位寄存器是空的,那么数据立即写入移位寄存器,然后8位的数据在串行时钟的作用下通过MOSI引脚依次送出,同时从机的数据通过MISO引脚依次送入主机。在数据传输过程中,SCK信号就是在主模式下由主机产生,并且此时SS信号作为主机的输出,用来传输对别的从机的选择和控制信号。当主机和从机开始通信时,SS信号为低电平;在空闲状态时,SS信号为高电平。
在从模式下,每一位数据都是在接收到时钟信号之后才发送接收。此时,SCK信号由主机输入,数据在SCK信号作用下依次由MOSI引脚写入数据寄存器。SPI总线包括1根串行同步时钟信号线以及2根数据线。SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设间时钟相位和极性应该一致。
图2 CPHA=0时SPI总线数据传输时序
图3 CPHA=1时SPI总线数据传输时序
SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设音时钟相位和极性应该一致。一个典型的SPI系统,包括1个主机和1个或几个外围器件,其中传统的主机一般是微处理器,相应的接口是微机接口。主机通过SPI接口模块与外围器件相连。当该其以主机模式运行时,就可以与整个系统中的外部从机器件进行通信,而当它以从机模式工作时,就能与另外一个主机进行数据的通信。但同一时间内系统中只能有一个主机,否则系统就不能正常工作,本文设计时主要考虑的是工作在主模式的情况下。
SPI操作
在对SPI核操作的控制寄存器进行设置之后即可启动数据传输。数据传输的启动是通过向数据寄存器SPDR中写入数据。对数据寄存器执行写操作实际上是往一个4项写队列中添加数据项。每次写操作即往队列中写入一个字节的数据。当SPI核被使能时,并且写缓冲区不为空,SPI核就是会自动往发送写队列中最旧的数据项。接收数据与发送数据同时进行;每当发送一个字节的数据,同时总会收到一个字节的数据。如果想要的接收一个字节的数据,需要往写缓冲区写入一个字节的冗余数据,这样做是为了让SPI核启动数据传输,在发送冗余数据的同时接收想要的数据,每完成接收一个字节的数据,这个字节会被移入读缓冲区。读缓冲区和写缓冲区是对立的,是一个独立的4项队列。对数据寄存器执行读操作,便会得到读队列中的数据项。
3.部分核心代码
module spi_master(
addr, //地址
in_data, //并行数据输入
out_data, //并行数据输出
rd, //读使能信号
wr, //写使能信号
cs, //片选信号
clk, //系统时钟
miso, //主设备输入,从设备输出
mosi, //主设备输出,从设备输入
sclk //SPI_clk
);
input [1:0]addr;//地址
input [7:0]in_data;//并行数据输入
output[7:0]out_data;//并行数据输出
input rd;//读使能信号
input wr;//写使能信号
input cs;//片选信号
input clk;//系统时钟
inout miso;//主设备输入,从设备输出
inout mosi;//主设备输出,从设备输入
inout sclk;//SPI_clk
//==================================
reg [7:0] out_data;
reg sclk_buffer =1'b0;
reg mosi_buffer =1'b0;
reg busy =1'b0;
reg [7:0] in_buffer =8'd0;
reg [7:0] out_buffer=8'd0;
reg [7:0] clkcount =8'd0;
reg [7:0] clkdiv =8'd0;
reg [4:0] count =5'd0;
always@(cs or rd or addr or out_buffer or busy or clkdiv)
begin
if(cs && rd)
begin
case(addr)
2'b00:begin
out_data = out_buffer; //地址为00的时候,数据缓存给out_data信号
end
2'b01:begin
out_data = 7'b0, busy;//地址为01,表示SPI处于BUSY状态
end
2'b10:begin
out_data = clkdiv; //地址为10:则表示输入数据赋给out_data
end
default:begin
out_data = 8'bzzzz_zzzz;//默认状态为高阻态
end
endcase
end
end
always@(posedge clk)
begin
//系统处于BUSY状态,
//则在地址00处,将输入信号给予in_buffer,busy取反,
//在地址10处,将输入信号给予clkdiv
if(!busy)
begin
if(cs && wr)
begin
case(addr)
2'b00: begin
in_buffer = in_data;
busy = 1'b1;
end
2'b10: begin
clkdiv = in_data;
end
endcase
end
end
//系统处于工作状态
else begin
clkcount = clkcount + 1;
if(clkcount >= clkdiv)
begin
clkcount = 0;
if((count % 2) == 0)
begin
//将并行信号转变为SPI串行信号
mosi_buffer = in_buffer[7];
in_buffer = in_buffer << 1;
end
if(count > 0 && count < 17)
begin
//根据数据并串转变的时序产生SPI时钟
sclk_buffer = ~sclk_buffer;
end
count = count + 1;
//完成一个SPI时序工作周期
if(count > 17)
begin
count = 0;
busy = 1'b0;
end
end
end
end
//利用SPI时钟,发送串行信号
always@(posedge sclk_buffer)
begin
out_buffer = out_buffer << 1;
out_buffer[0] = miso;
end
assign sclk = sclk_buffer;
assign mosi = mosi_buffer;
endmodule
`timescale 1ns/1ns
module spi_master_tb();
reg [1:0] addr; //地址
reg [7:0] in_data; //并行数据输入
wire[7:0] out_data; //并行数据输出
reg rd; //读使能信号
reg wr; //写使能信号
reg cs; //片选信号
reg clk; //系统时钟
wire miso; //主设备输入,从设备输出
wire mosi; //主设备输出,从设备输入
wire sclk; //SPI_clk
integer counter = 0;
initial
begin
//初始化各个输入信号的值
addr = 2'b00;
in_data = 8'b0000_0000;
rd = 1'b0;
wr = 1'b0;
cs = 1'b0;
clk = 1'b0;
//===========================
//20个时间单位延迟后的值
#20
addr = 2'b10;
in_data = 8'b0000_0000;
wr = 1'b1;
cs = 1'b1;
//============================
//20个时间单位延迟后的值
#20
addr = 2'b00;
in_data = 8'b0000_0000;
wr = 1'b0;
cs = 1'b0;
//=============================
#20
for(counter = 0; counter < 256; counter = counter + 1)
begin
addr = 2'b00;
in_data = counter;
cs = 1'b1;
#20
addr = 2'b00;
in_data = 8'b0000_0000;
wr = 1'b0;
cs = 1'b0;
#20
addr = 2'b01;
cs = 1'b1;
rd = 1'b1;
#20
while(out_data[0] == 1'b1)
begin
#20;
end
cs = 1'b0;
rd = 1'b0;
#20;
end
$stop;
end
always #10 clk = ~clk; //产生测试时钟
spi_master uut(
.addr (addr),
.in_data (in_data),
.out_data (out_data),
.rd (rd),
.wr (wr),
.cs (cs),
.clk (clk ),
.miso (miso),
.mosi (mosi),
.sclk (sclk)
);
endmodule
A38-03
以上是关于基于FPGA的SPI协议接口的verilog设计的主要内容,如果未能解决你的问题,请参考以下文章