文章链接:SPI

https://www.diangon.com/wenku/rd/danpianji/201501/00017903.html

SPI是同步串行通信接口。 
SPI是英语Serial Peripheral Interface的缩写,顾名思义就是串行外围设备接口。SPI是一种高速的、全双工、同步通信总线,标准的SPI也仅仅使用4个引脚,常用于单片机和EEPROM、FLASH、实时时钟、数字信号处理器等器件的通信。SPI通信原理比I2C要简单,它主要是主从方式通信,这种模式通常只有一个主机和一个或者多个从机,标准的SPI是4根线,分别是SSEL(片选,也写作SCS)、SCLK(时钟,也写作SCK)、MOSI(主机输出从机输入Master Output/Slave Input)和MISO(主机输入从机输出Master Input/Slave Output)。

SSEL:从设备片选使能信号。如果从设备是低电平使能的话,当拉低这个引脚后,从设备就会被选中,主机和这个被选中的从机进行通信。 
SCLK:时钟信号,由主机产生,和I2C通信的SCL有点类似。 
MOSI:主机给从机发送指令或者数据的通道。 
MISO:主机读取从机的状态或者数据的通道。

单从设备:

多从设备:

在某些情况下,我们也可以用3根线的SPI或者2根线的SPI进行通信。比如主机只给从机发送命令,从机不需要回复数据的时候,那MISO就可以不要;而在主机只读取从机的数据,不需要给从机发送指令的时候,那MOSI可以不要;当一个主机一个从机的时候,从机的片选有时可以固定为有效电平而一直处于使能状态,那么SSEL可以不要;此时如果再加上主机只给从机发送数据,那么SSEL和MISO都可以不要;如果主机只读取从机送来的数据,SSEL和MOSI都可以不要。 3线和2线的SPI大家要知道怎么回事,实际使用也是有应用的,但是当我们提及SPI的时候,一般都是指标准SPI,都是指4根线的这种形式。

SPI通信的主机也是我们的单片机,在读写数据时序的过程中,有四种模式,要了解这四种模式,首先我们得学习一下2个名词。

CPOL:Clock Polarity,就是时钟的极性。 
时钟的极性是什么概念呢?通信的整个过程分为空闲时刻和通信时刻,SCLK在数据发送之前和之后的空闲状态是高电平那么CPOL=1,如果空闲状态SCLK是低电平,那么CPOL=0。 
CPHA:Clock Phase,就是时钟的相位。

主机和从机要交换数据,就牵涉到一个问题,即主机在什么时刻输出数据到MOSI上而从机在什么时刻采样这个数据,或者从机在什么时刻输出数据到MISO上而主机什么时刻采样这个数据。同步通信的一个特点就是所有数据的变化和采样都是伴随着时钟沿进行的,也就是说数据总是在时钟的边沿附近变化或被采样。而一个时钟周期必定包含了一个上升沿和一个下降沿,这是周期的定义所决定的,只是这两个沿的先后并无规定。又因为数据从产生的时刻到它的稳定是需要一定时间的,那么,如果主机在上升沿输出数据到MOSI上,从机就只能在下降沿去采样这个数据了。反之如果一方在下降沿输出数据,那么另一方就必须在上升沿采样这个数据。 
CPHA=1,就表示数据的输出是在一个时钟周期的第一个沿上,至于这个沿是上升沿还是下降沿,这要是CPOL的值而定,CPOL=1那就是下降沿,反之就是上升沿。那么数据的采样自然就是在第二个沿上了。 
CPHA=0,就表示数据的采样是在一个时钟周期的第一个沿上,同样它是什么沿由CPOL决定。那么数据的输出自然就在第二个沿上了。 
仔细想一下,这里会有一个问题:就是当一帧数据开始传输第一bit时,在第一个时钟沿上就采样该数据了,那么它是在什么时候输出来的呢?有两种情况:一是SSEL使能的边沿,二是上一帧数据的最后一个时钟沿,有时两种情况还会同时生效。

