OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 1193|回复: 0

[国产FPGA] 《ATK-DFPGL22G 之FPGA开发指南》第四十七章 SD卡读BMP图片LCD显示实验

[复制链接]

1117

主题

1128

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4666
金钱
4666
注册时间
2019-5-8
在线时间
1224 小时
发表于 2023-12-28 15:14:15 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-12-27 17:54 编辑

第四十七章 SD卡读BMP图片LCD显示实验
1)实验平台:正点原子 ATK-DFPGL22G开发板

2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0

3)购买链接:https://detail.tmall.com/item_o.htm?id=692712955836

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-PGL22G.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)FPGA技术交流QQ群:435699340

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

在“SD卡读写测试实验”中,我们成功地在开发板上实现了对SD卡的读写测试。在本次实验中,我们将学习如何从SD卡中循环读取两张BMP图片,并将其显示在LCD上。
本章分为以下几个章节:         
1.1        简介
1.2        实验任务
1.3        硬件设计
1.4        程序设计
1.5        下载验证

1.1 简介
我们常用的图片格式有很多,一般最常用的有四种:JPEG(或JPG)、BMP、PNG和GIF。其中JPEG(或JPG)、BMP和PNG是静态图片,而GIF则是动态图片。BMP全称是Bitmap(位图)的缩写,其特点是几乎不进行压缩,由此导致了它与生俱来的缺点,即占用磁盘空间较大;而其它三种图片格式均进行了不同程度的压缩,以节省磁盘空间。在本次实验中,我们选择使用不压缩的BMP图片格式,解析该格式的图片最为简单。
BMP(全称Bitmap)是Window操作系统中的标准图像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大,但是没有失真。BMP文件的图像深度可选lbit、4bit、8bit、16bit、24bit及32bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。
典型的BMP图像文件由四部分组成:
1、BMP文件头,它包含BMP图像文件的类型、大小等信息;
2、BMP信息头,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
3、调色板,这个部分是可选的,如果使用索引来表示图像,调色板就是索引与其对应颜色的映射表;
4、位图数据,即图像数据,在位深度为24位时直接使用RGB格式,而小于24位时使用调色板中颜色的索引值。
各个部分的大小如下图所示:                              
image001.png
图 47.1.1 BMP文件各部分及其大小
我们一般见到的图像以24位图像为主,即R、G、B三种颜色各用8个bit来表示,这样的图像我们称之为真彩色。在这种情况下是不需要调色板的,位图信息头后面紧跟的就是位图数据了。也就是说,位图文件从文件头开始偏移54个字节就是图像数据了。在这里我们就以一幅24位BMP图片为例,如图 47.1.2所示,详细介绍其文件结构。
image002.png
图 47.1.2 示例图片:24位BMP图片
首先我们来看一下该图片在Window中的属性信息,如下图所示:
image004.png
图 47.1.3 示例图片属性
图 47.1.3中包括BMP图片的文件属性以及其图像属性,文件大小为1.09MB,图像分辨率为800*480,每个像素点的颜色使用24位表示。
接下来,我们使用Notepad++以十六进制格式打开该BMP文件,如下图所示:
image006.png
图 47.1.4 示例图片16进制数据
图 47.1.4中红色矩形区域为BMP文件头,共14字节;蓝色区域为BMP信息头,共40字节;剩余部分为图像数据。左下角红色椭圆区域表明整个BMP文件共1152054个字节,除去文件头和信息头所占的54个字节,图像数据为1152000字节。由于示例图片每个像素点使用3个字节表示颜色,因此我们可以计算出图像数据的大小为800*480*3 = 1152000字节,与Notepad++中计算得到的结果一致。
首先来了解一下BMP文件头的数据结构,如下表所示:
QQ截图20231227175313.png
表 47.1.1 BMP文件头数据结构


我们将表 47.1.1中橙色区域与下图矩形区域中的数据一一对应:
image008.png
图 47.1.5 BMP文件头
对比后可得到如下结果:
1、bf_Size:位图文件的大小为0x119436,即1152054字节(1.09MB),与示例图片属性一致。需要注意的是,在BMP文件中,如果一个数据需要用几个字节来表示的话,那么该数据的低字节存放在低地址,高字节存放在高地址;
2、bfOffBits:文件头到图像数据之间的偏移量为0x36,即54字节。这个偏移量非常有用,我们可以利用它快速定位BMP文件中的图像数据的位置。
接下来是BMP信息头的数据结构,如下表所示:
QQ截图20231227175329.png
表 47.1.2 BMP信息头数据结构


