1)摘自【正點原子】領航者 ZYNQ 之嵌入式開發指南
2)實驗平台:正點原子領航者ZYNQ開發闆
3)平台購買位址:https://item.taobao.com/item.htm?&id=606160108761
4)全套實驗源碼+手冊+視訊下載下傳:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
5)對正點原子FPGA感興趣的同學可以加群讨論:876744900
6)關注正點原子公衆号,擷取最新資料
第十九章SD卡讀BMP圖檔LCD顯示實驗
在“SD卡讀寫TXT文本實驗”中,我們利用FATFS在SD卡中實作了TXT文本的建立、寫入與讀取。在本次實驗中,我們将學習如何從SD卡中讀取BMP圖檔,并将其顯示在LCD上。
本章包括以下幾個部分:
1919.1簡介
19.2實驗任務
19.3硬體設計
19.4軟體設計
19.5下載下傳驗證
19.1簡介
我們常用的圖檔格式有很多,一般最常用的有三種:JPEG(或JPG)、BMP和GIF。其中JPEG(或JPG)和BMP是靜态圖檔,而GIF則是可以實作動态圖檔。在本次實驗中,我們選擇使用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位時使用調色闆中顔色的索引值。
各個部分的大小如下圖所示:
圖 19.1.1 BMP檔案各部分及其大小
我們一般見到的圖像以24位圖像為主,即R、G、B三種顔色各用8個bit來表示,這樣的圖像我們稱之為真彩色。在這種情況下是不需要調色闆的,位圖資訊頭後面緊跟的就是位圖資料了。也就是說,位圖檔案從檔案頭開始偏移54個位元組就是圖像資料了。在這裡我們就以一幅24位BMP圖檔為例,如圖 19.1.2所示,詳細介紹其檔案結構。
圖 19.1.2 示例圖檔:24位BMP圖檔
首先我們來看一下該圖檔在Window中的屬性資訊,如下圖所示:
圖 19.1.3 示例圖檔屬性
圖 19.1.3中包括BMP圖檔的檔案屬性以及其圖像屬性,檔案大小為1.09MB,圖像分辨率為800*480,每個像素點的顔色使用24位表示。
接下來,我們使用Notepad++以十六進制格式打開該BMP檔案,如下圖所示:
圖 19.1.4 示例圖檔16進制資料
圖 19.1.4中紅色矩形區域為BMP檔案頭,共14位元組;藍色區域為BMP資訊頭,共40位元組;剩餘部分為圖像資料。左下角紅色橢圓區域表明整個BMP檔案共1152054個位元組,除去檔案頭和資訊頭所占的54個位元組,圖像資料為1152000位元組。由于示例圖檔每個像素點使用3個位元組表示顔色,是以我們可以計算出圖像資料的大小為800*480*3 = 1152000位元組,與Notepad++中計算得到的結果一緻。
首先來了解一下BMP檔案頭的資料結構,如下表所示:
表 19.1.1 BMP檔案頭資料結構
我們将表 19.1.1中橙色區域與下圖矩形區域中的資料一一對應:
圖 19.1.5 BMP檔案頭
對比後可得到如下結果:
1、bf_Size:位圖檔案的大小為0x119436,即1152054位元組(1.09MB),與示例圖檔屬性一緻。需要注意的是,在BMP檔案中,如果一個資料需要用幾個位元組來表示的話,那麼該資料的低位元組存放在低位址,高位元組存放在高位址;
2、bfOffBits:檔案頭到圖像資料之間的偏移量為0x36,即54位元組。這個偏移量非常有用,我們可以利用它快速定位BMP檔案中的圖像資料的位置。
接下來是BMP資訊頭的資料結構,如下表所示:
表 19.1.2 BMP資訊頭資料結構
同樣,将中橙色區域與下圖矩形區域中的資料一一對應:
圖 19.1.6 BMP資訊頭
對比後可得到如下結果:
1、biWidth:圖像的寬度為0x320,即800像素;
2、biHeight:圖像的高度為0x1e0,即480像素;
3、biBitCount:像素的位深度為0x18,即24位;
4、biSizeImage:圖像的大小為0x119400,即1152000位元組。
19.2實驗任務
本章的實驗任務是使用領航者ZYNQ開發闆讀取SD卡中存放的BMP格式圖檔,分辨率為800*480,并将其顯示在LCD上。
19.3硬體設計
根據實驗任務我們可以畫出本次實驗的系統框圖,如下圖所示:
圖 19.3.1 系統框圖
圖 19.3.1與“PS通過VDMA驅動LCD顯示實驗”中的系統框圖基本相同,其中各個子產品的功能介紹請大家參考相應的章節。唯一不同的地方是,在“PS通過VDMA驅動LCD顯示實驗”中我們顯示在LCD上的是CPU在DDR3記憶體中繪制的彩條圖案。而在本次實驗中,我們需要從SD卡中讀取BMP圖檔,并将其寫到DDR3相應的記憶體空間,用于LCD顯示。
本次實驗的硬體環境可以在“PS通過VDMA驅動LCD顯示實驗”的基礎上搭建。由于需要讀SD卡的功能,是以在配置ZYNQ7 PS子產品時,需要使能SD卡控制器外設,具體方法請參考“SD卡讀寫TXT文本實驗”中的硬體設計部分。
19.4軟體設計
為了能夠在軟體中讀取SD卡中的檔案,我們需要設定BSP工程,添加并設定FATFS庫。具體的方法請參考“SD卡讀寫TXT文本實驗”軟體設計部分。
接下來修改main.c檔案中的代碼,修改完成後代碼的主體部分如下所示:
- 1 #include
- 2 #include
- 3 #include
- 4 #include "xil_types.h"
- 5 #include "xil_cache.h"
- 6 #include "xparameters.h"
- 7 #include "xgpio.h"
- 8 #include "xaxivdma.h"
- 9 #include "xaxivdma_i.h"
- 10 #include "display_ctrl/display_ctrl.h"
- 11 #include "vdma_api/vdma_api.h"
- 12 #include "ff.h"
- 13
- 14 //宏定義
- 15 #define BYTES_PIXEL 3 //像素位元組數,RGB888占3個位元組
- 16 #define DYNCLK_BASEADDR XPAR_AXI_DYNCLK_0_BASEADDR //動态時鐘基位址
- 17 #define VDMA_ID XPAR_AXIVDMA_0_DEVICE_ID //VDMA器件ID
- 18 #define DISP_VTC_ID XPAR_VTC_0_DEVICE_ID //VTC器件ID
- 19 #define AXI_GPIO_0_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL端 AXI GPIO 0(lcd_id)器件ID
- 20 #define AXI_GPIO_0_CHANEL 1 //PL按鍵使用AXI GPIO(lcd_id)通道1
- 21
- 22 //函數聲明
- 23 void load_sd_bmp(u8 *frame);
- 24
- 25 //全局變量
- 26 XAxiVdma vdma;
- 27 DisplayCtrl dispCtrl;
- 28 XGpio axi_gpio_inst; //PL端 AXI GPIO 驅動執行個體
- 29 VideoMode vd_mode;
- 30 //frame buffer的起始位址
- 31 unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x1000000);
- 32 unsigned int lcd_id=0; //LCD ID
- 33
- 34 int main(void)
- 35 {
- 36 //擷取LCD的ID
- 37 XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID);
- 38 lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);
- 39 xil_printf("LCD ID: %x",lcd_id);
- 40
- 41 //根據擷取的LCD的ID号來進行video參數的選擇
- 42 switch(lcd_id){
- 43 case 0x4342 : vd_mode = VMODE_480x272; break; //4.3寸屏,480*272分辨率
- 44 case 0x4384 : vd_mode = VMODE_800x480; break; //4.3寸屏,800*480分辨率
- 45 case 0x7084 : vd_mode = VMODE_800x480; break; //7寸屏,800*480分辨率
- 46 case 0x7016 : vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率
- 47 case 0x1018 : vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率
- 48 default : vd_mode = VMODE_800x480; break;
- 49 }
- 50
- 51 //配置VDMA
- 52 run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,
- 53 frame_buffer_addr,0, 0,ONLY_READ);
- 54
- 55 //初始化Display controller
- 56 DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
- 57 //設定VideoMode
- 58 DisplaySetMode(&dispCtrl, &vd_mode);
- 59 DisplayStart(&dispCtrl);
- 60
- 61 //讀取SD卡圖檔并顯示
- 62 load_sd_bmp((u8*)frame_buffer_addr);
- 63
- 64 return 0;
- 65 }
- 66
主函數的代碼與“PS通過VDMA驅動LCD顯示實驗”幾乎完全相同,我們隻需要将其中寫彩條的函數修改為函數load_sd_bmp(u8 *frame)即可,如程式第23行和第62行所示。另外,由于對SD卡進行操作用到了FAT檔案系統相關的函數,是以在程式的第12行還包含了ff.h頭檔案。
有關這部分代碼更詳細的介紹,請大家參考“PS通過VDMA驅動LCD顯示實驗”軟體設計部分。
load_sd_bmp(u8 *frame)函數負責從SD卡中讀取BMP格式的圖檔,并将其寫入圖檔顯存所對應的位址空間中,其代碼如下所示:
- 67 //從SD卡中讀取BMP圖檔
- 68 void load_sd_bmp(u8 *frame)
- 69 {
- 70 static FATFS fatfs;
- 71 FIL fil;
- 72 u8 bmp_head[54];
- 73 UINT *bmp_width,*bmp_height,*bmp_size;
- 74 UINT br;
- 75 int i;
- 76
- 77 //挂載檔案系統
- 78 f_mount(&fatfs,"",1);
- 79
- 80 //打開檔案
- 81 f_open(&fil,"fengjing.bmp",FA_READ);
- 82
- 83 //移動檔案讀寫指針到檔案開頭
- 84 f_lseek(&fil,0);
- 85
- 86 //讀取BMP檔案頭
- 87 f_read(&fil,bmp_head,54,&br);
- 88 xil_printf("fengjing.bmp head: ");
- 89 for(i=0;i<54;i++)
- 90 xil_printf(" %x",bmp_head);
- 91
- 92 //列印BMP圖檔分辨率和大小
- 93 bmp_width = (UINT *)(bmp_head + 0x12);
- 94 bmp_height = (UINT *)(bmp_head + 0x16);
- 95 bmp_size = (UINT *)(bmp_head + 0x22);
- 96 xil_printf(" width = %d, height = %d, size = %d bytes ",
- 97 *bmp_width,*bmp_height,*bmp_size);
- 98
- 99 //讀出圖檔,寫入DDR
- 100 for(i=*bmp_height-1;i>=0;i--){
- 101 f_read(&fil,frame+i*(*bmp_width)*3,(*bmp_width)*3,&br);
- 102 }
- 103
- 104 //關閉檔案
- 105 f_close(&fil);
- 106
- 107 Xil_DCacheFlush(); //重新整理Cache,資料更新至DDR3中
- 108 xil_printf("show bmp");
- 109 }
- 110
在上面的程式中,主要是通過調用FATFS庫函數來讀取SD卡中的BMP圖檔檔案。本章的簡介部分詳細介紹了BMP圖檔的資料格式,我們首先要讀取BMP檔案前面54個位元組的資料,如程式第87行所示,其中包含了BMP圖檔的分辨率等資訊。然後在程式的92至97行,我們根據BMP資訊頭中各資料的偏移位址,找到并列印出圖像的分辨率和大小等資訊。
在讀取BMP檔案之前,我們先通過調用f_lseek(&fil,0)函數将檔案的讀寫指針移動到檔案開頭。然後在讀取54個位元組的資料之後,讀寫指針便移動到了BMP檔案中圖像資料的起始位置。接下來就可以通過f_read()函數繼續讀取下面的圖像資料。
我們需要注意的是程式的第99至102行。由于BMP檔案存儲資料時,圖像的掃描方式是按從下到上、從左到右的順序,是以如果我們直接将一整幅圖檔存入DDR顯存,那麼最終顯示出來的将是一個上下颠倒的圖檔。在這裡我們通過一個for循環來讀取一整幅BMP圖檔,從上到下每次讀取一行,然後把先讀出來的資料放到了圖檔顯存後面的位置。也就是說,讀出來的第一行資料實際上是BMP圖檔的最後一行圖像,是以要把它放在顯存的最後一行。通過這個for循環,我們就可以将上下颠倒的圖像給反過來。
最後通過調用Xil_DCacheFlush( )函數将緩存在DataCache中的資料重新整理到DDR3中,供PL中的子產品讀取并在LCD上顯示。
到這裡本次實驗的軟體設計就介紹完了,如果大家對FATFS庫函數的使用方法不熟悉的話,請參考“SD卡讀寫TXT文本實驗”中的軟體設計部分。
19.5下載下傳驗證
首先我們将下載下傳器與領航者底闆上的JTAG接口連接配接,下載下傳器另外一端與電腦連接配接。然後使用Mini USB連接配接線将開發闆左側的USB_UART接口與電腦連接配接,用于序列槽通信。
接下來使用FPC排線将正點原子的RGB LCD螢幕連接配接到領航者底闆上的LCD接口。然後把本章簡介部分所給出的示例圖檔重命名為“fengjing.bmp”,并拷貝到SD卡的根目錄下。最後将Micro SD卡插入領航者底闆背面的卡槽中。
需要注意的是,拷貝到Micro SD卡中的BMP圖檔分辨率應與開發闆所連接配接的LCD螢幕分辨率保持一緻。我們在工程目錄下建立了一個名為“風景圖檔”的檔案夾,裡面有四種不同分辨率的圖檔,如下圖所示:
圖 19.5.1 不同分辨率的BMP圖檔
比如本次實驗使用的LCD螢幕分辨率為800*480,那麼就将上圖中名為“fengjing_800x480.bmp”的圖檔拷貝到Micro SD卡根目錄下。在拷貝完成後,千萬不要忘記将Micro SD中圖檔的名稱修改為“fengjing.bmp”。
另外本次實驗要求使用的Micro SD卡為FAT32格式,如果不是,那麼在使用前需要将其格式化為FAT32格式。
準備工作完成之後,接下來連接配接開發闆的電源,并打開電源開關。
在SDK軟體下方的SDK Terminal視窗中點選右上角的加号來設定并連接配接序列槽。然後下載下傳本次實驗硬體設計過程中所生成的BIT檔案,來對PL進行配置。最後下載下傳軟體程式,下載下傳完成後,在下方的SDK Terminal中可以看到應用程式列印的資訊,如下圖所示:
圖 19.5.2 序列槽列印資訊
圖 19.5.2中列印出了BMP圖檔的檔案頭和資訊頭等資訊,與圖 19.1.4中的資料保持一緻。同時從資料中計算出BMP圖檔的寬度為800,高度為480。另外根據讀出的LCD螢幕ID看出,所使用的LCD螢幕分辨率為800*480,與Micro SD卡中的BMP圖檔分辨率一緻。如果不一緻,那麼螢幕上就無法正常顯示圖檔。
LCD螢幕上顯示的圖檔如下圖所示,說明本次實驗在領航者ZYNQ開發闆上面下載下傳驗證成功。
圖 19.5.3 下載下傳驗證