野火FPGA进阶(1):基于SPI协议的Flash驱动控制

news/2025/2/22 18:53:22/

文章目录

    • 第48讲:基于SPI协议的Flash驱动控制
      • 0. 理论部分
      • 1. Flash全擦除实验
        • key_filter
        • flash_be_ctrl
        • spi_flash_be
        • tb_flash_be_ctrl
        • tb_spi_flash_be
      • 2. Flash扇区擦除实验
        • key_filter
        • flash_se_ctrl
        • spi_flash_se
      • 3. 数据读操作
        • key_filter
        • uart_tx
        • flash_read_ctrl
        • spi_flash_read
        • tb_spi_flash_read
      • 4. 数据页写操作
        • key_filter
        • flash_pp_ctrl
        • spi_flash_pp
      • 5. 数据连续写操作

第48讲:基于SPI协议的Flash驱动控制

0. 理论部分

SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是Motorola公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输

应用:EEPROM、Flash、RTC、ADC、DSP等

优缺点:全双工通信,通讯方式较为简单,相对数据传输速率较快;没有应答机制确认数据是否接收,在数据可靠性上有一定缺陷(与I2C相比)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


1. Flash全擦除实验

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

key_filter

`timescale  1ns/1nsmodule  key_filter
#(parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(input   wire    sys_clk     ,   //系统时钟50Mhzinput   wire    sys_rst_n   ,   //全局复位input   wire    key_in      ,   //按键输入信号output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下//key_flag为0时表示没有检测到按键被按下
);//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_20ms <= 20'b0;else    if(key_in == 1'b1)cnt_20ms <= 20'b0;else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)cnt_20ms <= cnt_20ms;elsecnt_20ms <= cnt_20ms + 1'b1;//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)key_flag <= 1'b0;else    if(cnt_20ms == CNT_MAX - 1'b1)key_flag <= 1'b1;elsekey_flag <= 1'b0;endmodule

flash_be_ctrl

`timescale  1ns/1nsmodule  flash_be_ctrl
(input   wire            sys_clk     ,   //系统时钟,频率50MHzinput   wire            sys_rst_n   ,   //复位信号,低电平有效input   wire            key         ,   //按键输入信号output  reg             cs_n        ,   //片选信号output  reg             sck         ,   //串行时钟output  reg             mosi            //主输出从输入数据
);//parameter define
parameter   IDLE    =   4'b0001 ,   //初始状态WR_EN   =   4'b0010 ,   //写状态DELAY   =   4'b0100 ,   //等待状态BE      =   4'b1000 ;   //全擦除状态
parameter   WR_EN_INST  =   8'b0000_0110,   //写使能指令BE_INST     =   8'b1100_0111;   //全擦除指令//reg   define
reg     [2:0]   cnt_byte;   //字节计数器
reg     [3:0]   state   ;   //状态机状态
reg     [4:0]   cnt_clk ;   //系统时钟计数器
reg     [1:0]   cnt_sck ;   //串行时钟计数器
reg     [2:0]   cnt_bit ;   //比特计数器//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or  negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_clk  <=  5'd0;else    if(state != IDLE)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    <=  3'd0;else    if((cnt_clk == 5'd31) && (cnt_byte == 3'd6))cnt_byte    <=  3'd0;else    if(cnt_clk == 31)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 == WR_EN) && (cnt_byte == 1'b1))cnt_sck <=  cnt_sck + 1'b1;else    if((state == BE) && (cnt_byte == 3'd5))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 == 3'd2) && (cnt_clk == 5'd31) && (state == WR_EN))cs_n    <=  1'b1;else    if((cnt_byte == 3'd3) && (cnt_clk == 5'd31) && (state == DELAY))cs_n    <=  1'b0;else    if((cnt_byte == 3'd6) && (cnt_clk == 5'd31) && (state == BE))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;elsecase(state)IDLE:   if(key == 1'b1)state   <=  WR_EN;WR_EN:  if((cnt_byte == 3'd2) && (cnt_clk == 5'd31))state   <=  DELAY;DELAY:  if((cnt_byte == 3'd3) && (cnt_clk == 5'd31))state   <=  BE;BE:     if((cnt_byte == 3'd6) && (cnt_clk == 5'd31))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 == WR_EN) && (cnt_byte == 3'd2))mosi    <=  1'b0;else    if((state == BE) && (cnt_byte == 3'd6))mosi    <=  1'b0;else    if((state == WR_EN) && (cnt_byte == 3'd1) && (cnt_sck == 5'd0))mosi    <=  WR_EN_INST[7 - cnt_bit];    //写使能指令else    if((state == BE) && (cnt_byte == 3'd5) && (cnt_sck == 5'd0))mosi    <=  BE_INST[7 - cnt_bit];       //全擦除指令endmodule

