第五讲spi通过SPI接口模拟主机读写从机寄存

Vivado版本:2019.2 Modelsim版本:Modelsim SE-64 10.7
实验内容:通过SPI接口模拟主机读写从机寄存器,从机SPI时序图在下方给出。
SPI:是Serial Peripheral Interface的缩写,既串行外设接口,是一种4线高速全双工同步的通信总线。通常由一个主模块和一个从模块或多个从模块组成,主模块选择一个从模块进行同步通信,从而完成数据传输。单向传输时,3根线也可以。
其工作方式为主从方式,双向通信至少需要4根线,分别是
CS——Chip Select,片选信号;
SCLK——Serial Clock,时钟信号;
MOSI——Master Output Slave Input,主设备输出,从设备输入信号、
MISO——Master Input Slave Output,主设备输入,从设备输出信号。
当有多个从机时,每个从机都有对应的CS,而SCLK、MOSI、MISO三根线是可以复用的,主机通过使能对应的CS信号来选择从机进行交互。SPI可以有4中工作模式,这四种模式以两个信号区分:
CPOL既Clock Polarity,时钟的极性,也就是SCLK在空闲状态是高电平还是低电平。CPOL为1时,SCLK在空闲时刻为高电平、CPOL为0时,SCLK在空闲时刻为低电平;
CPHA,既Clock Phase,时钟的相位决定了数据采样与输出的沿的位置。CPHA为1时,在时钟前沿输出数据,时钟后沿采样数据;CPHA为0时,时钟前沿采样,后沿输出。至于时钟的前沿和后沿是上升沿还是下降沿,需要看SCLK空闲时刻的状态,例如,SCLK空闲时刻为高时,其前沿为下降沿,后沿为上升沿。
四种模式对应如下:
在这里插入图片描述
在这里插入图片描述
当CPHA为0时,第一个bit需要在时钟采样沿前半个SCLK时钟周期输出到数据线上。

下面介绍一种典型的SPI时序,该SPI使用三线,其将MOSI与MISO合并为一根,变为单向通信,也就是主机在此时刻只能进行读或写,不能同时进行。
从机按照下面时序图工作,也就是每次通信24bit,前16位输入,后8位根据读写标志控制为输出或输入。24bit中第一位为读写标志,信号为低时执行写入操作,为高时执行读出操作,后跟两位低电平的空闲位,接着跟13位地址,最后是8位数据位。
在这里插入图片描述
按照上图从机时序,编写主机内容,访问从机寄存器。假定从机内部有8个寄存器,地址为0x100、0x101、0x102、0x103、0x104、0x105、0x106、0x107,其余地址操作无效;从机的sclk信号只支持10MHz且高低电平保持时间最小为10ns。

