QSPI简介
相信各位优秀的工程师们对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芯片进行读写。
QSPI实现的Verilog代码
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)。文章来源:https://www.toymoban.com/news/detail-768629.html
总结
综上,其实可以看出底层的QSPI代码与SPI代码的编写思路都是相似的,主要的区别就是写命令字、写地址以及4根数据线的数据采集。最关键的其实还是要结合实际的应用来进行编写,才能实现特定的功能!文章来源地址https://www.toymoban.com/news/detail-768629.html
到了这里,关于基于FPGA的QSPI底层驱动代码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!