spi_flash_be

`timescale  1ns/1nsmodule  spi_flash_be
(input   wire    sys_clk     ,   //系统时钟,频率50MHzinput   wire    sys_rst_n   ,   //复位信号,低电平有效input   wire    pi_key      ,   //按键输入信号output  wire    cs_n        ,   //片选信号output  wire    sck         ,   //串行时钟output  wire    mosi            //主输出从输入数据
);//parameter define
parameter   CNT_MAX =   20'd999_999;    //计数器计数最大值//wire  define
wire    po_key  ;//------------- key_filter_inst -------------
key_filter
#(.CNT_MAX    (CNT_MAX    )   //计数器计数最大值
)
key_filter_inst
(.sys_clk    (sys_clk    ),  //系统时钟,频率50MHz.sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效.key_in     (pi_key     ),  //按键输入信号.key_flag   (po_key     )   //消抖后信号
);//------------- flash_be_ctrl_inst -------------
flash_be_ctrl  flash_be_ctrl_inst
(.sys_clk    (sys_clk    ),  //系统时钟,频率50MHz.sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效.key        (po_key     ),  //按键输入信号.sck        (sck        ),  //片选信号.cs_n       (cs_n       ),  //串行时钟.mosi       (mosi       )   //主输出从输入数据
);endmodule

tb_flash_be_ctrl

`timescale  1ns/1ns
module  tb_flash_be_ctrl();//wire  define
wire            cs_n    ;   //Flash片选信号
wire            sck     ;   //Flash串行时钟
wire            mosi    ;   //Flash主输出从输入信号//reg   define
reg     sys_clk     ;   //模拟时钟信号
reg     sys_rst_n   ;   //模拟复位信号
reg     key         ;   //模拟全擦除触发信号//时钟、复位信号、模拟按键信号
initialbeginsys_clk     =   1'b1;sys_rst_n   <=  1'b0;key <=  1'b0;#100sys_rst_n   <=  1'b1;#1000key <=  1'b1;#20key <=  1'b0;endalways  #10 sys_clk <=  ~sys_clk;   //模拟时钟,频率50MHz//写入Flash仿真模型初始值(全F)
defparam memory.mem_access.initfile = "initmemory.txt";//------------- flash_be_ctrl_inst -------------
flash_be_ctrl  flash_be_ctrl_inst
(.sys_clk    (sys_clk    ),  //输入系统时钟,频率50MHz,1bit.sys_rst_n  (sys_rst_n  ),  //输入复位信号,低电平有效,1bit.key        (key        ),  //按键输入信号,1bit.sck        (sck        ),  //输出串行时钟,1bit.cs_n       (cs_n       ),  //输出片选信号,1bit.mosi       (mosi       )   //输出主输出从输入数据,1bit
);//------------- memory -------------
m25p16  memory
(.c          (sck    ),  //输入串行时钟,频率12.5Mhz,1bit.data_in    (mosi   ),  //输入串行指令或数据,1bit.s          (cs_n   ),  //输入片选信号,1bit.w          (1'b1   ),  //输入写保护信号,低有效,1bit.hold       (1'b1   ),  //输入hold信号,低有效,1bit.data_out   (       )   //输出串行数据
);endmodule