打开vivado软件,新建一个工程,配置芯片为xc7z020clg400-1:
在这里插入图片描述
进入工程,新建一个master.v文件。按照spi通信时序编写主机模块,该模块输入信号为系统时钟sysclk、系统复位sysrst_n、启动信号start、读写标志RW_flag、地址addr、写入数据wr_data,输出信号为返回数据rd_data,SPI接口信号为spi_CSn、spi_SCLK、spi_SDIO;其中spi_SDIO为双向接口。
模块内部首先检测start信号的上升沿,当出现上升沿,就拉低spi_CSn信号,启动发送读写标志与地址。然后判断,读写标志为高时发送完地址后将spi_SDIO置为输入,然后接收8位数据;读写标志为低时发送完地址后继续发送wr_data上的数据,至发送完成后拉高spi_CSn信号。
代码很简单。
这里提供一个从机的代码,该从机内部有8个寄存器,初值为0到7。
`timescale 1ns / 1ps

module slave(
input wire sysclk,
input wire sysrst_n,
input wire CSn,
input wire SCLK,
inout wire SDIO
);

//---------------------------------------------------------------
// Define Signal
//---------------------------------------------------------------
reg [ 7:0] slave_reg0 = 8’d0;
reg [ 7:0] slave_reg1 = 8’d1;
reg [ 7:0] slave_reg2 = 8’d2;
reg [ 7:0] slave_reg3 = 8’d3;
reg [ 7:0] slave_reg4 = 8’d4;
reg [ 7:0] slave_reg5 = 8’d5;
reg [ 7:0] slave_reg6 = 8’d6;
reg [ 7:0] slave_reg7 = 8’d7;

reg sclk_r = 0;
wire rx_pulse;
wire tx_pulse;
reg [ 4:0] cnt_rx_pulse = 0;
reg [ 4:0] cnt_tx_pulse = 0;

wire sdi;
reg RW_flag = 0;
reg [12:0] recv_addr = 0;
reg recv_addr_flag = 0;
reg [ 7:0] recv_data = 0;
reg recv_data_flag = 0;

reg [ 7:0] send_data = 0;
wire sdo;

reg tri_sdio = 1;

//---------------------------------------------------------------
// Main Program
//---------------------------------------------------------------
always @(posedge sysclk) begin
sclk_r <= SCLK;
end
assign rx_pulse = (CSn == 1’b0) ? (SCLK & (~sclk_r)) : 0;
assign tx_pulse = (CSn == 1’b0 && cnt_rx_pulse > 5’d16) ? ((~SCLK) & sclk_r) : 0;

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
cnt_rx_pulse <= 0;
end
else if (CSn == 1’b1) begin
cnt_rx_pulse <= 0;
end
else if (rx_pulse == 1’b1) begin
cnt_rx_pulse <= cnt_rx_pulse + 1;
end
else begin
cnt_rx_pulse <= cnt_rx_pulse;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
cnt_tx_pulse <= 0;
end
else if (CSn == 1’b1) begin
cnt_tx_pulse <= 0;
end
else if (tx_pulse == 1’b1) begin
cnt_tx_pulse <= cnt_tx_pulse + 1;
end
else begin
cnt_tx_pulse <= cnt_tx_pulse;
end
end

assign sdi = SDIO;
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
RW_flag <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse == 'd0) begin
RW_flag <= sdi;
end
else begin
RW_flag <= RW_flag;
end
end

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_addr <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse >= 'd3 && cnt_rx_pulse <= 'd15) begin
recv_addr <= {recv_addr[11:0],sdi};
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_addr_flag <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse == 'd15) begin
recv_addr_flag <= 1;
end
else begin
recv_addr_flag <= 0;
end
end

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_data <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse >= 'd16 && cnt_rx_pulse <= 'd23) begin
recv_data <= {recv_data[11:0],sdi};
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_data_flag <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse == 'd23) begin
recv_data_flag <= 1;
end
else begin
recv_data_flag <= 0;
end
end

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
slave_reg0 = 8’d0;
slave_reg1 = 8’d1;
slave_reg2 = 8’d2;
slave_reg3 = 8’d3;
slave_reg4 = 8’d4;
slave_reg5 = 8’d5;
slave_reg6 = 8’d6;
slave_reg7 = 8’d7;
end
else if (RW_flag == 1’b0 && recv_data_flag == 1’b1) begin
case (recv_addr)
13’h100 :slave_reg0 <= recv_data;
13’h101 :slave_reg1 <= recv_data;
13’h102 :slave_reg2 <= recv_data;
13’h103 :slave_reg3 <= recv_data;
13’h104 :slave_reg4 <= recv_data;
13’h105 :slave_reg5 <= recv_data;
13’h106 :slave_reg6 <= recv_data;
13’h107 :slave_reg7 <= recv_data;
default :begin
slave_reg0 = slave_reg0;
slave_reg1 = slave_reg1;
slave_reg2 = slave_reg2;
slave_reg3 = slave_reg3;
slave_reg4 = slave_reg4;
slave_reg5 = slave_reg5;
slave_reg6 = slave_reg6;
slave_reg7 = slave_reg7;
end
endcase
end
else begin
slave_reg0 = slave_reg0;
slave_reg1 = slave_reg1;
slave_reg2 = slave_reg2;
slave_reg3 = slave_reg3;
slave_reg4 = slave_reg4;
slave_reg5 = slave_reg5;
slave_reg6 = slave_reg6;
slave_reg7 = slave_reg7;
end
end

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
send_data <= 0;
end
else if (RW_flag == 1’b1 && recv_addr_flag == 1’b1) begin
case (recv_addr)
13’h100 :send_data <= slave_reg0;
13’h101 :send_data <= slave_reg1;
13’h102 :send_data <= slave_reg2;
13’h103 :send_data <= slave_reg3;
13’h104 :send_data <= slave_reg4;
13’h105 :send_data <= slave_reg5;
13’h106 :send_data <= slave_reg6;
13’h107 :send_data <= slave_reg7;
default :send_data <= 0;
endcase
end
else if (RW_flag == 1’b1 && tx_pulse == 1’b1) begin
send_data <= {send_data[6:0],1’b0};
end
end
assign sdo = send_data[7];
assign SDIO = (tri_sdio == 1’b1) ? 1’bz : sdo;

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
tri_sdio <= 1;
end
else if (CSn == 1’b1) begin
tri_sdio <= 1;
end
else if (RW_flag == 1’b1 && recv_addr_flag == 1’b1) begin
tri_sdio <= 0;
end
end
endmodule

编写完主机代码后,编写tb测试激励。
测试激励也很简单,将主机模块与从机模块例化进来,spi端口连接上,然后控制主机模块先读取地址0x106的寄存器,然后将0xEE写入地址为0x100寄存器中。
主机仿真图如下,当发送完读写标志与地址后,将SDIO置为输入,接收从机返回的数据,可以得到接收到的数据为0x06
在这里插入图片描述
从机仿真图如下,当接收完读写标志与地址后,判断读写标志,为读,则把地址对应的寄存器中的值通过移位输出出去。在这里插入图片描述
当接收到读标志与读地址后,从机将地址对应的寄存器中的值通过SDIO返回:
主机写入时序如下:在这里插入图片描述
从机接收写入的地址与数据,并放在对应的寄存器中,时序如下:
在这里插入图片描述
附:
主机代码:
`timescale 1ns / 1ps

