博客 / 詳情

返回

在ZYNQ上跑超炫酷GUI!手把手教你移植LVGL到ZYNQ平台!

在ZYNQ上跑超炫酷GUI!手把手教你移植LVGL到ZYNQ平台!

基於ZYNQ平台實現炫酷的GUI界面一般通常有兩種方式,一種是跑Linux系統運行QT程序;另外一種是跑裸機程序,通過調用圖形界面庫實現GUI界面,選擇哪種方式需結合使用場景。本次介紹第二種方式,教大家如何將圖形界面庫移植到ZYNQ平台,在ZYNQ平台實現炫酷的GUI界面,而無需依賴複雜的操作系統。此外,在移植成功後,大家後續就可以基於ZYNQ平台開發更有趣,更貼近實戰的項目,如示波器、信號發生器等。

目前主流的圖形界面庫有uc/GUI、emWin和LVGL等,其中LVGL是一個免費的輕量級開源圖形庫,有着諸多優點,在下文會展開進行介紹,所以本次移植的是LVGL圖形界面庫。如果大家在網上搜索移植LVGL的方法,不出意外的話,絕大多數都是基於微處理器如STM32移植LVGL,基於ZYNQ平台的很少,所以本文章將手把手教大家如何在ZYNQ平台移植LVGL。

領航者開發板購買鏈接:https://detail.tmall.com/item.htm?id=609032204975

一、認識LVGL

LVGL(Light and Versatile Graphics Library)是一個免費的輕量級開源圖形庫,其主要特徵有:

  1. 豐富的部件:開關、按鈕、圖表、列表、滑塊、圖片,等等
  2. 高級圖形屬性:具有動畫、抗鋸齒、不透明度、平滑滾動等高級圖形屬性
  3. 支持多種輸入設備:如觸摸屏、鼠標、鍵盤、編碼器等
  4. 支持多語言:UTF-8編碼
  5. 支持多顯示器:它可以同時使用多個TFT或者單色顯示器
  6. 支持多種樣式屬性:它具有類CSS的樣式,支持自定義圖形元素
  7. 獨立於硬件之外:它可以與任何微控制器或顯示器一起使用
  8. 可擴展性:它能夠以小內存運行(最低64KB閃存,16KB RAM)
  9. 支持操作系統、外部存儲器和GPU(不是必需的)
  10. 具有高級圖形效果:可進行單幀緩衝區操作。
  11. 純C編寫:LVGL基於C語言編寫,以獲得最大的兼容性。

綜上可知:LVGL是一款具有豐富部件,具備高級圖形特性,支持多種輸入設備和多國語言,獨立於硬件之外的開源圖形庫。官方地址為:LVGL — Light and Versatile Embedded Graphics Library,該網頁主要包含用户文檔、圖片轉換器和字體轉換器,該網頁打開後如圖1.1.1所示:


圖 1.1.1 LVGL官方頁面

上圖中,點擊右上角圖標即可進入LVGL源碼的github倉庫,在該倉庫中,可以下載LVGL相關的源碼;點擊Documentation既可打開LVGL官方文檔,該文檔中英文翻譯都包含,主要講解LVGL的基礎知識、移植、部件使用、示例等等。可以結合官方文檔以及本章內容一起學習LVGL的移植操作。

LVGL移植要求
市面上LVGL主要是在微處理器(MCU)上進行移植,然而ZYNQ的性能遠遠超過大多數MCU以及LVGL官方要求的最低性能要求,所以我們接下來只説它對於顯示屏的要求。
LVGL只需要一個簡單的驅動程序函數即可將像素陣列複製到顯示器的指定區域當中,其對顯示器的兼容性很強,具體要求如下(滿足其一即可):
1.具有8/16/24/32位色深的顯示器。
2.HDMI端口的顯示器。
3.小型單色顯示器。
4.LED矩陣。
5.其他可以控制像素顏色/狀態的顯示器。
我們正點原子的 2.8/3.5/4.3/7/10.1 寸 TFTLCD 模塊以及 RGBLCD 模塊都是16位色深的顯示屏,這些顯示屏皆可滿足 LVGL 的要求。