tb_spi_flash_be

`timescale  1ns/1ns
module  tb_spi_flash_be();//wire  define
wire    cs_n;
wire    sck ;
wire    mosi ;//reg   define
reg     clk     ;
reg     rst_n   ;
reg     key     ;//时钟、复位信号、模拟按键信号
initialbeginclk =   0;rst_n   <=  0;key <=  0;#100rst_n   <=  1;#1000key <=  1;#20key <=  0;endalways  #10 clk <=  ~clk;defparam memory.mem_access.initfile = "initmemory.txt";//-------------spi_flash_erase-------------
spi_flash_be    spi_flash_be_inst
(.sys_clk    (clk    ),  //系统时钟,频率50MHz.sys_rst_n  (rst_n  ),  //复位信号,低电平有效.pi_key     (key    ),  //按键输入信号.sck        (sck    ),  //串行时钟.cs_n       (cs_n   ),  //片选信号.mosi       (mosi   )   //主输出从输入数据
);m25p16  memory
(.c          (sck    ),  //输入串行时钟,频率12.5Mhz,1bit.data_in    (mosi   ),  //输入串行指令或数据,1bit.s          (cs_n   ),  //输入片选信号,1bit.w          (1'b1   ),  //输入写保护信号,低有效,1bit.hold       (1'b1   ),  //输入hold信号,低有效,1bit.data_out   (       )   //输出串行数据
);endmodule

2. Flash扇区擦除实验

在这里插入图片描述
在这里插入图片描述

key_filter

`timescale  1ns/1nsmodule  key_filter
#(parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(input   wire    sys_clk     ,   //系统时钟50Mhzinput   wire    sys_rst_n   ,   //全局复位input   wire    key_in      ,   //按键输入信号output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下//key_flag为0时表示没有检测到按键被按下
);//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_20ms <= 20'b0;else    if(key_in == 1'b1)cnt_20ms <= 20'b0;else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)cnt_20ms <= cnt_20ms;elsecnt_20ms <= cnt_20ms + 1'b1;//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)key_flag <= 1'b0;else    if(cnt_20ms == CNT_MAX - 1'b1)key_flag <= 1'b1;elsekey_flag <= 1'b0;endmodule

flash_se_ctrl

