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) 。

SPI通信协议_第1张图片

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模式 CPOL CPHA 空闲时SCK时钟 采样时刻
0 0 0 低电平 第1个边沿(奇)
1 0 1 低电平 第2个边沿(偶)
2 1 0 高电平 第1个边沿(奇)
3 1 1 高电平 第2个边沿(偶)

SPI通信协议_第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通信过程

SPI通信协议_第3张图片

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

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

时钟 主机数据 MOSI 从机数据 MISO
初始低电平 10101100 初始值0 11001010 初始值0
1 0101100X 1 1001010X 1
0 01011001 1 10010101 1
1 1011001X 0 0010101X 1
0 10110011 0 00101010 1
1 0110011X 1 0101010X 0
0 01100110 1 01010101 0
1 1100110X 0 1010101X 0
0 11001100 0 10101010 0
1 1001100X 1 0101010X 1
0 10011001 1 01010101 1
1 0011001X 1 1010101X 0
0 00110010 1 10101011 0
1 0110010X 0 0101011X 1
0 01100101 0 01010110 1
1 1100101X 0 1010110X 0
0 11001010 0 10101100 0

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

三、SPI协议实现

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

SPI通信协议_第4张图片

 发送

        当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)

你可能感兴趣的:(一般人学不会的FPGA,fpga开发,嵌入式硬件,SPI,协议)