同样,将表中橙色区域与下图矩形区域中的数据一一对应:
image010.png
图 47.1.6 BMP信息头
对比后可得到如下结果:
1、 biWidth:图像的宽度为0x320,即800像素;
2、 biHeight:图像的高度为0x1e0,即480像素;
3、 biBitCount:像素的位深度为0x18,等于24位,即RGB888数据格式;
4、 biSizeImage:图像的大小为0x119400,即1152000字节。
BMP信息头之后,如果没有调色板,紧跟着就是BMP的位图数据了,位图数据存储了BMP有效的图像数据,其数据个数由图片的分辨率和颜色深度决定。需要说明的,BMP图像数据格式是BGR,以BMP 24位真彩色为例,颜色分量“B”位于低地址位,颜色分量“G”位于中间地址位,颜色分量“R”位于高地址位。
以上部分是对BMP图片格式的介绍,接下来结合本次实验所要实现的功能,来分析下从TF卡中读出图片数据之后,如何进行存储和显示。
我们在“SD卡读写测试实验”中介绍过,SD卡在SD2.0版本协议下,SPI模式的理论最大传输速率为50Mbps,在SDIO模式下理论传输速率为200Mbps,加上命令号以及等待SD卡返回响应信号的时间,实际上的传输速率会比理论传输速率下降不少。对于采用分辨率为800*480@60Hz的LCD液晶屏来说,一幅图像的数据量达到800*480*16bit(RGB565为例)=6144000bit=6000Kbit(1Kbit=1024bit),每秒钟刷新60次,那么每秒钟需要传输的数据量达到6000Kbit*60=360000Kbit=351.56Mbit(1Mbit=1024Kbit)。由此可以看出,SD卡的读写速度完全跟不上LCD液晶屏的刷新速度,因此必须先缓存一幅图像,再通过LCD屏显示。我们在前面多次提到过,FPGA的片内存储资源较少,对于缓存如此大量的数据,只能使用开发板上的DDR3存储器缓存数据。
本次实验使用DDR3存储器来缓存图片数据,图片数据来自于TF(Micro SD)卡,那么我们就需要事先向TF卡中导入两张图片,也就是从电脑中拷贝两张图片放入TF卡。由于本次实验只是从TF卡的固定扇区地址中读取图片数据,并没有实现文件系统的功能,因此需要使用WinHex软件查看两个BMP图片的扇区地址,此时查询到的扇区地址就是BMP文件存放的起始扇区地址,我们只需要按照这个起始扇区地址,按顺序读出SD卡中的数据即可,直到读完一张图片中的所有数据。
SD卡的一个扇区存放512个字节,也就是256个16位数据,对于分辨率为800*480,24位真彩色的图片来说,共需要读出2250(800*480*3/512)个扇区数据。由于BMP图片除有些图像数据外,还有文件头和信息头共54个字节,因此还需要多读一个扇区,共2251个扇区。我们只需要将有效的图像数据写入DDR3帧缓存中,那么LCD驱动模块就可以从帧缓存中读出数据,将图片显示在LCD液晶屏上。
我们在“SD卡读写测试实验”中对SD卡的协议规范作了详细的介绍,包括SD卡的接口说明、初始化以及读写操作等。如果大家对这部分内容不是很熟悉的话,请参考“SD卡读写测试实验”中的SD卡简介部分。

1.2 实验任务
本章的实验任务是使用ATK-DFPGL22G开发板循环读取SD卡中存放的两张BMP格式图片,分辨率为800*480,并将其显示在LCD液晶屏上(其它分辨率的LCD屏只需按照本章下载验证部分,稍作修改即可)。

1.3 硬件设计
SD卡接口部分的硬件设计请参考“SD卡读写测试实验”中的硬件设计部分。
由于DDR3、SD卡和LCD接口的引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里不再列出管脚分配。

