QSPI驱动学习(N25Q128)

        N25Q128是一个128 Mbit(16Mb x 8)串行闪存,具有先进的写保护机制。它可以通过高速spi兼容的总线访问,并可以在XIP模式下工作。

引脚功能如下所示:

该芯片支持两种模式运行:

CPOL=0, CPHA=0
CPOL=1, CPHA=1
N25Q128内存可以使用3种不同的串行协议,该设备可以在三种不同的协议中工作:扩展SPI、DIO-SPI和QIO-SPI。每个协议都有一个专用的指令集,并且每个指令集都具有相同的功能:
           标准SPI协议通过新的四线和双线指令得到了增强(扩展SPI协议)。对于双线输入输出SPI(DIO-SPI),所有的指令代码、地址和数据总是通过两条数据线传输。对于四线输入输出SPI(QIO-SPI),指令代码、地址和数据总是通过四条数据线传输,从而实现了随机访问时间和数据吞吐量的巨大提升。
        芯片可以工作在“XIP模式”下,这意味着设备只需要地址而不需要指令就能输出数据。这种模式显著减少了随机访问时间,从而使得许多需要快速代码执行的应用程序能够在不将内存内容影射到RAM的情况下运行。
        
 对Flash进行操作的时候需要对参数有所了解
1、4KBytes为1个Sector(扇区);
2、16个Sector(扇区)是1个Block(块)64KBytes;
3、容量为16M=128Mbit字节,共有256个Block,4096个Sector

这里主要介绍Extendend SPI模式下的单线操作,并且只使用了以下几个操作:

1、RDID:读取Flash的识别码;

2、WREN:写使能;

3、SSE:Sector擦除

4、PP:页编程;

5、READ:读取数据;

擦除前要设置一次写使能,写入数据前也需要设置一次写使能;


 
模块接口,这里主要是验证时序是否正确,所以没有留下完整的数据接口,只保留了几个可以用vio来验证的接口:
module spi_driver(
    input                   clk_i           ,//50MHz
    input                   rst_i           ,

    input                   start_i         ,//开始信号,上升沿触发

    input       [7:0]       cmd_i           ,//指令
    input       [23:0]      addr_i          ,//读写地址

    output                  spi_clk_o       ,
    output                  spi_cs_o        ,
    output                  spi_mosi_o      ,
    input                   spi_miso_i
    );
常用的几个指令
// 基础读写指令  
localparam READ_DATA        = 8'h03;   // 标准读取[1,3,6](@ref)  
localparam FAST_READ        = 8'h0B;   // 单线快速读取[2,6](@ref)  
localparam PAGE_PROGRAM     = 8'h02;   // 单线页编程[1,3,6](@ref)  
localparam WRITE_ENABLE     = 8'h06;   // 写使能[1,3,6](@ref)  
localparam WRITE_DISABLE    = 8'h04;   // 写禁用  

// 擦除指令  
localparam SUBSECTOR_ERASE  = 8'h20;   // 4KB子扇区擦除[1,5,7](@ref)  
localparam SECTOR_ERASE     = 8'hD8;   // 64KB主扇区擦除[5,6](@ref)  
localparam CHIP_ERASE       = 8'hC7;   // 全片擦除[5,7](@ref)  

// 状态与配置指令  
localparam READ_STATUS_REG  = 8'h05;   // 读取状态寄存器[1,6](@ref)  
localparam WRITE_STATUS_REG = 8'h01;   // 写入状态寄存器[6](@ref)  

// 多线模式指令  
localparam QUAD_OUTPUT_FAST_READ = 8'h6B;  // 四线输出快速读取(QOFR)[2](@ref)  
localparam QUAD_IO_FAST_READ     = 8'hEB;  // 四线I/O快速读取(QIOFR)[2](@ref)  
localparam QUAD_INPUT_FAST_PROGRAM = 8'h32; // 四线输入快速编程(QIFP)[2](@ref)  

// 标识指令  
localparam READ_JEDEC_ID    = 8'h9F;   // 读取JEDEC ID[6,7](@ref)  
localparam READ_UNIQUE_ID   = 8'h4B;   // 读取唯一标识符[6](@ref)  

