本文简介
建立在FPGA的基础上,整理一下自己所遇到的使用到SPI协议的知识点与程序流程与仿真理解、实现。本文使用SPI协议有两种器件,一种是讲解并理解SPI协议的,使用的是所学ADC芯片开发,程序编写选用的是状态机中的特例——序列机来编写的,Testbenth是使用正弦txt文本导入进行验证SPI协议能够正常输入输出;另一种是实际运用上,高精度时间测量模块TDC-GPX2,按照芯片开发手册来进行代码状态机的编写与测试开发,最终实现FPGA联合TDC-GPX2模块来进行多通道时间测量。
一、SPI协议学习与自我理解
1.自我理解
在写这篇文章时,最近我已经阅读了多篇相关的知识点帖子,也在哔哩哔哩上看了不少讲解视频,然后此时如果要我说对SPI协议的理解,那大概就是,一种全双工的、一主可多从、然后还有片选信号CS_N,来进行控制从机有效通信,另外产生同步时钟的是主机,常见的SPI协议通常都是三线或者四线式,这里我以四线为例子(
CS_N:片选信号;
SLCK:时钟;
MOSI:主输从入,输出端口;
MISO:主入从进,输入端口
),
简单来说就是先CS_N拉低选取从机,然后主机按照规定频率发送时钟信号SLCK,同时采样主机的输入信号MISO,并按照协议输出经过芯片的信号MOSI。
2.SPI协议学习参考
串行外设接口(SPI)是微控制器和外围IC(如传感器、ADC、DAC、移位寄存器、SRAM等)之间使用最广泛的接口之一。SPI是一种同步、全双工、主从式接口。来自主机或从机的数据在时钟上升沿或下降沿同步。主机和从机可以同时传输数据。SPI接口可以是3线式或4线式。
产生时钟信号的器件称为主机。主机和从机之间传输的数据与主机产生的时钟同步。同I2C接口相比,SPI器件支持更高的时钟频率。用户应查阅产品数据手册以了解SPI接口的时钟频率规格。
来自主机的片选信号用于选择从机。这通常是一个低电平有效信号,拉高时从机与SPI总线断开连接。当使用多个从机时,主机需要为每个从机提供单独的片选信号。
MOSI和MISO是数据线。MOSI将数据从主机发送到从机,MISO将数据从从机发送到主机。
要开始SPI通信,主机必须发送时钟信号,并通过使能CS信号选择从机。片选通常是低电平有效信号。因此,主机必须在该信号上发送逻辑0以选择从机。SPI是全双工接口,主机和从机可以分别通过MOSI和MISO线路同时发送数据。
在SPI通信期间,数据的发送(串行移出到MOSI/SDO总线上)和接收(采样或读入总线(MISO/SDI)上的数据)同时进行。串行时钟沿同步数据的移位和采样。SPI接口允许用户灵活选择时钟的上升沿或下降沿来采样和/或移位数据。欲确定使用SPI接口传输的数据位数,请参阅器件数据手册。
需要说明的是,我们SPI通信有4种不同的模式,不同的从设备可能在出厂是就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式,具体如下:
Mode0:CPOL=0,CPHA=0
Mode1:CPOL=0,CPHA=1
Mode2:CPOL=1,CPHA=0
Mode3:CPOL=1,CPHA=1
时钟极性CPOL是用来配置SCLK的电平出于哪种状态时是空闲态或者有效态,时钟相位CPHA是用来配置数据采样是在第几个边沿。
优缺点
SPI通讯的优势
使SPI作为串行通信接口脱颖而出的原因很多;
(1)全双工串行通信;
(2)高速数据传输速率。。简单的软件配置;
(3)极其灵活的数据传输,不限于8位,它可以是任意大小的字;
(4)非常简单的硬件结构。从站不需要唯一地址(与I2C不同)。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同)。不需要收发器(与CAN不同)。
SPI的缺点
(1)没有硬件从机应答信号(主机可能在不知情的情况下无处发送);。通常仅支持一个主设备;
(2)需要更多的引脚(与l2C不同);。没有定义硬件级别的错误检查协议;
(3)与RS-232和CAN总线相比,只能支持非常短的距离;
二、ADC芯片开发流程
1.芯片SPI协议开发时序图
2.程序编写
代码如下:
`timescale 1ns / 1ps
//
// Module Name: SPI
//
module SPI(
input clk,
input rst_n,
input start,
input [2:0]channel,
//========ADC128S022===========//
output reg SCLK,
output reg DIN,
output reg CS_N,
input DOUT,
output reg done,
output reg [11:0]data
);
reg en;
reg [2:0]r_channel;
reg [4:0]cnt;
reg cnt_flag;
reg [5:0]SCLK_CNT;
reg [11:0]r_data;
//=============r_channel==================//
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
r_channel <= 'd0;
else if(start)
r_channel