工程下载链接:https://download.youkuaiyun.com/download/qq_45776815/88488282
SPI 接口是 Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(MasterSlave)架构;支持多 slave 模式应用,一般仅支持单 Master。时钟由 Master 控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后 (MSBfirst);SPI 接口有 2 根单向数据线,为全双工通信,由于在传输数据的同时也传输了时钟信号,所以是同步传输协议,目前应用中的数据速率可达几 Mbps 的水平。总线结构如下图所示。
接口定义:
SPI 接口共有 4 根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
(1)MOSI:主器件数据输出,从器件数据输入
(2)MISO:主器件数据输入,从器件数据输出
(3)SCLK:时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制
传输模式:
SPI 串行同步时钟可以设置为不同的极性(Clock Polarity ,CPOL)与相位(Clock Phase ,CPHA)。 时钟的极性(CPOL)用来决定在总线空闲时,同步时钟(SCK)信号线上的电位是高电平还是低电平。当时钟极 性为 0 时(CPOL=0),SCK 信号线在空闲时为低电平;当时钟极性为 1 时(CPOL=1),SCK 信号线在空闲时为 高电平;
时钟的相位(CPHA)用来决定何时进行信号采样。
当时钟相位为 1 时(CPHA=1),在 SCK 信号线的第二个跳变沿进行采样;
当时钟相位为 0 时(CPHA=0),在 SCK 信号线的第一个跳变沿进行采样。
由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)可以得到spi总线传输的四种模式:
模式0:CPOL= 0,CPHA=0。SCLK串行时钟线空闲是为低电平,数据在SCLK时钟的上升沿被采样,数据在SCLK时钟的下降沿切换;
模式1:CPOL= 0,CPHA=1。SCLK串行时钟线空闲是为低电平,数据在SCLK时钟的下降沿被采样,数据在SCLK时钟的上升沿切换;
模式2:CPOL= 1,CPHA=0。SCLK串行时钟线空闲是为高电平,数据在SCLK时钟的下降沿被采样,数据在SCLK时钟的上升沿切换;
模式3:CPOL= 1,CPHA=1。SCLK串行时钟线空闲是为高电平,数据在SCLK时钟的上升沿被采样,数据在SCLK时钟的下降沿切换。
Verilog实现
master发送
`timescale 1ns / 1ps
module spi_tx#(
parameter CPOL = 1'b0,
parameter CPHA = 1'b0
)
(
input clk_i, //系统时钟
output spi_tx_o, //mosi
output spi_clk_o, //SPI 时钟输出 SCK
input spi_tx_en_i, //发送发送使能信号
input [7:0] spi_tx_data_i, //待发送数据
// output spi_en_o, //SPI 传输使能
output spi_busy_o //SPI 正在传输
);
// 分频时钟,产生SPI的时钟输出
parameter SPI_DIV = 'd500;
parameter SPI_DIV1 = SPI_DIV >> 1'b1;
reg spi_en=1'b0;
// spi时钟输出,受到时钟极性控制
assign spi_clk_o = CPOL?~spi_clk:spi_clk;
// spi是否工作标识
assign spi_busy_o = spi_en;
// 时钟分频
reg [9:0] clk_div;
always @(posedge clk_i)
begin
if(spi_en && clk_div < (SPI_DIV-1'b1))//分频受使能控制
clk_div <= clk_div+1'b1;
else
clk_div <= 0;
end
wire clk_en1 = (clk_div == SPI_DIV1);
wire clk_en2 = (clk_div == SPI_DIV-1'b1);
reg spi_clk = 0;
always @(posedge clk_i)
begin
if(!spi_en)
spi_clk <= 0;
else if(clk_en1)
spi_clk <= 1'b1;
else if (clk_en2)
spi_clk <= 1'b0;
else
spi_clk <= spi_clk;
end
reg [7:0] spi_tx_data_r;
reg pcnt = 1'b0;
reg [3:0] tx_cnt = 4'd0;
assign spi_tx_o = spi_tx_data_r[7];
// 根据时钟相位决定数据在第一个还是第二个跳变沿采样
wire spi_strobe = CPHA?clk_en1:clk_en2;
// 传输
always @(posedge clk_i)
begin
if(spi_tx_en_i) begin//接收到发送使能信号,使能标志位置1,待发送数据暂存
spi_en <=1'b1;
spi_tx_data_r <= spi_tx_data_i;
end
else if(!spi_en)begin
spi_tx_data_r <= 8'b0;
pcnt <= 1'b0;
tx_cnt <= 4'd0;
end
if(tx_cnt==4'd8 && clk_en1)begin//发送结束复位
spi_en <= 1'b0;
tx_cnt <= 4'd0;
pcnt <= 1'b0;
end
else begin
if(clk_en1&&spi_en)//发送数据位数计数
tx_cnt <= tx_cnt + 1'b1;
if(spi_strobe)begin
if(pcnt < CPHA)//此处延迟,避免第二个跳变沿数据采样时丢失数据最高位
pcnt <= 1'b1;
else
spi_tx_data_r <= spi_tx_data_r <<1;
end
end
end
endmodule
master接收
`timescale 1ns / 1ps
module spi_rx#(
parameter CPOL = 1'b0,
parameter CPHA = 1'b0
)
(
input clk_i,
input spi_en_i,
input spi_clk_i,
input spi_data_i,
output reg [7:0] spi_rx_data
);
reg spi_clk;
reg spi_en_r;
//判断传输是否结束
wire spi_rx_end = spi_en_r&&~spi_en_i;
//数据暂存,(存储spi时钟信号与使能信号)
always@(posedge clk_i)begin
spi_en_r <= spi_en_i;
spi_clk <= spi_clk_i;
end
// spi工作模式决定数据采样时刻
reg spi_mode;
always@(*)begin
case({CPHA,CPOL})
2'b00,2'b11: spi_mode = ~spi_clk&&spi_clk_i;//上升沿采样
2'b01,2'b10: spi_mode = spi_clk&&~spi_clk_i;//下降沿采样
default: spi_mode = 1'b0;
endcase
end
// 数据接收
reg [7:0] spi_rx_data_r;
always@(posedge clk_i)begin
if(spi_mode)
spi_rx_data_r <= {spi_rx_data_r[6:0],spi_data_i};//数据移位存储
else
spi_rx_data_r <= spi_rx_data_r;//未接收完毕或者是没有在接收
if(spi_rx_end)
spi_rx_data <= spi_rx_data_r;//数据接收完毕
end
endmodule
控制代码
`timescale 1ns / 1ps
module spi_con(
input clk_i,
input rst_n,
output spi_clk_o,
input spi_miso_i,
output spi_mosi_o,
output [7:0] spi_rx_data
);
wire spi_busy;
// wire [7:0] spi_rx_data;
spi_rx#(
.CPOL(1'b0),
.CPHA(1'b0)
)
spi_rx(
.clk_i(clk_i),
.spi_en_i(spi_busy),
.spi_clk_i(spi_clk_o),
.spi_data_i(spi_miso_i),
.spi_rx_data(spi_rx_data)
);
reg spi_tx_en;
reg [7:0] spi_tx_data;
spi_tx#(
.CPOL(1'b0),
.CPHA(1'b0)
)
spi_tx_inst(
.clk_i(clk_i),
.spi_tx_o(spi_mosi_o),
.spi_clk_o(spi_clk_o),
.spi_tx_en_i(spi_tx_en),
.spi_tx_data_i(spi_tx_data),
.spi_busy_o(spi_busy)
);
//使用状态机 进行数据循环
reg [1:0] ms;
always @(posedge clk_i)begin
if(!rst_n)begin
ms <= 2'b0;
spi_tx_en <= 1'b0;
spi_tx_data <= 8'b0;
end
else begin
case(ms)
2'b0:
if(!spi_busy)begin
spi_tx_en <= 1'b1;
spi_tx_data <= spi_tx_data + 1'b1;
ms <= 2'b01;
end
2'b01:begin
spi_tx_en <= 1'b0;
if(spi_busy)
ms <= 2'd2;
end
2'b10:begin
if(!spi_busy)
ms <= 2'd3;
end
2'b11: ms <= 2'd0;
endcase
end
end
endmodule
测试代码
`timescale 1ns / 1ps
module spi_con_tb(
);
reg clk_i;
reg rst_n;
wire spi_clk_o;
wire spi_miso_i;
wire spi_mosi_o;
wire [7:0] spi_rx_data;
assign spi_miso_i = spi_mosi_o;
spi_con spi_con_inst(
.clk_i(clk_i),
.rst_n(rst_n),
.spi_clk_o(spi_clk_o),
.spi_miso_i(spi_miso_i),
.spi_mosi_o(spi_mosi_o),
.spi_rx_data(spi_rx_data)
);
initial begin
clk_i = 1'b0;
rst_n = 1'b0;
#100;
rst_n = 1'b1;
end
always
begin
#10 clk_i = ~clk_i;
end
endmodule