FPGA学习之路—接口(3)—SPI详解及Verilog源码分析

FPGA学习之路——SPI详解及Verilog源码分析

概述

SPI = Serial Peripheral Interface,是串行外围设备接口,是一种高速,全双工,同步的通信总线。

  • 优点
    支持全双工
    支持高速
    协议支持字长不限于8bit,可以根据应用灵活选择消息字长。
    硬件连接简单
  • 缺点
    相比I2C多两条线
    没有寻址机制,只能靠片选选择不同的设备
    没有回应ACK机制,主设备不知道消息发送是否成功
    典型应用仅支持单主控

硬件结构

  • 信号定义
    SCK:Serial Clock,时钟信号,由主设备产生。
    MOSI:Master Output,Slave Input 主发从收信号。在片选信号有效时,数据由高位到低位,在时钟的上升沿依次发送给从设备。
    MISO:Master Input,Slave Output 主收从发信号,在片选信号有效时,数据由高位到低位,在时钟的上升沿依次发送给主设备。
    SS/CS:Slave Select 片选信号,低有效,由主设备控制。即只有片选信号为预先规定的使能信号时,对应的芯片操作才有效,这使得在同一总线上连接多个SPI设备成为可能。
  • 电路连接
    单个主设备和单个从设备:
    SPI硬件结构
    单个主设备和多个从设备,通过用多个片选信号或者菊花链的形式完成。
    单主多从设备
  • 传输模式
    通过设置相关控制寄存器,SPI可以有四种传输模式。
    1、时钟空闲时的电平为高或低。
    2、数据采样发生在时钟(SCK)的上/下边沿。
    将以上两种情况两两组合,即可得四种传输模式。
    数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取,完成一位数据传输,输入与输出原理相近。这样通过至少8次时钟信号的改变(上升沿和下降沿为一次),就可以完成8位数据的传输。如下图所示。
    SPI时序图
  • 标准SPI读
    SPI读
    片选→读指令→地址→数据读出
  • 标准SPI写
    SPI写
    片选→写指令→地址→数据写入

Verilog代码解析

本文以SPI Master控制器为例来对Verilog源码进行分析,参考资料为《VERILOG HDL应用程序设计实例精讲》,仅供学习参考,项目在文章末尾给出下载链接。
1、时钟分频模块,将原始时钟进行四分频,过程较为简单,不再详述。

module clkdiv(clk,clkout);
input clk;
output clkout;

reg [1:0]cnt=2'd0;
reg clkout=1'b0;

always @(posedge clk)begin
    if(cnt==2'd1)begin
        clkout<=1'b1;
        cnt<=cnt+2'd1;
    end
    else if(cnt == 2'd3)begin
        clkout<=1'b0;
        cnt<=2'd0;
    end
    else begin
        cnt<=cnt+2'd1;
    end
end
endmodule

2、SPI发送数据部分,在spiclk的上升沿完成数据的传输。spics为片选信号,低有效;spido为输出的数据;dstate为FSM变量。dsend为待传送数据,其中部分数据过程重复,代码中仅保留首尾数据的传输过程。

begin
			case (dstate)
			8'd0:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				spido <= 1'b1;
				dstate <= 8'd1;
			end
			8'd1:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				spido <= 1'b1;
				dstate <= 8'd2;
			end
			8'd2:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				spido <= 1'b1;
				dstate <= 8'd3;
			end
			8'd3:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				spido <= dsend[7];
				dstate <= 8'd4;
			end
			8'd4:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				spido <= dsend[7];
				dstate <= 8'd5;
			end
			……
			8'd17:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				spido <= dsend[0];
				dstate <= 8'd18;
			end
			8'd18:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				spido <= dsend[0];
				dstate <= 8'd19;
			end
			8'd19:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				spido <= 1'b1;
				dstate <= 8'd20;
			end
			8'd20:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				spido <= 1'b1;
				dstate <= 8'd0;
				spistate <= idle;
			end
			endcase
		end
		default:
		begin
			spics <= 1'b1;
			spiclk <= 1'b1;
			spido <= 1'b1;
			spistate <= 2'b00;
		end
		endcase
	end

SPI发送模块
可以看到当片选信号spics为低有效时,数据在spiclk的上升沿按顺序被发送至SPI总线上,数据信号spido与输入数据相对应,SPI Master发送时序得到验证。
3、SPI接收数据部分,参数定义与2一样,其中部分数据过程重复,代码中仅保留首尾数据的传输过程。在时钟的下降沿进行数据的接收。

begin
			case (dstate)
			8'd0:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dstate <= 8'd1;
			end
			8'd1:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dstate <= 8'd2;
			end
			8'd2:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dstate <= 8'd3;
			end
			8'd3:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;				
				dstate <= 8'd4;
			end
			8'd4:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dreceive[7] <= spidi;
				dstate <= 8'd5;
			end
			8'd5:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;				
				dstate <= 8'd6;
			end
			……
			8'd18:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dreceive[0] <= spidi;
				dstate <= 8'd19;
			end
			8'd19:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dstate <= 8'd20;
				dataout <= dreceive;
			end
			8'd20:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dstate <= 8'd0;
				spistate <= 2'b00;
			end
			endcase
		end

SPI接收
由上图分析看出,收到的数据与SPI传输的数据相对应,SPI接收数据部分得到正确验证。

实验项目框图:
项目框图

实验心得
1、在给复位信号低电平时时间过短,分频后时钟上升沿还未到复位信号就拉高了,导致未进行有效复位。
2、进行发送/接收状态检测时设置的计数子过大,如下图,每过40个时钟周期进行一次检测,当仿真时间过短时,容易看不到结果。

if(cnt == 8'd40)
			begin
				cnt <= 8'd0;
				if((wr == 1'b0) && (rd == 1'b1))
				begin
					spistate <= send_data;
					dstate <= 8'd0;
					dsend <= datain;
				end
				else if((wr == 1'b1) && (rd == 1'b0))
				begin
					spistate <= receive_data;
					dstate <= 8'd0;
				end
			end
			else
			begin
				cnt <= cnt + 8'd1;
			end

3、若结果出现问题,将相关控制信号添加至窗口,观察其状态,分析程序存在的问题。

项目下载链接:https://download.csdn.net/download/qq_42334072/12384179
参考资料:https://blog.csdn.net/weixin_42509369/article/details/83096349
《VERILOG HDL应用程序设计实例精讲》

原创文章 4 获赞 8 访问量 155

猜你喜欢

转载自blog.csdn.net/qq_42334072/article/details/105900315