1 SPI通信协议简介
SPI通信协议基础知识学习:硬件设计基础----通信协议SPI
2 实验任务
设计SPI驱动模块,并进行仿真验证,观察仿真波形
3 实验设计
3.1 创建工程
新建工程,操作如图所示:
输入工程名和路径,如图:
选择创建RTL工程,如图:
直接点击Next:
继续点击Next:
添加芯片型号,操作如图:
工程创建完成:
3.2 设计输入
创建工程文件,操作如图所示:
创建spi_drive文件:
创建完成:
双击打开,输入代码如下:
// 模式0
module spi_drive
(
// 系统接口
input sys_clk , // 全局时钟50MHz
input sys_rst_n , // 复位信号,低电平有效
// 接口
input spi_start , // 发送传输开始信号,一个高电平
input spi_end , // 发送传输结束信号,一个高电平
input [7:0] data_send , // 要发送的数据
output reg [7:0] data_rec , // 接收到的数据
output reg send_done , // 主机发送一个字节完毕标志位
output reg rec_done , // 主机接收一个字节完毕标志位
// SPI物理接口
input spi_miso , // SPI串行输入,用来接收从机的数据
output reg spi_sclk , // SPI时钟
output reg spi_cs , // SPI片选信号,低电平有效
output reg spi_mosi // SPI输出,用来给从机发送数据
);
reg [1:0] cnt; //4分频计数器
reg [3:0] bit_cnt_send; //发送计数器
reg [3:0] bit_cnt_rec; //接收计数器
reg spi_end_req; //结束请求
//4分频计数器
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt <= 2'd0;
else if(!spi_cs)begin
if(cnt == 2'd3)
cnt <= 2'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 2'd0;
end
// 生成spi_sclk时钟
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
spi_sclk <= 1'b0; //模式0默认为低电平
else if(!spi_cs)begin //在SPI传输过程中
if(cnt == 2'd0 )
spi_sclk <= 1'b0;
else if (cnt == 2'd2)
spi_sclk <= 1'b1;
else
spi_sclk <= spi_sclk;
end
else
spi_sclk <= 1'b0; //模式0默认为低电平
end
// 生成片选信号spi_cs
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
spi_cs <= 1'b1; //默认为高电平
else if(spi_start) //开始SPI准备传输,拉低片选信号
spi_cs <= 1'b0;
//收到了SPI结束信号,且结束了最近的一个BYTE
else if(spi_end_req && (cnt == 2'd1 && bit_cnt_rec == 4'd0))
spi_cs <= 1'b1; //拉高片选信号,结束SPI传输
end
// 生成结束请求信号(捕捉spi_end信号)
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
spi_end_req <= 1'b0; //默认不使能
else if(spi_cs)
spi_end_req <= 1'b0; //结束SPI传输后拉低请求
else if(spi_end)
spi_end_req <= 1'b1; //接收到SPI结束信号后就把结束请求拉高
end
// 发送数据过程
// 发送数据
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
spi_mosi <= 1'b0; //模式0空闲
bit_cnt_send <= 4'd0;
end
else if(cnt == 2'd0 && !spi_cs)begin //模式0的上升沿
spi_mosi <= data_send[7-bit_cnt_send]; //发送数据移位
if(bit_cnt_send == 4'd7) //发送完8bit
bit_cnt_send <= 4'd0;
else
bit_cnt_send <= bit_cnt_send + 1'b1;
end
else if(spi_cs)begin //非传输时间段
spi_mosi <= 1'b0; //模式0空闲
bit_cnt_send <= 4'd0;
end
else begin
spi_mosi <= spi_mosi;
bit_cnt_send <= bit_cnt_send;
end
end
// 发送数据标志
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
send_done <= 1'b0;
else if(cnt == 2'd0 && bit_cnt_send == 4'd7) //发送完了8bit数据
send_done <= 1'b1; //拉高一个周期,表示发送完成
else
send_done <= 1'b0;
end
// 接收数据过程
// 接收数据spi_miso
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
data_rec <= 8'd0;
bit_cnt_rec <= 4'd0;
end
else if(cnt == 2'd2 && !spi_cs)begin //模式0的上升沿
data_rec[7-bit_cnt_rec] <= spi_miso; //移位接收
if(bit_cnt_rec == 4'd7) //接收完了8bit
bit_cnt_rec <= 4'd0;
else
bit_cnt_rec <= bit_cnt_rec + 1'b1;
end
else if(spi_cs)begin
bit_cnt_rec <= 4'd0;
end
else begin
data_rec <= data_rec;
bit_cnt_rec <= bit_cnt_rec;
end
end
// 接收数据标志
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rec_done <= 1'b0;
else if(cnt == 2'd2 && bit_cnt_rec == 4'd7) //接收完了8bit
rec_done <= 1'b1; //拉高一个周期,表示接收完成
else
rec_done <= 1'b0;
end
endmodule
如图所示:
3.3 分析与综合
对设计进行分析,操作如图:
分析后的设计,Vivado自动生成顶层原理图,如图:
对设计进行综合,操作如图:
综合完成后,弹出窗口如下,直接关闭:
3.4 约束输入
创建约束文件,操作如图所示:
创建约束文件,输入文件名:
双击打开,输入约束代码:
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
如图所示:
3.5 设计实现
点击 Flow Navigator 窗口中的 Run Implementation,如图所示:
点击OK:
完成后,关闭即可:
3.6 功能仿真
创建TestBench,操作如图所示:
创建激励文件,输入文件名:
创建完成:
双击打开,输入TestBench(激励)代码:
//--SPI驱动仿真(模式0)
`timescale 1ns/1ns //时间单位/精度
module tb_spi_drive();
//系统接口
reg sys_clk ; // 全局时钟50MHz
reg sys_rst_n ; // 复位信号,低电平有效
//用户接口
reg spi_start ; // 发送传输开始信号,一个高电平
reg spi_end ; // 发送传输结束信号,一个高电平
reg [7:0] data_send ; // 要发送的数据
wire [7:0] data_rec ; // 接收到的数据
wire send_done ; // 主机发送一个字节完毕标志位
wire rec_done ; // 主机接收一个字节完毕标志位
//SPI物理接口
reg spi_miso ; // SPI串行输入,用来接收从机的数据
wire spi_sclk ; // SPI时钟
wire spi_cs ; // SPI片选信号
wire spi_mosi ; // SPI输出,用来给从机发送数据
//仿真用
reg [3:0] cnt_send ; //发送数据计数器,0-15
//例化SPI驱动模块
spi_drive spi_drive_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.spi_start (spi_start ),
.spi_end (spi_end ),
.data_send (data_send ),
.data_rec (data_rec ),
.send_done (send_done ),
.rec_done (rec_done ),
.spi_miso (spi_miso ),
.spi_sclk (spi_sclk ),
.spi_cs (spi_cs ),
.spi_mosi (spi_mosi )
);
//------------<设置初始测试条件>----------------------------------------
initial begin
sys_clk = 1'b0; //初始时钟为0
sys_rst_n <= 1'b0; //初始复位
spi_start <= 1'b0;
data_send <= 8'd0;
spi_miso <= 1'bz;
spi_end <= 1'b0;
#80 //80个时钟周期后
sys_rst_n <= 1'b1; //拉高复位,系统进入工作状态
#30 //30个时钟周期后拉高SPI开始信号,开始SPI传输
spi_start <= 1'b1; #20 spi_start <= 1'b0;
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
data_send <= 8'd0;
spi_end <= 1'b0;
cnt_send <= 4'd0;
end
else if(send_done)begin //数据发送完成
if(cnt_send == 4'd10)begin
cnt_send <= 4'd0;
spi_end <= 1'b1; //拉高结束标志,结束SPI传输过程
data_send <= 8'd0;
end
else begin
cnt_send <= cnt_send + 4'd1;
spi_end <= 1'b0;
data_send <= data_send + 4'd1; //发送数据累加
end
end
else begin
data_send <= data_send;
spi_end <= 1'b0; //其他时候保持SPI传输(不结束)
end
end
//设置时钟>
always #10 sys_clk = ~sys_clk; //系统时钟周期20ns
endmodule
如图所示:
开始进行仿真,操作如下:
开始仿真:
仿真波形如图:
程序参考: FPGA实现的SPI协议(一)----SPI驱动,感谢分享
致谢领航者ZYNQ开发板,开启FPGA学习之路!
希望本文对大家有帮助,上文若有不妥之处,欢迎指正
分享决定高度,学习拉开差距