测试逻辑spi_driver.v:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2025/03/27 22:27:22
// Design Name: 
// Module Name: spi_driver
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module spi_driver(
    input                   clk_i           ,//50MHz
    input                   rst_i           ,

    input                   start_i         ,//开始信号,上升沿触发

    input       [7:0]       cmd_i           ,//指令
    input       [23:0]      addr_i          ,//读写地址


    output                  spi_clk_o       ,
    output                  spi_cs_o        ,
    output                  spi_mosi_o      ,
    input                   spi_miso_i
);
parameter DIV_NUM = 4;
parameter DIV_HALF = DIV_NUM/2;

// 基础读写指令  
localparam READ_DATA        = 8'h03;   // 标准读取[1,3,6](@ref)  
localparam FAST_READ        = 8'h0B;   // 单线快速读取[2,6](@ref)  
localparam PAGE_PROGRAM     = 8'h02;   // 单线页编程[1,3,6](@ref)  
localparam WRITE_ENABLE     = 8'h06;   // 写使能[1,3,6](@ref)  
localparam WRITE_DISABLE    = 8'h04;   // 写禁用  

// 擦除指令  
localparam SUBSECTOR_ERASE  = 8'h20;   // 4KB子扇区擦除[1,5,7](@ref)  
localparam SECTOR_ERASE     = 8'hD8;   // 64KB主扇区擦除[5,6](@ref)  
localparam CHIP_ERASE       = 8'hC7;   // 全片擦除[5,7](@ref)  

// 状态与配置指令  
localparam READ_STATUS_REG  = 8'h05;   // 读取状态寄存器[1,6](@ref)  
localparam WRITE_STATUS_REG = 8'h01;   // 写入状态寄存器[6](@ref)  

// 多线模式指令  
localparam QUAD_OUTPUT_FAST_READ = 8'h6B;  // 四线输出快速读取(QOFR)[2](@ref)  
localparam QUAD_IO_FAST_READ     = 8'hEB;  // 四线I/O快速读取(QIOFR)[2](@ref)  
localparam QUAD_INPUT_FAST_PROGRAM = 8'h32; // 四线输入快速编程(QIFP)[2](@ref)  

// 标识指令  
localparam READ_JEDEC_ID    = 8'h9F;   // 读取JEDEC ID[6,7](@ref)  
localparam READ_UNIQUE_ID   = 8'h4B;   // 读取唯一标识符[6](@ref)  

reg start_d0;
reg start_d1;
wire start_pdge;

reg [7:0]rec_data;

always@(posedge clk_i)begin
    start_d0 <= start_i;
    start_d1 <= start_d0;
end
assign start_pdge = start_d0 & (!start_d1);

reg work_en;
always@(posedge clk_i)begin
    if(rst_i)begin
        work_en <= 1'b0;
    end
    else if(start_pdge)begin
        work_en <= 1'b1;
    end
end

reg [3:0]div_cnt;


reg spi_clk;

reg [3:0]state;
localparam IDLE     = 4'd0;
localparam WRITE_CMD = 4'd1;
localparam READ_ID = 4'd2;
localparam WRITE_ERASER_ADDR = 4'd3;
localparam WRITE_DATA_ADDR = 4'd4;
localparam WRITE_DATA = 4'd5;
localparam READ_Flash_DATA_ADDR = 4'd6;
localparam READ_Flash_DATA = 4'd7;

localparam DONE = 4'd15;

reg [15:0]byte_cnt;
reg [7:0]rec_byte;

reg spi_cs;
reg spi_mosi;
wire spi_miso;
reg [3:0]bit_cnt;

