Verilog | SPI通信

工程下载链接:https://download.csdn.net/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

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值