赞
踩
提示:使用正点原子达芬奇pro做的小例子,由于教程中无flash的读写,因此撰写记录
主要实现标准的spi_flash读写,以及4路quad_spi_flash读写
通过datasheet知道他的工作时序,实现读写。
可以看到该spi有3种模式
可以看到flash状态寄存器的信息,当我们执行擦除、读写之类的命令后,有时是需要查看一下状态寄存器的,确保之前的指令执行完成后,再进行下一步的操作。
这章节就包含了读写之类的时序,根据这里的时序编写状态机就行了。9.1是传统标准的spi时序(其中也有两路和四路的spi读写),9.2是两路的,9.3是四路的
代码的编写,写了两种,一种是传统spi的,一种是四路的quad_spi的,但用的时序都是datasheet第9.1里的时序。试过用9.3的时序实现四路的spi,但读出的状态寄存器不太对头,就暂时没有尝试了。因为用9.1里的时序也能实现。
(这里代码借鉴了https://www.cnblogs.com/liujinggang/p/9651170.html)
基本上标准上spi和四路的quad_spi的工作时序和借鉴处代码相同。就配寄存器的值不太一样等一些小细节不同。所以就不列出太多代码了。
只讲几个需要注意的地方
因为外部存储器的时钟管脚一般与fpga的CCLK_0连接,这时时钟管脚已经连接到CCLK_0,但我们想要自己制造spi_flash的时钟来使用,这时STARTUPE2就派上用场了
代码如下:
//给予flash时钟 STARTUPE2 #( .PROG_USR("FALSE"), // Activate program event security feature. Requires encrypted bitstreams. .SIM_CCLK_FREQ(0.0) // Set the Configuration Clock Frequency(ns) for simulation ) STARTUPE2_inst ( .CFGCLK(), // 1-bit output: Configuration main clock output .CFGMCLK(), // 1-bit output: Configuration internal oscillator clock output .EOS(), // 1-bit output: Active high output signal indicating the End Of Startup. .PREQ(), // 1-bit output: PROGRAM request to fabric output .CLK(0), // 1-bit input: User start-up clock input .GSR(0), // 1-bit input: Global Set/Reset input (GSR cannot be used for the port name) .GTS(0), // 1-bit input: Global 3-state input (GTS cannot be used for the port name) .KEYCLEARB(1), // 1-bit input: Clear AES Decrypter Key input from Battery-Backed RAM (BBRAM) .PACK(1), // 1-bit input: PROGRAM acknowledge input .USRCCLKO(f_clk), // 1-bit input: User CCLK input**将SPI的时钟链接到这里** .USRCCLKTS(0), // 1-bit input: User CCLK 3-state enable input .USRDONEO(1), // 1-bit input: User DONE pin output control .USRDONETS(1) // 1-bit input: User DONE 3-state enable outpu );
第二个需要注意的地方就是vivado想要进行debug的时候,用inout类型是不能debug的会报错,所以需要用iobuf原语,再对T_qspi_io0和R_qspi_io0进行debug。(T_qspi_io0相当于flash输出的数据,R_qspi_io0相当于flash输入的数据)
代码如下:
//wire T_qspi_io0;
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst0 (
.O(T_qspi_io0), // Buffer output
.IO(IO_qspi_io0), // Buffer inout port (connect directly to top-level port)
.I(R_qspi_io0), // Buffer input
.T(~R_qspi_io0_out_en) // 3-state enable input, high=input, low=output
);
对N25Q128_flash的驱动撰写心得就是这些。
可以参考https://www.cnblogs.com/liujinggang/p/9651170.html尝试,里面讲的很好
`timescale 1ns / 1ps module flash_driver ( output O_qspi_clk , // QSPI Flash Quad SPI(QPI)总线串行时钟线 output reg O_qspi_cs , // QPI总线片选信号 inout IO_qspi_io0 , // QPI总线输入/输出信号线 inout IO_qspi_io1 , // QPI总线输入/输出信号线 inout IO_qspi_io2 , // QPI总线输入/输出信号线 inout IO_qspi_io3 , // QPI总线输入/输出信号线 //---------------------------------------------- output T_qspi_io0, output T_qspi_io1, output T_qspi_io2, output T_qspi_io3, output reg R_qspi_io0, output reg R_qspi_io1, output reg R_qspi_io2, output reg R_qspi_io3, //----------------------------------------------- input I_rst_n , // 复位信号 input I_clk_25M , // 25MHz时钟信号 input [4:0] I_cmd_type , // 命令类型 input [7:0] I_cmd_code , // 命令码 input [23:0] I_qspi_addr , // QSPI Flash地址 output reg O_done_sig , // 指令执行结束标志 output reg [7:0] O_read_data , // 从QSPI Flash读出的数据 output reg O_read_byte_valid , // 读一个字节完成的标志 output reg [3:0] O_qspi_state // 状态机,用于在顶层调试用 ); parameter C_IDLE = 4'b0000 ; // 空闲状态 parameter C_SEND_CMD = 4'b0001 ; // 发送命令码 parameter C_SEND_ADDR = 4'b0010 ; // 发送地址码 parameter C_READ_WAIT = 4'b0011 ; // 读等待 parameter C_WRITE_DATA = 4'b0101 ; // 写数据 parameter C_FINISH_DONE = 4'b0110 ; // 一条指令执行结束 parameter C_WRITE_DATA_QUAD = 4'b1000 ; // 四线模式写数据到QSPI Flash parameter C_DUMMY = 4'b1001 ; // 四线模式读数据需要8个时钟周期的dummy clock,这可以加快读数据的速度 parameter C_READ_WAIT_QUAD = 4'b1010 ; // 四线模式读等待状态 reg R_qspi_io0_out_en ; reg R_qspi_io1_out_en ; reg R_qspi_io2_out_en ; reg R_qspi_io3_out_en ; reg [7:0] R_read_data_reg ; // 从Flash中读出的数据用这个变量进行缓存,等读完了在把这个变量的值给输出 reg R_qspi_clk_en ; // 串行时钟使能信号 reg R_data_come_single ; // 单线操作读数据使能信号,当这个信号为高时 reg R_data_come_quad ; // 单线操作读数据使能信号,当这个信号为高时 reg [7:0] R_cmd_reg ; // 命令码寄存器 reg [23:0] R_address_reg ; // 地址码寄存器 reg [7:0] R_write_bits_cnt ; // 写bit计数器,写数据之前把它初始化为7,发送一个bit就减1 reg [8:0] R_write_bytes_cnt ; // 写字节计数器,发送一个字节数据就把它加1 reg [7:0] R_read_bits_cnt ; // 写bit计数器,接收一个bit就加1 reg [8:0] R_read_bytes_cnt ; // 读字节计数器,接收一个字节数据就把它加1 reg [8:0] R_read_bytes_num ; // 要接收的数据总数 reg R_read_finish ; // 读数据结束标志位 wire [7:0] W_rom_addr ; wire [7:0] W_rom_out ; assign O_qspi_clk = R_qspi_clk_en ? I_clk_25M : 0 ; // 产生串行时钟信号 assign W_rom_addr = R_write_bytes_cnt ; // QSPI IO方向控制 //assign IO_qspi_io0 = R_qspi_io0_out_en ? R_qspi_io0 : 1'bz ; //assign IO_qspi_io1 = R_qspi_io1_out_en ? R_qspi_io1 : 1'bz ; //assign IO_qspi_io2 = R_qspi_io2_out_en ? R_qspi_io2 : 1'bz ; //assign IO_qspi_io3 = R_qspi_io3_out_en ? R_qspi_io3 : 1'bz ; //wire T_qspi_io0; IOBUF #( .DRIVE(12), // Specify the output drive strength .IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE" .IOSTANDARD("DEFAULT"), // Specify the I/O standard .SLEW("SLOW") // Specify the output slew rate ) IOBUF_inst0 ( .O(T_qspi_io0), // Buffer output .IO(IO_qspi_io0), // Buffer inout port (connect directly to top-level port) .I(R_qspi_io0), // Buffer input .T(~R_qspi_io0_out_en) // 3-state enable input, high=input, low=output ); //wire T_qspi_io1; IOBUF #( .DRIVE(12), // Specify the output drive strength .IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE" .IOSTANDARD("DEFAULT"), // Specify the I/O standard .SLEW("SLOW") // Specify the output slew rate ) IOBUF_inst1 ( .O(T_qspi_io1), // Buffer output .IO(IO_qspi_io1), // Buffer inout port (connect directly to top-level port) .I(R_qspi_io1), // Buffer input .T(~R_qspi_io1_out_en) // 3-state enable input, high=input, low=output ); //wire T_qspi_io2; IOBUF #( .DRIVE(12), // Specify the output drive strength .IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE" .IOSTANDARD("DEFAULT"), // Specify the I/O standard .SLEW("SLOW") // Specify the output slew rate ) IOBUF_inst2 ( .O(T_qspi_io2), // Buffer output .IO(IO_qspi_io2), // Buffer inout port (connect directly to top-level port) .I(R_qspi_io2), // Buffer input .T(~R_qspi_io2_out_en) // 3-state enable input, high=input, low=output ); //wire T_qspi_io3; IOBUF #( .DRIVE(12), // Specify the output drive strength .IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE" .IOSTANDARD("DEFAULT"), // Specify the I/O standard .SLEW("SLOW") // Specify the output slew rate ) IOBUF_inst3 ( .O(T_qspi_io3), // Buffer output .IO(IO_qspi_io3), // Buffer inout port (connect directly to top-level port) .I(R_qspi_io3), // Buffer input .T(~R_qspi_io3_out_en) // 3-state enable input, high=input, low=output ); // 功能:用时钟的下降沿发送数据 always @(negedge I_clk_25M) begin if(!I_rst_n) begin O_qspi_cs <= 1'b1 ; O_qspi_state <= C_IDLE ; R_cmd_reg <= 0 ; R_address_reg <= 0 ; R_qspi_clk_en <= 1'b0 ; //SPI clock输出不使能 R_write_bits_cnt <= 0 ; R_write_bytes_cnt <= 0 ; R_read_bytes_num <= 0 ; R_address_reg <= 0 ; O_done_sig <= 1'b0 ; R_data_come_single <= 1'b0 ; end else begin case(O_qspi_state) C_IDLE: // 初始化各个寄存器,当检测到命令类型有效(命令类型的最高位位1)以后,进入发送命令码状态 begin R_qspi_clk_en <= 1'b0 ; O_qspi_cs <= 1'b1 ; R_qspi_io0 <= 1'b0 ; R_cmd_reg <= I_cmd_code ; R_address_reg <= I_qspi_addr ; O_done_sig <= 1'b0 ; R_qspi_io3_out_en <= 1'b0 ; // 设置IO_qspi_io3为高阻 R_qspi_io2_out_en <= 1'b0 ; // 设置IO_qspi_io2为高阻 R_qspi_io1_out_en <= 1'b0 ; // 设置IO_qspi_io1为高阻 R_qspi_io0_out_en <= 1'b0 ; // 设置IO_qspi_io0为高阻 if(I_cmd_type[4] == 1'b1) begin //如果flash操作命令请求 O_qspi_state <= C_SEND_CMD ; R_write_bits_cnt <= 7 ; R_write_bytes_cnt <= 0 ; R_read_bytes_num <= 0 ; end end C_SEND_CMD: // 发送8-bit命令码状态 begin R_qspi_io0_out_en <= 1'b1 ; // 设置IO0为输出 R_qspi_clk_en <= 1'b1 ; // 打开SPI串行时钟SCLK的使能开关 O_qspi_cs <= 1'b0 ; // 拉低片选信号CS if((I_cmd_type[3:0] == 4'b1000) || (I_cmd_type[3:0] == 4'b1001) ) begin R_qspi_io3_out_en <= 1'b1; // 设置IO0为输出 end if(R_write_bits_cnt > 0) begin //如果R_cmd_reg还没有发送完 R_qspi_io0 <= R_cmd_reg[R_write_bits_cnt] ; //发送bit7~bit1位 R_qspi_io3 <= 1'b1; R_write_bits_cnt <= R_write_bits_cnt-1'b1 ; end else begin //发送bit0 R_qspi_io0 <= R_cmd_reg[0] ; if ((I_cmd_type[3:0] == 4'b0001) | (I_cmd_type[3:0] == 4'b0100)) begin //如果是写使能指令(Write Enable)或者写不使能指令(Write Disable) O_qspi_state <= C_FINISH_DONE ; end else if (I_cmd_type[3:0] == 4'b0011) begin //如果是读状态寄存器指令(Read Register) O_qspi_state <= C_READ_WAIT ; R_write_bits_cnt <= 7 ; R_read_bytes_num <= 2 ;//读状态寄存器指令需要接收一个数据 end else if(I_cmd_type[3:0] == 4'b0000)//读设备ID指令(Read Device ID) begin O_qspi_state <= C_READ_WAIT ; R_read_bytes_num <= 20 ; // end else if( (I_cmd_type[3:0] == 4'b0010) || (I_cmd_type[3:0] == 4'b0101) || (I_cmd_type[3:0] == 4'b0111) || (I_cmd_type[3:0] == 4'b1000) // 如果是四线模式页编程指令(Quad Page Program) ) begin // 如果是扇区擦除(Sector Erase),页编程指令(Page Program),读数据指令(Read Data) O_qspi_state <= C_SEND_ADDR ; R_write_bits_cnt <= 23 ; // 这几条指令后面都需要跟一个24-bit的地址码 end else if(I_cmd_type[3:0] == 4'b1001) // 如果是四线模式读数据指令(Quad Read Data) begin O_qspi_state <= C_SEND_ADDR ; R_write_bits_cnt <= 23 ; // 这几条指令后面都需要跟一个24-bit的地址码 end end end C_SEND_ADDR: // 发送地址状态 begin if(R_write_bits_cnt > 0) //如果R_cmd_reg还没有发送完 begin R_qspi_io0 <= R_address_reg[R_write_bits_cnt] ; //发送bit23~bit1位 R_write_bits_cnt <= R_write_bits_cnt - 1 ; end else begin R_qspi_io0 <= R_address_reg[0] ; //发送bit0 if(I_cmd_type[3:0] == 4'b0010) // 扇区擦除(Sector Erase)指令 begin //扇区擦除(Sector Erase)指令发完24-bit地址码就执行结束了,所以直接跳到结束状态 O_qspi_state <= C_FINISH_DONE ; end else if (I_cmd_type[3:0] == 4'b0101) // 页编程(Page Program)指令 begin O_qspi_state <= C_WRITE_DATA ; R_write_bits_cnt <= 7 ; end else if (I_cmd_type[3:0] == 4'b0111) // 读数据(Read Data)指令 begin O_qspi_state <= C_READ_WAIT ; R_read_bytes_num <= 256 ; //接收256个数据 end else if (I_cmd_type[3:0] == 4'b1000) begin //如果是四线模式页编程指令(Quad Page Program) O_qspi_state <= C_WRITE_DATA_QUAD ; R_write_bits_cnt <= 7 ; end else if (I_cmd_type[3:0] == 4'b1001) begin //如果是四线读操作 O_qspi_state <= C_DUMMY ; R_read_bytes_num <= 256 ; //接收256个数据 R_write_bits_cnt <= 7 ; end end end C_DUMMY: // 四线读操作之前需要等待8个dummy clock begin R_qspi_io3_out_en <= 1'b0 ; // 设置IO_qspi_io3为高阻 R_qspi_io2_out_en <= 1'b0 ; // 设置IO_qspi_io2为高阻 R_qspi_io1_out_en <= 1'b0 ; // 设置IO_qspi_io1为高阻 R_qspi_io0_out_en <= 1'b0 ; // 设置IO_qspi_io0为高阻 if(R_write_bits_cnt > 0) R_write_bits_cnt <= R_write_bits_cnt - 1 ; else O_qspi_state <= C_READ_WAIT_QUAD ; end C_READ_WAIT_QUAD: // 四线模式读等待状态 begin if(R_read_finish) begin O_qspi_state <= C_FINISH_DONE ; R_data_come_quad <= 1'b0 ; end else R_data_come_quad <= 1'b1 ; end C_WRITE_DATA_QUAD : begin R_qspi_io0_out_en <= 1'b1 ; // 设置IO0为输出 R_qspi_io1_out_en <= 1'b1 ; // 设置IO1为输出 R_qspi_io2_out_en <= 1'b1 ; // 设置IO2为输出 R_qspi_io3_out_en <= 1'b1 ; // 设置IO3为输出 if(R_write_bytes_cnt == 9'd256) begin O_qspi_state <= C_FINISH_DONE ; R_qspi_clk_en <= 1'b0 ; end else begin if(R_write_bits_cnt == 8'd3) begin R_write_bytes_cnt <= R_write_bytes_cnt + 1'b1 ; R_write_bits_cnt <= 8'd7 ; R_qspi_io3 <= W_rom_out[3] ; // 分别发送bit3 R_qspi_io2 <= W_rom_out[2] ; // 分别发送bit2 R_qspi_io1 <= W_rom_out[1] ; // 分别发送bit1 R_qspi_io0 <= W_rom_out[0] ; // 分别发送bit0 end else begin R_write_bits_cnt <= R_write_bits_cnt - 4 ; R_qspi_io3 <= W_rom_out[R_write_bits_cnt - 0] ; // 分别发送bit7 R_qspi_io2 <= W_rom_out[R_write_bits_cnt - 1] ; // 分别发送bit6 R_qspi_io1 <= W_rom_out[R_write_bits_cnt - 2] ; // 分别发送bit5 R_qspi_io0 <= W_rom_out[R_write_bits_cnt - 3] ; // 分别发送bit4 end end end C_READ_WAIT: // 读等待状态 begin if(R_read_finish) begin O_qspi_state <= C_FINISH_DONE ; R_data_come_single <= 1'b0 ; end else begin R_data_come_single <= 1'b1 ; // 单线模式下读数据标志信号,此信号为高标志正在接收数据 end end C_WRITE_DATA: // 写数据状态 begin if(R_write_bytes_cnt < 256) // 往QSPI Flash中写入 256个数据 begin if(R_write_bits_cnt > 0) //如果数据还没有发送完 begin R_qspi_io0 <= W_rom_out[R_write_bits_cnt] ; //发送bit7~bit1位 R_write_bits_cnt <= R_write_bits_cnt - 1'b1 ; end else begin R_qspi_io0 <= W_rom_out[0] ; //发送bit0 R_write_bits_cnt <= 7 ; R_write_bytes_cnt <= R_write_bytes_cnt + 1'b1 ; end end else begin O_qspi_state <= C_FINISH_DONE ; R_qspi_clk_en <= 1'b0 ; end end C_FINISH_DONE: begin O_qspi_cs <= 1'b1 ; R_qspi_io0 <= 1'b0 ; R_qspi_clk_en <= 1'b0 ; O_done_sig <= 1'b1 ; R_qspi_io3_out_en <= 1'b0 ; // 设置IO_qspi_io3为高阻 R_qspi_io2_out_en <= 1'b0 ; // 设置IO_qspi_io2为高阻 R_qspi_io1_out_en <= 1'b0 ; // 设置IO_qspi_io1为高阻 R_qspi_io0_out_en <= 1'b0 ; // 设置IO_qspi_io0为高阻 R_data_come_single <= 1'b0 ; R_data_come_quad <= 1'b0 ; O_qspi_state <= C_IDLE ; end default:O_qspi_state <= C_IDLE ; endcase end end // // 功能:接收QSPI Flash发送过来的数据 // always @(posedge I_clk_25M) begin if(!I_rst_n) begin R_read_bytes_cnt <= 0 ; R_read_bits_cnt <= 0 ; R_read_finish <= 1'b0 ; O_read_byte_valid <= 1'b0 ; R_read_data_reg <= 0 ; O_read_data <= 0 ; end else if(R_data_come_single) // 此信号为高表示接收数据从QSPI Flash发过来的数据 begin if(R_read_bytes_cnt < R_read_bytes_num) begin if(R_read_bits_cnt < 7) //接收一个Byte的bit0~bit6 begin O_read_byte_valid <= 1'b0 ; R_read_data_reg <= {R_read_data_reg[6:0],T_qspi_io1} ; R_read_bits_cnt <= R_read_bits_cnt + 1'b1 ; end else begin O_read_byte_valid <= 1'b1 ; //一个byte数据有效 O_read_data <= {R_read_data_reg[6:0],T_qspi_io1} ; //接收bit7 R_read_bits_cnt <= 0 ; R_read_bytes_cnt <= R_read_bytes_cnt + 1'b1 ; end end else begin R_read_bytes_cnt <= 0 ; R_read_finish <= 1'b1 ; O_read_byte_valid <= 1'b0 ; end end else if(R_data_come_quad) begin if(R_read_bytes_cnt < R_read_bytes_num) begin //接收数据 if(R_read_bits_cnt < 8'd1) begin O_read_byte_valid <= 1'b0 ; R_read_data_reg <= {R_read_data_reg[3:0],T_qspi_io3,T_qspi_io2,T_qspi_io1,T_qspi_io0};//接收前四位 R_read_bits_cnt <= R_read_bits_cnt + 1 ; end else begin O_read_byte_valid <= 1'b1 ; O_read_data <= {R_read_data_reg[3:0],T_qspi_io3,T_qspi_io2,T_qspi_io1,T_qspi_io0}; //接收后四位 R_read_bits_cnt <= 0 ; R_read_bytes_cnt <= R_read_bytes_cnt + 1'b1 ; end end else begin R_read_bytes_cnt <= 0 ; R_read_finish <= 1'b1 ; O_read_byte_valid <= 1'b0 ; end end else begin R_read_bytes_cnt <= 0 ; R_read_bits_cnt <= 0 ; R_read_finish <= 1'b0 ; O_read_byte_valid <= 1'b0 ; R_read_data_reg <= 0 ; end end rom_data rom_data_inst ( .clka(I_clk_25M), // input clka .ena (1'b1), .addra(W_rom_addr), // input [7 : 0] addra .douta(W_rom_out) // output [7 : 0] douta ); endmodule
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。