LVGL源碼下載
LVGL 相關的源碼和工程都是存放在 GitHub 遠程倉庫中,該 GitHub 遠程倉庫地址為GitHub - lvgl/lvgl at release/v8.2,用户可以在該倉庫中下載LVGL圖形庫的源碼。由於GitHub倉庫的服務器在國外,如果用户在國內訪問該服務器可能登錄不成功。我們可以從正點原子網盤資料中獲取LVGL的V8.2版本源碼,如圖 1.1.2所示:


圖 1.1.2 LVGL源碼

上圖中,“LVGL使用工具.zip”壓縮包裏存放了 LVGL 相關的離線轉換工具,這些離線工具是廣大愛好者根據 LVGL 的字庫定義規則和圖片的處理特性而編寫的軟件;“lvgl-release-v8.2.zip”壓縮包裏存放了LVGL圖形庫的V8.2 版本源碼,其解壓後如圖 1.1.3所示:


圖 1.1.3 LVGL源碼文件

由上圖可知,LVGL 源碼的目錄下有很多文件和文件夾,但用户並不需要完全瞭解它們,我們只需要瞭解與移植相關的部分即可。各文件夾和文件的功能如表 1.1.1所示:


表 1.1.1 lvgl-release-v8.2文件説明

上表中,與LVGL移植相關的有examples文件夾、src文件夾、lv_con_template.h和lvgl.h文件,其它的部分均與移植無關,用户可以忽略。接下來我們分別看一下examples、src這兩個文件夾的文件結構:

examples文件夾
該文件夾主要包含LVGL部件實例、動畫實例、其它第三方庫實例以及輸入設備和顯示器驅動文件等內容,具體如表 1.1.2所示:


表 1.1.2 examples文件夾內容

上表中,只有porting文件夾與移植相關,其它文件夾中存放的是各種實例。

src文件夾
該文件夾主要包含LVGL源文件(部件源碼、多種解碼庫),具體如表 1.1.3所示:


表 1.1.3 src文件夾的內容

上表中的內容都是與移植相關的,具體的移植方法我們後面將詳細介紹,目前大家只需要對LVGL源碼的文件結構有一定的瞭解即可。

二、移植前的準備工作

1)一款ZYNQ開發板:本次以領航者ZYNQ開發板來演示;
2)一款RGB LCD屏:本次以正點原子7寸RGB LCD屏來演示;
如果大家沒有開發板和RGB LCD屏,可以在正點原子官方旗艦店購買,鏈接如下:正點原子領航者ZYNQ開發板FPGA XILINX 7010 7020 PYNQ Linux核心-tmall.com天貓

三、Vivado BlockDesign的工程搭建

本次實驗需要完成RGB LCD屏顯示GUI界面,並且支持觸摸的功能,顯示的功能可以由VDMA IP核、Video Timing Controller IP核、AXI-Stream to Video Out IP核、rgb2lcd IP核實現;觸摸功能由EMIO模擬IIC接口實現。所以本次Block Design工程實際上和《領航者嵌入式Vitis開發指南》中的“RGB LCD畫板實驗”完全一致,可以直接在此工程基礎上,另存為本次實驗的工程即可,本次工程命名為“zynq_lvgl”。

本次實驗Block Design的系統架構圖如下圖所示:


圖 1.3.1 系統框圖