`timescale  1ns/1nsmodule  flash_se_ctrl
(input   wire    sys_clk     ,   //系统时钟,频率50MHzinput   wire    sys_rst_n   ,   //复位信号,低电平有效input   wire    key         ,   //按键输入信号output  reg     cs_n        ,   //片选信号output  reg     sck         ,   //串行时钟output  reg     mosi            //主输出从输入数据
);//parameter define
parameter   IDLE    =   4'b0001 ,   //初始状态WR_EN   =   4'b0010 ,   //写状态DELAY   =   4'b0100 ,   //等待状态SE      =   4'b1000 ;   //扇区擦除状态
parameter   WR_EN_INST  =   8'b0000_0110,   //写使能指令SE_INST     =   8'b1101_1000;   //扇区擦除指令
parameter   SECTOR_ADDR =   8'b0000_0000,   //扇区地址PAGE_ADDR   =   8'b0000_0100,   //页地址BYTE_ADDR   =   8'b0010_0101;   //字节地址//reg   define
reg     [3:0]   cnt_byte;   //字节计数器
reg     [3:0]   state   ;   //状态机状态
reg     [4:0]   cnt_clk ;   //系统时钟计数器
reg     [1:0]   cnt_sck ;   //串行时钟计数器
reg     [2:0]   cnt_bit ;   //比特计数器//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or  negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_clk  <=  5'd0;else    if(state != IDLE)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    <=  4'd0;else    if((cnt_clk == 5'd31) && (cnt_byte == 4'd9))cnt_byte    <=  4'd0;else    if(cnt_clk == 31)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 == WR_EN) && (cnt_byte == 1'b1))cnt_sck <=  cnt_sck + 1'b1;else    if((state == SE) && (cnt_byte >= 4'd5) && (cnt_byte <= 4'd8))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 == 4'd2) && (cnt_clk == 5'd31) && (state == WR_EN))cs_n    <=  1'b1;else    if((cnt_byte == 4'd3) && (cnt_clk == 5'd31) && (state == DELAY))cs_n    <=  1'b0;else    if((cnt_byte == 4'd9) && (cnt_clk == 5'd31) && (state == SE))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;elsecase(state)IDLE:   if(key == 1'b1)state   <=  WR_EN;WR_EN:  if((cnt_byte == 4'd2) && (cnt_clk == 5'd31))state   <=  DELAY;DELAY:  if((cnt_byte == 4'd3) && (cnt_clk == 5'd31))state   <=  SE;SE:     if((cnt_byte == 4'd9) && (cnt_clk == 5'd31))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 == WR_EN) && (cnt_byte == 4'd2))mosi    <=  1'b0;else    if((state == SE) && (cnt_byte == 4'd9))mosi    <=  1'b0;else    if((state == WR_EN) && (cnt_byte == 4'd1) && (cnt_sck == 5'd0))mosi    <=  WR_EN_INST[7 - cnt_bit];  //写使能指令else    if((state == SE) && (cnt_byte == 4'd5) && (cnt_sck == 5'd0))mosi    <=  SE_INST[7 - cnt_bit];    //扇区擦除指令else    if((state == SE) && (cnt_byte == 4'd6) && (cnt_sck == 5'd0))mosi    <=  SECTOR_ADDR[7 - cnt_bit];  //扇区地址else    if((state == SE) && (cnt_byte == 4'd7) && (cnt_sck == 5'd0))mosi    <=  PAGE_ADDR[7 - cnt_bit];    //页地址else    if((state == SE) && (cnt_byte == 4'd8) && (cnt_sck == 5'd0))mosi    <=  BYTE_ADDR[7 - cnt_bit];    //字节地址endmodule

spi_flash_se

`timescale  1ns/1nsmodule  spi_flash_se
(input   wire    sys_clk     ,   //系统时钟,频率50MHzinput   wire    sys_rst_n   ,   //复位信号,低电平有效input   wire    pi_key      ,   //按键输入信号output  wire    cs_n        ,   //片选信号output  wire    sck         ,   //串行时钟output  wire    mosi            //主输出从输入数据
);//parameter define
parameter   CNT_MAX =   20'd999_999;    //计数器计数最大值//wire  define
wire    po_key  ;//------------- key_filter_inst -------------
key_filter
#(.CNT_MAX    (CNT_MAX    )   //计数器计数最大值
)
key_filter_inst
(.sys_clk    (sys_clk    ),  //系统时钟,频率50MHz.sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效.key_in     (pi_key     ),  //按键输入信号.key_flag   (po_key     )   //消抖后信号
);//------------- flash_se_ctrl_inst -------------
flash_se_ctrl  flash_se_ctrl_inst
(.sys_clk    (sys_clk    ),  //系统时钟,频率50MHz.sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效.key        (po_key     ),  //按键输入信号.sck        (sck        ),  //片选信号.cs_n       (cs_n       ),  //串行时钟.mosi       (mosi       )   //主输出从输入数据
);endmodule



3. 数据读操作

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

key_filter