我们以CPOL=1/CPHA=1为例,把时序图画出来给大家看一下,如图1所示,。

大家看图1所示,当数据未发送时以及发送完毕后,SCK都是高电平,因此CPOL=1。可以看出,在SCK第一个沿的时候,MOSI和MISO会发生变化,同时SCK第二个沿的时候,数据是稳定的,此刻采样数据是合适的,也就是上升沿即一个时钟周期的后沿锁存读取数据,即CPHA=1。注意最后最隐蔽的SSEL片选,一般情况下,这个引脚通常用来决定是哪个从机和主机进行通信。剩余的三种模式,我把图画出来,简化起见把MOSI和MISO合在一起了,大家仔细对照看看研究一下,把所有的理论过程都弄清楚,有利于你对SPI通信的深刻理解,如图2所示。

当CPOL= 0以及CPHA = 0时候的Verilog HDL设计为:

`timescale 1ns / 1ps
//
// Create Date:    17:02:00 03/15/2019
// Design Name:
// Module Name:    spi_master
// Additional Comments:
// CPOL = 0 => SCK在数据发送前后的空闲状态都是低电平;
// CPHA = 0 => 数据采样在第一个时钟周期的第一个沿,数据输出在第二个沿;
// clk = 100MHZ,通过分频产生1MHZ的sck时钟;
//
module spi_master(input mosi,input busy,input rst_n,input clk,input spi_send,input [7:0] spi_data_out,output reg sck,output reg miso,output reg cs,output reg spi_send_done);reg [3:0] count;//状态机分为四个状态:等待localparam IDLE = 0,CS_L = 1,DATA = 2,FINISH = 3;reg [4:0] cur_st,nxt_st;reg [7:0] reg_data;reg sck_reg;reg [8:0] delay_count;//时钟分频always@(posedge clk) beginif(!rst_n) delay_count <= 0;else if(delay_count == 49) delay_count <= 0;else delay_count <= delay_count + 1;end//产生一个1MHZ的时钟always@(posedge clk) beginif(!rst_n) sck_reg <= 0;else if(delay_count == 49) sck_reg <= !sck_reg;end//sck只有在cs拉低时才变化,其他都为低,响应CPOL = 0,sck在数据发送前后的空闲状态都为地always@(*) beginif(cs) sck = 0;  //cs为高,则没有选中从设备;else if(cur_st == FINISH) sck = 0;else if(!cs) sck = sck_reg;else sck = 1;end//状态机部分always@(posedge sck_reg) beginif(!rst_n) cur_st <= IDLE;else cur_st <= nxt_st;endalways@(*) beginnxt_st = cur_st;case(cur_st)IDLE: if(spi_send) nxt_st = CS_L;CS_L: nxt_st = DATA;DATA: if(count == 7) nxt_st = FINISH;FINISH: if(busy) nxt_st = IDLE;default: nxt_st = IDLE;endcaseend//产生发送结束标志always@(*) beginif(!rst_n) spi_send_done = 0;else if(cur_st == FINISH) spi_send_done = 1;else spi_send_done = 0;end//产生CSalways@(posedge sck_reg) beginif(!rst_n) cs <= 1;else if(cur_st == CS_L) cs <= 0;else if(cur_st == DATA) cs <= 0;else cs <= 1;end//发送数据计数always@(posedge sck_reg) beginif(!rst_n) count <= 0;else if(cur_st == DATA) count <= count + 1;else if(cur_st == IDLE|cur_st == FINISH) count <= 0;end//MISO数据always@(negedge sck_reg) beginif(!rst_n) miso <= 0;else if(cur_st == DATA) beginreg_data[7:1] <= reg_data[6:0];miso <= reg_data[7];endelse if(spi_send) reg_data <= spi_data_out;endendmodule

给出输入输出的原理图:

有空仿真下看看,这是书上的例子,但还是心存疑惑!



下面再给出一个博文中的Verilog实现的SPI,虽然我不知道意义多大,但还能凑合着看:https://blog.csdn.net/IamSarah/article/details/76269737

(改变了代码的格式,原格式看着真心累!)

以上就是SPI接口的工作原理,下面给出其verilog实现,这里主器件用读命令和写命令来控制数据的输入和输出,并且对于一个字节的数据读和写分别用一个任务实现,如下:

module spi(clk,rd,wr,rst,data_in,si,so,sclk,cs,data_out);parameter bit7=4'd0,bit6=4'd1,bit5=4'd2,bit4=4'd3,bit3=4'd4,bit2=4'd5,bit1=4'd6,bit0=4'd7,bit_end=4'd8;parameter bit70=4'd0,bit60=4'd1,bit50=4'd2,bit40=4'd3,bit30=4'd4,bit20=4'd5,bit10=4'd6,bit00=4'd7,bit_end0=4'd8;parameter size=8;input clk,rst;input wr,rd;//读写命令input si;//spi数据输入端input [size-1:0]data_in;//待发送的数据output[size-1:0]data_out;//待接收的数据output sclk;//spi中的时钟output so;//spi的发送端output cs;//片选信号wire [size-1:0]data_out;reg [size-1:0]dout_buf;reg FF;reg sclk;reg so;reg cs;reg [3:0]send_state;//发送状态寄存器reg [3:0]receive_state;//接收状态寄存器always@(posedge clk)beginif(!rst)beginsclk<=0;cs<=1;endelse beginif(rd|wr) beginsclk<=~sclk;//当开始读或者写的时候,需要启动时钟cs<=0;endelse beginsclk=0;cs<=1;endendendalways@(posedge sclk)//发送数据beginif(wr)beginsend_state <= bit7;send_data;endendalways@(posedge sclk)//接收数据beginif(rd)beginreceive_state<=bit70;FF<=0;receive_data;endendassign data_out=(FF==1)?dout_buf:8'hz;task send_data;//发送数据任务begincase(send_state)bit7:beginso<=data_in[7];send_state<=bit6;endbit6:beginso<=data_in[6];send_state<=bit5;endbit5:beginso<=data_in[5];send_state<=bit4;endbit4:beginso<=data_in[4];send_state<=bit3;endbit3:beginso<=data_in[3];send_state<=bit2;endbit2:beginso<=data_in[2];send_state<=bit1;endbit1:beginso<=data_in[1];send_state<=bit0;endbit0:beginso<=data_in[0];send_state<=bit_end;endbit_end:beginso=1'bz;send_state<=bit7;endendcaseendendtasktask receive_data;begincase (receive_state)bit70:begindout_buf[7]<=si;receive_state<=bit60;endbit60:begindout_buf[6]<=si;receive_state<=bit50;endbit50:begindout_buf[5]<=si;receive_state<=bit40;endbit40:begindout_buf[4]<=si;receive_state<=bit30;endbit30:begindout_buf[3]<=si;receive_state<=bit20;endbit20:begindout_buf[2]<=si;receive_state<=bit10;endbit10:begindout_buf[1]<=si;receive_state<=bit00;endbit00:begindout_buf[0]<=si;receive_state<=bit_end;FF<=1;endbit_end0:begindout_buf<=8'hzz;receive_state<=bit70;endendcaseendendtaskendmodule

为了看清这段代码,上图更加直观:

SPI的原理以及Verilog HDL实现相关推荐

  1. verilog hdl数字集成电路设计原理与应用_数字IC设计经典书籍推荐

    数字IC设计流程很复杂,从前端到后端,也有很多职位.在这里整理了个数字IC各个环节的经典必读书籍.市面上的书籍种类纷繁复杂,这里每种只推荐两本左右,如果需要,建议知识类的书籍还是购买正版,尊重作者,也 ...

  2. (50)Verilog HDL SPI发送设计

    (50)Verilog HDL SPI发送设计 1.1 目录 1)目录 2)FPGA简介 3)Verilog HDL简介 4)Verilog HDL SPI发送设计 5)结语 1.2 FPGA简介 F ...

  3. (49)Verilog HDL SPI接收设计

    (49)Verilog HDL SPI接收设计 1.1 目录 1)目录 2)FPGA简介 3)Verilog HDL简介 4)Verilog HDL SPI接收设计 5)结语 1.2 FPGA简介 F ...

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

    FPGA学习之路--SPI详解及Verilog源码分析 概述 SPI = Serial Peripheral Interface,是串行外围设备接口,是一种高速,全双工,同步的通信总线. 优点 支持全 ...

  5. 爆肝4万字❤️零基础掌握Verilog HDL

    文章目录 0.前言 1.Verilog HDL简介 1.1 什么是Verilog HDL 1.2 verilog发展历史ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ 1.3 为什么要使用verilog ...

  6. 移位寄存器专题(verilog HDL设计)

    目录 移位寄存器简介 分类 4位右移位寄存器工作原理 1. 16位右移位寄存器 2. 16位左移寄存器 3. 串行输入并行输出寄存器 4. 并行输入串行输出移位寄存器 移位寄存器简介 移位寄存器内的数 ...

  7. 【Verilog HDL 训练】第 06 天(边沿检测)

    1. 复习verilog语法 [选做题] - reg和wire的区别 寄存器数据类型 Verilog中规定,凡是在程序块中被赋值的变量,都必须是寄存器类型的.(程序块:例如always块) 这里未免还 ...

  8. 有限状态机设计实例之空调控制器(Verilog HDL语言描述)(仿真与综合)(附用Edraw(亿图)画状态转移图)

    目录 前言 空调控制器 简介 状态转移图如下: Verilog HDL语言描述 测试文件 仿真图 ISE综合 RTL Schematic Technology Schematic 前言 关于工具的使用 ...

  9. 模24的8421BCD码计数器(Verilog HDL语言描述)(仿真与综合)

    目录 前言 原理 Verilog HDL程序设计 测试代码 仿真波形图 ISE综合后 RTL Schematic Technology Schematic 前言 本博文用Verilog HDL语言描述 ...

最新文章

  1. vs2008 外部调用ActiveX控件接口方法
  2. boost::serialization相关的测试程序
  3. Swift之缓存文件处理
  4. C++ 多态和虚函数
  5. PyTorch官方权威教程书来了,LeCun力荐!意外的通俗易懂
  6. PostgreSQL在何处处理 sql查询之二
  7. 外联样式表添加到html中,CSS联样式表之内联式、外联式和嵌入式
  8. 如何在SqlServer中快速有条件删除海量数据
  9. db powerdesigner mysql-odbc连接注意事项
  10. 慢性病管理系统/案列/APP/小程序/网站
  11. 处于托管模式时无法删除mcafee解决办法
  12. 开关电源正负极两端加104电容的作用是什么?
  13. maskrcnn selected_polygons.append(self.polygons[i]) IndexError: list index out of range
  14. Linux发行版本及常用国产系统+系统优化
  15. Jenkins ——The server rejected the connection
  16. php无法导出excel,PHPExcel导出Excel文件时出现错误的解决办法
  17. BUUCTF-PWN刷题记录-17
  18. 【PHP】保留两位小数并向上取整
  19. 杭电ACM1049题
  20. 全网最全音视频媒体流

热门文章

  1. html不能超出div的宽度,DIV设置width后超出父元素应该如何解决
  2. 简单Android手机APP地图,android最简单手机地图APP(只需5分钟)
  3. Linux启动屏幕打印日志,linux启动时如何在屏幕上找到文本显示?这...
  4. 旋转矩阵与欧拉角之间的转换
  5. 第十六届全国大学生智能车比赛掠影
  6. FIE2020-2020年的论文相关记录
  7. 高频信号对LM386直流偏置的影响
  8. echarts词云图形状_怎么用Python画出好看的词云图?
  9. python空字典对象相当于false吗_python怎么判断某一对象是否为字典
  10. matlab变量区表示函数,MATLAB中的工作区,变量和函数