首页 > 其他分享 >SPI协议看这一篇就够了!(图文+代码+解析+仿真)

SPI协议看这一篇就够了!(图文+代码+解析+仿真)

时间:2024-09-13 19:50:05浏览次数:14  
标签:SCK clk 就够 sys SPI 图文 数据 时钟

目录

SPI协议简介

SPI工作原理

实验需求

模块图

时序图

小结

SPI协议简介

1.高速传输,SPI作为三大低速总线(UART、IIc、SPI)之一,其传输速度是这个个中最快的一个。它是一种高速、全双工、同步串行通信总线。所谓高速,指的是传输速度,最高能达到几十M/s,具体速度取决于硬件实现和时钟频率。

2.单工、全双工、半双工可参照下面这个介绍:

3.同步通信,.同步指的是收发双方使用同一个时钟,在传输过程中,保证数据传输无误。主设备提供时钟信号,从设备根据时钟信号的上升沿或下降沿进行数据的采样和发送。

4.简单的硬件接口

  • SPI 的硬件接口相对简单,通常只需要四根线:串行时钟线(SCLK)、主设备输出从设备输入线(MOSI)、主设备输入从设备输出线(MISO)和从设备选择线(SS)。
  • 这种简单的硬件接口使得 SPI 易于实现和集成到各种电子系统中,降低了系统的成本和复杂性。

SPI工作原理

SPI 的工作原理

  1. 主从设备结构

    • SPI 通信通常涉及一个主设备和一个或多个从设备。主设备负责发起通信并控制时钟信号,从设备则根据主设备的指令进行数据的发送和接收。
    • 主设备通过将从设备选择线(SS)拉低来选择要与之通信的从设备。一旦从设备被选中,主设备和从设备之间就可以通过 MOSI 和 MISO 线进行数据的传输。

  2. 数据传输过程

    • 在 SPI 通信中,数据是以字节为单位进行传输的。主设备(Master)首先将数据写入发送缓冲区,然后在时钟信号的控制下,将数据逐位地通过 MOSI 线发送给从设备(Slave)。
    • 从设备在时钟信号的同步下,通过 MISO 线将数据逐位地发送回主设备。主设备在接收数据的同时,可以继续发送下一个字节的数据。
    • 数据传输的顺序通常是高位在前,低位在后。在每个时钟周期,主设备和从设备都会同时发送和接收一位数据,直到一个字节的数据传输完成。

  3. 时钟信号

    • 时钟信号是 SPI 通信的关键,它由主设备提供,并用于同步数据的传输。时钟信号的频率可以根据需要进行设置,通常在几兆赫兹到几十兆赫兹之间。
    • 时钟信号的极性和相位可以通过配置寄存器进行设置,以适应不同的从设备要求。例如,可以设置时钟信号在上升沿或下降沿进行数据采样,或者设置时钟信号的初始相位为高电平或低电平。

SPI总线传输的模式

        SPI总线传输一共有4中模式,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了SCK时钟信号空闲状态的电平,CPHA规定了数据是在SCK时钟的上升沿被采样还是下降沿被采样。这四种模式的时序图如下图所示:    

        模式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时钟的下降沿切换

  其中比较常用的模式是模式0和模式3。为了更清晰的描述SPI总线的时序,下面展现了模式0下的SPI时序图

        其中在SS#低电平时,发送数据有效。当处于模式0下,时钟极性和时钟相位都为0,则主机发送数据时(对应的为MOSI信号变化),在sclk的奇数沿采样数据data,在偶数沿数据发生变化

下图是其他三种模式数据线MOSI和MISO的数据切换(Toggling)位置和数据采样位置的关系图:

实验需求

这里以模式0状态下,编写SPI通信下的主机发送模块,实现发送8bit数据的verilog代码

模块图

模块介绍:PLL模块:分频产生所需要的时钟信号

spi_m模块:控制主机发送所需要的数据

顶层模块:将子模块封装起来

时序图

要想实现上文模式0的时序,最简单的办法还是设计一个状态机。为了方便说明,手搓了一个viso时序图:

由于是要用FPGA实现发送数据,所以这里FPGA是主机。

             当FPGA通过SPI总线发送一个字节(8-bit)的数据时,首先FPGA把CS/SS片选信号设置为0(即cs#),表示准备开始发送数据,整个发送数据过程其实可以分为5个状态:

                IDLE状态:当开始发送数据信号(str_flag==1'd1)时,数据跳转到S0

                S0状态(可不设置这个状态):直接跳转到S1

                S1状态:在此时指示数据进行传输

                S2状态:CS#信号要拉高

                S3状态:直接跳转到IDLE状态

  一个字节数据发送完毕以后,产生一个发送完成标志位finish并把CS#/SS信号拉高完成一次发送。这里为了传输sck信号,设置了一个clk_en使能信号,目的是在传输数据时,把时钟信号也进行传输。

  思路理清楚以后就可以直接编写Verilog代码了,spi_m模块的代码如下:

module spi_send(

    input   wire    div_clk           ,//分频后的时钟
    input   wire    sys_rst_n         ,//系统复位,低电平有效
    input   wire    str_flag          ,//发送开始信号
    input   wire    [7:0]data         ,//输入数据
    //input   wire    data_width[7:0]   ,//发送的数据位宽
                                       
    output  wire    sck               ,//发送时钟
    output  reg     cs_n              ,//片选信号,低电平有效
    output  reg     mosi              ,//主机输出从机输入
    output  reg    finish             //一帧完成信号

);

/****************define code********************/
    parameter   IDLE    =   2'd0    ,
                S0      =   2'd1    ,
                S1      =   2'd2    ,
                S2      =   2'd3    ,
                S3      =   2'd4    ;
    wire div_clk_n      ;//对分频时钟取反
    reg [3:0]cnt_bit    ;//bit计数器0-8
    reg clk_en          ;//sck的使能信号
    reg [2:0]state      ;//状态机
    

/****************main code********************/

//describe div_clk
assign div_clk_n = ~div_clk ;

//describe cnt_bit
always@(posedge div_clk or negedge sys_rst_n)
    begin
        if(~sys_rst_n)
            cnt_bit <= 4'd0 ;
        else    if(cnt_bit == 4'd8)
            cnt_bit <= 4'd0 ;
        else    if(state == S1)
            cnt_bit <= cnt_bit + 1'd1   ;
    end

//describe clk_en
always@(posedge div_clk or negedge sys_rst_n)
    begin
        if(~sys_rst_n)
            clk_en <= 1'd0 ;
        else    if((cnt_bit >= 4'd0) && (cnt_bit <= 4'd7)&&(state == S1))
            clk_en <= 1'd1 ;
        else  
            clk_en <= 1'd0 ;
    end

//describe state
always@(posedge div_clk or negedge sys_rst_n)
    begin
        if(~sys_rst_n)
            state <= 3'd0 ;
        else    case(state)
            IDLE :
                begin
                    if(str_flag)
                        state <= S0 ;
                end
            S0   :
                begin
                    state <= S1 ;
                end
            S1   :
                begin
                    if(cnt_bit == 4'd8)
                        state <= S2 ;
                    else
                        state <= state ;
                end
            S2   :
                begin
                    state <= S3 ;
                end
            S3   :
                begin
                    state <= IDLE ;    
                end
            default :   state <= IDLE   ;
            endcase 
    end

//describe sck
assign sck = (clk_en == 1'd1) ? div_clk_n : 1'd0    ;

//describe cs_n
always@(posedge div_clk or negedge sys_rst_n)
    begin
        if(~sys_rst_n)
            cs_n <= 1'd1    ;
        else    if(str_flag)
            cs_n <= 1'd0    ;
        else    if(state == S2)
            cs_n <= 1'd1    ;
        else
            cs_n <= cs_n    ;
    end
//describe mosi
/* always@(posedge sck or negedge sys_rst_n)
    begin
        if(~sys_rst_n)
            mosi <= 1'd0    ;
        else    if((clk_en)&&(~cs_n))
            mosi <= data[7-cnt_bit] ;
        else    
            mosi <= 1'd0    ;
    end */
  
always@(posedge div_clk or negedge sys_rst_n)
    if(sys_rst_n==1'b0) 
        mosi <= 1'b0;
    else    if(state==S1)
        case(cnt_bit)
            4'd0    :   mosi <= data[7];
            4'd1    :   mosi <= data[6];
            4'd2    :   mosi <= data[5];
            4'd3    :   mosi <= data[4];
            4'd4    :   mosi <= data[3];
            4'd5    :   mosi <= data[2];
            4'd6    :   mosi <= data[1];
            4'd7    :   mosi <= data[0];
            default :   mosi <= 1'b0;
        endcase
    else
        mosi <= 1'b0;  
//describe finish
always@(posedge div_clk or negedge sys_rst_n)
    begin
        if(~sys_rst_n)
            finish <= 1'd0  ;
        else    if(state == S2)
            finish <= 1'd1  ;
        else
            finish <= 1'd0  ;
    end

endmodule

        整个代码的流程与之前分析的流程完全一致。接下来就对这个代码用ModelSim进行基本的仿真。这里继续编写一个testbench仿真文件:

`timescale 1ns/1ps

module top_tb();

    reg     sys_clk           ;
    reg     sys_rst_n         ;
    reg     str_flag          ;
    reg     [7:0]data         ;
                              
    wire    sck               ;
    wire    cs_n              ;
    wire    mosi              ;
    wire    finish            ;


initial begin

    str_flag  = 1'd0    ;
    sys_clk   = 1'd0    ;
    sys_rst_n = 1'd0    ;
    #100.1
    sys_rst_n = 1'd1    ;
    data      = 8'b11101110 ;//模拟数据输入为8'b11101110
    #1000
    str_flag  = 1'd1    ;
    #80
    str_flag  = 1'd0    ;//模拟数据开始传输信号
end

always #10 sys_clk = ~sys_clk   ;



top top(

    .sys_clk  (sys_clk  )         ,
    .sys_rst_n(sys_rst_n)         ,
    .str_flag (str_flag )         ,
    .data     (data     )         ,
      
    .sck      (sck      )         ,
    .cs_n     (cs_n     )         ,
    .mosi     (mosi     )         ,
    .finish   (finish   )          
    
);





endmodule

  ModelSim的仿真图如下图所示:

  由图可以看到仿真得到的时序与SPI模式0的时序完全一致。

小结

1.如果外设芯片的数据位宽是16-bit或者32-bit怎么办?

  上文已经完成了8-bit数据发送的例子,16-bit和32-bit可以照着葫芦画瓢,无非就是多增加几个状态而已。

2.发送数据的状态机和接收数据的状态机可以用移位的方式来做吗?

        事实上那个状态机的发送8-bit数据和接收8-bit数据的部分只有一行代码是不同的,所以也可以用移位的方法来做,然后把偶数状态也可以整合到一起,这样写的代码会更短更精炼。但出于理解更容易的角度,还是分开写较好。

标签:SCK,clk,就够,sys,SPI,图文,数据,时钟
From: https://blog.csdn.net/weixin_60580395/article/details/142218155

相关文章

  • 【HBuilderX-从下载到项目创建】编程初学者适用的HBuilderX开发环境(超详细的)下载安装
    简介:HBuilderX是一款由DCloud公司开发的集成开发环境(IDE),专为前端开发设计,同时也支持多平台应用开发。它支持HTML、CSS、JavaScript、Vue、React、Uni-app等多种编程语言和框架,具备代码编辑、调试、测试等功能,并且提供了丰富的插件生态系统以扩展其功能。“......
  • 黑客零基础入门教程,从入门到精通学习路线&规划,看完这篇就够了。
    很多人上来就说想学习黑客,但是连方向都没搞清楚就开始学习,最终也只是会无疾而终!黑客是一个大的概念,里面包含了许多方向,不同的方向需要学习的内容也不一样。想要成为黑客,却苦于没有方向,不知道从何学起,下面这篇黑客入门教程可以帮你实现自己的黑客梦想,如果想学,可以继续看下去......
  • 毕业设计—Spingboot休闲食品类微博数据的可视分析方法研究 (案例分析)
    摘要信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对休闲食品类微博数据等问题,对休闲食品类微博数据进行研究分析,然后开发设计出休闲食品类微博数......
  • 2024自学黑客看着一篇就够了!
    2024自学手册——网络安全(黑客技术)......
  • 24K star!来GitHub免费学大模型,零基础入门到精通,收藏这一篇就够了
    前言现在的网上充斥着各种割韭菜的AI课程,比如李一舟的199课程。为了让大家别被割韭菜了,今天推荐一个开源项目,它帮你整理好了大模型学习的roadmap,有资料有代码还免费,它就是:llm-course。llm-course是什么?本项目的内容是一个针对大语言模型的课程,在之前的热点汇总中和大家......
  • 大模型入门书籍,零基础入门大模型(非常详细)收藏这一篇就够了!
    前言在人工智能的浪潮中,大模型已经成为技术创新和应用突破的核心。它们在语音识别、自动驾驶、个性化推荐等多个领域展现出巨大的潜力。但对于初学者来说,如何快速入门并掌握大模型的知识与技能,成为了一个迫切需要解决的问题。本文将为初学者提供一份精心挑选的大模型学习......
  • Java/JDK下载安装与环境配置(Windows 10 超详细的图文版教程 )
    Java/JDK下载安装与环境配置(Windows10超详细的图文版教程)一.JDK的下载与安装(免安装)安装包路径:D:\work\Vue3\安装包\jdk1.8.0_66.zip二.JDK环境配置JDK环境配置是JDK安装过程中最为重要的内容,大多数初学者安装JDK最大的问题就是出在环境配置上面,所以这里我以windows10......
  • 大规模语言模型的书籍分享,从零基础入门到精通非常详细收藏我这一篇就够了
    在当今人工智能领域,大规模语言模型成为了研究和应用的热点之一。它们以其大规模的参数和强大的性能表现,推动着机器学习和深度学习技术的发展。对于GPT系列大规模语言模型的发展历程,有两点令人印象深刻。第一点是可拓展的训练架构与学习范式:Transformer架构能够拓展到百......
  • stm32之硬件SPI读写W25Q64存储器应用案例
    系列文章目录1.stm32之SPI通信协议2.stm32之软件SPI读写W25Q64存储器应用案例3.stm32之SPI通信外设文章目录系列文章目录前言一、电路接线图二、应用案例代码三、应用案例代码分析3.1基本思路3.2相关库函数介绍3.3MySPI模块3.3.1模块初始化3.3.2SPI基本时序......
  • stm32 SPI通信协议&W25Q64(软件SPI读写W25Q64)
    理论SPI1.SPI通信SPI(SerialPeripheralInterface)是由Motorola公司开发的一种通用数据总线四根通信线:SCK(SerialClock)、MOSI(MasterOutputSlaveInput)、MISO(MasterInputSlaveOutput)、SS(SlaveSelect)同步,全双工支持总线挂载多设备(一主多从)SCK:时钟线MOSI:主机输出,从......