`timescale  1ns/1nsmodule  key_filter
#(parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(input   wire    sys_clk     ,   //系统时钟50Mhzinput   wire    sys_rst_n   ,   //全局复位input   wire    key_in      ,   //按键输入信号output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下//key_flag为0时表示没有检测到按键被按下
);//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_20ms <= 20'b0;else    if(key_in == 1'b1)cnt_20ms <= 20'b0;else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)cnt_20ms <= cnt_20ms;elsecnt_20ms <= cnt_20ms + 1'b1;//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)key_flag <= 1'b0;else    if(cnt_20ms == CNT_MAX - 1'b1)key_flag <= 1'b1;elsekey_flag <= 1'b0;endmodule

uart_tx

`timescale  1ns/1nsmodule  uart_tx
#(parameter   UART_BPS    =   'd9600,         //串口波特率parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(input   wire            sys_clk     ,   //系统时钟50MHzinput   wire            sys_rst_n   ,   //全局复位input   wire    [7:0]   pi_data     ,   //模块输入的8bit数据input   wire            pi_flag     ,   //并行数据有效标志信号output  reg             tx              //串转并后的1bit数据
);//localparam    define
localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;//reg   define
reg [12:0]  baud_cnt;
reg         bit_flag;
reg [3:0]   bit_cnt ;
reg         work_en ;//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)work_en <= 1'b0;else    if(pi_flag == 1'b1)work_en <= 1'b1;else    if((bit_flag == 1'b1) && (bit_cnt == 4'd9))work_en <= 1'b0;//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)baud_cnt <= 13'b0;else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))baud_cnt <= 13'b0;else    if(work_en == 1'b1)baud_cnt <= baud_cnt + 1'b1;//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)bit_flag <= 1'b0;else    if(baud_cnt == 13'd1)bit_flag <= 1'b1;elsebit_flag <= 1'b0;//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)bit_cnt <= 4'b0;else    if((bit_flag == 1'b1) && (bit_cnt == 4'd9))bit_cnt <= 4'b0;else    if((bit_flag == 1'b1) && (work_en == 1'b1))bit_cnt <= bit_cnt + 1'b1;//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)tx <= 1'b1; //空闲状态时为高电平else    if(bit_flag == 1'b1)case(bit_cnt)0       : tx <= 1'b0;1       : tx <= pi_data[0];2       : tx <= pi_data[1];3       : tx <= pi_data[2];4       : tx <= pi_data[3];5       : tx <= pi_data[4];6       : tx <= pi_data[5];7       : tx <= pi_data[6];8       : tx <= pi_data[7];9       : tx <= 1'b1;default : tx <= 1'b1;endcaseendmodule

flash_read_ctrl

`timescale  1ns/1nsmodule  flash_read_ctrl(input   wire            sys_clk     ,   //系统时钟,频率50MHzinput   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;elsecase(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;elsemiso_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;elsepo_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;elsepo_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;elsepo_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;elsefifo_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;elsetx_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

spi_flash_read

`timescale  1ns/1nsmodule  spi_flash_read(input   wire    sys_clk     ,   //系统时钟,频率50MHzinput   wire    sys_rst_n   ,   //复位信号,低电平有效input   wire    pi_key      ,   //按键输入信号input   wire    miso        ,   //读出flash数据output  wire    cs_n        ,   //片选信号output  wire    sck         ,   //串行时钟output  wire    mosi        ,   //主输出从输入数据output  wire    tx              
);//parameter define
parameter   CNT_MAX     =   20'd999_999     ;   //计数器计数最大值
parameter   UART_BPS    =   14'd9600        ,   //比特率CLK_FREQ    =   26'd50_000_000  ;   //时钟频率//wire  define
wire            po_key  ;   //消抖处理后的按键信号
wire            tx_flag ;   //输入串口发送模块数据标志信号
wire    [7:0]   tx_data ;   //输入串口发送模块数据//------------- key_filter_inst -------------
key_filter
#(.CNT_MAX    (CNT_MAX    )   //计数器计数最大值
)
key_filter_inst
(.sys_clk    (sys_clk    ),  //系统时钟,频率50MHz.sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效.key_in     (pi_key     ),  //按键输入信号.key_flag   (po_key     )   //消抖后信号
);//-------------flash_read_ctrl_inst-------------
flash_read_ctrl  flash_read_ctrl_inst(.sys_clk    (sys_clk    ),  //系统时钟,频率50MHz.sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效.key        (po_key     ),  //按键输入信号.miso       (miso       ),  //读出flash数据.sck        (sck        ),  //片选信号.cs_n       (cs_n       ),  //串行时钟.mosi       (mosi       ),  //主输出从输入数据.tx_flag    (tx_flag    ),  //输出数据标志信号.tx_data    (tx_data    )   //输出数据
);//-------------uart_tx_inst-------------
uart_tx
#(.UART_BPS    (UART_BPS ),         //串口波特率.CLK_FREQ    (CLK_FREQ )          //时钟频率
)
uart_tx_inst(.sys_clk     (sys_clk  ),   //系统时钟50Mhz.sys_rst_n   (sys_rst_n),   //全局复位.pi_data     (tx_data  ),   //并行数据.pi_flag     (tx_flag  ),   //并行数据有效标志信号.tx          (tx       )    //串口发送数据
);endmodule

