spi总线和i2c总线的区别

一、SPI简介 SPI(Serial Peripheral Interface,串行外围设备接口)是一种高速、全双工、同步通信总线。SPI 通讯协议的优点是支持全双工通信,通讯方式较为简单,且相对数据传输速率较快;缺点是没有指定的流控制,没有应答机制,在数据可靠性上有一定缺陷。

一、SPI简介

SPI(Serial Peripheral Interface,串行外围设备接口)是一种高速、全双工、同步通信总线。SPI 通讯协议的优点是支持全双工通信,通讯方式较为简单,且相对数据传输速率较快;缺点是没有指定的流控制,没有应答机制,在数据可靠性上有一定缺陷。

来自主机或者从机的数据在clk上升沿或下降沿同步,主机和从机可以通过MOSI、MISO线路同时传输数据。SPI接口可以是3线式(SCLK、CS、DIO)或者4线式(SCLK、CS、MOSI、MISO)  

SPI采用主从控制模式,通常由一个主模块和一个或多个从模块组成(不支持多主机),来自主机或者从机的数据在clk上升沿或下降沿同步,一般使用四条线进行通信SCLK、CS、MOSI、MISO) 。

MISO ( Master Input Slave Output ) : 主设备数据输入,从设备数据输出;
MOSI ( Master Output Slave Input ) : 主设备数据输出,从设备数据输入;
SCLK ( Serial Clock ) : 时钟信号,由主设备产生;
CS/SS ( Chip Select/Slave Select ) : 从设备片选信号,由主设备控制,通常低电平有效。

二、SPI四种通信方式

SPI总线在传输数据的同时也传输了时钟信号,时钟信号通过时钟极性(CPOL)时钟相位(CPHA)控制两个SPI设备何时交换数据以及何时对接收数据进行采样,保证数据在两个设备之间同步传输。

时钟极性(Clock Polarity, CPOL/CKP),它是指时钟信号在空闲状态下是高电平还是低电平,当时钟空闲时为低电平即 CPOL=0,反之则 CPOL=1


时钟相位(Clock Phase, CPHA/CKE),它是指时钟信号开始有效的第一个边沿和数据的关系。当时钟信号有效的第一个边沿处于数据稳定期的正中间时定义CPHA=0,反之时钟信号有效的第一个边沿不处于数据稳定期的正中间定义CPHA=1。所以在时钟信号SCK的第一个跳变沿采样即CPHA=0,再时钟信号SCK的第二个跳变沿采样为CPHA=1。
 

那么根据SPI的时钟极性和时钟相位特性可以设置4种不同的SPI通信操作模式:

SPI模式CPOLCPHA空闲时SCK时钟采样时刻
000低电平第1个边沿(奇)
101低电平第2个边沿(偶)
210高电平第1个边沿(奇)
311高电平第2个边沿(偶)

 Mode0:CKP=0,CPHA =0:当空闲态时,SCK处于低电平,数据采样是在第1个边沿,即SCK由低电平到高电平的跳变,所以数据采样是在上升沿(准备数据),(发送数据)数据发送是在下降沿。
Mode1:CKP=0,CPHA=1:当空闲态时,SCK处于低电平,数据发送是在第2个边沿,即SCK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
Mode2:CKP=1,CPHA=0:当空闲态时,SCK处于高电平,数据采集是在第1个边沿,即SCK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
Mode3:CKP=1,CPHA=1:当空闲态时,SCK处于高电平,数据发送是在第2个边沿,即SCK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

SPI协议规定一个SPI设备不能在数据通信过程中仅仅充当一个发送者(Transmitter)或者接受者(Receiver)。在片选信号CS为0的情况下,每个clock周期内,SPI设备都会发送并接收1bit数据,相当于有1bit数据被交换。

MOSI及 MISO的数据在SCK的上升沿期间变化输出,在 SCK的下降沿时被采样。即在 SCK 的下降沿时刻,MOSI及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及 MISO为下一次表示数据做准备。

SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。

SPI通信过程

主机将8位二进制数据10101100发送给从机,从机将8位二进制数据11001010发送给主机。

首先主机要将从机片选信号SS拉低此时代表从机被选中,在此之前 SCK 信号要始终处于低电平。SS拉低后时钟信号SCK开始启动,此时主机和从机的数据开始交互。