module master(
input wire sysclk,
input wire sysrst_n,

input	wire		start,
input	wire 		RW_flag,
input	wire [12:0]	addr,
input	wire [ 7:0]	wr_data,
output	reg  [ 7:0]	rd_data = 0,

output	wire		spi_CSn,
output	wire		spi_SCLK,
inout	wire		spi_SDIO
);

//---------------------------------------------------------------
// Define Signal
//---------------------------------------------------------------
reg start_r = 0;

reg csn = 1;

reg sclk = 0;
reg [ 3:0] cnt_sclk = 0;

reg [23:0] send_data = 0;
reg sdo = 0;
reg tri_sdio = 0;

reg [ 4:0] cnt_recv = 0;
reg [ 7:0] recv_data = 0;
wire sdi;

//---------------------------------------------------------------
// Main Program
//---------------------------------------------------------------
always @(posedge sysclk) begin
start_r <= start;
end

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
csn <= 1;
end
else if (cnt_recv == 'd24 && cnt_sclk == 'd9) begin
csn <= 1;
end
else if (start == 1’b1 && start_r == 1’b0) begin
csn <= 0;
end
end
assign spi_CSn = csn;

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
sclk <= 0;
end
else if (cnt_sclk == 'd4) begin
sclk <= 1;
end
else if (cnt_sclk == 'd9) begin
sclk <= 0;
end
else begin
sclk <= sclk;
end
end
assign spi_SCLK = sclk;

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
cnt_sclk <= 0;
end
else if (csn == 1’b0 && cnt_sclk == 'd9) begin
cnt_sclk <= 0;
end
else if (csn == 1’b0) begin
cnt_sclk <= cnt_sclk + 1;
end
else begin
cnt_sclk <= 0;
end
end

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
send_data <= 0;
end
else if (start == 1’b1 && start_r == 1’b0) begin
send_data <= {RW_flag,2’b0,addr,wr_data};
end
else if (cnt_sclk == 'd9) begin
send_data <= {send_data[22:0],1’b0};
end
else begin
send_data <= send_data;
end
end
assign spi_SDIO = (tri_sdio == 1’b1) ? 1’bz : send_data[23];

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
tri_sdio <= 0;
end
else if (csn == 1’b1) begin
tri_sdio <= 0;
end
else if (RW_flag == 1’b1 && csn == 1’b0 && cnt_recv == 'd16 && cnt_sclk == 'd9) begin
tri_sdio <= 1;
end
else begin
tri_sdio <= tri_sdio;
end
end

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
cnt_recv <= 0;
end
else if (csn == 1’b1) begin
cnt_recv <= 0;
end
else if (csn == 1’b0 && cnt_sclk == 'd4) begin
cnt_recv <= cnt_recv + 1;
end
else begin
cnt_recv <= cnt_recv;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_data <= 0;
end
else if (RW_flag == 1’b1 && csn == 1’b0 && cnt_recv >= 'd16 && cnt_sclk == 'd4) begin
recv_data <= {recv_data[6:0],sdi};
end
end

always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
rd_data <= 0;
end
else if (RW_flag == 1’b1 && csn == 1’b0 && cnt_recv == 'd24 && cnt_sclk == 'd9) begin
rd_data <= recv_data;
end
end
assign sdi = spi_SDIO;

endmodule

测试激励:
`timescale 1ns / 1ps
module tb_top (); /* this is automatically generated */

// clock
reg clk;
initial begin
	clk = 'b0;
	forever #(5) clk = ~clk;
end

// synchronous reset
reg rst_n;
initial begin
	rst_n <= 'b0;
	repeat(100)@(posedge clk);
	rst_n <= 'b1;
end

// (*NOTE*) replace reset, clock, others

reg 		start = 0;
reg 		RW_flag = 0;
reg  [12:0]	addr = 0;
reg  [ 7:0]	wr_data = 0;
wire [ 7:0]	rd_data;
wire 		spi_CSn;
wire 		spi_SCLK;
wire 		spi_SDIO;

initial begin
	repeat(110)@(posedge clk);
	start	<=	1;
	RW_flag <=	1;
	addr 	<=	13'h106;
	@(posedge clk);
	start	<=	0;

	@(posedge spi_CSn);
	repeat(10)@(posedge clk);
	start	<=	1;
	RW_flag <=	0;
	addr 	<=	13'h100;
	wr_data <=	8'hEE;
	@(posedge clk);
	start	<=	0;
 end

master inst_master	(
	.sysclk   	(clk			),
	.sysrst_n 	(rst_n			),
	.start    	(start			),
	.RW_flag  	(RW_flag		),
	.addr     	(addr			),
	.wr_data  	(wr_data		),
	.rd_data  	(rd_data		),
	.spi_CSn  	(spi_CSn		),
	.spi_SCLK 	(spi_SCLK		),
	.spi_SDIO 	(spi_SDIO 		)
);

slave inst_slave (
	.sysclk 	(clk			), 
	.sysrst_n 	(rst_n			), 
	.CSn 		(spi_CSn		), 
	.SCLK 		(spi_SCLK		), 
	.SDIO 		(spi_SDIO		)
);
  • 3
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值