由上圖可知,首先我們需要在PS端寫彩條數據到DDR中,然後PS需要通過AXI Interconnect IP核與AXI_HP端口進行連接,用於VDMA IP和PS之間高速傳輸數據。VDMA IP核將DDR3中的視頻或圖像數據傳輸給AXI4-Stream to Video Out IP核,AXI4-Stream to Video Out IP核在VTC IP核的控制下,把AXI4-Stream格式的數據轉換成視頻輸出的數據格式(如RGB888),並將輸出的視頻數據流連接至RGB2LCD IP核(rgb2lcd)的輸入端。最後PS通過AXI GPIO IP核獲取LCD屏ID,並根據獲取到的ID配置VDMA IP核的幀緩存空間大小、讀通道等以及配置VTC IP核的輸出的時序參數。由於不同分辨率的LCD屏其驅動時鐘不一樣,因此本次實驗需要將時鐘配置成動態時鐘,PS根據LCD屏的ID配置時鐘IP輸出不同的時鐘。再通過加入EMIO來檢測LCD屏觸摸狀態的功能,這一部分功能是由PS端來實現的。PS不斷地掃描LCD的觸摸狀態,包括是否有觸摸動作發生、當前觸摸點的座標等信息,根據掃描到的信息來決定是否向VDMA的Frame Buffer中寫入數據以及向Frame Buffer的哪個地址寫入數據等等。然後位於PL端的VDMA讀取邏輯不斷地將Frame Buffer中存儲的數據送給LCD進行顯示。
PS與LCD的觸摸芯片之間的通信,是由PS端GPIO引出的4個EMIO來完成的,包括CT_RST、CT_INT、IIC2_SCL、IIC2_SDA。CT_RST是ZYNQ送給觸摸芯片的復位信號。CT_INT是觸摸芯片送給ZYNQ的中斷信號,用於指示是否有觸摸事件發生。IIC2_SCL、IIC2_SDA是觸摸芯片內置的IIC總線,ZYNQ使用它來訪問觸摸芯片的內部寄存器,來完成對觸摸芯片的初始化、讀取觸摸點的座標等動作。


圖 1.3.2 整體系統框圖

若想了解觸摸屏的具體驅動以及硬件詳解可以在網盤中下載《領航者ZYNQ之嵌入式Vitis開發指南》查看第二十四章《PS通過VDMA驅動LCD顯示實驗》以及第三十六章《RGB LCD畫板實驗》。

四、軟件設計

將硬件導出至Vitis,打開Vitis後,我們創建一個Application Project,應用工程名為“zynq_lvgl”,本實驗和“RGB LCD畫板”實驗相比,源文件下新增了timer和LVGL文件夾,分別是定時器驅動代碼和LVGL庫文件,timer文件夾的內容可以直接拷貝“定時器中斷實驗”的代碼。文件代碼結構如下圖所示:


圖 1.4.1 工程目錄結構

移植準備工作
首先將“RGB LCD畫板實驗”的代碼全部拷貝到本工程當中,其次將“定時器中斷實驗”的代碼拷貝到本工程當中,最後再準備LVGL源碼。
我們將上述圖 1.1.3獲得的LVGL源碼進行裁剪並將lv_conf_template.h文件改名為lv_conf.h。除了examples文件夾、src文件夾、lv_conf.h和lvgl.h文件,其它文件和文件夾均與移植無關,我們可以將它們刪除,這樣可以得到精簡的LVGL源碼。修改後的LVGL源碼(保留了demos文件夾)如圖 1.4.2所示:


圖 1.4.2 精簡後的LVGL源碼

打開 lv_conf.h 文件,修改條件編譯指令。
修改前:

#if 0 /*Set it to "1" to enable content*/

修改後:

#if 1 /*Set it to "1" to enable content*/

由上圖可知,demos文件夾沒有被刪除,該文件夾中存放的是LVGL官方的演示例程,後續我們將移植其中的示例進行演示。
接下來我們打開上圖中的examples文件夾,僅保留其中的porting文件夾,其它的文件和文件夾均可刪除,刪減後的examples文件夾如下所示:


圖 1.4.3 僅保留proting文件夾

向工程添加文件
我們在工程目錄下新建一個LVGL文件夾,並在該文件夾下面新建GUI文件夾以及GUI_APP文件夾,最後在GUI文件夾下新建lvgl文件夾。具體的文件夾結構如圖 1.4.4所示:


