文章目录
0、前言
在上一篇文章《基于FPGA的SD卡的数据读写实现(SD NAND FLASH)》中,我们了解到了SD NAND Flash的相关知识,并在FPGA平台上实现了对SD NAND的读写测试。SD NAND的读写测试可能会有点简单和枯燥,所以本文我们来搞点有乐趣性的----将存储在SD NAND内的两张图片通过FPGA读取,并通过VGA的方式在显示器上轮回显示。
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建立链接。
本次实验我依然选用的是深圳雷龙公司的一款SD NAND产品----CSNP32GCR01-AOW。 可以看到这款SD NAND开发板设计得很巧妙,把对外接口设计成了通用的micro接口,兼容性非常强,不管是插读卡器还是直接插FPGA开发板,都是即插即用,十分方便。
接着说回来对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 ,
//系统复位,低电平有效
-
-
//SD NAND接口
-
input sd_miso ,
//SD NANDSPI串行输入数据信号
-
output sd_clk ,
//SD NANDSPI时钟信号
-
output sd_cs ,
//SD NANDSPI片选信号
-
output sd_mosi ,
//SD NANDSPI串行输出数据信号
-
//SDRAM接口
-
output sdram_clk ,
//SDRAM 时钟
-
output sdram_cke ,
//SDRAM 时钟有效
-
output sdram_cs_n ,
//SDRAM 片选
-
output sdram_ras_n ,
//SDRAM 行有效
-
output sdram_cas_n ,
//SDRAM 列有效
-
output sdram_we_n ,
//SDRAM 写有效
-
output [
1:
0] sdram_ba ,
//SDRAM Bank地址
-
output [
1:
0] sdram_dqm ,
//SDRAM 数据掩码
-
output [
12:
0] sdram_addr ,
//SDRAM 地址
-
inout [
15:
0] sdram_data ,
//SDRAM 数据
-
//VGA接口
-
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 (clk_50m),
-
//系统初始化完成之后,再开始从SD NAND中读取图片
-
.
rst_n (rst_n & sys_init_done),
-
.
rd_busy (sd_rd_busy),
-
.
rd_start_en (sd_rd_start_en),
-
.
rd_sec_addr (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 NAND接口
-
.
sd_miso (sd_miso),
-
.
sd_clk (sd_clk),
-
.
sd_cs (sd_cs),
-
.
sd_mosi (sd_mosi),
-
//用户写SD NAND接口
-
.
wr_start_en (
1
'b0),
//不需要写入数据,写入接口赋值为0
-
.
wr_sec_addr (
32
'b0),
-
.
wr_data (
16
'b0),
-
.
wr_busy (),
-
.
wr_req (),
-
//用户读SD NAND接口
-
.
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(
-
input clk ,
//时钟信号
-
input rst_n ,
//复位信号,低电平有效
-
-
input rd_busy ,
//SD NAND读忙信号
-
output reg rd_start_en ,
//开始写SD NAND数据信号
-
output reg [
31:
0] rd_sec_addr
//读数据扇区地址
-
);
-
-
//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幅图片: 深圳市雷龙发展有限公司
第2幅图片:本博客图像(星爷yyds)
好啦,本次实验就做完啦。
如果屏幕前的你也有存储产品方面的需求的话,你都可以试试雷龙公司的SD NAND产品哦。
这是一家专业做存储产品的公司,NAND Flash是其主要产品。 该公司专注NAND Flash设计研发13年,在这一块可以说是相当专业。如果你对NAND Flash仍有疑惑的问题,或者你想在你的设计中使用NAND Flash产品,都可以直接联系:深圳市雷龙发展有限公司
术业有专攻,闻道有先后,专业的事就交给专业的人处理。顺便说一句,他们的样品都免费哦。如果你有这方面的设计需求都可以直接找他们要样品哦。我就是随便咨询了一下,就直接给我寄了这么多样品让我做前期调研,真的赞。
转载:https://blog.csdn.net/wuzhikaidetb/article/details/128107940