本文主要是介绍FPGA-DE2-115-实验四-flash读写操作-S29GL064N,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
flash读写操作-S29GL064N
- 1. 实验要求
- 2. 实验原理
- 2.1 选择地址扇区
- 2.2 设备总线操作
- 2.3 查看操作指令
- 2.4 查看时序图
- 3. 实验代码
- 3.1 顶层模块flash
- 3.2 flash读写操作模块flash_control
- 3.3 lcd1602显示模块lcd1602
- 3.4 串口发送模块uart_tx
- 3.5 按键滤波器模块key_filiter
- 4. SignalTap仿真图
- 5. 实物图
前言:
本文主要介绍了集成电路EDA这门课程的相关实验及代码。使用的软件是Quartus Ⅱ,该实验使用fpga芯片为cyclone IV EP4CE115F29C7。
FPGA内板载flash芯片没有使用spi-flash进行操作,而是单纯使用S29GL064N的flash芯片,所有引脚直接连接到fpga板块。
1. 实验要求
利用FPGA板及flash外设,编写代码实现按键按下写入flash,并且断电验证是否写入成功,读出的数据显示在lcd1602显示屏上,并且可以实现擦除数据功能。
2. 实验原理
通过阅读S29GL064N的flash芯片手册,再对比板载芯片名称,我们知道该flash属于S29GL064N (Model 04)。
其中引脚:22位地址线输入8/16位数据线其他一些功能配置位
下图为S29GL064N (Model 04)的引脚图:
下图为DE2-115中flash的连线图:
我们可以看到:
地址位多了一位A-1,变为23位,这样我们选择地址的时候就需要注意一下,最低位为A-1,自己随意配置即可
数据位使用了8位
2.1 选择地址扇区
由flash手册p25页可知整个flash分为134个扇区,写入读出数据的话都需要先确定扇区地址,比如在该实验中我们操作读写扇区SA45,得知地址位为: 0100110xxx
选择完扇区后,我们再从中任意选择一个地址使用: 比如23’010 0110 0000 0000 0000 0001 即23’h260001,该实验都在这个地址操作。
2.2 设备总线操作
选择完地址后,我们就需要查看如何对flash进行读写操作了。
在手册p16页中我们可以找到flash设备总线操作:
- 通过该图我们得知: 读写操作的一些引脚简单配置
- 接下来我们简单阅读下表格下面的一些解释: 比如8.3 Writing Commands/Command Sequences:从中我们得知第40页上的Word程序命令序列包含了关于使用标准和解锁旁路命令序列向设备编程数据的详细信息。
- 跳转到p40的10.4.1 Word Program Command Sequence:
由该节内容得知: 编程是一个四总线循环的操作。程序命令序列由写两个解锁写周期启动,然后是程序设置命令。然后写入程序地址和数据,进而启动嵌入式程序算法。
第47页的表17和第50页的表19分别显示了字程序命令序列的地址和数据要求。 - 我们跳转到第47页
可以看到是一些命令定义,不过我们的数据位为8位,所以找到p50页x8 Mode,这里就是我们需要找到的操作指令表。
2.3 查看操作指令
在上节中,我们找到了flash操作指令表:
其中划线的4个指令我们都是需要使用到的。
其中RA/PA为目标地址,PD/RD为数据。
很容易看出:
读操作不需要指令,直接输入需要读写的地址,并配置好目录2.2节的总线要求即可。
写操作与擦除指令都需要先往固定的地址写入固定的指令才能启动。
2.4 查看时序图
读时序(p62)如图:
写时序(p65)如图:
可以看到,在写入操作完成后,RY脚产生了一段时间的低脉冲,我们可以根据这个来判断是否执行完成过该写入操作。
擦除时序(p66)如图:
3. 实验代码
3.1 顶层模块flash
调用flash_control模块实现flash读写操作,读出数据显示在lcd1602模块上,并通过uart_tx串口发送模块发送给pc端,key_filiter为按键滤波器。
module flash (input wire sys_clk ,// 时钟信号input wire sys_rst_n ,// 复位信号input wire wr ,//写按键input wire rd ,//读按键input wire rset ,//扇区擦除按键input wire fl_ry_by ,//忙碌位,初始1为高,当写入完成后输出一段时间的低脉冲作为写入成功的判断 output wire fl_ce ,//芯片使能输入,低电平有效output wire fl_oe ,//输出使能,低电平读出有效output wire fl_we ,//输入使能,低电平写入有效output wire fl_reset ,//复位引脚输入,低电平复位 output wire fl_wp ,//硬件写保护输入,默认为1即可 output wire [22:0] fl_addr ,//地址链接到flash的23个地址脚output wire tx ,//传输给串口的数据output wire [7:0] lcd_dat ,//LCD的8位数据口output wire lcd_rs ,//数据命令选择信号,高电平表示数据,低电平表示命令output wire lcd_rw ,//读写标志,高电平表示读,低电平表示写,该程序我们只对液晶屏进行写操作output wire lcd_en ,//LCD的控制脚inout wire [7:0] fl_dq //数据输入/输出,链接到flash的8个地址脚
);parameter UART_BPS = 'd115200, //串口波特率CLK_FREQ = 'd50_000_000; //时钟频率//reg [2:0] cmd;
wire [22:0] addr; //地址
wire [7:0] data_in; //数据输入
wire [7:0] pi_data; //模块输入的8bit数据
wire pi_flag; //并行数据有效标志信号
wire [7:0] dat_out;
wire [7:0] lcd_out; //读取的数据给lcd1602
wire wr_flag;
wire rd_flag;
wire rset_flag; assign addr = 23'h260001; //23'010 0110 0000 0000 0000 0001
assign data_in = 8'h5a; flash_control flash_control_inst(.sys_clk (sys_clk ) ,// 时钟信号.sys_rst_n(sys_rst_n) ,// 复位信号.wr_flag (wr_flag ) ,//按下,写入数据一次.rd_flag (rd_flag ) ,//按下,读出数据一次.rset_flag(rset_flag ) ,//.addr (addr ) ,//写入/读出数据的地址.data_in (data_in ) ,//需要写入的数据.fl_ry_by (fl_ry_by ) ,//忙碌位,初始1为高,当写入完成后输出一段时间的低脉冲作为写入成功的判断.fl_ce (fl_ce ) ,//芯片使能输入,低电平有效.fl_oe (fl_oe ) ,//输出使能,低电平读出有效.fl_we (fl_we ) ,//输入使能,低电平写入有效.fl_reset (fl_reset ) ,//复位引脚输入,低电平复位 .fl_wp (fl_wp ) ,//硬件写保护输入,默认为1即可.fl_addr (fl_addr ) ,//地址链接到flash的23个地址脚.dat_out (dat_out ) ,//读取数据输出给串口.pi_flag (pi_flag ) ,//打印数据到串口标志位.lcd_out (lcd_out ) ,//读取的数据给lcd1602 .fl_dq (fl_dq ) //数据输入/输出
);lcd1602 lcd1602_inst( .sys_clk (sys_clk ),//系统时钟输入50M.sys_rst_n (sys_rst_n ),//复位,低电平有效.data (lcd_out ),.lcd_dat (lcd_dat ),//LCD的8位数据口.lcd_rs (lcd_rs ),//数据命令选择信号,高电平表示数据,低电平表示命令.lcd_rw (lcd_rw ),//读写标志,高电平表示读,低电平表示写,该程序我们只对液晶屏进行写操作.lcd_en (lcd_en ) //LCD的控制脚);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 (dat_out ) , //模块输入的8bit数据.pi_flag (pi_flag ) , //并行数据有效标志信号.tx (tx ) //串转并后的1bit数据
);key_filiter key_filiter_inst0
(.sys_clk (sys_clk ) ,.sys_rst_n (sys_rst_n) ,.key_in (wr ) ,.key_flag (wr_flag)
);
key_filiter key_filiter_inst1
(.sys_clk (sys_clk ) ,.sys_rst_n (sys_rst_n) ,.key_in (rd ) ,.key_flag (rd_flag)
);
key_filiter key_filiter_inst2
(.sys_clk (sys_clk ) ,.sys_rst_n (sys_rst_n) ,.key_in (rset ) ,.key_flag (rset_flag)
);endmodule
3.2 flash读写操作模块flash_control
相关时序图:
module flash_control (input wire sys_clk ,// 时钟信号input wire sys_rst_n ,// 复位信号input wire wr_flag ,//按下,写入数据一次input wire rd_flag ,//按下,读出数据一次input wire rset_flag ,//按下,擦除input wire [22:0] addr ,//写入/读出数据的地址input wire [7:0] data_in ,//需要写入的数据input wire fl_ry_by ,//忙碌位,初始1为高,当写入完成后输出一段时间的低脉冲作为写入成功的判断output reg fl_ce ,//芯片使能输入,低电平有效output reg fl_oe ,//输出使能,低电平读出有效output reg fl_we ,//输入使能,低电平写入有效output wire fl_reset ,//复位引脚输入,低电平复位 output wire fl_wp ,//硬件写保护输入,默认为1即可output reg [22:0] fl_addr ,//地址链接到flash的23个地址脚output wire [7:0] dat_out ,//读取数据输出output reg pi_flag ,//打印数据到串口标志位output reg [7:0] lcd_out ,//读取的数据给lcd1602inout wire [7:0] fl_dq //数据输入/输出
);parameter CNT_240US_MAX = 'd11999; parameter CNT_500MS_MAX = 28'd24_999_999;
// Flash控制器状态机
localparam IDLE = 2'b00; // 空闲状态
localparam READ = 2'b01; // 读操作状态
localparam WRITE = 2'b10; // 写操作状态
localparam ERASE = 2'b11; // 擦除操作状态reg [2:0] cmd;//状态控制寄存器,001:读操作 010:写操作 100:擦除操作
reg [1:0] state;//状态,四个状态
reg [3:0] cnt_200ns;//写入操作使用的计时器
reg [4:0] cnt_300ns;//读操作使用计时器
reg [2:0] cnt_addr;//写入地址次数,因为写入前要先写命令,在写数据
reg [15:0] cnt_240us;//写入指令输入后,等待一段时间再回到空闲态
reg cnt_240us_flag;
reg [27:0] cnt_500ms;
reg cnt_500ms_flag;
reg [7:0] data_reg;//写入的数据寄存器
reg write_en;//写入控制,为高时才能写入
reg read_en;//读控制位,为高时才能读数据
reg erase_en;//擦除控制位,为高时才会擦除操作//assign fl_dq[7:0] = (state == READ) ? 8'hzz : data_reg[7:0];
assign fl_dq[7:0] = ((state != WRITE)&&(state != ERASE)) ? 8'hzz : data_reg[7:0];
assign fl_wp = 1'b1;
assign fl_reset = 1'b1;
assign dat_out = fl_dq;always @(posedge sys_clk or negedge sys_rst_n) beginif (!sys_rst_n) beginstate <= IDLE;end else begincase (state)IDLE: begin//空闲态case (cmd)3'b001: begin // 读操作if(read_en)state <= READ;end3'b010: begin // 写操作 if(write_en)state <= WRITE; end3'b100: begin // 擦除操作if(erase_en)state <= ERASE;enddefault: beginstate <= IDLE;endendcaseendREAD: begin//读if(cnt_300ns == 5'd14) //需要连续读的话,14改成任意大于14的值,注意要在位宽内,同时波特率=9600state <= IDLE;endWRITE: begin//写if(cnt_240us == CNT_240US_MAX)state <= IDLE;endERASE: begin//擦除if(cnt_500ms == CNT_500MS_MAX)state <= IDLE;enddefault:beginstate <= IDLE;end endcaseend
end//cmd 状态控制寄存器,001:读操作 010:写操作 100:擦除操作
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) cmd <= 3'b000;else if(wr_flag)cmd <= 3'b010;else if(rd_flag)cmd <= 3'b001;else if(rset_flag)cmd <= 3'b100;
//write_en 写入控制,为高时才能写入
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) write_en <= 1'b1;else if(cnt_240us == CNT_240US_MAX)write_en <= 1'b0;else if(wr_flag)write_en <= 1'b1;
//read_en 读控制位,为高时才能读数据
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) read_en <= 1'b1;else if(cnt_300ns == 5'd14)read_en <= 1'b0;else if(rd_flag)read_en <= 1'b1;
//erase_en 擦除控制位,为高时才会擦除操作
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) erase_en <= 1'b1;else if(cnt_500ms == CNT_500MS_MAX)erase_en <= 1'b0; else if(rset_flag)erase_en <= 1'b1;
//cnt_300ns 读操作使用计时器
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) cnt_300ns <= 5'd0;else if(cnt_300ns == 5'd14)cnt_300ns <= 5'd0;else if(state == READ)cnt_300ns <= cnt_300ns + 1'd1;elsecnt_300ns <= 5'd0;
//cnt_200ns 写入操作使用的计时器
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) cnt_200ns <= 4'd0;else if((state == WRITE)&&((cnt_200ns == 4'd9)||((cnt_200ns == 4'd0)&&(cnt_addr == 3'd4))))cnt_200ns <= 4'd0;else if(state == WRITE)cnt_200ns <= cnt_200ns + 1'd1;else if((state == ERASE)&&((cnt_200ns == 4'd9)||((cnt_200ns == 4'd0)&&(cnt_addr == 3'd6))))cnt_200ns <= 4'd0;else if(state == ERASE)cnt_200ns <= cnt_200ns + 1'd1; elsecnt_200ns <= 4'd0;
//cnt_addr 写入地址次数,因为写入前要先写命令,在写数据
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) cnt_addr <= 3'd0;else if((state != WRITE)&&(state != ERASE))cnt_addr <= 3'd0;else if((state == WRITE) && (cnt_200ns == 4'd0)&&(cnt_addr < 3'd4))cnt_addr <= cnt_addr + 1'd1;else if((state == ERASE) && (cnt_200ns == 4'd0)&&(cnt_addr < 3'd6))cnt_addr <= cnt_addr + 1'd1;
//cnt_240us_flag
always @(posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) cnt_240us_flag <= 1'b0;else if(state != WRITE) cnt_240us_flag <= 1'b0; else if((cnt_addr == 3'd4)&&(cnt_200ns == 4'd6))cnt_240us_flag <= 1'b1;
//cnt_240us 写入指令输入后,等待一段时间再回到空闲态
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) cnt_240us <= 16'd0;else if(cnt_240us == CNT_240US_MAX)//11999cnt_240us <= 16'd0; else if(cnt_240us_flag)cnt_240us <= cnt_240us + 1'd1;
//cnt_500ms_flag
always @(posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) cnt_500ms_flag <= 1'b0;else if(state != ERASE) cnt_500ms_flag <= 1'b0; else if((cnt_addr == 3'd6)&&(cnt_200ns == 4'd6))cnt_500ms_flag <= 1'b1;
//cnt_500ms 擦除指令输入后,等待一段时间再回到空闲态
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) cnt_500ms <= 28'd0;else if(cnt_500ms == CNT_500MS_MAX)cnt_500ms <= 28'd0; else if(cnt_500ms_flag)cnt_500ms <= cnt_500ms + 1'd1;
//fl_addr 写入或者读出的地址
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) fl_addr <= 23'd0;else if(state == READ)fl_addr <= addr;else if(state == WRITE) case(cnt_addr)3'd0: fl_addr <= 23'd0;3'd1: fl_addr <= 23'h000AAA; //这三个地址位写入操作需要写入命令的对应地址3'd2: fl_addr <= 23'h000555;3'd3: fl_addr <= 23'h000AAA;3'd4: fl_addr <= addr; //实际需要写入数据的地址default:fl_addr <= 23'd0;endcaseelse if(state == ERASE) case(cnt_addr)3'd0: fl_addr <= 23'd0;3'd1: fl_addr <= 23'h000AAA; //这三个地址位写入操作需要写入命令的对应地址3'd2: fl_addr <= 23'h000555;3'd3: fl_addr <= 23'h000AAA;3'd4: fl_addr <= 23'h000AAA;3'd5: fl_addr <= 23'h000555;3'd6: fl_addr <= 23'h0F0000; //全擦除改成23'h000AAAdefault:fl_addr <= 23'd0;endcaseelsefl_addr <= 23'd0;//fl_ce 芯片使能输入,低电平有效
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) fl_ce <= 1'b1;else if((state == READ)&&(cnt_300ns <= 5'd14))fl_ce <= 1'b0; else if((state == WRITE)&&(cnt_200ns == 4'd1))fl_ce <= 1'b1;else if(state == WRITE)fl_ce <= 1'b0;else if((state == ERASE)&&(cnt_200ns == 4'd1))fl_ce <= 1'b1;else if(state == ERASE)fl_ce <= 1'b0;else fl_ce <= 1'b1;
//fl_oe 输出使能,低电平读出有效
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) fl_oe <= 1'b1;else if((state == READ)&&(cnt_300ns <= 5'd14))fl_oe <= 1'b0; else if(state == WRITE)fl_oe <= 1'b1; else if(state == ERASE)fl_oe <= 1'b1; elsefl_oe <= 1'b0;
//fl_we 输入使能,低电平写入有效
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) fl_we <= 1'b0;else if((state == READ)&&(cnt_300ns <= 5'd14))fl_we <= 1'b1;else if((state == WRITE)&&((cnt_addr == 3'd0)||(cnt_200ns == 4'd7)))fl_we <= 1'b1; else if((state == WRITE)&&(cnt_200ns == 4'd3)) fl_we <= 1'b0;else if((state == ERASE)&&((cnt_addr == 3'd0)||(cnt_200ns == 4'd7)))fl_we <= 1'b1; else if((state == ERASE)&&(cnt_200ns == 4'd3)) fl_we <= 1'b0;
//data_reg 写入的数据寄存器
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) data_reg <= 8'd0;else if((state == WRITE)&&(cnt_200ns == 4'd3)) case(cnt_addr)3'd0: data_reg <= 8'h0;3'd1: data_reg <= 8'hAA;3'd2: data_reg <= 8'h55;3'd3: data_reg <= 8'hA0;3'd4: data_reg <= data_in;default:data_reg <= 8'h0;endcaseelse if((state == ERASE)&&(cnt_200ns == 4'd3)) case(cnt_addr)3'd0: data_reg <= 8'h0;3'd1: data_reg <= 8'hAA;3'd2: data_reg <= 8'h55;3'd3: data_reg <= 8'h80;3'd4: data_reg <= 8'hAA;3'd5: data_reg <= 8'h55; 3'd6: data_reg <= 8'h30; //全擦除改成8'h10default:data_reg <= 8'h0;endcase//pi_flag 打印数据到串口标志位
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) pi_flag <= 1'b0;else if((state == READ)&&(cnt_300ns == 5'd9)) pi_flag <= 1'b1; else if((state == WRITE)&&(cnt_200ns == 4'd6)) pi_flag <= 1'b1;elsepi_flag <= 1'b0;
//lcd_out
always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n)lcd_out <= 8'd0;else if((state == READ)&&(pi_flag == 1'b1))lcd_out <= dat_out;endmodule
3.3 lcd1602显示模块lcd1602
//将输入的8位数据,16进制输出显示2位在lcd上
module lcd1602 ( input sys_clk ,//系统时钟输入50Minput sys_rst_n ,//复位,低电平有效input [7:0] data ,output reg [7:0] lcd_dat ,//LCD的8位数据口output reg lcd_rs ,//数据命令选择信号,高电平表示数据,低电平表示命令output lcd_rw ,//读写标志,高电平表示读,低电平表示写,该程序我们只对液晶屏进行写操作output reg lcd_en //LCD的控制脚);
//--------------------lcd1602 order----------------------------
parameter Mode_Set = 8'h38,//*设置8位格式,2行,5*7*Cursor_Set = 8'h0c,//*整体显示,关光标,不闪烁*/ Address_Set = 8'h06,//*设定输入方式,增量不移位*/Clear_Set = 8'h01;//*清除显示*//****************************LCD1602 Display Data****************************/
wire [7:0] addr; //write address
reg [7:0] data_h,data_l;assign addr = 8'h80;//设置显示第一行
assign lcd_rw = 1'b0;always @(posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) data_h <= 8'd0;else if((data[7:4]>=10)&&(data[7:4]<=15)) data_h <= data[7:4]+87; //a-felsedata_h <= data[7:4]+8'h30; //数字
always @(posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) data_l <= 8'd0;else if((data[3:0]>=10)&&(data[3:0]<=15))data_l <= data[3:0]+87;elsedata_l <= data[3:0]+8'h30;
/****************************LCD1602 Driver****************************/
//-----------------------lcd1602 clk_en---------------------
reg [31:0] cnt;
reg lcd_clk_en;
always @(posedge sys_clk or negedge sys_rst_n)
begin if(!sys_rst_n)begincnt <= 1'b0;lcd_clk_en <= 1'b0;endelse if(cnt == 32'd24999) //0.5msbeginlcd_clk_en <= 1'b1;cnt <= 1'b0;endelsebegincnt <= cnt + 1'b1;lcd_clk_en <= 1'b0;end
end //-----------------------lcd1602 display state-------------------------------------------
reg [5:0] state;
always@(posedge sys_clk or negedge sys_rst_n)
beginif(!sys_rst_n)beginstate <= 1'b0;lcd_rs <= 1'b0;lcd_en <= 1'b0;lcd_dat <= 1'b0; endelse if(lcd_clk_en) begincase(state)//-------------------init_state---------------------5'd0: begin lcd_rs <= 1'b0;lcd_en <= 1'b1;lcd_dat <= Mode_Set; state <= state + 1'd1;end5'd1: beginlcd_en <= 1'b0;state <= state + 1'd1;end5'd2: beginlcd_rs <= 1'b0;lcd_en <= 1'b1;lcd_dat <= Cursor_Set;state <= state + 1'd1;end5'd3: beginlcd_en <= 1'b0;state <= state + 1'd1;end5'd4: beginlcd_rs <= 1'b0;lcd_en <= 1'b1;lcd_dat <= Address_Set;state <= state + 1'd1;end5'd5: beginlcd_en <= 1'b0;state <= state + 1'd1;end5'd6: beginlcd_rs <= 1'b0;lcd_en <= 1'b1;lcd_dat <= Clear_Set;state <= state + 1'd1;end5'd7: beginlcd_en <= 1'b0;state <= state + 1'd1;end//--------------------work state--------------------5'd8: begin lcd_rs <= 1'b0;lcd_en <= 1'b1;lcd_dat <= addr; //write addrstate <= state + 1'd1;end5'd9: beginlcd_en <= 1'b0;state <= state + 1'd1;end5'd10: beginlcd_rs <= 1'b1;lcd_en <= 1'b1;lcd_dat <= "V"; //write datastate <= state + 1'd1;end5'd11: beginlcd_en <= 1'b0;state <= state + 1'd1;end5'd12: beginlcd_rs <= 1'b1;lcd_en <= 1'b1;lcd_dat <= "a"; //write datastate <= state + 1'd1;end5'd13: beginlcd_en <= 1'b0;state <= state + 1'd1;end5'd14: beginlcd_rs <= 1'b1;lcd_en <= 1'b1;lcd_dat <= "l"; //write datastate <= state + 1'd1;end5'd15: beginlcd_en <= 1'b0;state <= state + 1'd1;end5'd16: beginlcd_rs <= 1'b1;lcd_en <= 1'b1;lcd_dat <= ":"; //write datastate <= state + 1'd1;end5'd17: beginlcd_en <= 1'b0;state <= state + 1'd1;end 5'd18: beginlcd_rs <= 1'b1;lcd_en <= 1'b1;lcd_dat <= data_h; //write data: tens digitstate <= state + 1'd1;end5'd19: beginlcd_en <= 1'b0;state <= state + 1'd1;end5'd20: beginlcd_rs <= 1'b1;lcd_en <= 1'b1;lcd_dat <= data_l; //write data: single digitstate <= state + 1'd1;end5'd21: beginlcd_en <= 1'b0;state <= state + 1'd1;end5'd22: beginlcd_rs <= 1'b1;lcd_en <= 1'b1;lcd_dat <= 8'h68; //write data: single digitstate <= state + 1'd1;end5'd23: beginlcd_en <= 1'b0;state <= 5'd8;enddefault: state <= 5'bxxxxx;endcaseend
endendmodule
3.4 串口发送模块uart_tx
//功能:将FPGA数据以固定的速率发送给PC端,PC端通过串口调试助手接收并打印出来
`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数据
);//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//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 ;//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//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'b1)&&(work_en == 1'b1))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;endcase
endmodule
3.5 按键滤波器模块key_filiter
//输入按键信号,输出一个系统时钟的高电平脉冲
module key_filiter
(input sys_clk ,input sys_rst_n ,input key_in ,output reg key_flag
);reg [19:0] cnt_20ms;
parameter CNT_MAX=20'd999999;always@(posedge sys_clk or negedge sys_rst_n)if(!sys_rst_n)cnt_20ms<=20'd0;else if(key_in)cnt_20ms<=20'd0;else if(cnt_20ms==CNT_MAX) //最大值保持cnt_20ms<=CNT_MAX;elsecnt_20ms<=cnt_20ms+1'd1;always@(posedge sys_clk or negedge sys_rst_n)if(!sys_rst_n)key_flag<=1'b0;else if(cnt_20ms==CNT_MAX-20'd1)key_flag<=1'b1;elsekey_flag<=1'b0;endmodule
4. SignalTap仿真图
按键key3写入,key2读出,key0扇区擦除
-
第一次按下读操作键,数据读出为FFh,证明无数据存储在里面
-
按下写操作键
可以看到,ry_by产生了一段时间的低电平,并且dq数据线也显示5Ah,代表写入成功
这是我们放大中间红线密集处: 可以看到我们写入的一些命令
-
再按下读操作键,正确读出
-
按下扇区擦除键
同样我们放大红线处:
-
再按下读操作键,无数值,ff
5. 实物图
读操作:
写操作后再读:
擦除操作后再读:
这篇关于FPGA-DE2-115-实验四-flash读写操作-S29GL064N的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!