1.4 程序设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示。
image012.png
图 47.4.1 SD卡读BMP图片LCD显示系统框图
图47.4.2是根据本章实验任务画出的系统框图。时钟模块为其它各模块提供驱动时钟;SD卡/DDR3参数计算模块根据LCD ID,为SD卡读取图片控制模块和DDR3控制器模块提供参数;SD卡读取图片控制模块控制SD卡控制器的读接口,以及将SD卡中读出的RGB888格式数据转换成RGB565格式数据,写入DDR3控制器中;最后LCD顶层模块从DDR3控制器中读出图片数据,显示到LCD液晶屏上。
FPGA顶层模块(sd_bmp_lcd)例化了以下六个模块:时钟模块(pll_clk)、SD卡/DDR3参数计算模块(sd_ddr_size)、SD卡读取图片控制模块(sd_read_photo)、SD卡控制器模块(sd_ctrl_top)、DDR3控制器模块(ddr3_top)和LCD顶层模块(lcd_rgb_top)。
顶层模块(sd_bmp_lcd):顶层模块主要完成对其余各模块的例化,实现各模块之间的数据交互。需要注意的是,系统初始化完成是在SD卡以及DDR3都初始化完成后才开始拉高的,该信号控制着SD卡读取图片控制模块的复位信号,因此SD卡读取图片控制模块是在系统初始化完成后才工作的,防止因SD卡或者DDR3初始化未完成导致数据错误。
时钟模块(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数据(因为DDR3控制器和LCD预留的用户接口都是16位)。
SD卡读取图片控制模块(sd_read_photo):SD卡读取图片控制模块通过控制SD卡控制器的读接口,从SD卡中读取图像数据,并在读完一张图片后延时一段时间,再去读取另一张图片数据,实现两张图片的循环切换读取。
SD卡控制器模块(sd_ctrl_top):SD卡控制器模块负责驱动SD卡,该模块将SD卡的SPI读写操作封装成方便用户使用的接口。有关该模块的详细介绍请大家参考“SD卡读写测试实验”章节。
DDR3控制器模块(ddr3_top):DDR3读写控制器模块负责驱动DDR3片外存储器,缓存从SD卡中读出的图像数据。该模块将DDR3复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。有关该模块的详细介绍请大家参考“DDR3读写测试实验”章节。
LCD顶层模块(lcd_rgb_top):LCD顶层模块根据获取到的LCD ID,驱动LCD液晶屏的显示。
时钟模块、SD卡控制器模块、DDR3控制器模块和LCD顶层模块在前面相应的章节中都有做详细的介绍,这里不再赘述,本章节我们主要介绍SD卡/DDR3参数计算模块和SD卡读取图片控制模块。
SD卡/DDR3参数计算模块代码如下:
  1. 1 module sd_ddr_size (
  2. 2      input              clk           ,//时钟
  3. 3      input              rst_n         ,//复位,低电平有效
  4. 4                                         
  5. 5      input        [15:0 ID_lcd        ,//LCD ID
  6. 6                                         
  7. 7      output  reg  [23:0 ddr_max_addr  ,//DDR3读写最大地址
  8. 8      output  reg  [15:0 sd_sec_num      //SD卡读扇区个数
  9. 9 );
  10. 10
  11. 11 //parameter define
  12. 12 parameter  ID_4342 = 16'h4342;
  13. 13 parameter  ID_4384 = 16'h4384;
  14. 14 parameter  ID_7084 = 16'h7084;
  15. 15 parameter  ID_7016 = 16'h7016;
  16. 16 parameter  ID_1018 = 16'h1018;
  17. 17
  18. 18 //*****************************************************
  19. 19 //**                   main code                     
  20. 20 //*****************************************************
  21. 21
  22. 22 //根据LCDID,计算DDR3最大读写地址和SD卡读扇区个数
  23. 23 always @(posedge clk or negedge rst_n)begin
  24. 24     if(!rst_n)begin
  25. 25         ddr_max_addr <= 24'd0;   
  26. 26         sd_sec_num <= 16'd0;        
  27. 27     end
  28. 28     else begin   
  29. 29         case(ID_lcd )
  30. 30             ID_4342 : begin
  31. 31                 ddr_max_addr <= 24'd130560;    //480*272
  32. 32                 sd_sec_num <= 16'd765 + 1'b1;  //480*272*3/512     
  33. 33             end
  34. 34             ID_4384 : begin
  35. 35                 ddr_max_addr <= 24'd384000;    //800*480
  36. 36                 sd_sec_num <= 16'd2250 + 1'b1; //800*480*3/512 + 1
  37. 37             end
  38. 38             ID_7084 : begin      
  39. 39                 ddr_max_addr <= 24'd384000;    //800*480
  40. 40                 sd_sec_num <= 16'd2250 + 1'b1; //800*480*3/512 + 1
  41. 41             end
  42. 42             ID_7016 : begin         
  43. 43                 ddr_max_addr <= 24'd614400;    //1024*600
  44. 44                 sd_sec_num <= 16'd3600 + 1'b1; //800*480*3/512 +1  
  45. 45             end   
  46. 46             ID_1018 : begin         
  47. 47                 ddr_max_addr <= 24'd1024000;   //1280*800
  48. 48                 sd_sec_num <= 16'd6000 + 1'b1; //800*480*3/512 +1  
  49. 49             end
  50. 50         default : begin         
  51. 51                 ddr_max_addr <= 24'd384000;    //800*480
  52. 52                 sd_sec_num <= 16'd2250 + 1'b1; //800*480*3/512 +1  
  53. 53         end
  54. 54         endcase
  55. 55     end
  56. 56 end
  57. 57
  58. 58 endmodule
复制代码
SD卡/DDR3参数计算模块比较简单,根据输入的LCD ID,计算DDR3的读写最大地址(最小地址从0开始)和SD卡的读扇区个数。这里以800*480分辨率的LCD屏为例,SD卡单张图片的数据量是800*480*24b,一个扇区是512个字节,共需要读出800*480*24/(512*8) =800*480*3/512=765个扇区,由于BMP图片文件除有效图像数据外,还有共54个字节的文件头+信息头,所以需要多读一个扇区,即766。
尽管从SD卡中读出的是24位的RGB888格式,在写入DDR3存储器之前,转换成了16位的RGB565格式,所以向SD卡中写入的数据量实际上为800*480*16b,而DDR3控制器模块读写最大地址的单位是16bit,因此DDR3的最大读写地址为800*480*16/16=800*480=130560。
其它分辨率的LCD屏计算方法类似,此处不再赘述。
SD卡读取图片控制模块的代码如下:
  1. 1  module sd_read_photo(
  2. 2       input               clk           ,  //时钟信号
  3. 3       input               rst_n         ,  //复位信号,低电平有效
  4. 4  
  5. 5       input        [23:0  ddr3_max_addr, //DDR3读写最大地址
  6. 6       input        [15:0 sd_sec_num    ,  //SD卡读扇区个数
  7. 7       input               rd_busy       ,  //SD卡读忙信号
  8. 8       input               sd_rd_val_en  ,  //SD卡读数据有效信号
  9. 9       input        [15:0 sd_rd_val_data,  //SD卡读出的数据
  10. 10      output  reg         rd_start_en   ,  //开始写SD卡数据信号
  11. 11      output  reg  [31:0 rd_sec_addr   ,  //读数据扇区地址
  12. 12      output  reg          ddr_wr_en   ,  //DDR3写使能信号
  13. 13      output       [15:0  ddr_wr_data     //DDR3写数据
  14. 14      );
  15. 15
  16. 16 //parameter define                          
  17. 17 //设置两张图片的扇区地址,通过上位机WinHex软件查看
  18. 18 parameter PHOTO_SECTION_ADDR0 = 32'd1041792;//第一张图片扇区起始地址
  19. 19 parameter PHOTO_SECTION_ADDR1 = 32'd1044096;//第二张图片扇区起始地址
  20. 20 //BMP文件首部长度=BMP文件头+信息头
  21. 21 parameter BMP_HEAD_NUM = 6'd54;             //BMP文件头+信息头=14+40=54
  22. 22
  23. 23 //reg define
  24. 24 reg    [1:0         rd_flow_cnt      ;  //读数据流程控制计数器
  25. 25 reg    [15:0        rd_sec_cnt       ;  //读扇区次数计数器
  26. 26 reg                   rd_addr_sw       ;  //读两张图片切换
  27. 27 reg    [25:0        delay_cnt        ;  //延时切换图片计数器
  28. 28 reg                   bmp_rd_done      ;  //单张图片读取完成
  29. 29
  30. 30 reg                   rd_busy_d0       ;  //读忙信号打拍,用来采下降沿
  31. 31 reg                   rd_busy_d1       ;  
  32. 32
  33. 33 reg    [1:0         val_en_cnt       ;  //SD卡数据有效计数器
  34. 34 reg    [15:0        val_data_t       ;  //SD卡数据有效寄存
  35. 35 reg    [5:0         bmp_head_cnt     ;  //BMP首部计数器
  36. 36 reg                   bmp_head_flag    ;  //BMP首部标志
  37. 37 reg    [23:0        rgb888_data      ;  //24位RGB888数据
  38. 38 reg    [23:0         ddr_wr_cnt       ;  //DDR3写入计数器
  39. 39 reg    [1:0          ddr_flow_cnt     ;  //DDR3写数据流程控制器计数器
  40. 40
  41. 41 //wire define
  42. 42 wire                  neg_rd_busy      ;  //SD卡读忙信号下降沿
  43. 43        
  44. 44 //*****************************************************
  45. 45 //**                    main code
  46. 46 //*****************************************************
  47. 47
  48. 48 assign  neg_rd_busy = rd_busy_d1 & (~rd_busy_d0);
  49. 49 //24位RGB888格式转成16位RGB565格式
  50. 50 assign  ddr_wr_data = {rgb888_data[23:19,rgb888_data[15:10,rgb888_data[7:3]};
复制代码
SD卡读取图片控制模块控制SD卡控制器的读接口,以及将SD卡中读出的RGB888格式数据转换成RGB565格式数据,写入DDR3控制器中。
程序中第18和第19行代码分别定义了两张图片的起始扇区地址,PHOTO_SECTION_ADDR0(第一张图片的起始扇区)和PHOTO_SECTION_ADDR1(第二种图片的起始扇区地址)。这两个参数是由WinHex软件查看得到的,具体查看的方法在本章下载验证部分再详细讲解。
程序中第21行代码定义了BMP文件的首部长度,包括BMP文件头和信息头共54个字节。在从SD卡第一个扇区读出的数据中,会有54个字节的文件头和信息头,这些数据可直接丢弃,我们只把后面有效的图像数据写入DDR3存储器中。需要说明的是,BMP的文件头包含了图片有效数据的个数,本次实验是根据LCD ID来判断需要从SD卡中读取的扇区个数,而没有解析BMP文件头,当然通过解析文件头来计算从SD卡中读取的扇区个数,也是没有问题的。
程序中第48行代码实现的功能是当rd_busy由高电平变为低电平时,产生一个时钟周期的脉冲信号(neg_rd_busy),该信号用于表示SD卡的单个扇区读完成。程序中第52行至62代码就是对rd_busy进行延时打拍,用于采rd_busy信号的下降沿。
程序中第50行代码是将SD卡中读出的24位RGB888格式数据转换成16位的RGB565格式数据,直接舍弃每个颜色分量的低位,只保留高位即可。
  1. 52 //对rd_busy信号进行延时打拍,用于采rd_busy信号的下降沿
  2. 53 always @(posedge clk or negedge rst_n)begin
  3. 54      if(rst_n == 1'b0)begin
  4. 55          rd_busy_d0 <= 1'b0;
  5. 56          rd_busy_d1 <= 1'b0;
  6. 57      end
  7. 58      else begin
  8. 59          rd_busy_d0 <= rd_busy;
  9. 60          rd_busy_d1 <= rd_busy_d0;
  10. 61      end
  11. 62 end
  12. 63
  13. 64 //循环读取SD卡中的两张图片(读完之后延时1s再读下一个)
  14. 65 always @(posedge clk or negedge rst_n)begin
  15. 66      if(!rst_n)begin
  16. 67          rd_flow_cnt <= 2'd0;
  17. 68          rd_addr_sw <= 1'b0;
  18. 69          rd_sec_cnt <= 16'd0;
  19. 70          rd_start_en <= 1'b0;
  20. 71          rd_sec_addr <= 32'd0;
  21. 72          bmp_rd_done <= 1'b0;
  22. 73          delay_cnt <= 26'd0;
  23. 74      end
  24. 75      else begin
  25. 76          rd_start_en <= 1'b0;
  26. 77          bmp_rd_done <= 1'b0;
  27. 78          case(rd_flow_cnt)
  28. 79              2'd0 : begin
  29. 80                  //开始读取SD卡数据
  30. 81                  rd_flow_cnt <= rd_flow_cnt + 2'd1;
  31. 82                  rd_start_en <= 1'b1;
  32. 83                  rd_addr_sw <= ~rd_addr_sw;                     //读数据地址切换
  33. 84                  if(rd_addr_sw == 1'b0)
  34. 85                      rd_sec_addr <= PHOTO_SECTION_ADDR0;
  35. 86                  else
  36. 87                      rd_sec_addr <= PHOTO_SECTION_ADDR1;   
  37. 88              end
  38. 89              2'd1 : begin
  39. 90                  //读忙信号的下降沿代表读完一个扇区,开始读取下一扇区地址数据
  40. 91                  if(neg_rd_busy)begin                          
  41. 92                      rd_sec_cnt <= rd_sec_cnt + 1'b1;
  42. 93                      rd_sec_addr <= rd_sec_addr + 32'd1;
  43. 94                      //单张图片读完
  44. 95                      if(rd_sec_cnt == sd_sec_num - 1'b1)begin
  45. 96                          rd_sec_cnt <= 16'd0;
  46. 97                          rd_flow_cnt <= rd_flow_cnt + 2'd1;
  47. 98                          bmp_rd_done <= 1'b1;
  48. 99                      end   
  49. 100                     else
  50. 101                         rd_start_en <= 1'b1;                  
  51. 102                 end                  
  52. 103             end
  53. 104             2'd2 : begin
  54. 105                 delay_cnt <= delay_cnt + 1'b1;                 //单张图片读完后延时1秒
  55. 106                 if(delay_cnt == 26'd50_000_000 - 26'd1)begin //50_000_000*20ns = 1s
  56. 107                     delay_cnt <= 26'd0;
  57. 108                     rd_flow_cnt <= 2'd0;
  58. 109                 end
  59. 110             end   
  60. 111             default : ;
  61. 112         endcase   
  62. 113     end
  63. 114 end
  64. 115
  65. 116 //SD卡读取的16位数据,转成24位RGB888格式
  66. 117 always @(posedge clk or negedge rst_n)begin
  67. 118     if(!rst_n)begin
  68. 119         val_en_cnt <= 2'd0;
  69. 120         val_data_t <= 16'd0;
  70. 121         bmp_head_cnt <= 6'd0;
  71. 122         ddr_wr_en <= 1'b0;
  72. 123         rgb888_data <= 24'd0;
  73. 124         ddr_wr_cnt <= 24'd0;
  74. 125         ddr_flow_cnt <= 2'd0;
  75. 126     end
  76. 127     else begin
  77. 128         ddr_wr_en <= 1'b0;
  78. 129         case(ddr_flow_cnt)
  79. 130             2'd0 : begin  //BMP首部        
  80. 131                 if(sd_rd_val_en)begin
  81. 132                     bmp_head_cnt <= bmp_head_cnt + 1'b1;
  82. 133                     if(bmp_head_cnt == BMP_HEAD_NUM[5:1 - 1'b1) begin
  83. 134                         ddr_flow_cnt <= ddr_flow_cnt + 1'b1;
  84. 135                         bmp_head_cnt <= 6'd0;
  85. 136                     end   
  86. 137                 end   
  87. 138             end               
  88. 139             2'd1 : begin  //BMP有效数据
  89. 140                 if(sd_rd_val_en)begin
  90. 141                     val_en_cnt <= val_en_cnt + 1'b1;
  91. 142                     val_data_t <= sd_rd_val_data;               
  92. 143                     if(val_en_cnt == 2'd1)begin //3个16位数据转成2个24位数据
  93. 144                         ddr_wr_en <= 1'b1;
  94. 145                         rgb888_data <= {sd_rd_val_data[15:8,val_data_t[7:0,
  95. 146                                       val_data_t[15:8]};
  96. 147                     end
  97. 148                     else if(val_en_cnt == 2'd2) begin
  98. 149                         ddr_wr_en <= 1'b1;
  99. 150                         rgb888_data <= {sd_rd_val_data[7:0,sd_rd_val_data[15:8,
  100. 151                                        val_data_t[7:0]};
  101. 152                         val_en_cnt <= 2'd0;
  102. 153                     end   
  103. 154                 end     
  104. 155                 if(ddr_wr_en)begin
  105. 156                     ddr_wr_cnt <= ddr_wr_cnt + 1'b1;
  106. 157                     if(ddr_wr_cnt == ddr_max_addr - 1'b1)begin
  107. 158                         ddr_wr_cnt <= 24'd0;
  108. 159                         ddr_flow_cnt <= ddr_flow_cnt + 1'b1;
  109. 160                     end
  110. 161                 end
  111. 162             end
  112. 163             2'd2 : begin //等待单张BMP图片读取结束
  113. 164                 if(bmp_rd_done)
  114. 165                     ddr_flow_cnt <= 2'd0;
  115. 166             end
  116. 167             default :;
  117. 168         endcase
  118. 169     end
  119. 170 end
  120. 171
  121. 172 endmodule
复制代码
在代码第65行开始的always语句块中,实现的功能是根据第一张图片的起始扇区地址向SD卡控制器模块发送读命令,单个扇区读完后扇区地址累加,直至读完单张图片的所有数据。随后延时1秒钟,并根据第二张图片的起始扇区地址继续向SD卡控制器模块发送读命令,直至读完第二张图片的所有数据。然后继续延时1秒钟,读取第一张图片的扇区地址,循环往复。程序实现的步骤由流程控制计数器(rd_flow_cnt)来实现,读忙信号的下降沿(neg_rd_busy)表示当前扇区读取完成,可以进行其它操作。
下图为SD卡读取图片数据过程中采集的Fabric Debugger波形图。第二张BMP图片读完并延时完成后,重新开始读取第一张图片的起始地址,扇区地址由1039051(第二张图片的最后一个扇区地址)变为1034496(第一张图片的起始扇区地址)。需要注意的是,不同的SD卡和不同分辨率的BMP图片,其扇区地址是不一样的,所以大家在用Fabric Debugger采集扇区地址时,可能和下图中的扇区地址不一致。而rd_flow_cnt的变化为2→0→1。
image014.png
图 47.4.3 控制SD卡读取图片的Fabric Debugger波形图
在代码第117行开始的always语句块中,实现的功能是将SD卡读取的16位有效图像数据,转换成24位的RGB888格式数据。以800*480分辨率为例,BMP有效的图像数据量为800*480*24b,除此之外,还有54个字节的BMP文件头和信息头,这里直接将BMP的首部丢弃,只解析有效的图像数据。程序中当ddr_flow_cnt等于0时,对BMP首部进行计数,当首部计数完成后,ddr_flow_cnt的值等于1,准备接收有效数据。需要说明的是,SD卡控制器模块以16位数据进行输出,而BMP_HEAD_NUM参数表示的是字节(8bit),所以BMP_HEAD_NUM需要把最低位Bit0舍弃,相当于除以2,如程序中第133行代码所示。
在程序的第139行至162行代码是将16位的有效图像数据转成24位的RGB888格式数据,即3个16位数据转成2个24位数据,在转换的过程中需要注意,BMP有效数据是按照BGR的方式排列,其示意图如下:
image016.png
图 47.4.4 BMP有效数据BGR示意图
在接收两个16位数据,才能开始拼第一个RGB888数据,因此需要通过val_data_t寄存上一次收到的数据,第一次收到的数据为{B0,G0}(P0),第二次收到的数据为{R0,B1}(P1),所以第一个RGB888数据为{P1[15:8],P0[7:0],P1[15:8]},如程序中第145~146行代码所示;第三次收到的数据为{G1,R1}(P2),所以第二个RGB888数据为{P2[7:0],P2[15:8],P1[7:0]},如程序中第150~151行代码所示。
由于每副图片多读了一个扇区,一个扇区是512个字节,而BMP的文件头+信息头只有54个字节,多以会多读出一些无效数据,这些无效数据也是直接丢弃,不写入DDR3中,因此程序中第155行至161行代码会对写入DDR3中的数据进行累加,超过DDR3的最大地址后,停止写入。然后根据bmp_rd_done信号,重新开始准备解析BMP数据。

1.5 下载验证
在打开下载界面之前,我们还需要先做一些准备工作,也就是向SD卡中拷贝两张BMP图片。我们向大家准备好了BMP的图片,位于该工程 sd_bmp_lcd\doc\风景图片\目录下,如下图所示。
image018.png
图 47.5.1 BMP风景图片
大家根据自己所使用的LCD屏分辨率,选择对应分辨率的BMP图片,否则会显示不正常。这里是以800*480分辨率为例,所以拷贝的是fengjing1_800x480.bmp和fengjing2_800x480.bmp。
接下来将TF(Micro SD)卡连接读卡器,读卡器连接电脑,此时可以通过电脑对TF卡中的文件进行存取。这里需要注意的是,SD卡在经过多次存放数据与删除数据之后,存入的文件有可能不是按照连续的扇区地址存储的,为了避免图片显示错误,我们将BMP图片拷贝至SD卡之前,先把SD卡格式化,格式化的设置如下图所示,然后点击开始按钮完成格式化。
image020.png
图 47.5.2 SD卡格式化界面
接下来我们将对应的两个BMP图片拷贝到SD卡中,拷贝完成后如下图所示:
image022.png
图 47.5.3 SD卡图片拷贝完成
文件拷贝完成后,接下来我们使用WinHex工具软件查看这两个文件的扇区起始地址,该工具位于开发板所随附的资料盘(A盘)中“6_软件资料/1_软件/WinHex”目录下,双击“WinHex.exe”或者“WinHex64.exe”打开软件。软件打开后,在菜单栏中点击“工具”,然后点击“打开磁盘”,如下图所示。
image024.png
图 47.5.4 WinHex打开界面
注意:如果提示缺少管理员权限,是否重新程序WinHex,选择“是”,然后重新选择“打开磁盘”。
image026.png
图 47.5.5 WinHex磁盘打开界面
在“物理驱动器”下,我们看到上图标记位2的地方有RM2、Generic STORAGE DEVICE(14.8GB,USB)的字样,该物理驱动器对应的是TF卡,标号为RM2,我们找到标号RM2在逻辑驱动器中的位置,即箭头1所指的地方,选中后点击“确定”按钮即可查看文件的起始扇区地址,打开后的界面如下图所示:
image028.png
图 47.5.6 WinHex查看扇区起始地址界面
由上图可知,两张BMP图片的起始扇区地址分别为1041792和1044096,这和我们sd_read_photo模块定义的PHOTO_SECTION_ADDR0 = 32'd1041792,PHOTO_SECTION_ADDR1 = 32'd1044096是一致的。如果查看的值不是上图中的值,需要将代码中定义的这两个参数值改成WinHex查看的扇区地址,需要注意的是如果所选中的图片的第一扇区地址与右下角的物理扇区号如果不对应,需要以物理扇区号的地址为准!
接下来我们将TF卡插入开发板的SD卡插槽,注意带有金属引脚的一面朝下;然后将FPC排线一端与 RGB-LCD模块上的J1接口连接,另一端与ATK-DFPGL22G开发板上的RGB-LCD接口连接。连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝上插入连接器,最后将黑色翻盖压下以固定FPC排线。最后将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,如下图所示。
image030.png
图 47.5.7 硬件连接图
接下来我们打开电源开关,并下载程序。程序下载完成后,此时LCD液晶屏上循环切换显示TF卡中的两张图片,说明TF卡图片显示实验(LCD显示)下载验证成功。如图 47.5.8所示:
image032.jpg
图 47.5.8 LCD屏显示其中一张图片
值得一提的是,由于BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序,因此我们直接将一整幅图片存入DDR显存,那么最终显示出来的将是一个上下颠倒的图片。
另外本次实验采用的是SD卡的SPI模式,并且读数据时使用的是读单个扇区命令(CMD17),这样会降低SD卡的读写效率,因此从LCD屏上能明显看到图像加载的过程,可以通过使用SD卡的SDIO模式或者使用SD卡读连续扇区命令解决。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 17:36

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表