圖 1.4.4 LVGL目錄下文件夾結構

這裏説明一點,我們的工程之所以採用這樣的文件夾結構,主要是為了兼容LVGL源碼中的包含頭文件的格式。對於初學者,這裏建議按照此結構新建文件夾,否則有可能會遇到很多關於頭文件的報錯。
把精簡後的LVGL源碼(圖 1.4.2中的文件夾和文件)複製到上述創建的LVGL/GUI/lvgl目錄下,具體如所示:


圖 1.4.5 移植LVGL源碼

添加LVGL源碼的文件夾路徑,包含源碼文件,右鍵選擇應用工程,選擇Properties選項,如下圖所示:


圖 1.4.6 選擇Properties選項

在打開的Properties選項卡中選擇C/C++ General選項下的Paths and Symbols,Add添加頭文件路徑,如下圖所示:


圖 1.4.7 添加頭文件路徑

點擊Workspace選擇要添加的頭文件路徑:


圖 1.4.8 選擇頭文件路徑

將所有頭文件添加完成之後,點擊Apply應用之後點擊Apply and Close,確認並關閉選項卡,移植LVGL只需要添加關鍵的頭文件路徑即可,因為lvgl.h文件已經為我們省去了很多包含頭文件的操作,添加的頭文件路徑如下圖所示:


圖 1.4.9 頭文件路徑

為LVGL提供時基
修改定時器驅動代碼為LVGL提供時基,打開timer.c文件,聲明LVGL的頭文件,修改後的timer.c如下所示:

1  #include "timer.h"
2  #include "lvgl.h"
3  #include "xil_cache.h"
4  
5  #define TIMER_DEVICE_ID     XPAR_XSCUTIMER_0_DEVICE_ID   //定時器ID
6  #define INTC_DEVICE_ID      XPAR_SCUGIC_SINGLE_DEVICE_ID //中斷ID
7  #define TIMER_IRPT_INTR     XPAR_SCUTIMER_INTR           //定時器中斷ID
8  
9  //私有定時器的時鐘頻率 = CPU時鐘頻率/2 = 333MHz
10 //0.001s(1ms)   0.001*1000_000_000/(1000/333) - 1 = 0x514c7
11 #define TIMER_LOAD_VALUE    0x514c7                      //定時器裝載值
12
13 XScuTimer Timer;            //定時器驅動程序實例
14
15 //定時器初始化程序
16 int timer_init(XScuGic *intc_ptr)  //,XScuTimer *timer_ptr
17 {
18  int status;
19     //私有定時器初始化
20     XScuTimer_Config *timer_cfg_ptr;
21     timer_cfg_ptr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
22     if (NULL == timer_cfg_ptr)
23         return XST_FAILURE;
24     status = XScuTimer_CfgInitialize(&Timer, timer_cfg_ptr,timer_cfg_ptr->BaseAddr);
25         if (status != XST_SUCCESS) {
26         xil_printf("Timer Initial Failed\r\n");
27         return XST_FAILURE;
28     }
29     XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE); // 加載計數週期
30     XScuTimer_EnableAutoReload(&Timer);            // 設置自動裝載模式
31
32     
33     //初始化中斷控制器
34     XScuGic_Config *intc_cfg_ptr;
35     intc_cfg_ptr = XScuGic_LookupConfig(INTC_DEVICE_ID);
36     XScuGic_CfgInitialize(intc_ptr, intc_cfg_ptr,intc_cfg_ptr->CpuBaseAddress);
37     //設置並打開中斷異常處理功能
38     Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
39             (Xil_ExceptionHandler)XScuGic_InterruptHandler, intc_ptr);
40     Xil_ExceptionEnable();
41
42     //設置定時器中斷
43     XScuGic_Connect(intc_ptr, TIMER_IRPT_INTR,
44           (Xil_ExceptionHandler)timer_intr_handler, (void *)(&Timer));
45
46     XScuGic_Enable(intc_ptr, TIMER_IRPT_INTR); //使能GIC中的定時器中斷
47     XScuTimer_EnableInterrupt(&Timer);      //使能定時器中斷
48     
49     XScuTimer_Start(&Timer);         //啓動定時器
50     return XST_SUCCESS;
51 }
52
53 //定時器中斷處理程序
54 void timer_intr_handler(void *CallBackRef)
55 {
56  static int ms_cnt = 0;
57  XScuTimer *timer_ptr = (XScuTimer *) CallBackRef;
58
59     lv_tick_inc(1); /* lvgl 的 1ms 心跳 */
60     ms_cnt++;
61     if(ms_cnt == 5)
62     {
63      ms_cnt = 0;
64      Xil_DCacheFlush();     //刷新Cache,數據更新至內存
65     }
66     //清除定時器中斷標誌
67     XScuTimer_ClearInterruptStatus(timer_ptr);
68 }

