基于SPI的flash读写操作

1、实验目标

        使用页写或连续写操作向Flash芯片写入数据,再使用数据读操作读取之前写入数据,将读取的数据使用串口传回PC机,使用串口助手传回数据并与之前写入数据比较,判断正误。

注意:在向Flash芯片写入数据之前,先要对芯片执行全擦除操作。

2、操作时序

2.1 读操作时序

        结合数据手册来详细说明一下SPI-Flash芯片数据读操作的相关内容。数据读操作,操作指令为8’b0000_0011(03h),具体见图

        要执行数据读指令,首先拉低片选信号选中Flash芯片,随后写入数据读(READ)指令,紧跟指令写入3字节的数据读取首地址,指令和地址会在串行时钟上升沿被芯片锁存。随后存储地址对应存储单元中的数据在串行时钟下降沿通过串行数据总线输出。

        数据读取首地址可以为芯片中的任何一个有效地址,使用数据读(READ)指令可以对芯片内数据连续读取,当首地址数据读取完成,会自动对首地址的下一个地址进行数据读取。若最高位地址内数据读取完成,会自动跳转到芯片首地址继续进行数据读取,只有再次拉高片选信号,才能停止数据读操作,否者会对芯片执行无线循环读操作 。

        数据读操作的详细介绍及时序图,具体见图 

         数据读操作指令写入之前无需先写入写使能指令,且执行数据读操作过程中,片选信号拉低后和拉高前无需做规定时间等待,上图中的时序图就是完整的数据读操作时序。

2.2 写操作时序

        写操作分为页写操作和

2.2.1 页写操作时序

实验目标:

        使用页写指令,向Flash中写入N字节数据,N为整数,且大于0小于等于256。在本本实验中我们向Flash芯片中写入0-99,共100字节数据,数据初始地址为24’h00_04_25。

        注意:在向Flash芯片写入数据之前,先要对芯片执行全擦除操作。

        页写(Page Program)操作,简称PP,操作指令为8’b0000_0010(02h),具体见图:

        由数据手册中页写操作可知,页写指令是根据写入数据将存储单元中的“1”置为“0”,实现数据的写入。在写入页写指令之前,需要先写入写使能(WREN)指令,将芯片设置为写使能锁存(WEL)状态;随后要拉低片选信号,写入页写指令、扇区地址、页地址、字节地址,紧跟地址写入要存储在Flash的字节数据, 在指令、地址以及数据写入过程中,片选信号始终保持低电平,待指令、地址、数据被芯片锁存后,将片选信号拉高;片选信号拉高后,等待一个完整的页写周期(tPP),才能完成Flash芯片的页写操作。

        Flash芯片中一页最多可以存储256字节数据,这也表示页写操作一次最多向Flash芯片写入256字节数据。页写指令写入后,随即写入3字节数据写入首地址,首地址为扇区地址、页地址、字节地址组成,扇区地址与页地址是确定数据写入Flash的特定扇区的特定页,字节地址位再该页数据写入的字 节首地址。

        当数据写入的字节首地址为该页的首地址,及字节首地址为8’b0000_0000,数据写入个数为0-256字节,数据可以被正确写入Flash芯片;

        当数据写入的字节首地址不是该页的首地址,及字节首地址不是8’b0000_0000,数据写入个数为0-256字节,若数据写入个数少于字节首地址地址到末地址之间的存储单元个数,数据可以被正确写入Flash芯片;若数据写入个数多于字节首地址地址到末地址之间的存储单元个数,等于字节首地址地址到末地址之间的存储单元个数的数据可以被正确写入Flash芯片,超出的那部分数据,会以8’b0000_0000为字节首地址顺序写入本页,覆盖改地址之前存储的数据。

3、实验设计

3.1 读实验设计

        整个工程也分为3个模块,按键消抖模块(key_filter)、数据读模块(flash_read_ctrl)、串口数据发送模块(uart_tx)和包含各模块实例化的顶层模块(spi_flash_read),模块框图,具体见图

模块名称

功能描述

spi_flsah_read

数据读工程顶层模块

key_filter

按键消抖模块

flash_read _ctrl

数据读模块

uart_tx

串口数据发送模块