时钟主机数据MOSI从机数据MISO
初始低电平10101100初始值011001010初始值0
10101100X11001010X1
0010110011100101011
11011001X00010101X1
0101100110001010101
10110011X10101010X0
0011001101010101010
11100110X01010101X0
0110011000101010100
11001100X10101010X1
0100110011010101011
10011001X11010101X0
0001100101101010110
10110010X00101011X1
0011001010010101101
11100101X01010110X0
0110010100101011000

上表中时钟信号1代表时钟上升沿,0代表下降沿。
采用SPI通信Model 0,所以第一个时钟上升沿到来时完成数据采样,主机将要发送的数据最高位放到 MOSI上,而从机也将要发送的数据最高位放到 MISO上(保证两个 SPI 通讯设备之间使用同样的协定,一般都会采用MSB 先行模式),当第一个时钟的下降沿到来时主机把MISO上的数据存入移位寄存器中,而从机把 MOSI上的数据存入移位寄存器中,这样主机从机就完成了1bit 数据的交互。
当第二个时钟上升沿到来时,重复第一个时钟的操作就完成了 2bit 数据的交互,当 8 个时钟过去后,主机和从机就完成了 8bit 数据交互,此时主机中的数据由10101100 变成了11001010,而从机中的数据也由 11001010变成了10101100。

三、SPI协议实现

通过状态机的方式实现SPC协议Model 0

 发送

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

        状态0:SCK为0,MOSI为要发送的数据的最高位,即I_data_in[7]
        状态1:SCK为1,MOSI保持不变
        状态2:SCK为0,MOSI为要发送的数据的次高位,即I_data_in[6]
        状态3:SCK为1,MOSI保持不变
        状态4:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[5]
        状态5:SCK为1,MOSI保持不变
        状态6:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[4]
   状态7:SCK为1,MOSI保持不变
   状态8:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[3]
        状态9:SCK为1,MOSI保持不变
        状态10:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[2]
        状态11:SCK为1,MOSI保持不变
        状态12:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[1]
        状态13:SCK为1,MOSI保持不变
        状态14:SCK为0,MOSI为要发送的数据的最低位,即I_data_in[0]
        状态15:SCK为1,MOSI保持不变
        一个字节数据发送完毕以后,产生一个发送完成标志位O_tx_done并把CS/SS信号拉高完成一次发送。

接收:

        当FPGA通过SPI总线从从机中接收一个字节(8-bit)的数据时,首先FPGA把CS/SS片选信号设置为0,表示准备开始接收数据,整个接收数据过程其实也可以分为16个状态,但是与发送过程不同的是,为了保证接收到的数据准确,必须在数据的正中间采样,接收过程的每个状态执行的操作为:

        状态0:SCK为0,不锁存MISO上的数据
        状态1:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[7]
        状态2:SCK为0,不锁存MISO上的数据
        状态3:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[6]
        状态4:SCK为0,不锁存MISO上的数据
        状态5:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[5]
        状态6:SCK为0,不锁存MISO上的数据
        状态7:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[4]
        状态8:SCK为0,不锁存MISO上的数据
        状态9:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[3]
        状态10:SCK为0,不锁存MISO上的数据
        状态11:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[2]
        状态12:SCK为0,不锁存MISO上的数
        状态13:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[1]        
        状态14:SCK为0,不锁存MISO上的数据
        状态15:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给O_data_out[0]
        一个字节数据接收完毕以后,产生一个接收完成标志位O_rx_done并把CS/SS信号拉高完成一次数据的接收。

module spi_module
(
    input               I_clk       , // 全局时钟50MHz
    input               I_rst_n     , // 复位信号,低电平有效
    input               I_rx_en     , // 读使能信号
    input               I_tx_en     , // 发送使能信号
    input        [7:0]  I_data_in   , // 要发送的数据
    output  reg  [7:0]  O_data_out  , // 接收到的数据
    output  reg         O_tx_done   , // 发送一个字节完毕标志位
    output  reg         O_rx_done   , // 接收一个字节完毕标志位
    
    // 四线标准SPI信号定义
    input               I_spi_miso  , // SPI串行输入,用来接收从机的数据
    output  reg         O_spi_sck   , // SPI时钟
    output  reg         O_spi_cs    , // SPI片选信号
    output  reg         O_spi_mosi    // SPI输出,用来给从机发送数据          
);

reg [3:0]   R_tx_state      ; 
reg [3:0]   R_rx_state      ;