always@(posedge clk_i)begin
    if(rst_i)begin
        div_cnt <= 4'd0;
    end
    else if(spi_cs == 1'b0)begin
        if(div_cnt >= (DIV_NUM-1'b1))begin
            div_cnt <= 4'd0;
        end
        else begin
            div_cnt <= div_cnt + 1'b1;
        end
    end
    else begin
        div_cnt <= 4'd0;
    end
end

always@(posedge clk_i)begin
    if(rst_i)begin
        spi_clk <= 4'd1;
    end
    else if(start_pdge)begin
        spi_clk <= 4'd0;
    end
    else if(spi_cs==1'b0)begin
        if(div_cnt == (DIV_HALF-1'b1))begin
            spi_clk <= 4'd1;
        end
        else if(div_cnt == (DIV_NUM-1'b1))begin
            spi_clk <= 4'd0;
        end
    end
    else begin
        spi_clk <= 4'd1;
    end
end


always@(posedge clk_i)begin
    if(rst_i)begin
        bit_cnt <= 4'd0;
    end
    else begin
        if(div_cnt == (DIV_NUM-1'b1))begin
            if(bit_cnt >= 4'd7)begin
                bit_cnt <= 4'd0;
            end
            else  begin
                bit_cnt <= bit_cnt + 1'b1;
            end
        end
    end
end

reg [7:0]test_data;

always@(posedge clk_i)begin
    if(rst_i)begin
        state <= 4'd0;
        spi_cs <= 1'b1;
        byte_cnt <= 16'd0;
        test_data <= 8'd240;
    end
    else begin
        case(state)
            IDLE:begin
                if(start_pdge)begin
                    spi_cs <= 1'b0;
                    state <= WRITE_CMD;
                    byte_cnt <= 16'd0;
                    test_data <= 8'd240;
                end
                else begin
                    state <= IDLE;
                end
            end
            WRITE_CMD:begin
                if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
                    case(cmd_i)
                        READ_JEDEC_ID:state <= READ_ID;
                        WRITE_ENABLE: state <= DONE;
                        WRITE_DISABLE:state <= DONE;
                        SUBSECTOR_ERASE:state <= WRITE_ERASER_ADDR;
                        PAGE_PROGRAM : state <= WRITE_DATA_ADDR;
                        READ_DATA:state <= READ_Flash_DATA_ADDR;
                    endcase
                end
                else begin
                    state <= WRITE_CMD;
                end
            end
            READ_ID:begin
                if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd16))begin
                    byte_cnt <= 16'd0;
                    state <= DONE;
                    spi_cs <= 1'b1;
                end
                else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
                    byte_cnt <= byte_cnt + 1'b1;
                end
            end
            WRITE_ERASER_ADDR:begin
                if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd2))begin
                    state <= DONE;
                    byte_cnt <= 16'd0;
                    spi_cs <= 1'b1;
                end
                else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
                    byte_cnt <= byte_cnt + 1'b1;
                end
            end
            WRITE_DATA_ADDR:begin
                if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd2))begin
                    state <= WRITE_DATA;
                    byte_cnt <= 16'd0;
                end
                else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
                    byte_cnt <= byte_cnt + 1'b1;
                end
            end
            WRITE_DATA:begin
                if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd255))begin
                    state <= DONE;
                    byte_cnt <= 16'd0;
                    spi_cs <= 1'b1;
                end
                else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
                    byte_cnt <= byte_cnt + 1'b1;
                    test_data <= test_data+1'b1;
                end
            end
            READ_Flash_DATA_ADDR:begin
                if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd2))begin
                    state <= READ_Flash_DATA;
                    byte_cnt <= 16'd0;
                end
                else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
                    byte_cnt <= byte_cnt + 1'b1;
                end
            end
            READ_Flash_DATA:begin
                if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd255))begin
                    state <= DONE;
                    spi_cs <= 1'b1;
                    byte_cnt <= 16'd0;
                end
                else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
                    byte_cnt <= byte_cnt + 1'b1;
                end
            end
            DONE:begin
                spi_cs <= 1'b1;
                byte_cnt <= 16'd0;
                state <= IDLE;
                byte_cnt <= 16'd0;
            end
            default:begin

            end
        endcase
    end
end
reg [23:0]cnt;
always@(*)begin
    case(state)
        WRITE_CMD:begin
            spi_mosi = cmd_i[7-bit_cnt];
        end
        WRITE_ERASER_ADDR:begin
            cnt = 23-(byte_cnt*8+bit_cnt);
            spi_mosi = addr_i[23-(byte_cnt*8+bit_cnt)];
        end
        WRITE_DATA_ADDR:begin
            spi_mosi = addr_i[23-(byte_cnt*8+bit_cnt)];
        end
        WRITE_DATA:begin
            spi_mosi = test_data[7-bit_cnt];
        end
        READ_Flash_DATA_ADDR:begin
            spi_mosi = addr_i[23-(byte_cnt*8+bit_cnt)];
        end
    endcase