上述源碼對比“定時器中斷實驗”,修改了重裝載值,讓cpu每1ms進一次中斷,定時器中斷回調函數調用了LVGL的lv_tick_inc函數,該函數可以讓LVGL內部的時基參數加1(入口參數為1的情況下),然後在中斷中,每5ms刷新一次Cache,使數據更新至內存,若沒有此操作,顯示畫面會有撕裂情況。

配置顯示屏、觸摸輸入驅動
打開LVGL/GUI/lvgl/examples/porting下的lv_port_disp_template.c/h(顯示屏相關)和lv_port_indev_template.c/h(觸摸輸入相關)文件,將這4個文件中的條件編譯指令#if 0都修改成#if 1,如下所示:
修改前:

#if 0 /* lv_port_disp_template.c/h 和 lv_port_indev_template.c/h */

修改後:

#if 1 /* 把 #if 0 修改成 #if 1 */

修改lv_port_disp_template.c文件
該文件用於配置顯示屏,它可以將用户的底層顯示驅動與LVGL的顯示驅動銜接起來。我們打開官方提供的lv_port_disp_template.c文件,包含LCD驅動頭文件,然後配置LCD顯示相關的驅動程序,修改後的源碼如下:

1  #if 1
2  
3  #include "lv_port_disp_template.h"
4  #include "../../lvgl.h"
5  #include "../../../../../src/display_ctrl/display_ctrl.h"
6  
7  
8  #define MY_DISP_HOR_RES (800)   /* 屏幕寬度 */
9  #define MY_DISP_VER_RES (480)   /* 屏幕高度 */
10
11
12 extern VideoMode    vd_mode;
13 extern unsigned int const frame_buffer_addr;
14
15 static void disp_init(void);
16
17 static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
18
19 void lv_port_disp_init(void)
20 {
21     disp_init();
22
23     /* Example for 1) */
24     static lv_disp_draw_buf_t draw_buf_dsc_1;
25     static lv_color_t buf_1[MY_DISP_HOR_RES * MY_DISP_VER_RES];                          /*A buffer for 10 rows*/
26     lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * MY_DISP_VER_RES);   /*Initialize the display buffer*/
27
28     static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
29     lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/
30
31     /*Set up the functions to access to your display*/
32
33     /*Set the resolution of the display*/
34     disp_drv.hor_res = vd_mode.width;
35     disp_drv.ver_res = vd_mode.height;
36
37     /*Used to copy the buffer's content to the display*/
38     disp_drv.flush_cb = disp_flush;
39
40     /*Set a display buffer*/
41     disp_drv.draw_buf = &draw_buf_dsc_1;
42
43     /*Finally register the driver*/
44     lv_disp_drv_register(&disp_drv);
45 }
46
47 /*Initialize your display and the required peripherals.*/
48 static void disp_init(void)
49 {
50     /*You code here*/
51 }
52
53 static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
54 {
55     /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
56
57
58     uint16_t x;
59     uint16_t y;
60     uint32_t color_index = 0;
61     uint8_t * lcd_base_addr = (uint8_t *)frame_buffer_addr;
62     uint8_t * color_p_t = (uint8_t *)color_p;
63
64     for(y = area->y1; y <= area->y2; y++)
65     {
66         for(x = area->x1; x <= area->x2; x++)
67          {
68             /*Put a pixel to the display. For example:*/
69          /*put_px(x, y, *color_p)*/
70             lcd_base_addr[y*vd_mode.width*3+x*3] = color_p_t[color_index+0];
71             lcd_base_addr[y*vd_mode.width*3+x*3+1] = color_p_t[color_index+1];
72             lcd_base_addr[y*vd_mode.width*3+x*3+2] = color_p_t[color_index+2];
73             color_index = color_index + 4;
74          }
75      }
76
77     /*IMPORTANT!!!
78      *Inform the graphics library that you are ready with the flushing*/
79     lv_disp_flush_ready(disp_drv);
80 }
81
82 #else /*Enable this file at the top*/
83
84 /*This dummy typedef exists purely to silence -Wpedantic.*/
85 typedef int keep_pedantic_happy;
86 #endif
87