tb_spi_flash_read

`timescale  1ns/1ns
module  tb_spi_flash_read();//wire  define
wire    cs_n;
wire    sck ;
wire    mosi;
wire    miso;
wire    tx  ;//reg   define
reg     clk     ;
reg     rst_n   ;
reg     key     ;//时钟、复位信号、模拟按键信号
initialbeginclk =   0;rst_n   <=  0;key <=  0;#100rst_n   <=  1;#1000key <=  1;#20key <=  0;endalways  #10 clk <=  ~clk;defparam memory.mem_access.initfile = "initM25P16_test.txt";
defparam spi_flash_read_inst.flash_read_ctrl_inst.CNT_WAIT_MAX = 1000;
defparam spi_flash_read_inst.uart_tx_inst.CLK_FREQ = 100000;//------------- spi_flash_read -------------
spi_flash_read    spi_flash_read_inst(.sys_clk    (clk    ),  //input     sys_clk.sys_rst_n  (rst_n  ),  //input     sys_rst.pi_key     (key    ),  //input     key.miso       (miso   ),.sck        (sck    ),  //output    sck.cs_n       (cs_n   ),  //output    cs_n.mosi       (mosi   ),  //output    mosi.tx         (tx     )
);//------------- memory -------------
m25p16  memory (.c          (sck    ), .data_in    (mosi   ), .s          (cs_n   ), .w          (1'b1   ), .hold       (1'b1   ), .data_out   (miso   )
);endmodule



4. 数据页写操作

两种写入方式:页写,连续写

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

key_filter

`timescale  1ns/1nsmodule  key_filter
#(parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(input   wire    sys_clk     ,   //系统时钟50Mhzinput   wire    sys_rst_n   ,   //全局复位input   wire    key_in      ,   //按键输入信号output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下//key_flag为0时表示没有检测到按键被按下
);//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_20ms <= 20'b0;else    if(key_in == 1'b1)cnt_20ms <= 20'b0;else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)cnt_20ms <= cnt_20ms;elsecnt_20ms <= cnt_20ms + 1'b1;//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)key_flag <= 1'b0;else    if(cnt_20ms == CNT_MAX - 1'b1)key_flag <= 1'b1;elsekey_flag <= 1'b0;endmodule

flash_pp_ctrl

