Verilog SPI发送实现

系列文章目录

FPGA Verilog 串口发送
使用modelsim进行Verilog仿真(包含testbench编写)
FPGA 串口收发


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

之前折腾Verilog折腾了几次,把串口收发弄出来了,但是总感觉问题还是挺多的,从单片机转过来搞数字逻辑电路搞得我没有逻辑了。看时序总是要不少一个状态,要不就多一个状态,反正就是达不到想要的,挺头秃的。现在决心先专心搞通搞懂一样东西再说其他的,后面毕竟目标是要把数字信号处理在FPGA上实现的。


提示:以下是本篇文章正文内容。

一、SPI

想到搞SPI是为了以后有可能FPGA和ARM这些相互通信传数据,所以专门研究下这个。相比于串口,SPI总线对时钟要求没有那么严格,只关心SCK线的上升沿或者下降沿时的数据线的状态。此外,SPI的时钟可以达到很高的速率,传输的效率挺高。SPI的定义啥的就不多说了,对于初学者,SPI和I2C应该是用的最多的总线了,都可以很多个器件共用同一个时钟和数据线。方便。与SPI相关的数据线有四根,分别是SPI、MOSI、MISO和CS,在半双工的情况下MOSI和MISO可能只有其一。
SPI的模式有4种,模式0、模式1、模式2和模式3.区别就在于起始时刻的时钟线的电平高低和数据动作是在时钟的上升沿和下降沿上。
本文应用于模式0,即时钟起始电平为低电平,数据在SCK下降沿动作。下图是对W25Q128的操作,可以看到在CLK信号实线对应的就是模式0。
在这里插入图片描述
对于时序来说,可以这样理解:

1.在最原始的时刻,CS电平为高,CLK电平为低,MISO和MOSI无所谓;
2. 将CS电平拉低,SCK时钟线拉高,同时把数据放到数据线上,由于本文先做SPI发送,所以MOSI作为数据线;
3. 将SCK时钟线拉低;
4. 重复步骤2和步骤3,直到数据发送完成;
5. 将CS线拉高,标志着一次传输结束。

二、代码构思

1.直接法

本人最开始的思路就是使用直接法,直接按照上面的时序来进行操作,中间出现了一些问题,有点不太好解决。

  1. 首先本人对时序不太熟,写出来的东西要么多一个状态,要么少一个状态,很难搞;
  2. 对于下面的代码,好像被编译器给优化了,或者说根本就不能行
reg SCK;
always @(posedge clk or negedge rst)
begin
SCK<=1;
SCK<=0;
end

编译器就只取了最新的状态即SCK<=0,而对于这个状态的翻转,体现不出来。

2.状态机

思来想去,还是选择状态机(和谐~说机不说吧文明你我他)吧,这个东西思路也容易理清楚,然后程序的健壮性也相当好。
状态机的思路参考了@jgliu的【接口时序】4、SPI总线的原理与Verilog实现,这一篇写的还是相当不错的,有原理又有代码,讲的挺清楚的。这个老哥是对8bit的SPI总线的mode 0进行分析,其中状态机是将每个时钟周期拆成了两个状态,在电平的上升沿和下降沿进行单独罗列。如下图所示。
在这里插入图片描述
考虑到SPI的位宽不总是8bit,有可能是16bit甚至更多,所以本人在@jgliu老哥的基础上对状态机进行拓展。状态机如下思路所示:
首先也就是最初始的时刻,令该状态为stage=0,如果没有tx_en的使能信号,则一直停留在该状态,反之进入到奇偶状态,这个状态可以理解为一旦开始,就能一直顺着状态机往下走,在stage为偶时,将数据放到数据线上,状态+1,时钟线拉高;在stage为偶时产生时钟下降沿,状态+1。以8bit的SPI为例,当stage=15时数据传完,进入到stage=17状态,也就是终止状态,该状态下发出传输完成信号,然后将stage清0,后面进入初始状态。在这里插入图片描述

3.代码

module spi_module
#(parameter LEN=8)
(
	input clk,
	input rst,
	input [LEN-1:0] data_in,//要发送的数据
	input tx_en,//外部控制是否发送
	output reg tx_done,
/************基本四线***********/
	output reg sck,
	output reg mosi,
	output reg cs,
	input miso
);
/************参数**************/
reg [5:0] stage;
reg tx_flag;
reg [4:0] bit_cnt;

initial
begin
	stage<=0;
	bit_cnt<=0;
	tx_flag<=0;
end
always @(posedge clk or negedge rst)
begin
	if(!rst)
	begin
		sck<=0;
		mosi<=0;
		cs<=1;
		bit_cnt<=0;
		tx_done<=0;
		stage<=0;
		tx_flag<=0;
	end
	else
		if(stage==0)
		begin
			tx_done<=0;
			bit_cnt<=0;
		  	tx_flag<=0;
			stage<=0;
			mosi<=0;
			cs<=1;			
		end		
		if(tx_en&&(!tx_flag))
		begin
		cs<=0;
		if((stage[0]==1) && (bit_cnt<=(LEN)))//奇状态
			begin
			sck<=1;
			stage<=stage+1;
			end
		else				//偶状态
			if((stage[0]==0) && (bit_cnt<=(LEN)))
				begin
				sck<=0;
				mosi<=data_in[LEN-1-bit_cnt];
				stage<=stage+1;
				bit_cnt<=bit_cnt+1;
				if(bit_cnt==(LEN))
					begin
					tx_done<=1;
					mosi<=0;
					bit_cnt<=0;
					tx_flag<=1;
					stage<=0;
					cs<=1;
					end
				end
		end
end
endmodule

模块带入参数LEN,表示SPI线的位宽,如果LEN为8,则状态总共有82+1=17个,如果为16,状态为216+1=33个。程序中有用到一个计数器,用于对传输的位数进行统计,当计数器计到LEN后,标志着传输完成,程序回到初始状态。testbench如下,有些信号没有用上,留着后面做SPI接收用。

`timescale 1ns/1ps
module spi_module_tb;

	reg CLK;
	reg RST;
	reg [8-1:0] data_in;//要发送的数据
	reg tx_en;
	wire tx_done;
/************基本四线***********/
	wire sck;
	wire mosi;
	wire cs;
	reg miso;

spi_module
#(.LEN(8))
spi_module_inst
(
	.clk(CLK),
	.rst(RST),
	.data_in(data_in),//要发送的数据
	.tx_en(tx_en),
	.tx_done(tx_done),
/************基本四线***********/
	.sck(sck),
	.mosi(mosi),
	.cs(cs),
	.miso(miso)
);
initial
begin
	CLK=0;	
	RST=0;
	#10 tx_en=1;
	#20 RST=1;
	forever #1 CLK=~CLK;
end
always @(posedge CLK or negedge RST)
begin
	if(!RST)
		data_in<=8'd5;
	else
		if(tx_done)
		begin
//			tx_en<=0;
			data_in<=data_in+1;
			if(data_in==8'hff)
				data_in<=8'h0;			
		end
end
endmodule

三、结果

波形仿真结果如下图所示。结果表明,时序能够达到要求。
在这里插入图片描述


总结

本文根据状态机的思路,提出了一种基于状态机思路的改进广义状态机,实现了可变位深度的SPI发送功能,经过ModelSim SE-64 2020.4软件的仿真,能够达到SPI的通信要求。后面将继续实现SPI接收功能以及FPGA与STM32进行SPI通信。

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值