由上述源碼可知,配置LVGL顯示屏驅動的步驟可分為以下7步:
(1)調用函數disp_init初始化LCD驅動。
(2)調用函數lv_disp_draw_buf_init初始化緩衝區(最低標準:顯示屏的寬度分辨率/10)。
(3)調用函數lv_disp_drv_init初始化顯示驅動。
(4)設置顯示的高度和寬度。
(5)註冊顯示驅動回調。
(6)設置顯示驅動的繪畫緩衝區。
(7)調用lv_disp_drv_register函數註冊顯示驅動到LVGL列表中。
在整個配置的過程中,實際上我們只需要設置緩衝區的大小和傳輸圖像數據。如果想設置屏幕的方向,需要在touch.c文件中的TP_Init函數開頭添加“tp_dev.touchtype |= 0x01”設置為橫屏顯示(默認為豎屏)。在本章中,lv_port_disp_template.c文件我們做了精簡處理,刪去了很多沒有用到的東西,例如:緩衝區的刷新方式有三種,我們只是使用了其中一種,若想要使用其他兩種刷新方式可以查看官方源碼以及結合官方文檔嘗試移植使用。

修改lv_port_indev_template.c文件
該文件用於配置輸入設備,例如:觸摸屏、鼠標、鍵盤、編碼器、按鍵等,它可以將用户的底層輸入設備驅動與LVGL的輸入驅動銜接起來。這裏我們使用觸摸屏作為輸入設備(將源碼中其他輸入設備的代碼進行了裁剪處理,若想要使用其他的輸入設備建議查看官方文檔移植),具體的配置源碼如下所示:

1  #if 1
2  
3  #include "lv_port_indev_template.h"
4  #include "../../lvgl.h"
5  #include "../../../../../src/TOUCH/touch.h"
6  
7  static void touchpad_init(void);
8  static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
9  static bool touchpad_is_pressed(void);
10 static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y);
11
12 lv_indev_t * indev_touchpad;
13
14 void lv_port_indev_init(void)
15 {
16
17     static lv_indev_drv_t indev_drv;
18
19     /*Initialize your touchpad if you have*/
20     touchpad_init();
21
22     /*Register a touchpad input device*/
23     lv_indev_drv_init(&indev_drv);
24     indev_drv.type = LV_INDEV_TYPE_POINTER;
25     indev_drv.read_cb = touchpad_read;
26     indev_touchpad = lv_indev_drv_register(&indev_drv);
27 }
28
29 /*Initialize your touchpad*/
30 static void touchpad_init(void)
31 {
32     /*Your code comes here*/
33  tp_dev.init();//&#254;
34 }
35
36 /*Will be called by the library to read the touchpad*/
37 static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
38 {
39     static lv_coord_t last_x = 0;
40     static lv_coord_t last_y = 0;
41
42     /*Save the pressed coordinates and the state*/
43     if(touchpad_is_pressed()) {
44         touchpad_get_xy(&last_x, &last_y);
45         data->state = LV_INDEV_STATE_PR;
46     } else {
47         data->state = LV_INDEV_STATE_REL;
48     }
49
50     /*Set the last pressed coordinates*/
51     data->point.x = last_x;
52     data->point.y = last_y;
53 }
54
55 /*Return true is the touchpad is pressed*/
56 static bool touchpad_is_pressed(void)
57 {
58     /*Your code comes here*/
59
60  tp_dev.scan();
61
62  if (tp_dev.sta & TP_PRES_DOWN)
63  {
64      return true;
65  }
66
67     return false;
68 }
69
70 /*Get the x and y coordinates if the touchpad is pressed*/
71 static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
72 {
73     /*Your code comes here*/
74
75     (*x) = tp_dev.x[0];
76     (*y) = tp_dev.y[0];
77 }
78
79 #else /*Enable this file at the top*/
80
81 /*This dummy typedef exists purely to silence -Wpedantic.*/
82 typedef int keep_pedantic_happy;
83 #endif
84