`timescale  1ns/1nsmodule  flash_pp_ctrl(input   wire            sys_clk     ,   //系统时钟,频率50MHzinput   wire            sys_rst_n   ,   //复位信号,低电平有效input   wire            key         ,   //按键输入信号output  reg             cs_n        ,   //片选信号output  reg             sck         ,   //串行时钟output  reg             mosi            //主输出从输入数据
);//parameter define
parameter   IDLE    =   4'b0001 ,   //初始状态WR_EN   =   4'b0010 ,   //写状态DELAY   =   4'b0100 ,   //等待状态PP      =   4'b1000 ;   //页写状态
parameter   WR_EN_INST      =   8'b0000_0110,   //写使能指令PP_INST         =   8'b0000_0010;   //页写指令
parameter   SECTOR_ADDR     =   8'b0000_0000,   //扇区地址PAGE_ADDR       =   8'b0000_0100,   //页地址BYTE_ADDR       =   8'b0010_0101;   //字节地址
parameter   NUM_DATA        =   8'd100      ;   //页写数据个数(0-99)//reg   define
reg     [7:0]   cnt_byte        ;   //字节计数器
reg     [3:0]   state           ;   //状态机状态
reg     [4:0]   cnt_clk         ;   //系统时钟计数器
reg     [1:0]   cnt_sck         ;   //串行时钟计数器
reg     [2:0]   cnt_bit         ;   //比特计数器
reg     [7:0]   data            ;   //页写入数据//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or  negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_clk  <=  5'd0;else    if(state != IDLE)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    <=  8'd0;else    if((cnt_clk == 5'd31) && (cnt_byte == NUM_DATA + 8'd9))cnt_byte    <=  8'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 == WR_EN) && (cnt_byte == 8'd1))cnt_sck <=  cnt_sck + 1'b1;else    if((state == PP) && (cnt_byte >= 8'd5)&& (cnt_byte <= NUM_DATA + 8'd9 - 1'b1))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 == 8'd2) && (cnt_clk == 5'd31) && (state == WR_EN))cs_n    <=  1'b1;else    if((cnt_byte == 8'd3) && (cnt_clk == 5'd31) && (state == DELAY))cs_n    <=  1'b0;else    if((cnt_byte == NUM_DATA + 8'd9) && (cnt_clk == 5'd31) && (state == PP))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;//data:页写入数据
always@(posedge sys_clk or  negedge sys_rst_n)if(sys_rst_n == 1'b0)data <=  8'd0;else    if((cnt_clk == 5'd31) && ((cnt_byte >= 8'd9)&& (cnt_byte < NUM_DATA + 8'd9 - 1'b1)))data <=  data + 1'b1;//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or  negedge sys_rst_n)if(sys_rst_n == 1'b0)state   <=  IDLE;elsecase(state)IDLE:   if(key == 1'b1)state   <=  WR_EN;WR_EN:  if((cnt_byte == 8'd2) && (cnt_clk == 5'd31))state   <=  DELAY;DELAY:  if((cnt_byte == 8'd3) && (cnt_clk == 5'd31))state   <=  PP;PP:     if((cnt_byte == NUM_DATA + 8'd9) && (cnt_clk == 5'd31))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 == WR_EN) && (cnt_byte== 8'd2))mosi    <=  1'b0;else    if((state == PP) && (cnt_byte == NUM_DATA + 8'd9))mosi    <=  1'b0;else    if((state == WR_EN) && (cnt_byte == 8'd1) && (cnt_sck == 5'd0))mosi    <=  WR_EN_INST[7 - cnt_bit];  //写使能指令else    if((state == PP) && (cnt_byte == 8'd5) && (cnt_sck == 5'd0))mosi    <=  PP_INST[7 - cnt_bit];    //页写指令else    if((state == PP) && (cnt_byte == 8'd6) && (cnt_sck == 5'd0))mosi    <=  SECTOR_ADDR[7 - cnt_bit];  //扇区地址else    if((state == PP) && (cnt_byte == 8'd7) && (cnt_sck == 5'd0))mosi    <=  PAGE_ADDR[7 - cnt_bit];    //页地址else    if((state == PP) && (cnt_byte == 8'd8) && (cnt_sck == 5'd0))mosi    <=  BYTE_ADDR[7 - cnt_bit];    //字节地址else    if((state == PP) && ((cnt_byte >= 8'd9)&& (cnt_byte <= NUM_DATA + 8'd9 - 1'b1)) && (cnt_sck == 5'd0))mosi    <=  data[7 - cnt_bit];  //页写入数据endmodule

spi_flash_pp

`timescale  1ns/1nsmodule  spi_flash_pp
(input   wire    sys_clk     ,   //系统时钟,频率50MHzinput   wire    sys_rst_n   ,   //复位信号,低电平有效input   wire    pi_key      ,   //按键输入信号output  wire    cs_n        ,   //片选信号output  wire    sck         ,   //串行时钟output  wire    mosi            //主输出从输入数据
);//parameter define
parameter   CNT_MAX =   20'd999_999;    //计数器计数最大值//wire  define
wire    po_key  ;//------------- key_filter_inst -------------
key_filter
#(.CNT_MAX    (CNT_MAX    )   //计数器计数最大值
)
key_filter_inst
(.sys_clk    (sys_clk    ),  //系统时钟,频率50MHz.sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效.key_in     (pi_key     ),  //按键输入信号.key_flag   (po_key     )   //消抖后信号
);//------------- flash_pp_ctrl_inst -------------
flash_pp_ctrl  flash_pp_ctrl_inst
(.sys_clk    (sys_clk    ),  //系统时钟,频率50MHz.sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效.key        (po_key     ),  //按键输入信号.sck        (sck        ),  //片选信号.cs_n       (cs_n       ),  //串行时钟.mosi       (mosi       )   //主输出从输入数据
);endmodule