always @(posedge I_clk or negedge I_rst_n)
begin
    if(!I_rst_n)
        begin
            R_tx_state  <=  4'd0    ;
            R_rx_state  <=  4'd0    ;
            O_spi_cs    <=  1'b1    ;
            O_spi_sck   <=  1'b0    ;
            O_spi_mosi  <=  1'b0    ;
            O_tx_done   <=  1'b0    ;
            O_rx_done   <=  1'b0    ;
            O_data_out  <=  8'd0    ;
        end 
    else if(I_tx_en) // 发送使能信号打开的情况下
        begin
            O_spi_cs    <=  1'b0    ; // 把片选CS拉低
            case(R_tx_state)
                4'd1, 4'd3 , 4'd5 , 4'd7  , 
                4'd9, 4'd11, 4'd13, 4'd15 : //整合奇数状态
                    begin
                        O_spi_sck   <=  1'b1                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end
                4'd0:    // 发送第7位
                    begin
                        O_spi_mosi  <=  I_data_in[7]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end
                4'd2:    // 发送第6位
                    begin
                        O_spi_mosi  <=  I_data_in[6]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end
                4'd4:    // 发送第5位
                    begin
                        O_spi_mosi  <=  I_data_in[5]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end 
                4'd6:    // 发送第4位
                    begin
                        O_spi_mosi  <=  I_data_in[4]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end 
                4'd8:    // 发送第3位
                    begin
                        O_spi_mosi  <=  I_data_in[3]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end                            
                4'd10:    // 发送第2位
                    begin
                        O_spi_mosi  <=  I_data_in[2]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end 
                4'd12:    // 发送第1位
                    begin
                        O_spi_mosi  <=  I_data_in[1]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b0                ;
                    end 
                4'd14:    // 发送第0位
                    begin
                        O_spi_mosi  <=  I_data_in[0]        ;
                        O_spi_sck   <=  1'b0                ;
                        R_tx_state  <=  R_tx_state + 1'b1   ;
                        O_tx_done   <=  1'b1                ;
                    end
                default:R_tx_state  <=  4'd0                ;   
            endcase 
        end
    else if(I_rx_en) // 接收使能信号打开的情况下
        begin
            O_spi_cs    <=  1'b0        ; // 拉低片选信号CS
            case(R_rx_state)
                4'd0, 4'd2 , 4'd4 , 4'd6  , 
                4'd8, 4'd10, 4'd12, 4'd14 : //整合偶数状态
                    begin
                        O_spi_sck      <=  1'b0                ;
                        R_rx_state     <=  R_rx_state + 1'b1   ;
                        O_rx_done      <=  1'b0                ;
                    end
                4'd1:    // 接收第7位
                    begin                       
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[7]   <=  I_spi_miso          ;   
                    end
                4'd3:    // 接收第6位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[6]   <=  I_spi_miso          ; 
                    end
                4'd5:    // 接收第5位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[5]   <=  I_spi_miso          ; 
                    end 
                4'd7:    // 接收第4位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[4]   <=  I_spi_miso          ; 
                    end 
                4'd9:    // 接收第3位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[3]   <=  I_spi_miso          ; 
                    end                            
                4'd11:    // 接收第2位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[2]   <=  I_spi_miso          ; 
                    end 
                4'd13:    // 接收第1位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b0                ;
                        O_data_out[1]   <=  I_spi_miso          ; 
                    end 
                4'd15:    // 接收第0位
                    begin
                        O_spi_sck       <=  1'b1                ;
                        R_rx_state      <=  R_rx_state + 1'b1   ;
                        O_rx_done       <=  1'b1                ;
                        O_data_out[0]   <=  I_spi_miso          ; 
                    end
                default:R_rx_state  <=  4'd0                    ;   
            endcase 
        end    
    else
        begin
            R_tx_state  <=  4'd0    ;
            R_rx_state  <=  4'd0    ;
            O_tx_done   <=  1'b0    ;
            O_rx_done   <=  1'b0    ;
            O_spi_cs    <=  1'b1    ;
            O_spi_sck   <=  1'b0    ;
            O_spi_mosi  <=  1'b0    ;
            O_data_out  <=  8'd0    ;
        end      
end

endmodule

参考文献:

 正点原子、野火FPGA

【接口时序】4、SPI总线的原理与Verilog实现 - jgliu - 博客园 (cnblogs.com)

知秋君
上一篇 2024-08-26 20:12
下一篇 2024-08-26 19:48

相关推荐