CrazyBingo

SPI时序的FPGA接收解码设计

1
阅读(11490)

1. 写在前面的话

纵然FPGA无所不能,也不能代替MCU的处理能力。很多系统中,都是FPGA与MCU的完美结合。因此难免有MCU与FPGA之间的数据交换。MCU与FPGA之间的通讯协议的稳定性,很大程序上决定了系统数据交换的成败率。在此设计了最基本的SPI时序,借以奠定通讯的基础。

2. SPI时序详解

SPI总线是Motorola公司推出的三线同步接口,同步串行3线方式进行通信:一条时钟线SCK,一条数据输入线MOSI,一条数据输出线MISO;用于 CPU与各种外围器件进行全双工、同步串行通讯。SPI主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。

SPI总线有四种工作方式(SP0, SP1, SP2, SP3),其中使用的最为广泛的是SPI0和SPI3方式。SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果 CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。

SPI主模块和与之通信的外设音时钟相位和极性应该一致。

SPI时序详解---SPI接口在模式0下输出第一位数据的时刻SPI接口有四种不同的数据传输时序,取决于CPOL和CPHL这两位的组合。图1中表现了这四种时序,时序与CPOL、CPHL的关系也可以从图中看出。

wps_clip_image-22940

CPOL是用来决定SCK时钟信号空闲时的电平,CPOL=0,空闲电平为低电平,CPOL=1时,空闲电平为高电平。CPHA是用来决定采样时刻 的,CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,在每个周期的第二个时钟沿采样。由于我使用的器件工作在模式0这种时序 (CPOL=0,CPHA=0),所以将图1简化为图2,只关注模式0的时序。

wps_clip_image-16226

我们来关注SCK的第一个时钟周期,在时钟的前沿采样数据(上升沿,第一个时钟沿),在时钟的后沿输出数据(下降沿,第二个时钟沿)。首先来看主器件,主器件的输出口(MOSI)输出的数据bit1,在时钟的前沿被从器件采样,那主器件是在何时刻输出bit1的呢?bit1的输出时刻实际上在SCK信号有效以前,比SCK的上升沿还要早半个时钟周期。bit1的输出时刻与SSEL信号没有关系。再来看从器件,主器件的输入口MISO同样是在时钟的前沿采样 从器件输出的bit1的,那从器件又是在何时刻输出bit1的呢。从器件是在SSEL信号有效后,立即输出bit1,尽管此时SCK信号还没有起效。

3. SPI时序的FPGA接收解码设计

3.1. SPI接收时序分析

wps_clip_image-26817

如上图所示,基于FPGA设计SPI时序的接收解码电路,其中Master为MCU

,Slave为FPGA。当然此处SPI的时序因为MCU我们可以自行设计,从而改变FPGA的接收模式。

为了兼容8080时序,FPGA在cs有效,在sclk(we)上升沿接收数据即MOSI,在sclk下降沿发送数据即MISO,为此,作如下配置

1) MCU配置为CPOL = 1,CPHA = 1;

2) FPGA设计为上升沿读取数据,从Bit[7]到Bit[0]共8数据:Data[7:0],时序图如下:

wps_clip_image-5313

wps_clip_image-12245

wps_clip_image-31504

3.2. SPI接收时序设计

3.2.1. 异步数据同步化

由于MCU(或者其他外设)与FPGA是异步关系,为了达到数据的同步,以及边沿的采样,需要用几个寄存器,将异步数据同步化,实现时序系统稳定性。

其中片选,数据,以及时钟同步电路Verilog HDL代码如下所示:

//------------------------------------- //mcu data sync to fpga reg    spi_cs_r0,         spi_cs_r1;        
reg    spi_sck_r0,        spi_sck_r1; //fsmc default 0; 8080 default 1; spi default 1; reg    spi_mosi_r0,     spi_mosi_r1;
always@(posedge clk or negedge rst_n)
begin if(!rst_n)
        begin
        spi_cs_r0 <= 1;        spi_cs_r1 <= 1; //chip select enable spi_sck_r0 <= 1;    spi_sck_r1 <= 1; //data transfer clock spi_mosi_r0 <= 0;    spi_mosi_r1 <= 0; //Master output and slave input end else begin
        spi_cs_r0 <= spi_cs;        spi_cs_r1 <= spi_cs_r0;
        spi_sck_r0 <= spi_sck;         spi_sck_r1 <= spi_sck_r0;
        spi_mosi_r0 <= spi_mosi;    spi_mosi_r1 <= spi_mosi_r0;
        end