5. 数据连续写操作

在这里插入图片描述


http://www.ppmy.cn/news/286.html

相关文章

比搞笑诺奖还离谱,看完国产AIGC最新创作,把我给整不会了

杨净 萧箫 发自 凹非寺量子位 | 公众号 QbitAI现在&#xff0c;AI生成的东西&#xff0c;“真实”得都让我有点害怕了——只是给出《马斯克获得诺贝尔物理学奖》这个标题&#xff0c;AI竟然就刷刷刷几下&#xff0c;蹦出了一整套大纲来&#xff1f;&#xff01;如果让AI生成一些…

SecXOps 技术发展趋势

可以预见&#xff0c;安全智能分析技术的发展&#xff0c;将全面提升网络安全 关键应用场景下威胁检测的效果&#xff0c;推动安全分析从基础级、领先级&#xff0c;向卓越级演进。根据 Gartner 2021 年十大数据和分 析技术趋势&#xff0c;XOps 的目标是利用 DevOps 最佳实践实…

微信小程序开发笔记

微信小程序开发笔记html条件渲染列表渲染按钮组件 buttoncss单位 rpx、px、vw、vh、rem定位方法设置图片为页面的背景自定义按钮的图标js全局变量底部导航栏 tabBar其他配置config.js带参数跳转页面云数据库的使用初始化查找地图组件 map从云数据库中调用经纬度&#xff0c;并且…

Redis 内存淘汰和过期删除策略

提起使用Redis的优点&#xff0c;大家可以列举出许多&#xff0c;比如&#xff1a;数据存储在内存&#xff0c;读写速度快&#xff0c;性能优异。比如数据持久化&#xff0c;便于数据备份及恢复等等。 分布式服务系统平台发展至今&#xff0c;Redis活跃在平台的各个领域&#…

SAP ABAP——数据类型(二)【TYPES自定义数据类型详解】

&#x1f4ac;个人网站&#xff1a;【芒果个人日志】​​​​​​ &#x1f4ac;原文地址&#xff1a;SAP ABAP——数据类型&#xff08;二&#xff09;【TYPES自定义数据类型详解】 - 芒果个人日志 (wyz-math.cn) &#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名…

【C++】特殊类的设计

目录 前言 一、请设计一个类&#xff0c;不能被拷贝 二、请设计一个类&#xff0c;只能在堆上创建对象 三、请设计一个类&#xff0c;只能在栈上创建对象 四、请设计一个类&#xff0c;不能被继承 五、请设计一个类&#xff0c;只能创建一个对象 1、设计模式 2、单例模…

火山引擎 DataLeap 的 Data Catalog 系统公有云实践

Data Catalog 通过汇总技术和业务元数据&#xff0c;解决大数据生产者组织梳理数据、数据消费者找数和理解数的业务场景。本篇内容源自于火山引擎大数据研发治理套件 DataLeap 中的 Data Catalog 功能模块的实践&#xff0c;主要介绍 Data Catalog 在公有云部署和发布中遇到挑战…

[附源码]Python计算机毕业设计Django-大学生健康档案管理

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…