简介
SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是Motorola公司提出的一种同步串行接口技术。是一种高速、全双工、同步通信总线。在芯片中只占用四根管脚用来控制及数据传输。
优缺点:
SPI通讯协议的优点是支持全双工通信,通讯方式较为简单,且相对数据传输速率较快;
缺点是没有指定的流控制,没有应答机制确认数据是否接收,与IIC总线通讯协议相比,在数据可靠性上有一定缺陷。
物理层
对于SPI协议的物理层,需要讲解的就是SPI通讯设备的连接方式和设备引脚的功能描述。
SPI通讯设备的通讯模式是主从通讯模式,通讯双方有主从之分,根据从机设备的个数,SPI通讯设备之间的连接方式可分为一主一从和一主多从,具体见下图1、2。
图 1 一主一从SPI通讯设备连接图
图 2 一主多从SPI通讯设备连接图
SPI通讯协议包含1条时钟信号线、2条数据总线和1条片选信号线, 时钟信号线为SCK,2条数据总线分别为MOSI(主输出从输入)、MISO(主输入从输出),片选信号线为,它们的作用介绍如下:
-
SCK (Serial Clock):时钟信号线,用于同步通讯数据。由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不同,两个设备之间通讯时,通讯速率受限于低速设备。
-
MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,数据方向由主机到从机。
-
MISO (Master Input,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,数据方向由从机到主机。
-
(Chip Select):片选信号线,也称为CS_N,以下用CS_N表示。当有多个SPI从设备与SPI主机相连时,设备的其它信号线SCK、MOSI及MISO同时并联到相同的SPI总线上,即无论有多少个从设备,都共同使用这3条总线;而每个从设备都有独立的这一条CS_N信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。
I2C协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而SPI协议中没有设备地址,它使用CS_N信号线来寻址,当主机要选择从设备时,把该从设备的CS_N信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI通讯。所以SPI通讯以CS_N线置低电平为开始信号,以CS_N线被拉高作为结束信号。
四种通讯模式
SPI通讯协议一共有四种通讯模式,模式0、模式1、模式2以及模式3,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义。
CPOL参数规定了空闲状态(CS_N为高电平,设备未被选中)时SCK时钟信号的电平状态;
CPHA规定了数据采样是在SCK时钟的奇数边沿还是偶数边沿。
极性和相位
SPI的极性Polarity和相位Phase,最常见的写法是CPOL和CPHA,不过也有一些其他写法,简单总结如下:
(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性
(2) CKPHA (Clock Phase) = CPHA = PHA = Phase = (时钟)相位
(3) SCK=SCLK=SPI的时钟
(4) Edge=边沿,即时钟电平变化的时刻,即上升沿(rising edge)或者下降沿(falling edge)
模式判别
CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:
CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high;
CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low;
CPHA=0,表示第一个边沿:
对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;
CPHA=1,表示第二个边沿:
对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;
SPI通讯协议的4种模式如下,通讯模式时序图,具体见下图 :
模式0:CPOL= 0,CPHA=0。空闲状态时SCK串行时钟为低电平;数据采样在SCK时钟的上升沿;数据更新在SCK时钟的下降沿。
模式1:CPOL= 0,CPHA=1。空闲状态时SCK串行时钟为低电平;数据采样在SCK时钟的下降沿;数据更新在SCK时钟的上升沿。
模式2:CPOL= 1,CPHA=0。空闲状态时SCK串行时钟为高电平;数据采样在SCK时钟的下降沿;数据更新在SCK时钟的上升沿。
模式3:CPOL= 1,CPHA=1。空闲状态时SCK串行时钟为高电平;数据采样在SCK时钟的上升沿;数据更新在SCK时钟的下降沿。
模式的判断:
如果起始的SCLK的电平是0,那么CPOL=0,如果是1,那么CPOL=1,
然后看数据采样时刻,对应到上面SCLK时钟的位置,对应着是第一个边沿或是第二个边沿,即CPHA是0或1
时序需求
tSLCH:cs_n拉低到sck高的时间
tCHSH:sck高到cs_n拉高的时间
SCK上升沿MOSI的建立时间保持时间需求
tSHSL:取消选择的时间,两串数据间隔时间
SPI通用模块
实现功能
用于将任意宽度向量型数据转换为SPI串行输出,模式0:CPOL= 0,CPHA=0;
默认串行数据mosi数据的建立时间和保持时间均为2个clk(0.5*DIV_FREQUENCY)周期,即sck上升沿的前后2个(0.5*DIV_FREQUENCY)clk数据稳定,串行时钟sck周期为4*clk(DIV_FREQUENCY)的周期;
默认tSLCH(cs_n拉低到sck高的时间为6*clk(1.5*DIV_FREQUENCY)周期),tCHSH(sck高到cs_n拉高的时间为2*clk(0.5*DIV_FREQUENCY)周期);
默认tSHSL为10个clk的周期;
若时序满足此模块可以不做修改。若需要修改在外部例化时修改DIV_FREQUENCY(只能偶分频)和PERIOD_WIDTH_MAX,CNT_SHSL_MAX和CNT_SHSL_WIDTH即可。
使用方法
输入data的位宽和计数器位宽在外部进行例化时修改参数的值即可,不必修改SPI模块
DATA_WIDTH_MAX修改为输入数据的位宽,如[31:0]的数据则DATA_WIDTH_MAX=32
CNT_DATA_WIDTH_MAX修改为输入数据的位宽计数器需要的位宽上限,即DATA_WIDTH_MAX=32对应的二进制位宽32=6’b10_0000,所以CNT_DATA_WIDTH_MAX=6
输入输出端口说明:
//input
input wire clk , //系统时钟,spi串行时钟的分频基准
input wire clr_n , //spi信号标志信号,允许发送时一直拉高,重新发送时拉低复位再拉高
input wire [DATA_WIDTH_MAX-1:0] data , //需要data与clr_n一同进入
//output
output reg cs_n , //片选信号
output reg sck , //串行时钟
output reg mosi , //主输出从输入数据
output reg flag //spi发送完成标志位,完成则一直拉高;clr_n置0时拉低
例化模板:文章来源:https://www.toymoban.com/news/detail-824371.html
spi #(
.DATA_WIDTH_MAX (8 ),
.CNT_DATA_WIDTH_MAX (4 )
)
u_spi(
.clk (clk ),
.clr_n (clr_n ),
.data (data_in ),
.cs_n (cs_n ),
.sck (sck ),
.mosi (mosi ),
.flag (flag )
);
SPI模块
//========================================================================
// module_name.v :spi.v
// Author :YprgDay
// Description :用于将任意宽度向量型数据转换为SPI串行输出,模式0:CPOL= 0,CPHA=0。
//========================================================================
module spi
#(
//=========================< Parameter >==============================
parameter DATA_WIDTH_MAX = 32 ,//例化时修改为输入数据的位宽,如[31:0]的数据则DATA_WIDTH_MAX =32
parameter CNT_DATA_WIDTH_MAX = 6 ,//例化时修改为输入数据的位宽计数器需要的位宽上限,即DATA_WIDTH_MAX=32对应的二进制位宽32=6'b10_0000,所以CNT_DATA_WIDTH_MAX=6
parameter DIV_FREQUENCY = 4 ,//分频数(只允许偶分频),串行时钟sck周期为DIV_FREQUENCY*clk的周期
parameter PERIOD_WIDTH_MAX = 2 ,//(DIV_FREQUENCY-1)对应的二进制位宽即为PERIOD_WIDTH_MAX
parameter CNT_PERIOD_MAX = DIV_FREQUENCY-1 ,
parameter CNT_HALF_PERIOD_MAX = CNT_PERIOD_MAX >> 1 ,//计数分频中值
parameter CNT_SHSL_MAX = 10 ,//tSHSL计数10个clk周期
parameter CNT_SHSL_WIDTH = 4 //CNT_SHSL_MAX的二进制位宽
)
(
//=========================< Port Name >==============================
//input
input wire clk , //系统时钟,spi串行时钟的分频基准
input wire clr_n , //spi信号标志信号,允许发送时一直拉高,重新发送时拉低复位再拉高
input wire [DATA_WIDTH_MAX-1:0] data , //需要data与clr_n一同进入
//output
output reg cs_n , //片选信号
output reg sck , //串行时钟
output reg mosi , //主输出从输入数据
output reg flag //spi发送完成标志位,完成则一直拉高;clr_n置0时拉低
);
//=========================< Always block >===========================
reg [CNT_DATA_WIDTH_MAX-1:0] cnt_data_width ;//输出到第几位计数
reg [PERIOD_WIDTH_MAX-1:0] cnt_spi_period ;//时钟分频计数
reg [DATA_WIDTH_MAX-1:0] data_reg ;//存输入数据,保证一个spi发送周期数据不改变
reg [CNT_SHSL_WIDTH-1:0] cnt_shsl ;//SHSL时间计数,保证两串spi的时序需求
//输出的cs_n片选信号
always @(posedge clk or negedge clr_n)begin
if(clr_n == 1'b0)begin
cs_n <= 1'b1;
end
else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
cs_n <= 1'b1;
end
else begin
cs_n <= 1'b0;
end
end
//输出的sck串行时钟信号,
always @(posedge clk or negedge clr_n)begin
if(clr_n == 1'b0)begin
sck <= 0;
end
else if(cnt_data_width > 0 && cnt_spi_period == CNT_PERIOD_MAX)begin
sck <= 0;
end
else if(cnt_data_width > 0 && cnt_spi_period == CNT_HALF_PERIOD_MAX)begin
sck <= 1;
end
else begin
sck <= sck;
end
end
//mosi的串行输出
always @(posedge clk or negedge clr_n)begin
if(clr_n == 1'b0)begin
mosi <= 0;
end
else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
mosi <= 0;
end
else if(cnt_spi_period == CNT_PERIOD_MAX)begin
mosi <= data_reg[DATA_WIDTH_MAX-1-cnt_data_width];
end
else begin
mosi <= mosi;
end
end
//输出的串行数据发送完成标志信号,发送完成即拉高
always @(posedge clk or negedge clr_n)begin
if(clr_n == 1'b0)begin
flag <= 0;
end
else if(cnt_shsl == CNT_SHSL_MAX)begin
flag <= 1;
end
else begin
flag <= 0;
end
end
//输入数据寄存,保证data在一串SPI数据发完之间不发生变化
always @(posedge clk or negedge clr_n)begin
if(clr_n == 1'b0)begin
data_reg <= 0;
end
else if(cnt_data_width == 0 && cnt_spi_period == 1)begin
data_reg <= data;
end
else begin
data_reg <= data_reg;
end
end
//时钟四分频计数
always @(posedge clk or negedge clr_n)begin
if(clr_n == 1'b0)begin
cnt_spi_period <= 0;
end
else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
cnt_spi_period <= cnt_spi_period;
end
else if(cnt_spi_period == CNT_PERIOD_MAX)begin
cnt_spi_period <= 0;
end
else if(cs_n == 0)begin
cnt_spi_period <= cnt_spi_period + 1'b1;
end
else;
end
//计数表示此时输出到[DATA_WIDTH_MAX:0] data的第几位位置
always @(posedge clk or negedge clr_n)begin
if(clr_n == 1'b0)begin
cnt_data_width <= 0;
end
else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
cnt_data_width <= cnt_data_width;
end
else if(cnt_spi_period == CNT_PERIOD_MAX)begin
cnt_data_width <= cnt_data_width + 1'b1;
end
else;
end
//SHSL时间计数,保证两串spi的时序需求,用于对flga信号拉高判断
always @(posedge clk or negedge clr_n)begin
if(clr_n == 1'b0)begin
cnt_shsl <= 0;
end
else if(cnt_shsl == CNT_SHSL_MAX)begin
cnt_shsl <= cnt_shsl;
end
else if(cnt_data_width == DATA_WIDTH_MAX && cnt_spi_period == CNT_PERIOD_MAX)begin
cnt_shsl <= cnt_shsl + 1'b1;
end
else;
end
endmodule
仿真模块
`timescale 1ns / 1ps
//
// Module Name: tb_spi
// Dependencies: spi模块仿真
//
module tb_spi();
//=========================< Parameter >==============================
parameter SPI_CLK_PERIOD = 2 ;//设置spi时钟信号周期
parameter HALF_SPI_CLK_PERIOD = SPI_CLK_PERIOD/2;//生成spi时钟信号半周期
//=========================< Port Name >==============================
//input
reg clk ;
reg clr_n ;
reg [7:0] data_in ;
//output
wire mosi ;
wire cs_n ;
wire sck ;
wire flag ;
//==========================< Clock block >============================
always #HALF_SPI_CLK_PERIOD clk = ~clk ;
//==========================< Reset block >============================
initial begin
clk = 1'b1 ;
clr_n <= 1'b0 ;
data_in <= 0 ;
#HALF_SPI_CLK_PERIOD
clr_n <= 1'b1 ;
data_in <= 8'h83 ;
#30
data_in <= 8'h54 ;
#300
clr_n <= 1'b0 ;
data_in <= 8'hc7 ;
#10
clr_n <= 1'b1 ;
end
//==========================< Module Instance >============================
spi #(
.DATA_WIDTH_MAX (8 ),
.CNT_DATA_WIDTH_MAX (4 )
)
u_spi(
.clk (clk ),
.clr_n (clr_n ),
.data (data_in ),
.cs_n (cs_n ),
.sck (sck ),
.mosi (mosi ),
.flag (flag )
);
endmodule
仿真时序图
以8位输入数据[7:0]data的SPI时序图为例
文章来源地址https://www.toymoban.com/news/detail-824371.html
到了这里,关于SPI简介及FPGA通用MOSI模块实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!