end
wire    mcu_cs = spi_cs_r1;    
wire    mcu_data = spi_mosi_r1;
wire    mcu_read_flag = (~spi_sck_r1 & spi_sck_r0) ? 1'b1 : 1'b0; //posedge of sck //wire    mcu_write_flag = (spi_sck_r1 & ~spi_sck_r0) ? 1'b1 : 1'b0;    //nededge of sck
3.2.2. SPI数据接受解码

由于已经在上将SPI时序设计成下降沿输出,因此上升沿处于保持器,FPGA在此时,即SCK上升沿采集数据。同时,发挥FPGA强大的并行工作功能,将串行数据并行化,实现数据的解码电路,如下所示:

//------------------------------------- //sample signal, receive data reg        rxd_flag_r;
reg    [2:0]    rxd_cnt;
reg    [7:0]    rxd_data;
always@(posedge clk or negedge rst_n)
begin if(!rst_n)
        begin
        rxd_flag_r <= 0;
        rxd_cnt <= 0;
        rxd_data <= 0;
        end else if(mcu_cs == 1'b0)
        begin
        if(mcu_read_flag)
            begin
            rxd_cnt <= rxd_cnt + 1'b1;
            rxd_data[3'd7 - rxd_cnt] <= mcu_data;
            if(rxd_cnt == 3'd7)
                rxd_flag_r <= 1; else rxd_flag_r <= 0;
            end else begin
            rxd_cnt <= rxd_cnt;
            rxd_data <= rxd_data;
            end
        end else begin
        rxd_flag_r <= 0;
        rxd_cnt <= 0;
        rxd_data <= rxd_data;
        end
end
3.2.3. 采样完成标志

往往很多人以为设计到上面一步就算完成了,其实不然。

要实现后续模块对SPI接收解码数据的稳定处理,设计了一个采样完成标志信号,用来标志SPI一个串行时序的采样完毕,后续模块可以开始捕获。具体代码实现如下所示,其实就是在数据接收完成后,输出了一个脉冲信号。

//--------------------------------- //the signal flag of rxd receive over reg    rxd_flag_r0,rxd_flag_r1;
always@(posedge clk or negedge rst_n)
begin if(!rst_n)
        begin
        rxd_flag_r0 <= 0;
        rxd_flag_r1 <= 0;
        end else begin
        rxd_flag_r0 <= rxd_flag_r;
        rxd_flag_r1 <= rxd_flag_r0;
        end
end
wire    rxd_flag = ~rxd_flag_r1 & rxd_flag_r0;
assign    led_data = (rxd_flag) ? rxd_data : led_data;

3.3. SPI接收解码的Modelsim仿真

3.3.1. Testbench SPI发送的设计

//--------------------------------------------- //mcu spi data transfer task task_mcu_spi_txd;
input    [7:0] mcu_data;
begin
    spi_cs = 0; #100;
    spi_sck = 0;    spi_mosi = mcu_data[7];     #100;    spi_sck = 1; #100; //Bit[7] spi_sck = 0;    spi_mosi = mcu_data[6];     #100;    spi_sck = 1; #100; //Bit[6] spi_sck = 0;    spi_mosi = mcu_data[5];     #100;    spi_sck = 1; #100; //Bit[5] spi_sck = 0;    spi_mosi = mcu_data[4];     #100;    spi_sck = 1; #100; //Bit[4] spi_sck = 0;    spi_mosi = mcu_data[3];     #100;    spi_sck = 1; #100; //Bit[3] spi_sck = 0;    spi_mosi = mcu_data[2];     #100;    spi_sck = 1; #100; //Bit[2] spi_sck = 0;    spi_mosi = mcu_data[1];     #100;    spi_sck = 1; #100; //Bit[1] spi_sck = 0;    spi_mosi = mcu_data[0];     #100;    spi_sck = 1; #100; //Bit[0] spi_cs = 1; spi_sck = 1; #100;
end
endtask

3.3.2. SPI时序仿真电路

initial
begin
    task_sysinit;
    task_reset;
    
    #100;    
    task_mcu_spi_txd(8'h95);
    
    #100;    
    task_mcu_spi_txd(8'hbe);

end

模拟MCU发送SPI数据,经过FPGA的接收解码电路,最后的Modelsim仿真图如下所示:

wps_clip_image-5864

wps_clip_image-4601

改代码已经应用在MCU2FPGA的SPI LED点阵项目中,经过长时间的测试校验,目前稳定可靠,希望能给大家在平时的设计中,带来一定的启发与帮助。我的设计永远是我的,除非你已经掌握了精髓,行云流水而自如。