赞
踩
相信各位优秀的工程师们对SPI协议已经是非常了解了,SPI全名为串行外围设备接口(Serial Peripheral Interface),是一种高速全双工的同步通信总线,广泛应用于设备间的通讯传输。
而本文所要讲的QSPI,为SPI接口的扩展,Q代表quad即4倍传输的意思,也称为四线制SPI,因此该接口的传输速率将远远快于标准的SPI,其广泛应用于SPI Flash存储介质。下面本文将通过一个Flash芯片的Datasheet,来详细的描述该如何利用FPGA实现QSPI的通信。
由时序图可以看到,图中总共有6个信号,从上至下分别为CE(片选信号)、CLK(时钟信号)、SIO0–SIO34根数据线。其中与SPI接口相似的是片选和时钟信号不变,在读写数据时片选信号均为低电平,在采样或发送数据时均在时钟的上升沿或者下降沿。唯一的区别在数据线由原来的MOSI、MISO变成了4根数据线,那么我们该如何将这四根数据线应用起来呢?由图中可知,SIO0会发送命令、地址以及数据,而SIO1–SIO3则只发送命令和数据,再进一步观察可以看到,写命令为0x38,它由8个CLK发送完成,地址信号总共24bit,由4跟数据线在6个CLK内同时完成发送,且每根数据线所发送的起始比特位都不同,最后便是发送数据,同理也由4根数据线同时进行发送,1个CLK发送4bit数据,发送的大小可由用户自己设定。总结一下,QSPI通信写的流程可以概括为先发送一个字节的命令字(这个命令字对于不同的芯片是不一样的),再是发送3个字节的地址(同理),最后才是发送数据。因此在FPGA的设计上就有思路了,最简单的方法就是采用状态机来描述这一过程,具体代码将在下面展示。
由图可知,读时序的操作流程与写时序大同小异,只是命令字由0x38变成了0xEB,其余操作流程均与写时序相同,因此不再进行详细阐述。
但需要注意的是,由SPI扩展为QSPI,它已经不是全双工通信了,而是变成了半双工。SIO0–SIO3 4根线将变成三态门,也就是FPGA中的inout接口,需要满足特定的条件才能输入或者输出数据。
下面将给出QSPI通信的底层驱动代码,在实际工程应用中,还需要结合芯片的数据手册来编写应用层的程序,再结合底层的逻辑来实现特定的功能,例如利用QSPI或者SPI接口对某个Flash芯片进行读写。
module QSPI_DRIVE #( parameter DIV = 3 )( input wire clk, input wire rst, //--------应用层传输进该模块的命令、地址、数据等--------// input wire [3:0] i_cmd_mode, input wire [7:0] i_flash_cmd, input wire [23:0] i_addr, input wire [7:0] i_data, input wire [15:0] i_data_num, input wire i_wr, output reg [7:0] o_data, //---------QSPI 接口---------// output reg qspi_cs, output reg qspi_csk, inout reg qspi_sio0, inout reg qspi_sio1, inout reg qspi_sio2, inout reg qspi_sio3 ); reg [7:0] div_cnt; reg [7:0] cmd_cnt; reg [7:0] addr_cnt; reg [7:0] data_cnt; reg [15:0] num_cnt; reg [3:0] cmd_mode_lock; reg [7:0] flash_cmd_lock; reg [23:0] addr_lock; reg [7:0] r_data_temp; reg qspi_sckd0; wire qspi_sck_p,qspi_sck_n; //---------------FSM---------------// reg [7:0] state,n_state; localparam IDLE = 8'h00, START = 8'h01, CMD = 8'h02, ADDR = 8'h04, DATA = 8'h08, STOP = 8'h10; always@(posedge clk)begin if(rst) state <= IDLE; else state <= n_state; end always@(*)begin if(rst)begin n_state = IDLE; end else begin case(state) IDLE : begin if(i_cmd_mode[3]) n_state = START; else n_state = IDLE; end START : begin n_state = CMD; end CMD : begin if(cmd_cnt == 8'd15) if(cmd_mode_lock[1])begin n_state = ADDR; end else if(cmd_mode_lock[0])begin n_state = DATA; end else begin n_state = STOP; end else n_state = CMD; end ADDR : begin if(addr_cnt == 8'd12) if(cmd_mode_lock[0])begin n_state = DATA; end else begin n_state =STOP; end else n_state = ADDR; end DATA : begin if(data_cnt == 8'd4) if(cmd_mode_lock[2] && (num_cnt == 16'b0))begin n_state = STOP; end else if(!cmd_mode_lock[2])begin n_state = STOP; end else begin n_state = DATA; end else n_state = DATA; end STOP : begin n_state = IDLE; end default : begin n_state = IDLE; end endcase end end //----------锁数据-----------// always@(posedge clk)begin if(rst)begin cmd_mode_lock <= 4'b0; flash_cmd_lock <= 8'b0; addr_lock <= 24'b0; end else if(i_cmd_mode[3] && (state == IDLE))begin cmd_mode_lock <= i_cmd_mode; flash_cmd_lock <= i_flash_cmd; addr_lock <= i_addr; end else begin cmd_mode_lock <= cmd_mode_lock ; flash_cmd_lock <= flash_cmd_lock ; addr_lock <= addr_lock ; end end //-----------各个功能计数器计数---------// always@(posedge clk)begin//时钟分频,DIV为分频系数 if(rst) div_cnt <= 8'h00; else if(div_cnt == DIV) div_cnt <= 8'h00; else if((state == CMD) || (state == ADDR) || (state == DATA )) div_cnt <= div_cnt + 1'b1; else div_cnt <= 8'h00; end always@(posedge clk)begin//命令字计数 if(rst) cmd_cnt <= 8'h00; else if((state == CMD) && (div_cnt == DIV)) cmd_cnt <= cmd_cnt + 1'b1; else if(state == CMD) cmd_cnt <= cmd_cnt; else cmd_cnt <= 8'h00; end always@(posedge clk)begin//地址计数 if(rst) addr_cnt <= 8'h00; else if((state == ADDR) && (div_cnt == DIV)) addr_cnt <= addr_cnt + 1'b1; else if(state == ADDR) addr_cnt <= addr_cnt ; else addr_cnt <= 8'h00; end always@(posedge clk)begin//数据计数,在sck上升沿和下降沿均会加1 if(rst) data_cnt <= 8'h00; else if((state == DATA) && cmd_mode_lock[1] && (data_cnt == 8'd4)) data_cnt <= 8'h00; else if((state == DATA) && (qspi_sck_p || qspi_sck_n)) data_cnt <= data_cnt + 1'b1; else if(state == DATA) data_cnt <=data_cnt; else data_cnt <= 8'h00; end always@(posedge clk)begin//传输的数据长度计数,传输完成后num为0 if(rst) num_cnt <= 16'h00; else if((state == IDLE) && i_cmd_mode[3]) num_cnt <= i_data_num; else if((cmd_mode_lock[3] && (div_cnt == DIV) && (data_cnt == 8'd3)) num_cnt <= num_cnt - 1'b1; else num_cnt <=num_cnt ; end //-------------QSPI数据采样及发送--------------// always@(posedge clk)begin//产生片选信号 if(rst) qspi_cs <=1'b1; else if(state == START) qspi_cs <=1'b0; else if(state == STOP) qspi_cs <=1'b1; else qspi_cs <=qspi_cs ; end always@(posedge clk)begin//产生qspi采样时钟 if(rst) qspi_sck <=1'b0; else if((state == CMD) || (state == ADDR) || (state == DATA) && (div_cnt == DIV)) qspi_sck <=!qspi_sck ; else if((state == CMD) || (state == ADDR) || (state == DATA)) qspi_sck <=qspi_sck ; else qspi_sck <=1'b0; end always@(posedge clk)begin if(rst) qspi_sckd0 <= 1'b1; else qspi_sckd0 <= qspi_sck; end assign qspi_sck_n = (qspi_sckd0 && (!qspi_sck)) ? 1'b1 : 1'b0;//取sck下降沿 assign qspi_sck_p = ((!qspi_sckd0) && qspi_sck) ? 1'b1 : 1'b0;//取sck上升沿 always@(posedge clk)begin//sio0数据线传输命令字、地址以及数据 if(rst) qspi_sio0_temp <=1'b0; else if((state == START) || (state == ADDR) || (state == DATA) && (div_cnt == DIV)) qspi_sio0_temp <=i_flash_cmd[7]; else if(qspi_sck_n)begin if(state == CMD) qspi_sio0_temp <=flash_cmd_lock[7 - (cmd_cnt>>1)]; else if(state == ADDR) qspi_sio0_temp <= addr_lock[20 - (addr_cnt<<1)]; else if(state == DATA) qspi_sio0_temp <= i_data[4 - (data_cnt<<1)]; else qspi_sio0_temp <= qspi_sio0_temp ; end else qspi_sio0_temp <= qspi_sio0_temp ; end always@(posedge clk)begin//sio1数据线传输地址以及数据 if(rst) qspi_sio1_temp <=1'b0; else if(qspi_sck_n)begin if(state == ADDR) qspi_sio1_temp <= addr_lock[21 - (addr_cnt<<1)]; else if(state == DATA) qspi_sio1_temp <= i_data[5 - (data_cnt<<1)]; else qspi_sio1_temp <= qspi_sio1_temp ; end else qspi_sio1_temp <= qspi_sio1_temp ; end always@(posedge clk)begin//sio2数据线传输地址以及数据 if(rst) qspi_sio2_temp <=1'b0; else if(qspi_sck_n)begin if(state == ADDR) qspi_sio2_temp <= addr_lock[22 - (addr_cnt<<1)]; else if(state == DATA) qspi_sio2_temp <= i_data[6 - (data_cnt<<1)]; else qspi_sio2_temp <= qspi_sio2_temp ; end else qspi_sio2_temp <= qspi_sio2_temp ; end always@(posedge clk)begin//sio3数据线传输地址以及数据 if(rst) qspi_sio3_temp <=1'b0; else if(qspi_sck_n)begin if(state == ADDR) qspi_sio3_temp <= addr_lock[23 - (addr_cnt<<1)]; else if(state == DATA) qspi_sio3_temp <= i_data[7 - (data_cnt<<1)]; else qspi_sio3_temp <= qspi_sio3_temp ; end else qspi_sio3_temp <= qspi_sio3_temp ; end reg qspi_sio0_temp;//由于是三态门,需要定义中间变量 reg qspi_sio1_temp; reg qspi_sio2_temp; reg qspi_sio3_temp; //在各状态下赋相对应的值,在写数据的时候i_wr信号为高,读时为低 assign qspi_sio0 = (state == CMD || state == ADDR) ? qspi_sio0_temp : (i_wr) ? qspi_sio0_temp : 1'bz; assign qspi_sio1 = (state == ADDR) ? qspi_sio1_temp: (i_wr) ? qspi_sio1_temp: 1'bz; assign qspi_sio2 = (state == ADDR) ? qspi_sio2_temp: (i_wr) ? qspi_sio2_temp: 1'bz; assign qspi_sio3 = (state == ADDR) ? qspi_sio3_temp: (i_wr) ? qspi_sio3_temp: 1'bz; always@(posedge clk)begin//QSPI发送数据,将数据线上的数据移位至r_data_temp寄存器 if(rst)begin r_data_temp <= 8'b0; end else if(qspi_sck_p && (state == DATA))begin r_data_temp[7 - (data_cnt-1)<<1] <= qspi_sio3 ; r_data_temp[6 - (data_cnt-1)<<1] <= qspi_sio2 ; r_data_temp[5 - (data_cnt-1)<<1] <= qspi_sio1 ; r_data_temp[4 - (data_cnt-1)<<1] <= qspi_sio0 ; end else begin r_data_temp <= r_data_temp; end end always@(posedge clk)begin//将移位寄存器中的数据输出 if(rst) o_data <= 8'b0; else if(data_cnt == 8'd4) o_data <= r_data_temp; else o_data <= o_data; end endmodule
波形图中命令字0xEB代表读操作,0x38代表写操作,0x06代表写使能命令,本次所写入的数据为0x01、0x12、0x23依次按照顺序至0xF0,由图中可以看出写入的数据与读出的数据是一致的,表明QSPI通讯功能正常。经过实测,本文的QSPI速率可达到75MHz。(FPGA时钟频率为150MHz)。
综上,其实可以看出底层的QSPI代码与SPI代码的编写思路都是相似的,主要的区别就是写命令字、写地址以及4根数据线的数据采集。最关键的其实还是要结合实际的应用来进行编写,才能实现特定的功能!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。