SPI简介及FPGA通用MOSI模块实现

SPI简介及FPGA通用MOSI模块实现SPI 简介及 FPGA 的通用 MOSI 模块实现

大家好,欢迎来到IT知识分享网。

简介

SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是Motorola公司提出的一种同步串行接口技术。是一种高速、全双工、同步通信总线。在芯片中只占用四根管脚用来控制及数据传输。

优缺点:

物理层

SPI通讯协议包含1条时钟信号线、2条数据总线和1条片选信号线, 时钟信号线为SCK,2条数据总线分别为MOSI(主输出从输入)、MISO(主输入从输出),片选信号线为,它们的作用介绍如下:

  1. SCK (Serial Clock):时钟信号线,用于同步通讯数据。由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不同,两个设备之间通讯时,通讯速率受限于低速设备。
  2. MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,数据方向由主机到从机。
  3. MISO (Master Input,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,数据方向由从机到主机。
  4. (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时钟的奇数边沿还是偶数边沿。

极性和相位

模式判别

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时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;

时序需求

tSLCH:cs_n拉低到sck高的时间
tCHSH:sck高到cs_n拉高的时间
SCK上升沿MOSI的建立时间保持时间需求
tSHSL:取消选择的时间,两串数据间隔时间
在这里插入图片描述



SPI通用模块

实现功能

使用方法

输入输出端口说明:

//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时拉低 

例化模板:

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串行时钟信号,模式1:CPOL= 0,CPHA=0;用此时钟 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 //模式3:CPOL= 1,CPHA=1;用此时钟 /* always @(posedge clk or negedge clr_n)begin if(clr_n == 1'b0)begin sck <= 1; end else if(cnt_data_width < DATA_WIDTH_MAX && 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 && cs_n == 0 && cnt_spi_period == 0)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 

仿真时序图

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/133153.html

(0)
上一篇 2025-07-25 21:45
下一篇 2025-07-25 22:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信