由上述源碼可知,配置LVGL的觸摸部分分為以下5個步驟:
(1)調用函數touchpad_init初始化觸摸設備。
(2)調用函數lv_indev_drv_init初始化輸入設備。
(3)設置設備的類型。
(4)設置觸摸回調函數,該函數用於獲取觸摸屏的座標。
(5)調用函數lv_indev_drv_register註冊輸入設備。
LVGL官方提供的獲取觸摸座標函數是touchpad_get_xy,該函數的x和y的值需要用户提供。

移植官方例程
在精簡LVGL源碼的時候,我們保留了demos文件夾,該文件夾中存放的就是LVGL的官方示例。由於demos文件夾中存在多個官方例程,不同的例程對硬件的要求有所不同,這裏以官方的音樂播放器為例(該示例對硬件要求較高),為大家介紹官方示例的移植流程,具體步驟如下:
把demos文件夾複製到LVGL/GUI_APP路徑下,只保留music文件夾,如下圖所示:


圖 1.4.10 複製官方例程

添加文件路徑:


圖 1.4.11 添加文件路徑

打開lv_conf.h文件,找到LV_USE_DEMO_MUSIC宏定義並設置為1,開啓該實驗,並且修改顏色深度配置LV_COLOR_DEPTH宏定義為32,編譯工程,若出現下圖所示報錯,這説明14號以及16號字體沒有定義。


圖 1.4.12 字體沒有定義

如果大家遇到此報錯,可以打開lv_conf.h文件,找到相應字體的宏定義,並將其設置為1即可,如下圖所示:


圖 1.4.13 定義字體

定義相應的字體之後,再一次編譯工程,如果配置的內存足夠,此時就不會出現報錯了。接下來即可包含"lv_demo_music.h"頭文件在main函數中調用lv_demo_music函數,運行音樂播放器示例,具體源碼如下:

