一、简介
SPI(Serial Periphera Interface,串行外围设备接口)通讯协议,是 Motorola 公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输,广泛用于 EEPROM、FIash、RTC(实时时钟)、ADC(数模转换器)、DSP(数字信号处理器)以及数字信号解码器上,是常用的、重要的低速通讯协议之一。
多从机
常规寻址:
通信过程:
SPI协议
时钟极性(CPOL):空闲时时钟的电平极性
时钟相位(CPHA):控制数据更新时刻和采样时刻
CPOL=0,CPHA=0
Sendstrobe:内部发送模块发送的触发信号;Capstrobe:内部接收数据模块采样的触发信号
CPOL=0,CPHA=1
CPOL=1,CPHA=0
CPOL=1,CPHA=1
驱动基本模块
FPGA时序要求
二、程序设计
1、本实验设计一个SPI发送驱动,包含SPI四种工作模式
2、握手信号
只有当req请求信号拉高并低电平时才会开启一次数据传输,数据传输过程中busy信号拉高表示设备忙。
3、SPI发送状态机控制器设计
Haskell
`timescale 1ns / 1ps
module spi_tx#(
parameter CLK_DIV = 'd100 , //
parameter CPOL = 1'b0 , //时钟极性
parameter CPHA = 1'b0 //时钟相位
)
(
input I_clk , //系统时钟
input I_rstn , //系统复位
input I_spi_tx_req , //发送数据请求
input [7:0] I_spi_tx_data , //发送数据
output O_spi_mosi , //输出SPI数据
output O_spi_sclk , //输出SPI时钟
output O_spi_busy //输出忙信号
);
localparam [9:0] SPI_DIV = CLK_DIV ; //第二时钟边沿计数器
localparam [9:0] SPI_DIV1 = SPI_DIV / 2 ; //第一时钟边沿计数器 //分频系数一半
reg [9:0] clk_div ;
reg spi_en ; //发送使能
reg [3:0] tx_cnt ;
reg spi_clk ;
reg [7:0] I_spi_tx_data_r ;
reg spi_strobe_en ;
wire clk_end ; //
wire clk_en1 ; //第一内部时钟使能
wire clk_en2 ; //第二内部时钟使能
wire spi_strobe ;
//计数器发送第一个时钟0-7次,当计数达到8时,不发送时钟
assign clk_en1 = (clk_div == SPI_DIV1); //第一内部时钟边沿使能 一半
assign clk_en2 = (clk_div == SPI_DIV ); //第二内部时钟边沿使能 记满
assign clk_end = ((clk_div == SPI_DIV1)&&(tx_cnt == 4'd8)); //时钟结束信号 计数半个周期
//当CPHA=0时,数据的第一个SCLK转换边缘被采样,因此数据更新在第二个转换边缘上
//当CPHA=1时,数据的第二个SCLK转换边缘被采样,因此数据更新在第一个转换边缘上
assign spi_strobe = CPHA ? clk_en1 & spi_strobe_en : clk_en2 & spi_strobe_en;
assign O_spi_sclk = (CPOL == 1'b1) ? !spi_clk : spi_clk ; //设置SPI初始时钟
assign O_spi_mosi = I_spi_tx_data_r[7] ; //SPI输出数据
assign O_spi_busy = spi_en;
//SPI时钟计数器
always @(posedge I_clk ) begin
if (spi_en == 1'b0) begin
clk_div <= 10'd0;
end
else if(clk_div < SPI_DIV)begin
clk_div <= clk_div + 1'b1;
end
else begin
clk_div <= 10'd0;
end
end
//SPI时钟生成
always @(posedge I_clk ) begin
if (spi_en == 1'b0) begin
spi_clk <= 1'b0;
end
else if(clk_en2 == 1'b1)begin
spi_clk <= 1'b0;
end
else if ((clk_en1 == 1'b1)&&(tx_cnt < 4'd8)) begin
spi_clk <= 1'b1;
end
else begin
spi_clk <= spi_clk;
end
end
//SPI bit计数器
always @(posedge I_clk ) begin
if ((!I_rstn)||(spi_en == 1'b0)) begin
tx_cnt <= 4'd0;
end
else if(clk_en1 == 1'b1)begin
tx_cnt <= tx_cnt + 1'b1;
end
end
//
always @(posedge I_clk ) begin
if (!I_rstn) begin
spi_strobe_en <= 1'b0;
end
else if(tx_cnt < 4'd8)begin
if (clk_en1 == 1'b1) begin
spi_strobe_en <= 1'b1;
end
else begin
spi_strobe_en <= spi_strobe_en;
end
end
else begin
spi_strobe_en <= 1'b0;
end
end
//SPI发送模块
always @(posedge I_clk ) begin
if ((!I_rstn)||(clk_end == 1'b1)) begin
spi_en <= 1'b0;
I_spi_tx_data_r <= 8'b0;
end
else if((I_spi_tx_req == 1'b1)&&(spi_en == 1'b0))begin //启动传输
spi_en <= 1'b1;
I_spi_tx_data_r <= I_spi_tx_data;
end
else if (spi_en == 1'b1) begin
I_spi_tx_data_r[7:0] <= (spi_strobe)?{I_spi_tx_data_r[6:0],1'b1} : I_spi_tx_data_r;
end
end
endmodule
Haskell
`timescale 1ns / 1ps
module spi_master_tx#(
parameter CLK_DIV = 100
)
(
input I_clk , //
input I_rstn , //
output O_spi_sclk , //
output O_spi_mosi //
);
wire spi_busy ; //spi忙的标志
reg spi_tx_req ; //spi请求发送
reg [7:0] spi_tx_data ; //spi待发送数据
reg [1:0] M_S ; //状态机
always @(posedge I_clk ) begin
if (!I_rstn) begin
spi_tx_req <= 1'b0;
spi_tx_data <= 8'd0;
M_S <= 2'd0;
end
else begin
case (M_S)
0:begin
if (spi_busy == 1'b0) begin
spi_tx_req <= 1'b1; //请求发送
spi_tx_data <= spi_tx_data + 1'b1; //测试累加数据
M_S <= 2'd1;
end
end
1:begin
if (spi_busy == 1'b1) begin //清楚请求信号
spi_tx_req <= 1'b0;
M_S <= 2'd0;
end
end
default:M_S <= 2'd0;
endcase
end
end
spi_tx#(
.CLK_DIV (CLK_DIV), //
.CPOL (1'b0 ), //时钟极性
.CPHA (1'b0 ) //时钟相位
)
u_spi_tx(
.I_clk (I_clk ), //系统时钟
.I_rstn (I_rstn ), //系统复位
.I_spi_tx_req (spi_tx_req ), //发送数据请求
.I_spi_tx_data (spi_tx_data ), //发送数据
.O_spi_mosi (O_spi_mosi ), //输出SPI数据
.O_spi_sclk (O_spi_sclk ), //输出SPI时钟
.O_spi_busy (spi_busy ) //输出忙信号
);
endmodule
Haskell
`timescale 1ns / 1ps
module tb( );
localparam SYS_TIME = 4'd10;
reg I_clk;
reg I_rstn;
wire O_spi_mosi;
wire O_spi_sclk;
spi_master_tx#(
.CLK_DIV (100)
)
u_spi_master_tx(
.I_clk (I_clk ), //
.I_rstn (I_rstn ), //
.O_spi_sclk (O_spi_sclk ), //
.O_spi_mosi (O_spi_mosi ) //
);
initial begin
I_clk = 0;
I_rstn = 0;
#100;
I_rstn = 1;
end
always #(SYS_TIME / 2) I_clk = !I_clk;
endmodule
00
4、SPI接收模块
Haskell
`timescale 1ns / 1ps
module spi_rx#
(
parameter BITS_LEM = 8 ,//bit数量
parameter CPOL = 1'b0 ,
parameter CPHA = 1'b0
)
(
input I_clk , //系统时钟
input I_rstn , //系统复位
input I_spi_clk , //SPI时钟
input I_spi_rx , //SPI rx数据总线
input I_spi_ss , //SPI片选信号
output O_spi_rvalid, //SPI rx接收数据有效信号 1:rdata有效 0:无效
output [BITS_LEM - 1'b1:0] O_spi_rdata //SPI rx接收到的数据输出
);
reg [3:0] spi_clk_r ; //时钟打拍
reg [3:0] spi_ss_r ; //片选打拍
reg spi_cap ; //采样信号
reg [3:0] spi_bit_cnt ; //bit计数器
reg [BITS_LEM - 1'b1:0] spi_rx_r1 ; //接收缓存
wire spi_rx_en ; //接收使能
wire spi_clkp ; //spi上升沿
wire spi_clkn ; //spi下升沿
assign spi_clkp = (spi_clk_r[3:2] == 2'b01) ; //spi上升沿
assign spi_clkn = (spi_clk_r[3:2] == 2'b10) ; //spi下升沿
assign spi_rx_en = (!spi_ss_r[3]) ;
assign O_spi_rdata = spi_rx_r1 ;
assign O_spi_rvalid = (spi_bit_cnt == BITS_LEM) ;
//I_spi_clk去毛刺
always @(posedge I_clk or negedge I_rstn ) begin
if (!I_rstn) begin
spi_clk_r <= 1'b0;
end
else begin
spi_clk_r <= {spi_clk_r[2:0],I_spi_clk};
end
end
//I_spi_ss去毛刺
always @(posedge I_clk or negedge I_rstn ) begin
if (!I_rstn) begin
spi_ss_r <= 1'b0;
end
else begin
spi_ss_r <= {spi_ss_r[2:0],I_spi_ss};
end
end
//cap信号生成 何时采样
always @( * ) begin
if (CPHA == 1'b1) begin
if (CPOL == 1'b1) begin
spi_cap = spi_clkn; //CPHA = 1 CPOL = 1
end
else begin
spi_cap = spi_clkp; //CPHA = 1 CPOL = 0
end
end
else begin
if (CPOL == 1'b1) begin
spi_cap = spi_clkn; //CPHA = 0 CPOL = 1
end
else begin
spi_cap = spi_clkp; //CPHA = 0 CPOL = 0
end
end
end
//bit计数器
always @(posedge I_clk ) begin
if ((spi_rx_en == 1'b1)&&(spi_bit_cnt < BITS_LEM)&&(spi_cap == 1'b1)) begin //开启接收,并且未达到计数最大值
spi_bit_cnt <= spi_bit_cnt + 1'b1;
end
else if((spi_rx_en == 1'b0)||(spi_bit_cnt == BITS_LEM))begin //未开启接收 或计数到最大追
spi_bit_cnt <= 4'd0;
end
end
//bit移位 串转并
always @(posedge I_clk ) begin
if ((spi_rx_en == 1'b1)&&(spi_cap == 1'b1)) begin
spi_rx_r1 <= {spi_rx_r1[BITS_LEM - 2:0],I_spi_rx}; //移位寄存 高位在前
end
else if(spi_rx_en == 1'b0)begin
spi_rx_r1 <= 'd0;
end
end
endmodule
Haskell
`timescale 1ns / 1ps
module tb( );
localparam BYTES = 8 ;
localparam CPOL = 1'b0 ;
localparam CPHA = 1'b0 ;
localparam TCNT = BYTES * 8 * 2 - 1;
reg I_clk ; //系统时钟
reg [7:0] i ; //计数器,产生SPI时钟
reg I_rstn ; //系统复位
reg I_spi_clk ; //SPI时钟
reg I_spi_ss ; //SPI片选
reg [3:0] bit_cnt ; //bit计数器
reg [7:0] spi_tx_buf ; //发送缓冲(移位寄存器)
reg [7:0] spi_tx_buf_r ; //发送化缓冲,用于生成测试文件
reg first_data_flag ; //是否是一个时钟该变数据
wire O_spi_rvalid ; //SPI数据接收有效
wire [7:0] O_spi_rdata ;
wire I_spi_rx ;
assign I_spi_rx = spi_tx_buf[7];
spi_rx#
(
.BITS_LEM (8 ) ,//bit数量
.CPOL (1'b0 ) ,
.CPHA (1'b0 )
)
u_spi_rx(
.I_clk (I_clk ), //系统时钟
.I_rstn (I_rstn ), //系统复位
.I_spi_clk (I_spi_clk ), //SPI时钟
.I_spi_rx (I_spi_rx ), //SPI rx数据总线
.I_spi_ss (I_spi_ss ), //SPI片选信号
.O_spi_rvalid(O_spi_rvalid ), //SPI rx接收数据有效信号 1:rdata有效 0:无效
.O_spi_rdata (O_spi_rdata ) //SPI rx接收到的数据输出
);
initial begin
I_clk = 0;
I_rstn = 0;
#100;
I_rstn = 1;
end
always #5 I_clk = !I_clk;
initial begin
#100
i = 0;
forever begin
I_spi_clk = CPOL;
I_spi_ss = 1;
#2000
I_spi_ss = 0;
for (i = 0;i<TCNT ;i = i + 1 ) begin
#1000
I_spi_clk = !I_spi_clk;
end
#2000
I_spi_ss = 1;
end
end
initial begin
#100
bit_cnt = 0;
first_data_flag = 0;
spi_tx_buf[7:0] = 8'ha0;
spi_tx_buf_r[7:0] = 5'ha0;
forever begin
wait(I_spi_ss);
bit_cnt = 0;
spi_tx_buf[7:0] = 8'ha0;
spi_tx_buf_r[7:0] = 8'ha0;
if ((CPHA == 1 && CPOL == 0)||(CPHA == 1 && CPOL == 1)) begin
first_data_flag = 1;
end
wait(!I_spi_ss);
while (!I_spi_ss) begin
if (CPHA == 0 && CPOL == 0) begin
@(negedge I_spi_clk)begin
if (bit_cnt == 7) begin
bit_cnt = 0;
spi_tx_buf_r = spi_tx_buf_r + 1'b1;
spi_tx_buf = spi_tx_buf_r;
end
else begin
spi_tx_buf = {spi_tx_buf[6:0],1'b0};
bit_cnt = bit_cnt + 1'b1;
end
end
end
if (CPHA == 0 && CPOL == 1) begin
@(posedge I_spi_clk)begin
if (bit_cnt == 7) begin
bit_cnt = 0;
spi_tx_buf_r = spi_tx_buf_r + 1'b1;
spi_tx_buf = spi_tx_buf_r;
end
else begin
spi_tx_buf = {spi_tx_buf[6:0],1'b0};
bit_cnt = bit_cnt + 1'b1;
end
end
end
if (CPHA == 1 && CPOL == 0) begin
@(posedge I_spi_clk)begin
if (first_data_flag == 1'b1) begin
first_data_flag = 0;
end
else begin
if (bit_cnt == 7) begin
bit_cnt = 0;
spi_tx_buf_r = spi_tx_buf_r + 1'b1;
spi_tx_buf = spi_tx_buf_r;
end
else begin
spi_tx_buf = {spi_tx_buf[6:0],1'b0};
bit_cnt = bit_cnt + 1'b1;
end
end
end
end
if (CPHA == 1 && CPOL == 1) begin
@(negedge I_spi_clk)begin
if (first_data_flag == 1'b1) begin
first_data_flag = 0;
end
else begin
if (bit_cnt == 7) begin
bit_cnt = 0;
spi_tx_buf_r = spi_tx_buf_r + 1'b1;
spi_tx_buf = spi_tx_buf_r;
end
else begin
spi_tx_buf = {spi_tx_buf[6:0],1'b0};
bit_cnt = bit_cnt + 1'b1;
end
end
end
end
end
end
end
endmodule