SPI时序的FPGA接收解码设计
1赞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的关系也可以从图中看出。
CPOL是用来决定SCK时钟信号空闲时的电平,CPOL=0,空闲电平为低电平,CPOL=1时,空闲电平为高电平。CPHA是用来决定采样时刻 的,CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,在每个周期的第二个时钟沿采样。由于我使用的器件工作在模式0这种时序 (CPOL=0,CPHA=0),所以将图1简化为图2,只关注模式0的时序。
我们来关注SCK的第一个时钟周期,在时钟的前沿采样数据(上升沿,第一个时钟沿),在时钟的后沿输出数据(下降沿,第二个时钟沿)。首先来看主器件,主器件的输出口(MOSI)输出的数据bit1,在时钟的前沿被从器件采样,那主器件是在何时刻输出bit1的呢?bit1的输出时刻实际上在SCK信号有效以前,比SCK的上升沿还要早半个时钟周期。bit1的输出时刻与SSEL信号没有关系。再来看从器件,主器件的输入口MISO同样是在时钟的前沿采样 从器件输出的bit1的,那从器件又是在何时刻输出bit1的呢。从器件是在SSEL信号有效后,立即输出bit1,尽管此时SCK信号还没有起效。
3. SPI时序的FPGA接收解码设计
3.1. SPI接收时序分析
如上图所示,基于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],时序图如下:
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仿真图如下所示:
改代码已经应用在MCU2FPGA的SPI LED点阵项目中,经过长时间的测试校验,目前稳定可靠,希望能给大家在平时的设计中,带来一定的启发与帮助。我的设计永远是我的,除非你已经掌握了精髓,行云流水而自如。