end
assign spi_mosi_o = (spi_cs==1'b0) ? spi_mosi : 1'b1;

always@(posedge clk_i)begin
    if(rst_i)begin
        rec_byte <= 8'd0;
    end
    else begin
        case(state)
            READ_ID:begin
                if(div_cnt == DIV_HALF)begin
                    rec_byte[7-bit_cnt] <= spi_miso_i;
                end
            end
            READ_Flash_DATA:begin
                if(div_cnt == DIV_HALF)begin
                    rec_byte[7-bit_cnt] <= spi_miso_i;
                end
            end
        endcase
    end
end

always@(posedge clk_i)begin
    if(rst_i)begin
        rec_data <= 8'd0;
    end
    else begin
        case(state)
            READ_ID:begin
                if((bit_cnt == 4'd0)&&(div_cnt==4'd0))begin
                    rec_data <= rec_byte;
                end
            end
            READ_Flash_DATA:begin
                if((bit_cnt == 4'd0)&&(div_cnt==4'd0))begin
                    rec_data <= rec_byte;
                end
            end
        endcase
    end
end

assign spi_cs_o = spi_cs;
assign spi_clk_o = spi_clk;

ila_0 your_instance_name (
	.clk(clk_i), // input wire clk


	.probe0({
        data_req_o ,
        data_i     ,
        data_vld_o ,
        data_o     ,
        spi_clk_o  ,
        spi_cs   ,
        spi_mosi_o ,
        spi_miso_i ,
        state       ,
        spi_clk,
        rec_byte    ,
        byte_cnt    ,
        bit_cnt     ,
        rec_data
    }) // input wire [127:0] probe0
);

endmodule

顶层文件SPI_TOP.v

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2025/03/29 14:25:52
// Design Name: 
// Module Name: SPI_TOP
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module SPI_TOP(
    input	  wire			  clk_sys     ,	//OXCO_10M
    input   wire 			  sys_rst_n   ,
    output              spi_cs      ,          
    output              spi_mosi_o  ,
    input               spi_miso_i
);
wire                   spi_clk            ;
wire       [7:0]       flash_instruction  ;
wire       [23:0]      flash_addr         ;   

vio_0 u_vio_0 (
  .clk(clk_sys),                // input wire clk
  .probe_out0(i_start),  // output wire [0 : 0] probe_out0
  .probe_out1(flash_instruction),  // output wire [7 : 0] probe_out1
  .probe_out2(flash_addr)  // output wire [23 : 0] probe_out2
);

//原语
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(spi_clk), // 1-bit input: User CCLK input**SPI CLK**
  .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
); 

spi_driver u_spi_driver(
    .clk_i       (clk_sys           ),
    .rst_i       (!sys_rst_n        ),

    .start_i     (i_start           ),

    .cmd_i       (flash_instruction ),
    .addr_i      (flash_addr        ),

    .spi_clk_o   (spi_clk           ),
    .spi_cs_o    (spi_cs            ),
    .spi_mosi_o  (spi_mosi_o        ),
    .spi_miso_i  (spi_miso_i        )
    );

endmodule

时序仿真,读取和写入的一致,满足要求:

在Linux内核下的设备树(Device Tree,DT)中,描述像N25Q128A11这种基于QSPI(Quad-SPI)的Nor Flash存储器,通常会包含一些关键节点和属性,以便驱动程序能识别并配置它。下面是一个简化的设备树描述示例: ``` 树模型结构大致如下: 树根 > memory@0x00000000 { compatible = "st,nor-flash", "qcom,n25q128a11"; // 兼容性字符串指定设备类型 reg = <0x00000000 0x40000000>; // 地址范围,这里是整个闪存的起始地址和大小 spi-max-frequency = <10000000>; // 最大SPI频率 qspi-controller; // 指向控制器节点,可能有特定的qspi控制属性 nand, nand-page-size = <2048>; // 如果设备支持NAND模式,这里的页大小 ... } qspi-controller@some_offset { compatible = "st,qspi-controller"; reg = ...; // 控制器的具体寄存器映射区域 clocks = ...; // 与控制器关联的时钟信息 spi-slaves; // 子节点列表,包含N25Q128A11这样的设备 ... } spi-slave@slave_id { compatible = "st,nor-flash,spi-nor"; reg = ...; // 特定于该设备的内存区域 spi-max-frequency = <10000000>; spi-mode = <2>; // SPI模式,例如MSB First spi-datasize = <8>; // 数据线宽度 device-tree-bindings-qspi; // 引用设备树绑定文件,提供额外配置 ... } ``` 在这个例子中,`device-tree-bindings-qspi`是一个引用,用于加载专门针对QSPI Nor Flash设备的设备树配置规则。具体的数值和属性可能会因硬件实际特性有所不同,你需要查阅N25Q128A11的数据手册以及内核源码中的相关驱动来获取准确的信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值