`timescale  1ns/1ns
module  flash_read_ctrl(

    input   wire            sys_clk     ,   //系统时钟,频率50MHz
    input   wire            sys_rst_n   ,   //复位信号,低电平有效
    input   wire            key         ,   //按键输入信号
    input   wire            miso        ,   //读出flash数据

    output  reg             sck         ,   //串行时钟
    output  reg             cs_n        ,   //片选信号
    output  reg             mosi        ,   //主输出从输入数据
    output  reg             tx_flag     ,   //输出数据标志信号
    output  wire    [7:0]   tx_data         //输出数据

);

//parameter define
parameter   IDLE    =   3'b001  ,   //初始状态
            READ    =   3'b010  ,   //数据读状态
            SEND    =   3'b100  ;   //数据发送状态

parameter   READ_INST   =   8'b0000_0011;   //读指令
parameter   NUM_DATA    =   16'd100     ;   //读出数据个数
parameter   SECTOR_ADDR =   8'b0000_0000,   //扇区地址
            PAGE_ADDR   =   8'b0000_0100,   //页地址
            BYTE_ADDR   =   8'b0010_0101;   //字节地址
parameter   CNT_WAIT_MAX=   16'd6_00_00 ;

//wire  define
wire    [7:0]   fifo_data_num   ;   //fifo内数据个数
//reg   define
reg     [4:0]   cnt_clk         ;   //系统时钟计数器
reg     [2:0]   state           ;   //状态机状态
reg     [15:0]  cnt_byte        ;   //字节计数器
reg     [1:0]   cnt_sck         ;   //串行时钟计数器
reg     [2:0]   cnt_bit         ;   //比特计数器
reg             miso_flag       ;   //miso提取标志信号
reg     [7:0]   data            ;   //拼接数据
reg             po_flag_reg     ;   //输出数据标志信号
reg             po_flag         ;   //输出数据
reg     [7:0]   po_data         ;   //输出数据
reg             fifo_read_valid ;   //fifo读有效信号
reg     [15:0]  cnt_wait        ;   //等待计数器
reg             fifo_read_en    ;   //fifo读使能
reg     [7:0]   read_data_num   ;   //读出fifo数据个数

//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk  <=  5'd0;
    else    if(state == READ)
        cnt_clk  <=  cnt_clk + 1'b1;

//cnt_byte:记录输出字节个数和等待时间
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_byte    <=  16'd0;
    else    if((cnt_clk == 5'd31) && (cnt_byte == NUM_DATA + 16'd3))
        cnt_byte    <=  16'd0;
    else    if(cnt_clk == 5'd31)
        cnt_byte    <=  cnt_byte + 1'b1;

//cnt_sck:串行时钟计数器,用以生成串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_sck <=  2'd0;
    else    if(state == READ)
        cnt_sck <=  cnt_sck + 1'b1;

//cs_n:片选信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cs_n    <=  1'b1;
    else    if(key == 1'b1)
        cs_n    <=  1'b0;
    else    if((cnt_byte == NUM_DATA + 16'd3) && (cnt_clk == 5'd31) && (state == READ))
        cs_n    <=  1'b1;

//sck:输出串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd2)
        sck <=  1'b1;

//cnt_bit:高低位对调,控制mosi输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <=  3'd0;
    else    if(cnt_sck == 2'd2)
        cnt_bit <=  cnt_bit + 1'b1;

//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else
    case(state)
        IDLE:   if(key == 1'b1)
                    state   <=  READ;
        READ:   if((cnt_byte == NUM_DATA + 16'd3) && (cnt_clk == 5'd31))
                    state   <=  SEND;
        SEND:   if((read_data_num == NUM_DATA)
                && ((cnt_wait == (CNT_WAIT_MAX - 1'b1))))
                    state   <=  IDLE;
        default:    state   <=  IDLE;
    endcase

//mosi:两段式状态机第二段,逻辑输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        mosi    <=  1'b0;
    else    if((state == READ) && (cnt_byte>= 16'd4))
        mosi    <=  1'b0;
    else    if((state == READ) && (cnt_byte == 16'd0) && (cnt_sck == 2'd0))
        mosi    <=  READ_INST[7 - cnt_bit];  //读指令
    else    if((state == READ) && (cnt_byte == 16'd1) && (cnt_sck == 2'd0))
        mosi    <=  SECTOR_ADDR[7 - cnt_bit];  //扇区地址
    else    if((state == READ) && (cnt_byte == 16'd2) && (cnt_sck == 2'd0))
        mosi    <=  PAGE_ADDR[7 - cnt_bit];    //页地址
    else    if((state == READ) && (cnt_byte == 16'd3) && (cnt_sck == 2'd0))
        mosi    <=  BYTE_ADDR[7 - cnt_bit];    //字节地址

//miso_flag:miso提取标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        miso_flag   <=  1'b0;
    else    if((cnt_byte >= 16'd4) && (cnt_sck == 2'd1))
        miso_flag   <=  1'b1;
    else
        miso_flag   <=  1'b0;

//data:拼接数据
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data    <=  8'd0;
    else    if(miso_flag == 1'b1)
        data    <=  {data[6:0],miso};

//po_flag_reg:输出数据标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_flag_reg <=  1'b0;
    else    if((cnt_bit == 3'd7) && (miso_flag == 1'b1))
        po_flag_reg <=  1'b1;
    else
        po_flag_reg <=  1'b0;

//po_flag:输出数据标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_flag <=  1'b0;
    else
        po_flag <=  po_flag_reg;

//po_data:输出数据
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_data <=  8'd0;
    else    if(po_flag_reg == 1'b1)
        po_data <=  data;
    else
        po_data <=  po_data;

//fifo_read_valid:fifo读有效信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        fifo_read_valid <=  1'b0;
    else    if((read_data_num == NUM_DATA)
                && ((cnt_wait == (CNT_WAIT_MAX - 1'b1))))
        fifo_read_valid <=  1'b0;
    else    if(fifo_data_num == NUM_DATA)
        fifo_read_valid <=  1'b1;

//cnt_wait:两数据读取时间间隔
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_wait    <=  16'd0;
    else    if(fifo_read_valid == 1'b0)
        cnt_wait    <=  16'd0;
    else    if(cnt_wait == (CNT_WAIT_MAX - 1'b1))
        cnt_wait    <=  16'd0;
    else    if(fifo_read_valid == 1'b1)
        cnt_wait    <=  cnt_wait + 1'b1;

//fifo_read_en:fifo读使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        fifo_read_en <=  1'b0;
    else    if((cnt_wait == (CNT_WAIT_MAX - 1'b1))
                && (read_data_num < NUM_DATA))
        fifo_read_en <=  1'b1;
    else
        fifo_read_en <=  1'b0;

//read_data_num:自fifo中读出数据个数计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        read_data_num <=  8'd0;
    else    if(fifo_read_valid == 1'b0)
        read_data_num <=  8'd0;
    else    if(fifo_read_en == 1'b1)
        read_data_num <=  read_data_num + 1'b1;

//tx_flag
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        tx_flag <=  1'b0;
    else
        tx_flag <=  fifo_read_en;

//-------------fifo_data_inst--------------
fifo_data fifo_data_inst(
    .clock  (sys_clk      ),    //时钟信号
    .data   (po_data      ),    //写数据,8bit
    .wrreq  (po_flag      ),    //写请求
    .rdreq  (fifo_read_en ),    //读请求

    .q      (tx_data      ),    //数据读出,8bit
    .usedw  (fifo_data_num)     //fifo内数据个数
);

endmodule

经过仿真后,设计正确,由于波形图时域很长,便不负图了,将实验工程分享出来供参考。

3.2 页写实验设计

模块名称

功能描述

spi_flsah_pp

页写工程顶层模块

key_filter

按键消抖模块

flash_pp_ctrl

页写模块

        状态机状态跳转流程如下:系统上电后,状态机状态变量state一直处于初始状态(IDLE);当传入的扇区擦除触发信号key有效时,表示实验工程开始执行对Flash芯片的扇区擦除操作,状态机跳转到写使能状态(WR_EN),同时片选信号拉低,选中要进行扇区擦除操作的Flash芯片;状态跳转到写使能状态且片 选信号拉低后,要进行tSLCH≥5ns的等待时间,等待时间过后对主输出从输入信号写入写使能指令,指令写入完成后需要进行tCHSH≥5ns的等待时间,等待时间过后拉高片选信号,取消对Flash芯片的选择,同时状态机跳转到两指令间等待状态(DELAY);在此状态等待时间tSHSL≥ 100ns后,状态机跳转到页写状态(PP),同时片选信号拉低,选中已写入写使能指令的Flash芯片;状态机跳转到扇区擦除状态且片选信号拉低后,要进行tSLCH≥5ns的等待时间,等待时间过后对主输出从输入信号写入页写指令、3字节的数据写入首地址地址和待写入数据,数据写入完成后需要进行tCHSH≥5ns的等待时间,等待时间过后拉高片选信号,取消对Flash芯片的选择,同时状态机跳回初始状态(IDLE),一次完整的页写操作完成。

(写的内容持续更新。。。。)

SPI(Serial Peripheral Interface)是一种串行外设接口,用于在微控制器和外围设备之间进行通信。SPI读写时序图详解如下。 首先,SPI的通信包括一个主设备(通常是微控制器)和一个或多个从设备。主设备控制通信的起点和终点,而从设备响应主设备的命令和提供数据。 在读操作中,主设备首先选择一个从设备,并发送一个读取命令。然后从设备根据命令进行处理,准备好要返回的数据。主设备向从设备发送时钟信号来指示数据传输的开始。 从设备根据时钟信号准备好数据后,将数据在数据线上发送给主设备。主设备在每个时钟周期中,读取数据线上的数据,并保存到接收缓冲区中。该过程会一直持续,直到读取到所需的所有数据为止。 读取过程完成后,主设备会停止时钟信号,表示数据传输的结束。 在写操作中,主设备选择一个从设备,并发送一个写入命令和要写入的数据。从设备根据命令进行处理,并准备好接收数据。主设备向从设备发送时钟信号来指示数据传输的开始。主设备在每个时钟周期中,将数据从发送缓冲区发送到数据线上,并且从设备在每个时钟周期中,从数据线上读取数据。该过程会一直持续,直到发送完所有的数据为止。 写入过程完成后,主设备会停止时钟信号,并完成数据的传输。 SPI读写时序图详解了主从设备之间的通信过程。通过时钟信号和相应的命令和数据传输,主设备与从设备之间实现了可靠的数据交换。这种通信方式通常用于连接外围设备,如传感器、存储器和显示器等,以完成各种应用需求。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Super_WY_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值