文章目录
0、前言
1、目标
2、图片的预处理
3、SD NAND的预处理
4、FPGA实现
4.1、详细设计
4.2、仿真
4.3、实验结果
·前言
在上一篇文章《基于FPGA的SD卡的数据读写实现(SD NAND FLASH)》中,我们了解到了SD NAND Flash的相关知识,并在FPGA平台上实现了对SD NAND的读写测试。SD NAND的读写测试可能会有点简单和枯燥,所以本文我们来搞点有乐趣性的
1、目标
使用 SD NAND数据读写控制器读取事先存储在 SD NAND的图片数据,将读取的图片数据通过SDRAM 数据读写控制器暂存在 SDRAM 芯片中,通过 VGA 显示器将暂存在 SDRAM 的图片显示出来。 SD 卡内存储两张图片,其交替显示在 VGA 显示器上,分辨率为 640*480。
SD NAND在SD2.0版本协议下,SPI模式的理论最大传输速率为50Mbps,加上命令号以及等待返回响应信号的时间,实际上的传输速率还会下降。对于采用分辨率为640*480@60Hz 的显示器来说,一幅图像的数据量达到640*480*16bit = 4915200bit = 4800Kbit(1Kbit=1024bit), 每秒钟刷新60次,那么每秒钟需要传输的数据量达到4800Kbit*60 = 288000Kbit =281.25Mbit (1Mbit=1024Kbit)。由此可以看出,SD卡的读写速度完全跟不上VGA的数据发送速度,因此必须先缓存一幅图像到内部或外部存储器,再通过VGA接口显示。FPGA的片内存储资源较少,对于缓存如此大量的数据,只能使用SDRAM或DDR3缓存数据。
2、图片的预处理
首先选取要显示的图片两张,使用 Window 系统自带的画图工具对图片进行处理,将图片处理为分辨率 640*480。
VGA的显示格式为16位RGB565格式,为了使SD NAND读出的数据可以直接在VGA上显示,需要将图片通过 “ IMG2LCD ” 上位机软件转成16位的RGB565格式的bin文件,再将bin文件导入SD NAND中。
使用 “ IMG2LCD ” 上位机软件打开两张图片,按如下设置相关参数,然后点击保存,就生成了两个图片的二进制文件(像素值)。
3、SD NAND的预处理
SD NAND在经过多次存放数据与删除数据之后,存入的文件有可能不是按照连续的扇区地址存储的,为了避免图片显示错误,我们将bin文件导入SD NAND之前,需要对SD NAND进行一个格式化处理。
首先得找个读卡器,再把所用到的SD NAND开发板插到读卡器上边,通过USB接口与PC建立链接。
本次实验我依然选用的是[深圳雷龙公司](http:
接着说回来对SD NAND的初始化处理。插上读卡器后,选择对应的磁盘,点击“格式化”,并点击“开始”
格式化完成后,将前面生成的两张图片对应的bin文件存入对应的SD NAND磁盘中:
SD NAND内部的存储资源是以扇区的形式进行划分的,为了将图片的bin数据从SD NAND中读取出来,我们需要找到图片存储对应的扇区地址。扇区地址可以用“WinHex 软件”来查看。
以管理员身份运行软件 WinHex 软件,点击“工具 ”,然后点击“打开磁盘”:
双击打开对应的SD NAND,记录下两个 bin文件的第一扇区地址:
此时查询到的扇区地址就是bin文件存放的起始扇区地址,我们只需要按照这个起始扇区地址,按顺序读出SD NAND中的数据即可,直到读完一张图片中的所有数据。SD NAND中一个扇区存放512个字节,也就是256个16位数据,对于分辨率为640*480的图片来说,共需要读出1200(640*480/256)个扇区数据。
4、FPGA实现
先说下总体思路:
· SD NAND中存有两幅图片,一副为雷龙公司的官网截图,另一幅则是本博客的头像
· FPGA从SD NAND中读取这两幅图片的像素信息,并缓存到SDRAM中
· 将SDRAM中的数据(两幅图片的像素信息)通过VGA接口显示在显示器上
根据这个思路,可以对应的画对应的系统框图:
FPGA顶层模块例化了以下五个模块:PLL时钟模块、SD NAND读取图片控制模块、SD NAND控制器模块、SDRAM控制器模块和VGA驱动模块。
4.1、详细设计
(1) 顶层模块
顶层模块:顶层模块主要完成对其余各模块的例化,实现各模块之间的数据交互。需要注意的是,系统初始化完成是在SD NAND以及SDRAM都初始化完成后才开始拉高的,该信号控制着SD NAND读取图片控制模块的复位信号,因此SD NAND读取图片控制模块是在系统初始化完成后才工作的,防止因SD NAND或者SDRAM初始化未完成导致数据错误。
此部分代码如下:
module top_sd_photo_vga(
input sys_clk ,
input sys_rst_n ,
input sd_miso ,
output sd_clk ,
output sd_cs ,
output sd_mosi ,
output sdram_clk ,
output sdram_cke ,
output sdram_cs_n ,
output sdram_ras_n ,
output sdram_cas_n ,
output sdram_we_n ,
output [1:0] sdram_ba ,
output [1:0] sdram_dqm ,
output [12:0] sdram_addr ,
inout [15:0] sdram_data ,
output vga_hs ,
output vga_vs ,
output [15:0] vga_rgb
);
//parameter define
parameter PHOTO_H_PIXEL = 24'd640 ; //设置SDRAM缓存大小
parameter PHOTO_V_PIXEL = 24'd480 ; //设置SDRAM缓存大小
//wire define
wire clk_100m ; //100mhz时钟,SDRAM操作时钟
wire clk_100m_shift ; //100mhz时钟,SDRAM相位偏移时钟
wire clk_50m ;
wire clk_50m_180deg ;
wire clk_25m ;
wire rst_n ;
wire locked ;
wire sys_init_done ; //系统初始化完成
wire sd_rd_start_en ; //开始写SD NAND数据信号
wire [31:0] sd_rd_sec_addr ; //读数据扇区地址
wire sd_rd_busy ; //读忙信号
wire sd_rd_val_en ; //数据读取有效使能信号
wire [15:0] sd_rd_val_data ; //读数据
wire sd_init_done ; //SD NAND初始化完成信号
wire wr_en ; //sdram_ctrl模块写使能
wire [15:0] wr_data ; //sdram_ctrl模块写数据
wire rd_en ; //sdram_ctrl模块读使能
wire [15:0] rd_data ; //sdram_ctrl模块读数据
wire sdram_init_done ; //SDRAM初始化完成
//*****************************************************
//** main code
//*****************************************************
assign rst_n = sys_rst_n & locked;
assign sys_init_done = sd_init_done & sdram_init_done; //SD NAND和SDRAM都初始化完成
assign wr_en = sd_rd_val_en;
assign wr_data = sd_rd_val_data;
//锁相环
pll_clk u_pll_clk(
.areset (1'b0 ),
.inclk0 (sys_clk ),
.c0 (clk_100m ),
.c1 (clk_100m_shift ),
.c2 (clk_50m ),
.c3 (clk_50m_180deg ),
.c4 (clk_25m ),
.locked (locked )
);
//读取SD NAND图片
sd_read_photo u_sd_read_photo(
(clk_50m),
(rst_n & sys_init_done),
(sd_rd_busy),
(sd_rd_start_en),
(sd_rd_sec_addr)
);
//SD NAND顶层控制模块
sd_ctrl_top u_sd_ctrl_top(
.clk_ref (clk_50m),
.clk_ref_180deg (clk_50m_180deg),
.rst_n (rst_n),
.sd_miso (sd_miso),
.sd_clk (sd_clk),
.sd_cs (sd_cs),
.sd_mosi (sd_mosi),
.wr_start_en (1'b0),
.wr_sec_addr (32'b0),
.wr_data (16'b0),
.wr_busy (),
.wr_req (),
.rd_start_en (sd_rd_start_en),
.rd_sec_addr (sd_rd_sec_addr),
.rd_busy (sd_rd_busy),
.rd_val_en (sd_rd_val_en),
.rd_val_data (sd_rd_val_data),
.sd_init_done (sd_init_done)
);
//SDRAM 控制器顶层模块,封装成FIFO接口
//SDRAM 控制器地址组成: {bank_addr[1:0],row_addr[12:0],col_addr[8:0]}
sdram_top u_sdram_top(
.ref_clk (clk_100m), //sdram 控制器参考时钟
.out_clk (clk_100m_shift), //用于输出的相位偏移时钟
.rst_n (rst_n), //系统复位
//用户写端口
.wr_clk (clk_50m), //写端口FIFO: 写时钟
.wr_en (wr_en), //写端口FIFO: 写使能
.wr_data (wr_data), //写端口FIFO: 写数据
.wr_min_addr (24'd0), //写SDRAM的起始地址
.wr_max_addr (PHOTO_H_PIXEL*PHOTO_V_PIXEL),//写SDRAM的结束地址
.wr_len (10'd512), //写SDRAM时的数据突发长度
.wr_load (~rst_n), //写端口复位: 复位写地址,清空写FIFO
//用户读端口
.rd_clk (clk_25m), //读端口FIFO: 读时钟
.rd_en (rd_en), //读端口FIFO: 读使能
.rd_data (rd_data), //读端口FIFO: 读数据
.rd_min_addr (24'd0), //读SDRAM的起始地址
.rd_max_addr (PHOTO_H_PIXEL*PHOTO_V_PIXEL),//读SDRAM的结束地址
.rd_len (10'd512), //从SDRAM中读数据时的突发长度
.rd_load (~rst_n), //读端口复位: 复位读地址,清空读FIFO
//用户控制端口
.sdram_read_valid (1'b1), //SDRAM 读使能
.sdram_pingpang_en (1'b0), //SDRAM 乒乓操作使能
.sdram_init_done (sdram_init_done), //SDRAM 初始化完成标志
//SDRAM 芯片接口
.sdram_clk (sdram_clk), //SDRAM 芯片时钟
.sdram_cke (sdram_cke), //SDRAM 时钟有效
.sdram_cs_n (sdram_cs_n), //SDRAM 片选
.sdram_ras_n (sdram_ras_n), //SDRAM 行有效
.sdram_cas_n (sdram_cas_n), //SDRAM 列有效
.sdram_we_n (sdram_we_n), //SDRAM 写有效
.sdram_ba (sdram_ba), //SDRAM Bank地址
.sdram_addr (sdram_addr), //SDRAM 行/列地址
.sdram_data (sdram_data), //SDRAM 数据
.sdram_dqm (sdram_dqm) //SDRAM 数据掩码
);
//VGA驱动模块
vga_driver u_vga_driver(
.vga_clk (clk_25m),
.sys_rst_n (rst_n),
.vga_hs (vga_hs),
.vga_vs (vga_vs),
.vga_rgb (vga_rgb),
.pixel_data (rd_data),
.data_req (rd_en),
.pixel_xpos (),
.pixel_ypos ()
);
endmodule
(2) PLL时钟模块
PLL时钟模块:PLL时钟模块通过调用锁相环(PLL)IP核实现,总共输出五个时钟,频率分别为100Mhz、100Mhz(相位偏移-180度)、50Mhz、50Mhz(相位偏移180度)和25Mhz。 两个100Mhz的时钟用于为SDRAM控制器模块提供驱动时钟;两个50Mhz的时钟用于为SD NAND控制器模块提供驱动时钟;25Mhz用于为VGA驱动模块提供驱动时钟。
(3) SD NAND读取图片控制模块
SD NAND读取图片控制模块:SD NAND读取图片控制模块通过控制SD NAND控制器的读接口,从SD NAND中读取图像数据,并在读完一张图片后延时一段时间,再去读取另一张图片数据,实现两张图片的循环切换读取。
此部分代码:
module sd_read_photo(
);
//parameter define
parameter PHOTO_SECCTION_ADDR0 = 32'd16640; //第一张图片扇区起始地址
parameter PHOTO_SECTION_ADDR1 = 32'd17856; //第二张图片扇区起始地址
//640*480/256 = 1200
parameter RD_SECTION_NUM = 11'd1200 ; //单张图片总共读出的次数
//reg define
reg [1:0] rd_flow_cnt ; //读数据流程控制计数器
reg [10:0] rd_sec_cnt ; //读扇区次数计数器
reg rd_addr_sw ; //读两张图片切换
reg [25:0] delay_cnt ; //延时切换图片计数器
reg rd_busy_d0 ; //读忙信号打拍,用来采下降沿
reg rd_busy_d1 ;
//wire define
wire neg_rd_busy ; //SD NAND读忙信号下降沿
//*****************************************************
//** main code
//*****************************************************
assign neg_rd_busy = rd_busy_d1 & (~rd_busy_d0);
//对rd_busy信号进行延时打拍,用于采rd_busy信号的下降沿
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
rd_busy_d0 <= 1'b0;
rd_busy_d1 <= 1'b0;
end
else begin
rd_busy_d0 <= rd_busy;
rd_busy_d1 <= rd_busy_d0;
end
end
//循环读取SD NAND中的两张图片(读完之后延时1s再读下一个)
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rd_flow_cnt <= 2'd0;
rd_addr_sw <= 1'b0;
rd_sec_cnt <= 11'd0;
rd_start_en <= 1'b0;
rd_sec_addr <= 32'd0;
end
else begin
rd_start_en <= 1'b0;
case(rd_flow_cnt)
2'd0 : begin
//开始读取SD NAND数据
rd_flow_cnt <= rd_flow_cnt + 2'd1;
rd_start_en <= 1'b1;
rd_addr_sw <= ~rd_addr_sw; //读数据地址切换
if(rd_addr_sw == 1'b0)
rd_sec_addr <= PHOTO_SECCTION_ADDR0;
else
rd_sec_addr <= PHOTO_SECTION_ADDR1;
end
2'd1 : begin
//读忙信号的下降沿代表读完一个扇区,开始读取下一扇区地址数据
if(neg_rd_busy) begin
rd_sec_cnt <= rd_sec_cnt + 11'd1;
rd_sec_addr <= rd_sec_addr + 32'd1;
//单张图片读完
if(rd_sec_cnt == RD_SECTION_NUM - 11'b1) begin
rd_sec_cnt <= 11'd0;
rd_flow_cnt <= rd_flow_cnt + 2'd1;
end
else
rd_start_en <= 1'b1;
end
end
2'd2 : begin
delay_cnt <= delay_cnt + 26'd1; //读取完成后延时1秒
if(delay_cnt == 26'd50_000_000 - 26'd1) begin //50_000_000*20ns = 1s
delay_cnt <= 26'd0;
rd_flow_cnt <= 2'd0;
end
end
default : ;
endcase
end
end
endmodule
(4)SD NAND控制器模块
SD NAND控制器模块:SD NAND控制器模块负责驱动SD NAND,该模块将SD NAND的读写操作封装成方便用户使用的接口。关于SD NAND读写控制器模块在上一篇文章中已经详细说明了,可参考: 基于FPGA的SD卡的数据读写实现(SD NAND FLASH)
(5)SDRAM读写控制模块
SDRAM读写控制模块:SDRAM读写控制器模块负责驱动SDRAM存储器,缓存图像数据。该模块将SDRAM复杂的读写操作封装成类似FIFO的用户接口, 非常方便用户的使用。关于此部分,有详尽的系列文章供参考:相信我,SDRAM真的不难
(6)VGA驱动模块
VGA驱动模块根据VGA时序参数输出行、场同步信号;同时它还要输出数据请求信号用于读取SDRAM中的图片数据,并将图片通过VGA接口在显示器上显示。关于此部分,有详尽的文章供参考:如何用VGA接口乳法?
4.2、仿真
一般的测试中,我们都需要先进行仿真来观察时序等测试行为。此次实验由于找不到好的SD NAND的Verilog模型,所以仿真测试略。
4.3、实验结果
编译工程,把程序下载到FPGA开发板,通过VGA接口连接VGA线到显示器,如下:
接着观察显示器是否会交替显示我们事先保存的两幅图片。实验现象果然与预期一致:
第1幅图片: [深圳市雷龙发展有限公司](http:
第2幅图片:本博客图像(星爷yyds)
好啦,本次实验就做完啦。
如果屏幕前的你也有存储产品方面的需求的话,你都可以试试雷龙公司的**SD NAND**产品哦。
这是一家专业做存储产品的公司,NAND Flash是其主要产品。 该公司专注NAND Flash设计研发13年,在这一块可以说是相当专业。如果你对**NAND Flash**仍有疑惑的问题,或者你想在你的设计中使用NAND Flash产品,都可以直接联系:深圳市雷龙发展有限公司
术业有专攻,闻道有先后,专业的事就交给专业的人处理。如果你有这方面的设计需求都可以直接找他们要免费样品哦。
————————————————
【本文转载自CSDN,作者:孤独的单刀】