59  int main(void)
60  {
61      timer_init(&Intc);
62      //獲取LCD的ID
63      XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID);
64      XGpio_SetDataDirection(&axi_gpio_inst,AXI_GPIO_0_CHANEL,0x07); //設置AXI GPIO為輸入
65      lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);
66      XGpio_SetDataDirection(&axi_gpio_inst,AXI_GPIO_0_CHANEL,0x00); //設置AXI GPIO為輸出
67      xil_printf("LCD ID: %x\r\n",lcd_id);
68  
69      //根據獲取的LCD的ID號來進行video參數的選擇
70      switch(lcd_id){
71          case 0x4342 : vd_mode = VMODE_480x272; break;  //4.3寸屏,480*272分辨率
72          case 0x4384 : vd_mode = VMODE_800x480; break;  //4.3寸屏,800*480分辨率
73          case 0x7084 : vd_mode = VMODE_800x480; break;  //7寸屏,800*480分辨率
74          case 0x7016 : vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率
75          case 0x1018 : vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率
76          default : vd_mode = VMODE_800x480; break;
77      }
78  
79      emio_init();
80  
81      //配置VDMA
82      run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,
83                              frame_buffer_addr,0, 0,ONLY_READ);
84  
85      //設置時鐘IP核輸出的時鐘頻率
86      clk_wiz_cfg(CLK_WIZ_ID,vd_mode.freq);
87      //初始化Display controller
88      DisplayInitialize(&dispCtrl, DISP_VTC_ID);
89      //設置VideoMode
90      DisplaySetMode(&dispCtrl, &vd_mode);
91      DisplayStart(&dispCtrl);
92  
93      lv_init();                          /* lvgl系統初始化 */
94      lv_port_disp_init();                /* lvgl顯示接口初始化,放在lv_init()的後面 */
95      lv_port_indev_init();               /* lvgl輸入接口初始化,放在lv_init()的後面 */
96  
97      lv_demo_music();                   /* 官方例程測試 */
98      while(1)
99      {
100         lv_task_handler();
101     }
102
103     return 0;
104 }
105

五、下載驗證

首先我們將下載器與領航者底板上的JTAG接口連接,下載器另外一端與電腦連接。然後使用USB連接線將USB UART接口與電腦連接,用於串口通信。接下來使用FPC排線一端與RGB LCD液晶屏上的接口連接,另一端連接領航者底板上的RGB LCD接口,如圖 24.5.1和圖 24.5.2所示。連接時,先掀開FPC連接器上的黑色翻蓋,將FPC排線藍色面朝上插入連接器,最後將黑色蓋壓下以固定FPC排線。


圖 1.5.1 RGB LCD液晶屏

編譯工程並下載到開發板中。值得注意的是,這個官方例程僅支持 800 * 480 分辨率的屏幕,如果使用其他分辨率的屏幕,可能導致例程功能無法正常展現。音樂播放器例程正常運行的界面如下圖所示:


圖 1.5.3 音樂播放器界面

如果想要查看屏幕的FPS顯示,需要在lv_conf.h當中將LV_USE_PERF_MONITOR的宏定義置1就可以查看當前的幀數顯示以及CPU佔用率,可以看到右下角顯示的幀率非常的低,只有11FPS,我們可以通過更改編譯器的優化等級來優化幀率。
右鍵應用工程選擇C/C++ Build Settings選項,如下圖所示:


圖 1.5.4 C/C++ Build Settings選項

將優化等級更改為-O2級,如下圖所示:


圖 1.5.5 修改編譯器優化等級

接下來,我們簡單介紹一下不同優化等級的説明和優勢:


表 1.5.1 編譯器優化等級説明

由上表所示:-O2優化等級會更好的提高代碼的性能,並且不會增大體積和佔用太多的編譯時間,所以大多數編譯會選擇-O2優化等級。同樣我們也需要更改屏幕靜態的最大幀數顯示,搜索LV_DISP_DEF_REFR_PERIOD,此宏定義為定義屏幕的刷新週期,默認為30ms,此值設置過大可能會出現卡頓現象,設置過小會浪費性能,我們將他設置為10,使其靜態的最大幀數為100FPS;這裏可以將LV_INDEV_DEF_READ_PERIOD宏定義也修改為10,此宏定義為輸入設備的輪詢週期,10ms代表每隔10ms採樣一次輸入設備的狀態。編譯工程之後,下載代碼,如下圖所示:


圖 1.5.6 優化後的幀率顯示

從上圖可以看到,幀率從11FPS優化到了29FPS。
以上內容實現了將LVGL的官方Demo顯示到RGB LCD屏上,後續我們還會基於LVGL,實現一個簡易示波器的功能,讓大家更能深入體會到ZYNQ軟硬件協同開發的優勢,目前簡易示波器的實現方案正在評估中,做出來後也會第一時間分享給大家,敬請期待。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.