本帖最后由 1nnocent 于 2023-2-20 20:39 编辑
原本的测评计划是学习篇部分进行一个SD卡中读取图片LCD显示,但是由于安排计划时没有考虑到SD卡读取数据的速度不够快,SD卡无法及时提供LCD显示所需的数据,所以这里对测评计划做一个修改,修改后实现的效果是SD卡读取图片数据,并存到DDR中,最后从DDR中读取图片数据进行显示。
1、简介
BMP(全称 Bitmap)是 Window 操作系统中的标准图像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此, BMP 文件所占用的空间很大,但是没有失真。 BMP 文件的图像深度可选 lbit、 4bit、 8bit、 16bit、 24bit 及 32bit。 BMP 文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。
典型的 BMP 图像文件由四部分组成:
1、 BMP 文件头(14Byte),它包含 BMP 图像文件的类型、大小等信息;
2、 BMP 信息头(40Byte),它包含有 BMP 图像的宽、高、压缩方法,以及定义颜色等信息;
3、调色板,这个部分是可选的, 如果使用索引来表示图像,调色板就是索引与其对应颜色的映射表;
4、位图数据,即图像数据,在位深度为 24 位时直接使用 RGB 格式,而小于 24 位时使用调色板中颜色的索引值。
2、实验任务
使用 ATK-DFPGL22G 开发板循环读取 SD 卡中存放的两张 BMP 格式图片,分辨率为 480*272,并将其显示在 LCD 液晶屏上。
3、硬件设计
ATK-DFPGL22G 开发板上有一个 SD 卡插槽, 用于插入 SD 卡, 其原理图如图所示:
ATK-DFPGL22G 开发板上 RGB-LCD 接口部分的原理图如下图所示。FPGA 管脚输出的颜色数据位宽为 24bit, 数据格式为 RGB888,即数据高 8 位表示红色, 中间 8 位表示绿色,低 8 位表示蓝色。由于这 24 位数据不仅仅作为输出给 LCD 屏的颜色数据,同时 LCD_R7、 LCD_G7 和 LCD_B7 也用来获取 LCD 屏的 ID,因此这 24 位颜色数据对 ATK-DFPGL22G 开发板来说,是一个双向的引脚。
4、程序设计
时钟模块为其它各模块提供驱动时钟; SD 卡/DDR3 参数计算模块根据 LCD ID,为 SD 卡读取图片控制模块和 DDR3 控制器模块提供参数; SD 卡读取图片控制模块控制 SD 卡控制器的读接口,以及将 SD 卡中读出的 RGB888 格式数据转换成 RGB565 格式数据,写入DDR3 控制器中;最后 LCD 顶层模块从 DDR3 控制器中读出图片数据,显示到 LCD 液晶屏上。
顶层模块(sd_bmp_lcd):顶层模块主要完成对其余各模块的例化,实现各模块之间的数据交互。
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sd_bmp_lcd
// Last modified Date: 2020/11/22 15:16:38
// Last Version: V1.0
// Descriptions: SD卡读BMP图片LCD显示
//
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2020/11/22 15:16:38
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module sd_bmp_lcd(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位,低电平有效
//SD卡接口
input sd_miso , //SD卡SPI串行输入数据信号
output sd_clk , //SD卡SPI时钟信号
output sd_cs , //SD卡SPI片选信号
output sd_mosi , //SD卡SPI串行输出数据信号
//DDR3接口
input pad_loop_in , //低位温度补偿输入
input pad_loop_in_h , //高位温度补偿输入
output pad_rstn_ch0 , //Memory复位
output pad_ddr_clk_w , //Memory差分时钟正
output pad_ddr_clkn_w , //Memory差分时钟负
output pad_csn_ch0 , //Memory片选
output [15:0] pad_addr_ch0 , //Memory地址总线
inout [16-1:0] pad_dq_ch0 , //数据总线
inout [16/8-1:0] pad_dqs_ch0 , //数据时钟正端
inout [16/8-1:0] pad_dqsn_ch0 , //数据时钟负端
output [16/8-1:0] pad_dm_rdqs_ch0 , //数据Mask
output pad_cke_ch0 , //Memory差分时钟使
output pad_odt_ch0 , //On Die Terminati
output pad_rasn_ch0 , //行地址strobe
output pad_casn_ch0 , //列地址strobe
output pad_wen_ch0 , //写使能
output [2:0] pad_ba_ch0 , //Bank地址总线
output pad_loop_out , //低位温度补偿输出
output pad_loop_out_h , //高位温度补偿输出
//lcd接口
output lcd_hs , //LCD 行同步信号
output lcd_vs , //LCD 场同步信号
output lcd_de , //LCD 数据输入使能
inout [23:0] lcd_rgb , //LCD 颜色数据
output lcd_bl , //LCD 背光控制信号
output lcd_rst , //LCD 复位信号
output lcd_pclk //LCD 采样时钟
);
//parameter define
parameter APP_ADDR_MIN = 28'd0 ; //ddr3读写起始地址,以一个16bit的数据为一个单位
//APP_ADDR_MAX = 'd384000;//BURST_LENGTH * 8 * n(n表示突发次数)
parameter BURST_LENGTH = 8'd64 ; //ddr3读写突发长度,64个128bit的数据
//wire define
wire clk_50m ; //50mhz时钟,提供给lcd驱动时钟
wire clk_50m_180deg ;
wire locked ; //时钟锁定信号
wire rst_n ; //全局复位
//DDR
wire ddr_wr_en ; //DDR3控制器模块写使能
wire wr_en ; //DDR3控制器模块写使能
wire [15:0] ddr_wr_data ; //DDR3控制器模块写数据
wire [15:0] wr_data ; //DDR3控制器模块写数据
wire rdata_req ; //DDR3控制器模块读使能
wire [15:0] rd_data ; //DDR3控制器模块读数据
wire ddr_init_done ; //DDR3初始化完成ddr_init_done
wire sys_init_done ; //系统初始化完成(DDR初始化+摄像头初始化)
wire fram_done ; //DDR中已经存入一帧画面标志
wire [27:0] ddr_max_addr ; //存入DDR3的最大读写地址
wire rd_vsync ; //输出源更新信号
//SD卡
wire [15:0] sd_sec_num ; //SD卡读扇区个数
wire sd_rd_start_en ; //开始写SD卡数据信号
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卡初始化完成信号
//LCD
wire lcd_clk ; //分频产生的LCD 采样时钟
wire [12:0] h_disp ; //LCD屏水平分辨率
wire [15:0] lcd_id ; //LCD屏的ID号
//*****************************************************
//** main code
//*****************************************************
//待时钟锁定后产生复位结束信号
assign rst_n = sys_rst_n & locked;
//系统初始化完成:DDR3初始化完成 & SD卡初始化完成
assign sys_init_done = ddr_init_done & sd_init_done;
//DDR3控制器模块为写使能和写数据赋值
assign wr_en = ddr_wr_en;
assign wr_data = ddr_wr_data;
//DDR和SD卡参数计算模块
sd_ddr_size u_sd_rd_size(
.clk (clk_50m ),
.rst_n (rst_n),
.ID_lcd (lcd_id), //LCD的器件ID
.ddr_max_addr (ddr_max_addr),
.sd_sec_num (sd_sec_num)
);
//读取SD卡图片
sd_read_photo u_sd_read_photo(
.clk (clk_50m),
//系统初始化完成之后,再开始从SD卡中读取图片
.rst_n (rst_n & sys_init_done),
.ddr_max_addr (ddr_max_addr),
.sd_sec_num (sd_sec_num),
.rd_busy (sd_rd_busy),
.sd_rd_val_en (sd_rd_val_en),
.sd_rd_val_data (sd_rd_val_data),
.rd_start_en (sd_rd_start_en),
.rd_sec_addr (sd_rd_sec_addr),
.ddr_wr_en (ddr_wr_en),
.ddr_wr_data (ddr_wr_data)
);
//SD卡顶层控制模块
sd_ctrl_top u_sd_ctrl_top(
.clk_ref (clk_50m),
.clk_ref_180deg (clk_50m_180deg),
.rst_n (rst_n), //
//SD卡接口
.sd_miso (sd_miso),
.sd_clk (sd_clk),
.sd_cs (sd_cs),
.sd_mosi (sd_mosi),
//用户写SD卡接口
.wr_start_en (1'b0), //不需要写入数据,写入接口赋值为0
.wr_sec_addr (32'b0),
.wr_data (16'b0),
.wr_busy (),
.wr_req (),
//用户读SD卡接口
.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)
);
//ddr3
ddr3_top u_ddr3_top(
.refclk_in (clk_50m ),
.rst_n (sys_rst_n ),
.ddr_init_done (ddr_init_done ),
//ddr3接口信号
.app_addr_rd_min (28'd0 ),
.app_addr_rd_max (ddr_max_addr ),
.rd_bust_len (BURST_LENGTH ),
.app_addr_wr_min (28'd0 ),
.app_addr_wr_max (ddr_max_addr ),
.wr_bust_len (BURST_LENGTH ),
//用户
.ddr3_read_valid (1'b1 ),
.ddr3_pingpang_en (1'b0 ),
.wr_clk (clk_50m ),
.wr_load (1'b0 ),
.datain_valid (wr_en ),
.datain (wr_data ),
.rd_clk (lcd_clk ),
.rd_load (rd_vsync ),
.dataout (rd_data ),
.rdata_req (rdata_req ),
// DDR3 IO接口
.fram_done (fram_done ),
.pll_lock (pll_lock ),
.ddrphy_rst_done ( ),
.pad_loop_in (pad_loop_in ),
.pad_loop_in_h (pad_loop_in_h ),
.pad_rstn_ch0 (pad_rstn_ch0 ),
.pad_ddr_clk_w (pad_ddr_clk_w ),
.pad_ddr_clkn_w (pad_ddr_clkn_w ),
.pad_csn_ch0 (pad_csn_ch0 ),
.pad_addr_ch0 (pad_addr_ch0 ),
.pad_dq_ch0 (pad_dq_ch0 ),
.pad_dqs_ch0 (pad_dqs_ch0 ),
.pad_dqsn_ch0 (pad_dqsn_ch0 ),
.pad_dm_rdqs_ch0 (pad_dm_rdqs_ch0 ),
.pad_cke_ch0 (pad_cke_ch0 ),
.pad_odt_ch0 (pad_odt_ch0 ),
.pad_rasn_ch0 (pad_rasn_ch0 ),
.pad_casn_ch0 (pad_casn_ch0 ),
.pad_wen_ch0 (pad_wen_ch0 ),
.pad_ba_ch0 (pad_ba_ch0 ),
.pad_loop_out (pad_loop_out ),
.pad_loop_out_h (pad_loop_out_h )
);
//时钟IP核
pll_clk u_pll_clk
(
// Clock out ports
.clkout0 (clk_50m),
.clkout1 (clk_50m_180deg),
.pll_rst (1'b0),
.pll_lock (locked),
// Clock in ports
.clkin1 (sys_clk)
);
//LCD驱动显示模块
lcd_rgb_top u_lcd_rgb_top(
.sys_clk (clk_50m),
.sys_rst_n (rst_n),
.sys_init_done (sys_init_done),
//lcd接口
.lcd_id (lcd_id), //LCD屏的ID号
.lcd_hs (lcd_hs), //LCD 行同步信号
.lcd_vs (lcd_vs), //LCD 场同步信号
.lcd_de (lcd_de), //LCD 数据输入使能
.lcd_rgb (lcd_rgb), //LCD 颜色数据
.lcd_bl (lcd_bl), //LCD 背光控制信号
.lcd_rst (lcd_rst), //LCD 复位信号
.lcd_pclk (lcd_pclk), //LCD 采样时钟
.lcd_clk (lcd_clk), //LCD 驱动时钟
//用户接口
.out_vsync (rd_vsync), //lcd场信号
.h_disp (h_disp), //行分辨率
.v_disp (v_disp), //场分辨率
.pixel_xpos (),
.pixel_ypos (),
.data_in (rd_data), //rfifo输出数据
.data_req (rdata_req) //请求数据输入
);
endmodule
时钟模块(pll_clk):时钟模块通过调用时钟 IP 核实现,共输出 2 个时钟,频率分别为 50Mhz 和 50Mhz(相位偏移 180 度)时钟。 50Mhz 时钟和 50Mhz(相位偏移 180 度)作为 SD 卡/DDR3 参数计算模块、 SD卡读取图片控制模块、 SD 卡控制器模块和 LCD 顶层模块的驱动时钟。
SD 卡/DDR3 参数计算模块(sd_rd_size):由于不同分辨率的 LCD 屏, SD 卡中存放的 BMP 分辨率不一样,所以该模块根据 LCD ID,为 SD 卡读取图片控制模块提供需要从 SD 卡中的扇区个数,和 DDR3 读写的最大地址。除此之外,该模块也负责将 SD 卡中读取的 RGB888 格式的数据,转换成 16 位 RGB565 数据。
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sd_ddr_size
// Last modified Date: 2020/11/22 15:16:38
// Last Version: V1.0
// Descriptions: 根据LCD ID,计算DDR最大读写地址和SD卡读扇区个数
//
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2020/11/22 15:16:38
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module sd_ddr_size (
input clk , //时钟
input rst_n , //复位,低电平有效
input [15:0] ID_lcd , //LCD ID
output reg [27:0] ddr_max_addr , //DDR读写最大地址
output reg [15:0] sd_sec_num //SD卡读扇区个数
);
//parameter define
parameter ID_4342 = 16'h4342;
parameter ID_4384 = 16'h4384;
parameter ID_7084 = 16'h7084;
parameter ID_7016 = 16'h7016;
parameter ID_1018 = 16'h1018;
//*****************************************************
//** main code
//*****************************************************
//根据LCD ID,计算DDR最大读写地址和SD卡读扇区个数
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
ddr_max_addr <= 28'd0;
sd_sec_num <= 16'd0;
end
else begin
case(ID_lcd )
ID_4342 : begin
ddr_max_addr <= 28'd130560; //480*272
sd_sec_num <= 16'd765 + 1'b1; //480*272*3/512
end
ID_4384 : begin
ddr_max_addr <= 28'd384000; //800*480
sd_sec_num <= 16'd2250 + 1'b1; //800*480*3/512 + 1
end
ID_7084 : begin
ddr_max_addr <= 28'd384000; //800*480
sd_sec_num <= 16'd2250 + 1'b1; //800*480*3/512 + 1
end
ID_7016 : begin
ddr_max_addr <= 28'd614400; //1024*600
sd_sec_num <= 16'd3600 + 1'b1; //800*480*3/512 + 1
end
ID_1018 : begin
ddr_max_addr <= 28'd1024000; //1280*800
sd_sec_num <= 16'd6000 + 1'b1; //800*480*3/512 + 1
end
default : begin
ddr_max_addr <= 28'd384000; //800*480
sd_sec_num <= 16'd2250 + 1'b1; //800*480*3/512 + 1
end
endcase
end
end
endmodule
SD 卡读取图片控制模块(sd_read_photo): SD 卡读取图片控制模块通过控制 SD 卡控制器的读接口,从 SD 卡中读取图像数据,并在读完一张图片后延时一段时间,再去读取另一张图片数据,实现两张图片的循环切换读取。
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sd_read_photo
// Last modified Date: 2020/11/22 15:16:38
// Last Version: V1.0
// Descriptions: SD卡读取BMP图片
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2020/11/22 15:16:38
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module sd_read_photo(
input clk , //时钟信号
input rst_n , //复位信号,低电平有效
input [27:0] ddr_max_addr , //DDR读写最大地址
input [15:0] sd_sec_num , //SD卡读扇区个数
input rd_busy , //SD卡读忙信号
input sd_rd_val_en , //SD卡读数据有效信号
input [15:0] sd_rd_val_data, //SD卡读出的数据
output reg rd_start_en , //开始写SD卡数据信号
output reg [31:0] rd_sec_addr , //读数据扇区地址
output reg ddr_wr_en , //DDR写使能信号
output [15:0] ddr_wr_data //DDR写数据
);
//parameter define
//设置两张图片的扇区地址,通过上位机WinHex软件查看
parameter PHOTO_SECTION_ADDR0 = 32'd1034496;//1041792;//1,034,496第一张图片扇区起始地址1,040,448
parameter PHOTO_SECTION_ADDR1 = 32'd1045120;//1044096;//1,045,120第二张图片扇区起始地址1,051,072
//BMP文件首部长度=BMP文件头+信息头
parameter BMP_HEAD_NUM = 6'd54; //BMP文件头+信息头=14+40=54
//reg define
reg [1:0] rd_flow_cnt /* synthesis syn_preserve=1 */; //读数据流程控制计数器
reg [15:0] rd_sec_cnt ; //读扇区次数计数器
reg rd_addr_sw ; //读两张图片切换
reg [25:0] delay_cnt ; //延时切换图片计数器
reg bmp_rd_done ; //单张图片读取完成
reg rd_busy_d0 ; //读忙信号打拍,用来采下降沿
reg rd_busy_d1 ;
reg [1:0] val_en_cnt ; //SD卡数据有效计数器
reg [15:0] val_data_t ; //SD卡数据有效寄存
reg [5:0] bmp_head_cnt ; //BMP首部计数器
reg bmp_head_flag ; //BMP首部标志
reg [23:0] rgb888_data ; //24位RGB888数据
reg [27:0] ddr_wr_cnt ; //DDR写入计数器
reg [1:0] ddr_flow_cnt ; //DDR写数据流程控制器计数器
//wire define
wire neg_rd_busy; //SD卡读忙信号下降沿
//*****************************************************
//** main code
//*****************************************************
assign neg_rd_busy = rd_busy_d1 & (~rd_busy_d0);
//24位RGB888格式转成16位RGB565格式
assign ddr_wr_data = {rgb888_data[23:19],rgb888_data[15:10],rgb888_data[7:3]};
//对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卡中的两张图片(读完之后延时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 <= 16'd0;
rd_start_en <= 1'b0;
rd_sec_addr <= 32'd0;
bmp_rd_done <= 1'b0;
delay_cnt <= 26'd0;
end
else begin
rd_start_en <= 1'b0;
bmp_rd_done <= 1'b0;
case(rd_flow_cnt)
2'd0 : begin
//开始读取SD卡数据
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_SECTION_ADDR0;
else
rd_sec_addr <= PHOTO_SECTION_ADDR1;//PHOTO_SECTION_ADDR1;
end
2'd1 : begin
//读忙信号的下降沿代表读完一个扇区,开始读取下一扇区地址数据
if(neg_rd_busy) begin
rd_sec_cnt <= rd_sec_cnt + 1'b1;
rd_sec_addr <= rd_sec_addr + 32'd1;
//单张图片读完
if(rd_sec_cnt == sd_sec_num - 1'b1) begin
rd_sec_cnt <= 16'd0;
rd_flow_cnt <= rd_flow_cnt + 2'd1;
bmp_rd_done <= 1'b1;
end
else
rd_start_en <= 1'b1;
end
end
2'd2 : begin
delay_cnt <= delay_cnt + 1'b1; //单张图片读完后延时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
//SD卡读取的16位数据,转成24位RGB888格式
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
val_en_cnt <= 2'd0;
val_data_t <= 16'd0;
bmp_head_cnt <= 6'd0;
ddr_wr_en <= 1'b0;
rgb888_data <= 24'd0;
ddr_wr_cnt <= 28'd0;
ddr_flow_cnt <= 2'd0;
end
else begin
ddr_wr_en <= 1'b0;
case(ddr_flow_cnt)
2'd0 : begin //BMP首部
if(sd_rd_val_en) begin
bmp_head_cnt <= bmp_head_cnt + 1'b1;
if(bmp_head_cnt == BMP_HEAD_NUM[5:1] - 1'b1) begin
ddr_flow_cnt <= ddr_flow_cnt + 1'b1;
bmp_head_cnt <= 6'd0;
end
end
end
2'd1 : begin //BMP有效数据
if(sd_rd_val_en) begin
val_en_cnt <= val_en_cnt + 1'b1;
val_data_t <= sd_rd_val_data;
if(val_en_cnt == 2'd1) begin //3个16位数据转成2个24位数据
ddr_wr_en <= 1'b1;
rgb888_data <= {sd_rd_val_data[15:8],val_data_t[7:0],
val_data_t[15:8]};
end
else if(val_en_cnt == 2'd2) begin
ddr_wr_en <= 1'b1;
rgb888_data <= {sd_rd_val_data[7:0],sd_rd_val_data[15:8],
val_data_t[7:0]};
val_en_cnt <= 2'd0;
end
end
if(ddr_wr_en) begin
ddr_wr_cnt <= ddr_wr_cnt + 1'b1;
if(ddr_wr_cnt == ddr_max_addr - 1'b1) begin
ddr_wr_cnt <= 28'd0;
ddr_flow_cnt <= ddr_flow_cnt + 1'b1;
end
end
end
2'd2 : begin //等待单张BMP图片读取结束
if(bmp_rd_done)
ddr_flow_cnt <= 2'd0;
end
default :;
endcase
end
end
endmodule
SD 卡控制器模块(sd_ctrl_top): SD 卡控制器模块负责驱动 SD 卡,该模块将 SD 卡的 SPI 读写操作封装成方便用户使用的接口。有关该模块的详细介绍请大家参考“SD 卡读写测试实验”章节。
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: sd_ctrl_top
// Last modified Date: 2020/05/28 20:28:08
// Last Version: V1.0
// Descriptions: SD卡顶层控制模块
//
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2020/05/28 20:28:08
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module sd_ctrl_top(
input clk_ref , //时钟信号
input clk_ref_180deg, //时钟信号,与clk_ref相位相差180度
input rst_n , //复位信号,低电平有效
//SD卡接口
input sd_miso , //SD卡SPI串行输入数据信号
output sd_clk , //SD卡SPI时钟信号
output reg sd_cs , //SD卡SPI片选信号
output reg sd_mosi , //SD卡SPI串行输出数据信号
//用户写SD卡接口
input wr_start_en , //开始写SD卡数据信号
input [31:0] wr_sec_addr , //写数据扇区地址
input [15:0] wr_data , //写数据
output wr_busy , //写数据忙信号
output wr_req , //写数据请求信号
//用户读SD卡接口
input rd_start_en , //开始读SD卡数据信号
input [31:0] rd_sec_addr , //读数据扇区地址
output rd_busy , //读数据忙信号
output rd_val_en , //读数据有效信号
output [15:0] rd_val_data , //读数据
output sd_init_done //SD卡初始化完成信号
);
//wire define
wire init_sd_clk ; //初始化SD卡时的低速时钟
wire init_sd_cs ; //初始化模块SD片选信号
wire init_sd_mosi ; //初始化模块SD数据输出信号
wire wr_sd_cs ; //写数据模块SD片选信号
wire wr_sd_mosi ; //写数据模块SD数据输出信号
wire rd_sd_cs ; //读数据模块SD片选信号
wire rd_sd_mosi ; //读数据模块SD数据输出信号
wire sd_clk_t ; //SD卡时钟
//*****************************************************
//** main code
//*****************************************************
//SD卡的SPI_CLK
assign sd_clk = (sd_init_done==1'b0) ? init_sd_clk : clk_ref_180deg;
//SD卡接口信号选择
always @(*) begin
//SD卡初始化完成之前,端口信号和初始化模块信号相连
if(sd_init_done == 1'b0) begin
sd_cs = init_sd_cs;
sd_mosi = init_sd_mosi;
end
else if(wr_busy) begin
sd_cs = wr_sd_cs;
sd_mosi = wr_sd_mosi;
end
else if(rd_busy) begin
sd_cs = rd_sd_cs;
sd_mosi = rd_sd_mosi;
end
else begin
sd_cs = 1'b1;
sd_mosi = 1'b1;
end
end
//SD卡初始化
sd_init u_sd_init(
.clk_ref (clk_ref),
.rst_n (rst_n),
.sd_miso (sd_miso),
.sd_clk (init_sd_clk),
.sd_cs (init_sd_cs),
.sd_mosi (init_sd_mosi),
.sd_init_done (sd_init_done)
);
//SD卡读数据
sd_read u_sd_read(
.clk_ref (clk_ref),
.clk_ref_180deg (clk_ref_180deg),
.rst_n (rst_n),
.sd_miso (sd_miso),
.sd_cs (rd_sd_cs),
.sd_mosi (rd_sd_mosi),
//SD卡初始化完成之后响应读操作
.rd_start_en (rd_start_en & sd_init_done),
.rd_sec_addr (rd_sec_addr),
.rd_busy (rd_busy),
.rd_val_en (rd_val_en),
.rd_val_data (rd_val_data)
);
endmodule
DDR3 控制器模块(ddr3_top): DDR3 读写控制器模块负责驱动 DDR3 片外存储器,缓存从 SD 卡中读出的图像数据。该模块将 DDR3 复杂的读写操作封装成类似 FIFO 的用户接口,非常方便用户的使用。
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: ddr3_top
// Last modified Date: 2020/05/04 9:19:08
// Last Version: V1.0
// Descriptions: ddr3控制器顶层模块
//
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/05/04 9:19:08
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module ddr3_top(
input refclk_in ,//外部参考时钟输入
input rst_n ,//外部复位输入
input [25:0] app_addr_rd_min ,//读ddr3的起始地址
input [25:0] app_addr_rd_max ,//读ddr3的结束地址
input [7:0] rd_bust_len ,//从ddr3中读数据时的突发长度
input [25:0] app_addr_wr_min ,//读ddr3的起始地址
input [25:0] app_addr_wr_max ,//读ddr3的结束地址
input [7:0] wr_bust_len ,//从ddr3中读数据时的突发长度
//用户接口
input ddr3_read_valid ,//DDR3 读使能
input ddr3_pingpang_en ,//DDR3 乒乓操作使能
input wr_clk ,//wfifo时钟
input rd_clk ,//rfifo的读时钟
input datain_valid ,//数据有效使能信号
input [15:0] datain ,//有效数据
input rdata_req ,//请求像素点颜色数据输入
input rd_load ,//输出源更新信号
input wr_load ,//输入源更新信号
output [15:0] dataout ,//rfifo输出数据
output pll_lock ,//时钟锁定信号
output ddr_init_done ,//DDR初始化完成
output ddrphy_rst_done ,//DDRPHY 复位完成标志
output fram_done ,//DDR中已经存入一帧画面标志
//DDR3接口
input pad_loop_in ,
input pad_loop_in_h ,
output pad_rstn_ch0 ,
output pad_ddr_clk_w ,
output pad_ddr_clkn_w ,
output pad_csn_ch0 ,
output [15:0] pad_addr_ch0 ,
inout [16-1:0] pad_dq_ch0 ,
inout [16/8-1:0] pad_dqs_ch0 ,
inout [16/8-1:0] pad_dqsn_ch0 ,
output [16/8-1:0] pad_dm_rdqs_ch0 ,
output pad_cke_ch0 ,
output pad_odt_ch0 ,
output pad_rasn_ch0 ,
output pad_casn_ch0 ,
output pad_wen_ch0 ,
output [2:0] pad_ba_ch0 ,
output pad_loop_out ,
output pad_loop_out_h
);
//wire define
wire [32-1:0] axi_awaddr ;
wire [7:0] axi_awlen ;
wire [2:0] axi_awsize ;
wire [1:0] axi_awburst ;
wire axi_awlock ;
wire axi_awready ;
wire axi_awvalid ;
wire axi_awurgent ;
wire axi_awpoison ;
wire [128-1:0] axi_wdata ;
wire [16-1:0] axi_wstrb ;
wire axi_wvalid ;
wire axi_wready ;
wire axi_wlast ;
wire [7:0] axi_bid ;
wire [1:0] axi_bresp ;
wire axi_bvalid ;
wire axi_bready ;
wire [32-1:0] axi_araddr ;
wire [7:0] axi_arlen ;
wire [2:0] axi_arsize ;
wire [1:0] axi_arburst ;
wire axi_arlock ;
wire axi_arpoison ;
wire axi_arurgent ;
wire axi_arready ;
wire axi_arvalid ;
wire [128-1:0] axi_rdata ;
wire [7:0] axi_rid ;
wire axi_rlast ;
wire axi_rvalid ;
wire axi_rready ;
wire [1:0] axi_rresp ;
wire axi_csysreq ;
wire axi_csysack ;
wire axi_cactive ;
wire axi_clk ;
wire [10:0] wfifo_rcount ;//rfifo剩余数据计数
wire [10:0] rfifo_wcount ;//wfifo写进数据计数
wire wrfifo_en_ctrl ;//写FIFO数据读使能控制位
wire wfifo_rden ;//写FIFO数据读使能
wire pre_wfifo_rden ;//写FIFO数据预读使能
//*****************************************************
//** main code
//*****************************************************
//因为预读了一个数据所以读使能wfifo_rden要少一个周期通过wrfifo_en_ctrl控制
assign wfifo_rden = axi_wvalid && axi_wready && (~wrfifo_en_ctrl);
assign pre_wfifo_rden = axi_awvalid && axi_awready;
//ddr3读写控制器模块
rw_ctrl_128bit u_rw_ctrl_128bit(
.clk (axi_clk ),
.rst_n (rst_n ),
.ddr_init_done (ddr_init_done ),
.axi_awaddr (axi_awaddr ),
.axi_awlen (axi_awlen ),
.axi_awsize (axi_awsize ),
.axi_awburst (axi_awburst ),
.axi_awlock (axi_awlock ),
.axi_awready (axi_awready ),
.axi_awvalid (axi_awvalid ),
.axi_awurgent (axi_awurgent ),
.axi_awpoison (axi_awpoison ),
.axi_wstrb (axi_wstrb ),
.axi_wvalid (axi_wvalid ),
.axi_wready (axi_wready ),
.axi_wlast (axi_wlast ),
.axi_bready (axi_bready ),
.fram_done (fram_done ),
.wrfifo_en_ctrl (wrfifo_en_ctrl ),
.axi_araddr (axi_araddr ),
.axi_arlen (axi_arlen ),
.axi_arsize (axi_arsize ),
.axi_arburst (axi_arburst ),
.axi_arlock (axi_arlock ),
.axi_arpoison (axi_arpoison ),
.axi_arurgent (axi_arurgent ),
.axi_arready (axi_arready ),
.axi_arvalid (axi_arvalid ),
.axi_rlast (axi_rlast ),
.axi_rvalid (axi_rvalid ),
.axi_rready (axi_rready ),
.wfifo_rcount (wfifo_rcount ),
.rfifo_wcount (rfifo_wcount ),
.rd_load (rd_load ),
.wr_load (wr_load ),
.app_addr_rd_min (app_addr_rd_min ),
.app_addr_rd_max (app_addr_rd_max ),
.rd_bust_len (rd_bust_len ),
.app_addr_wr_min (app_addr_wr_min ),
.app_addr_wr_max (app_addr_wr_max ),
.wr_bust_len (wr_bust_len ),
.ddr3_read_valid (ddr3_read_valid ),
.ddr3_pingpang_en (ddr3_pingpang_en )
);
//ddr3IP核模块
ddr3_ip u_ddr3_ip (
.pll_refclk_in (refclk_in ), // input
.top_rst_n (rst_n ), // input
.ddrc_rst (0 ), // input
.csysreq_ddrc (1'b1 ), // input
.csysack_ddrc ( ), // output
.cactive_ddrc ( ), // output
.pll_lock (pll_lock ), // output
.pll_aclk_0 (axi_clk ), // output
.pll_aclk_1 ( ), // output
.pll_aclk_2 ( ), // output
.ddrphy_rst_done (ddrphy_rst_done), // output
.ddrc_init_done (ddr_init_done ), // output
.pad_loop_in (pad_loop_in ), // input
.pad_loop_in_h (pad_loop_in_h ), // input
.pad_rstn_ch0 (pad_rstn_ch0 ), // output
.pad_ddr_clk_w (pad_ddr_clk_w ), // output
.pad_ddr_clkn_w (pad_ddr_clkn_w ), // output
.pad_csn_ch0 (pad_csn_ch0 ), // output
.pad_addr_ch0 (pad_addr_ch0 ), // output [15:0]
.pad_dq_ch0 (pad_dq_ch0 ), // inout [15:0]
.pad_dqs_ch0 (pad_dqs_ch0 ), // inout [1:0]
.pad_dqsn_ch0 (pad_dqsn_ch0 ), // inout [1:0]
.pad_dm_rdqs_ch0 (pad_dm_rdqs_ch0), // output [1:0]
.pad_cke_ch0 (pad_cke_ch0 ), // output
.pad_odt_ch0 (pad_odt_ch0 ), // output
.pad_rasn_ch0 (pad_rasn_ch0 ), // output
.pad_casn_ch0 (pad_casn_ch0 ), // output
.pad_wen_ch0 (pad_wen_ch0 ), // output
.pad_ba_ch0 (pad_ba_ch0 ), // output [2:0]
.pad_loop_out (pad_loop_out ), // output
.pad_loop_out_h (pad_loop_out_h ), // output
.areset_0 (0 ), // input
.aclk_0 (axi_clk ), // input
.awid_0 (0 ), // input [7:0]
.awaddr_0 (axi_awaddr ), // input [31:0]
.awlen_0 (axi_awlen ), // input [7:0]
.awsize_0 (axi_awsize ), // input [2:0]
.awburst_0 (axi_awburst ), // input [1:0]
.awlock_0 (axi_awlock ), // input
.awvalid_0 (axi_awvalid ), // input
.awready_0 (axi_awready ), // output
.awurgent_0 (axi_awurgent ), // input
.awpoison_0 (axi_awpoison ), // input
.wdata_0 (axi_wdata ), // input [127:0]
.wstrb_0 (axi_wstrb ), // input [15:0]
.wlast_0 (axi_wlast ), // input
.wvalid_0 (axi_wvalid ), // input
.wready_0 (axi_wready ), // output
.bid_0 (axi_bid ), // output [7:0]
.bresp_0 (axi_bresp ), // output [1:0]
.bvalid_0 (axi_bvalid ), // output
.bready_0 (axi_bready ), // input
.arid_0 (0 ), // input [7:0]
.araddr_0 (axi_araddr ), // input [31:0]
.arlen_0 (axi_arlen ), // input [7:0]
.arsize_0 (axi_arsize ), // input [2:0]
.arburst_0 (axi_arburst ), // input [1:0]
.arlock_0 (axi_arlock ), // input
.arvalid_0 (axi_arvalid ), // input
.arready_0 (axi_arready ), // output
.arpoison_0 (axi_arpoison ), // input
.rid_0 (axi_rid ), // output [7:0]
.rdata_0 (axi_rdata ), // output [127:0]
.rresp_0 (axi_rresp ), // output [1:0]
.rlast_0 (axi_rlast ), // output
.rvalid_0 (axi_rvalid ), // output
.rready_0 (axi_rready ), // input
.arurgent_0 (axi_arurgent ), // input
.csysreq_0 (1'b1 ), // input
.csysack_0 ( ), // output
.cactive_0 ( ) // output
);
//ddr3控制器fifo控制模块
ddr3_fifo_ctrl u_ddr3_fifo_ctrl (
.rst_n (rst_n &&ddr_init_done ), //复位
//输入源接口
.wr_clk (wr_clk ), //写时钟
.rd_clk (rd_clk ), //读时钟
.clk_100 (axi_clk ), //用户时钟
.datain_valid (datain_valid ), //数据有效使能信号
.datain (datain ), //有效数据
.rfifo_din (axi_rdata ), //用户读数据
.rdata_req (rdata_req ), //请求像素点颜色数据输入
.rfifo_wren (axi_rvalid ), //ddr3读出数据的有效使能
.wfifo_rden (wfifo_rden||pre_wfifo_rden), //ddr3 写使能
//用户接口
.wfifo_rcount (wfifo_rcount ), //rfifo剩余数据计数
.rfifo_wcount (rfifo_wcount ), //wfifo写进数据计数
.wfifo_dout (axi_wdata ), //用户写数据
.rd_load (rd_load ), //输出源更新信号
.wr_load (wr_load ), //输入源更新信号
.pic_data (dataout ) //rfifo输出数据
);
endmodule
LCD 顶层模块(lcd_rgb_top): LCD 顶层模块根据获取到的 LCD ID,驱动 LCD 液晶屏的显示。
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: lcd_rgb_top
// Last modified Date: 2020/05/04 9:19:08
// Last Version: V1.0
// Descriptions: LCD顶层模块
//
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/05/04 9:19:08
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module lcd_rgb_top(
input sys_clk , //系统时钟
input sys_rst_n, //复位信号
input sys_init_done,
//lcd接口
output lcd_clk, //LCD驱动时钟
output lcd_hs, //LCD 行同步信号
output lcd_vs, //LCD 场同步信号
output lcd_de, //LCD 数据输入使能
inout [23:0] lcd_rgb, //LCD RGB颜色数据
output lcd_bl, //LCD 背光控制信号
output lcd_rst, //LCD 复位信号
output lcd_pclk, //LCD 采样时钟
output [15:0] lcd_id, //LCD屏ID
output out_vsync, //lcd场信号
output [10:0] pixel_xpos, //像素点横坐标
output [10:0] pixel_ypos, //像素点纵坐标
output [10:0] h_disp, //LCD屏水平分辨率
output [10:0] v_disp, //LCD屏垂直分辨率
input [23:0] data_in, //数据输入
output data_req //请求数据输入
);
//wire define
wire [15:0] lcd_rgb_565; //输出的16位lcd数据
wire [23:0] lcd_rgb_o ; //LCD 输出颜色数据
wire [23:0] lcd_rgb_i ; //LCD 输入颜色数据
//*****************************************************
//** main code
//*****************************************************
//将摄像头16bit数据转换为24bit的lcd数据
assign lcd_rgb_o = {lcd_rgb_565[15:11],3'b000,lcd_rgb_565[10:5],2'b00,
lcd_rgb_565[4:0],3'b000};
//像素数据方向切换
assign lcd_rgb = lcd_de ? lcd_rgb_o : {24{1'bz}};
assign lcd_rgb_i = lcd_rgb;
//时钟分频模块
clk_div u_clk_div(
.clk (sys_clk ),
.rst_n (sys_rst_n),
.lcd_id (lcd_id ),
.lcd_pclk (lcd_clk )
);
//读LCD ID模块
rd_id u_rd_id(
.clk (sys_clk ),
.rst_n (sys_rst_n),
.lcd_rgb (lcd_rgb_i),
.lcd_id (lcd_id )
);
//lcd驱动模块
lcd_driver u_lcd_driver(
.lcd_clk (lcd_clk),
.sys_rst_n (sys_rst_n & sys_init_done),
.lcd_id (lcd_id),
.lcd_hs (lcd_hs),
.lcd_vs (lcd_vs),
.lcd_de (lcd_de),
.lcd_rgb (lcd_rgb_565),
.lcd_bl (lcd_bl),
.lcd_rst (lcd_rst),
.lcd_pclk (lcd_pclk),
.pixel_data (data_in),
.data_req (data_req),
.out_vsync (out_vsync),
.h_disp (h_disp),
.v_disp (v_disp),
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos)
);
endmodule
5、下载验证
在打开下载界面之前,先准备BMP 图片。winhex查看图片对应的扇区地址:
两张 BMP 图片的起始扇区地址分别为 36992和 61888,这和我们 sd_read_photo 模块定义的 PHOTO_SECTION_ADDR0 = 32'd36992, PHOTO_SECTION_ADDR1 = 32'd61888是一致的。如果查看的值不是上图中的值,需要将代码中定义的这两个参数值改成 WinHex 查看的扇区地址, 需要注意的是如果所选中的图片的第一扇区地址与右下角的物理扇区号如果不对应,需要以物理扇区号的地址为准!实验效果:
2456d06f7c099785f8bdf20789a06939