一、前言
$ v; m' @+ w' d+ e( o" Clittlevgl是一个小型开源嵌入式 GUI 库(简称LVGL),界面精美,消耗资源小,可移植度高,支持响应式布局,全库采用纯 c 语言开发,移植上手简单。% t$ L+ W% Y5 I4 G, D+ ?' w
littleVGL 的主要特性如下:* k: A) L( E5 b g6 n! G
• 具有非常丰富的内置控件,像 buttons, charts, lists, sliders, images 等
" O b3 @; g* J b; I% P1 L• 高级图形效果:动画,反锯齿,透明度,平滑滚动
# ] N1 R$ q1 E: F6 H• 支持多种输入设备,像 touchpad, mouse, keyboard, encoder 等
# C: n/ H$ V. w' e! }7 n+ y• 支持多语言的 UTF-8 编码
1 L8 U% t" F4 l6 ?1 M• 支持多个和多种显示设备,例如同步显示在多个彩色屏或单色屏上" v. L4 ~+ l: @7 ~+ P' X5 H; ?' j; T
• 完全自定制的图形元素
$ X4 {% [( q: b) N& n9 T7 M• 硬件独立于任何微控制器或显示器
; n- ?" m2 |- ^2 ?) K( z• 可以缩小到最小内存 (64 kB Flash, 16 kB RAM)$ K- [1 R* s; j& j2 R c
• 支持操作系统、外部储存和 GPU(非必须)
. o, x# w: a3 [7 R0 e• 仅仅单个帧缓冲设备就可以呈现高级视觉特效
& b, w U$ R( m) U, a9 ^* _• 使用 C 编写以获得最大兼容性(兼容 C++) {. u5 N' L: N* e( ]) _3 c
• 支持 PC 模拟器
% v3 a0 w4 b% K& Z" t• 为加速 GUI 设计,提供教程,案例和主题,支持响应式布局4 u, L/ J% B2 `4 a
• 提供了在线和离线文档
5 K$ X9 y0 r, R; u8 l2 K• 基于自由和开源的 MIT 协议
6 b5 l* w: Q9 e* u$ ^效果图以及更多详细的说明请见官网+ S$ R5 G$ I* P1 x4 X6 n# S
littlevgl中文官网:https://littlevgl.cn/
" i9 P" }$ s' w% u! Y5 a% klittlevgl英文官网:https://lvgl.io/
! i/ s' Q6 p$ q% n7 X
0 m6 F$ I5 Y" O" f5 B
' l: D$ h; b }( T7 x二、学习资料" E3 G6 a# h( Y5 y' ?% V
官方文档:https://docs.lvgl.io/latest/en/html/index.html(注:官网打不开的话可能会需要梯子)
# e( g0 o& ~1 {Github: https://github.com/lvgl/lvgl. G0 Q* o( G/ O) h& @
三、移植前的准备工作
+ `5 |5 N p0 n' O6 j- [开发环境:
|# {5 j& M: Z. ?' tKeil MDK5 v5.28/ h3 Y& g7 i* H" m4 h P7 F
ARM Compiler 6.0版本或以上" e4 H' ?' }+ C0 V
关于编译器,老版本的5.x编译器也能编译,但是新的6版本的编译器在编译速度与效率上有很大的提升,并且解决了一些莫名其妙的bug,强烈建议升级到6版本的编译器。笔者使用的版本是v6.14.1。
2 N5 E! e; l6 I+ P0 K# }1 O. \3 S3 b( z- @; D( {2 N
# ]5 h9 A7 H( O/ h% s: C硬件:8 K0 ~ i# [& _, u' V& {3 b% r
STM32F103开发板* K. j* P4 e! \/ n
屏幕一块(单色点阵屏或者是LCD彩屏都可以)
# p; o% v/ Y e* Z- I% v笔者使用的开发板是正点原子的战舰V3开发板,芯片是STM32F103ZET6,72M主频+512k flash+64k ram,这样的性能足够跑LVGL了,当然,你的芯片性能越高肯定越好。哦,板上还外挂了一颗1M字节的SRAM,这对提升性能很有帮助。如果你用的不是这款芯片或者这块开发板,那也没关系,笔者会详细指出移植过程中不同的芯片移植需要注意的地方。
( N6 i& P) b6 Z8 C- k, B" A
6 ^$ F4 x# @) a% y) q; b0 [ f& O/ l; E! Z, u' E* D
获取源码:9 |2 i5 B: Z4 C
源码地址:https://github.com/lvgl/lvgl" D" S1 z! Q3 \4 i
源码有很多的发行版,不同的发行版对应着不同的LVGL版本,注意不同的版本之间是不同的,比如版本V6与版本V7之间就变化很大,具体的版本变化信息可以看它的release note,这里我以v7.1.0版本为例来移植,并且今后的学习都会使用这个版本。% {$ U" F! o) P7 g! u5 }! c- N% z
7 t h( o& @* ?8 j `4 N B1 U打开下载好的文件夹
7 L0 `& ]; X' m, G3 q" i5 k+ Z
- h3 [; m9 K% u8 D* V
我们重点关注src/目录和lv_conf_template.h, lvgl.h 这几个文件,其它的暂时不用管。
, p0 u. \# T% L* W; g3 z& ?7 Z* i# D) {
+ @5 v7 `2 L: H
准备模板工程:
& ~- ^# R" [5 U为你的开发板准备好一个模板工程! f; t( r. l/ U" s- @$ i
要求:这个模板工程至少能够驱动一块屏幕(做GUI必须得有屏幕呀= =), }; j& G* F* Y! J5 q
如果你的屏幕带有触摸功能,那你的工程还需要有触摸屏的驱动,其实触摸屏驱动需要但非必须,没有触摸功能的话你也可以做一些展示的界面。事实上LVGL也支持键盘,鼠标,触控板等外设,因为在LVGL核心那里,它会将这些全部抽象为输入设备,它只需要从输入设备那里取得数据,并不关心你所用的是何种设备。8 L1 \: F& ]. F7 D
当然,触摸屏已经用得相当广泛,笔者使用的屏幕同样是正点原子家开发板配套的屏幕,型号:4.3英寸MCU屏,800*480分辨率,带电容触摸功能,其接口为16位并口,驱动IC:NT35510,触摸控制IC:GT9147,触摸控制接口为IIC。
. q8 k+ a, y" ?" W/ f" _/ j因此我需要提前准备好并且测试好这些驱动,对于屏幕控制器,除了初始化API(就是函数),还需要提供填充LCD的API,像这样:
2 O7 F0 x0 H- ~9 f3 k9 h
: j4 ^% J9 p) U# m0 N; a8 A7 w8 _ q0 _, W$ L
LCD_Color_Fill(x1,y1,x2,y2,(u16*)color_p);* T) y4 O9 w0 A* C- E% `
1. T+ ]# [, n% {4 O( e$ ^9 `6 b
(x1,y1), (x2,y2)为俩坐标点,color_p为显存指针,该API要实现的功能为:将显存区域内的像素数据点,依次填充到以(x1,y1)为起点, (x2,y2)为重点的对角矩形区域内。
- g9 _4 c: q7 q9 ^! D, S注意,这个API是LVGL渲染屏幕的唯一API,哈哈哈简单吧。同样LVGL内部也不关心使用的何种屏幕,他们之间仅通过一个填充色块儿的API耦合,这种高内聚、低耦合的模块儿化设计思想是非常值得我们学习借鉴的。
% J" H1 ?' J3 W
5 e2 l4 {7 q: \0 H5 S% Z3 F& E, ^
- X" G# O; g: q/ |四、开始移植
7 i8 \' @7 P" A$ E8 Z8 N* Y①修改MDK工程(默认你已经会使用keil了)
! c6 D }" A n( [$ \7 w用keil mdk打开我们的模板工程,添加4个Groups:GUI_core, GUI_drv, GUI_demo, GUI_app,这些组用于添加我们的c代码到工程中,不同的组名意味着会存放不同功能的c代码。(注意:工程中的组与实际文件目录并不相同)$ C2 F1 m$ n6 H- I8 Y e7 W7 s( D
( N& b( y) P8 ?( @/ g- g
$ t$ }5 w+ O0 _' I# b+ F2 nGUI_core:GUI核心文件层+ H4 {$ v, t3 p
GUI_drv:GUI驱动设备层,包括显示设备与输入设备/ C/ N, c E) k4 w. [
GUI_demo:存放官方demo
i" n' M8 g( [& ?. V5 d+ C0 B, y1 f+ MGUI_app:存放我们自己写的GUI应用; ~1 V" A" y: H$ N
修改后的工程结构:+ d$ e0 V- u$ `% r
- K; \6 I- V7 f
②添加源文件9 ]. d& e4 m, r+ e( q
在项目的根路径下新建GUI目录,以及GUI_app目录,然后将准备工作第三步下载到的源码直接解压到GUI/目录下,然后把解压出来的文件夹改名为lvgl;在GUI/目录下新建lvgl_driver目录,然后:
: M& Y1 S( |5 y* @# C+ H- //从GUI\lvgl\examples\porting\目录拷贝:$ ]3 C9 K8 h( h+ r6 U" U3 _
- lv_port_disp_template.c% m8 y+ o9 V. a; e6 i
- lv_port_disp_template.h2 L" |+ b }' v: G/ Y
- lv_port_indev_template.c# _+ \! c8 t: t" Y
- lv_port_indev_template.h
8 [/ Q% @# p1 D1 I" A+ [9 f - // 到GUI/lvgl_driver/目录下并分别改名为:
# b. [8 u7 ]% Y, e6 D- D - lv_port_disp.c
0 q4 x2 ^) Y- h$ F; V - lv_port_disp.h& T) X1 I8 {7 p6 w. c
- lv_port_indev.c
% f9 t( u$ Z2 e9 a1 I/ | - lv_port_indev.h
5 w1 {" {! j \6 b
8 [4 [+ v. s7 t- //添加:* G. m% e F" w5 q) g0 Y9 w
- dma.c
! g- p# C; q6 i1 A' [! ? - dma.h. O4 a0 h4 }" w" ~
- //这两个文件时我编写的使用DMA进行加速的代码,如果你不使用DMA请忽略
) Q& m7 t5 T6 P4 S: `
复制代码 然后把GUI/lvgl/lv_conf_template.h拷贝到GUI/目录下并改名为lv_conf.h。
8 L6 s+ z- J2 u, ~( C修改后的目录结构:
4 r0 ?0 _3 R! l! u
- w4 Q5 G5 L( E! K |7 S# e3 d
$ n/ A% ~. x; O6 l6 r) Q
. h/ j1 D% X# `5 Y' Y
6 s6 [/ D+ ?; c' }/ Z5 t% k9 E+ K$ I2 |8 v! ?. M
③添加源文件到工程中
1 b" {' O; Y) M" k" K% I在GUI_core组中添加以下文件夹中所有的.c文件:
& H! T6 m1 l1 m& Q5 Y
1 t$ d" [& h' }. L9 P: T0 P- GUI/lvgl/src/lv_core
$ t$ B" v# `/ ]. P7 Q - GUI/lvgl/src/lv_draw
2 @6 N" P! y" p( q" D/ ^! {' Z - GUI/lvgl/src/lv_font7 S: ^1 g3 _& I1 W
- GUI/lvgl/src/lv_hal$ z v5 E& I+ X5 c
- GUI/lvgl/src/lv_misc
0 m( f2 z9 u2 R: U" R+ S3 f - GUI/lvgl/src/lv_themes
- ^* B/ x/ a& g# |- A# M - GUI/lvgl/src/lv_widgets. O' |; h" Z9 B3 W; a& ]$ |- A
- //注意不要添加 GUI/lvgl/src/lv_gpu中的文件,除非你用到了相关功能' U/ Z! S6 Q/ w$ w
复制代码 在GUI_drv组中添加以下.c文件:
) ]: V$ r C* }% y& ~# d- GUI/lvgl_driver/dma.c# c* F$ _7 j4 }7 [
- GUI/lvgl_driver/lv_port_disp.c
1 G6 u/ g- G4 K) W3 ]* X - GUI/lvgl_driver/lv_port_indev.c
复制代码 ④添加头文件路径- E! |% n. {# f9 T y8 k
0 S% C7 d& v6 K( h/ E; \
⑤修改配置文件/ H) G0 x6 l, ]4 y
打开GUI/lv_conf.h
9 n# r; J; E# b* X首先第十行设置为#if 1 使能整个配置文件
6 _/ |) T4 v! _3 \, C/ o/ A找到以下几个宏定义并修改5 L, b, k, B, T5 u
- n* n6 `, k/ J+ _( T! }. z% f
- #define LV_HOR_RES_MAX (480)//定义屏幕的最大水平分辨率. q# v9 l1 s9 ~; E6 G' k5 Z5 S
- #define LV_VER_RES_MAX (800)//定义屏幕的最大垂直分辨率
0 Y- t# m0 H, k - //修改为你的屏幕分辨率即可
! s* h" w& U% _/ Q% M - - e5 a2 L7 s7 H* g2 h+ P7 r
- /* Color depth:4 @& f& X% o( L! _
- * - 1: 1 byte per pixel& d7 ~! r( I" L" X
- * - 8: RGB332, ^ }) `- t) a/ b
- * - 16: RGB5651 M- k# M1 {6 o& X3 G9 j$ i
- * - 32: ARGB88889 g9 z) \; m$ R# R
- */
: j: v) q8 ?- f3 f3 J - #define LV_COLOR_DEPTH 16
; a6 M8 b; C1 o0 v+ j+ f) j+ Q9 f- V2 f - //定义颜色深度,如果是单色屏的话就改为13 d5 W- V: F# V7 E6 C, n
! h% L: W5 W Q) I# V* @- # define LV_MEM_SIZE (30U * 1024U)2 X: p" B- x1 V: v9 M2 j: J
- //给LVGL分配内存的大小,至少需要2k- p! B$ a& `( d2 C3 d8 v" l; N; ]4 b
复制代码 这个文件很重要,控制着整个LVGL的功能,但我们暂时就先修改这几个参数,其它参数暂时保持默认即可。等到我们深入了解了LVGL或者需要用到其它特性时再去研究修改也无妨。
" \# ~$ t% E, r& j6 F然后我们需要修改GUI/lvgl_driver/目录下的lv_port_disp.c, lv_port_disp.h,lv_port_indev.c, lv_port_indev.h这4个文件,这里直接给处修改好之后的文件。! D$ F7 B% X! q8 n& M
?/ {4 }- N9 e4 D3 d$ l% j1 A
lv_port_disp.c
7 t* m$ {' H4 q/ R" n- #if 1
" h5 T* G) O# L; |- `6 d3 ]/ Q' P5 Z, x - #include "lv_port_disp.h"4 K6 q' T0 h$ P! }" i- Y' u. ]
- #include "LCD.h"
I" R3 w1 O9 X8 n( o9 R - #include "dma.h"2 b% G1 I/ s5 Z) U8 W
- & m6 n5 G: f6 E+ M+ \
- #define COLOR_BUF_SIZE (LV_HOR_RES_MAX*LV_VER_RES_MAX)4 y7 |; z8 A4 Q9 m- ~8 l
- //分配到外部 1MB sram 的最起始处
! N4 w1 a$ a" y1 N$ X" m - static lv_color_t *color_buf;6 j5 H! f8 T- v( S
: J0 J i5 o) Z+ i2 W# Z9 h% o2 W7 U- static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);# x. ^: O ^# F* k
- ' m& C( w3 M' g7 y( ^/ m
- void lv_port_disp_init(void)
( C) u2 P2 g$ w. `) i" I4 _8 j - {
1 N% c/ c! x* @" a: X- b- _ - static lv_disp_buf_t disp_buf;+ r' M8 G4 o# T3 ]( N9 T) ?8 k
- color_buf = (lv_color_t *)0X68000000;//显示缓冲区初始化* c/ [* X6 s& W; u0 \0 y
- lv_disp_buf_init(&disp_buf, color_buf, NULL,COLOR_BUF_SIZE);
. t, }: J$ j" L5 l0 H8 I - lv_disp_drv_t disp_drv;
7 \/ Z% H& Y# l- S# \6 [7 F& Q8 i6 E: S - lv_disp_drv_init(&disp_drv);1 n5 |: N1 L) D2 T% W
- / M' ]5 P* a; T2 F1 A R
- //注册显示驱动回调3 W1 B/ ?1 g* B- v( a8 k/ r) s
- disp_drv.flush_cb = disp_flush;( q3 k+ L9 M! P% z/ R( f
-
5 @; A4 i7 p5 H( K4 V% W, N# q - //注册显示缓冲区% y+ n0 N5 Y+ Q
- disp_drv.buffer = &disp_buf;& t8 g+ A) Y2 Q
- . m1 `0 C8 c# H. }, H) o/ N
- //注册显示驱动到 lvgl 中
; B3 n( b( z7 n - lv_disp_drv_register(&disp_drv);
6 N, A$ W1 \( v+ c Z+ ?, U' J; Q - }
, C- {7 R/ `$ Z; c- ?- R) `
* v; s! E4 k8 o- //把指定区域的显示缓冲区内容写入到屏幕上,你可以使用 DMA 或者其他的硬件加速器1 ^/ p4 F& u/ i! ~ ]; H+ H
- //在后台去完成这个操作但是在完成之后,你必须得调用 lv_disp_flush_ready()
( q* J- l/ K1 D+ v) G+ L - static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
2 U9 K7 j* Q9 o' `" z8 q - {9 R8 E9 x0 ]( S/ F. i$ o" l
- //把指定区域的显示缓冲区内容写入到屏幕6 V1 g d d4 h5 C" \- s
- //LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);//TODO
, R4 M. X( N% B9 N c. p: y* w - DMA_Fill_Color(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
- M, b- Z7 c, U% S - lv_disp_flush_ready(disp_drv);//最后必须得调用,通知 lvgl 库你已经 flushing 拷贝完成了
0 F5 k# M$ g8 F7 H7 u" q - }3 n# A4 D! c2 P8 B- Q2 Y
- 4 } P& k3 v) [$ B
- #else /* Enable this file at the top */
6 ~/ M! l* Z/ k# K0 u2 k5 @8 t* x - /* This dummy typedef exists purely to silence -Wpedantic. */
8 o% H2 D0 O* s4 c* ?+ g - typedef int keep_pedantic_happy;, o7 P9 V+ D* m* r& @
- #endif
# u" q6 w5 E2 A5 ? U" B4 |$ H+ j
复制代码 lv_port_disp.h% D2 H P) v* \- K4 `
, H: e6 T8 `* X5 `+ S- #if 1
) K( f6 _* _8 [) ]8 M - #ifndef LV_PORT_DISP_H
, Z- x: A/ ?5 M' B) L - #define LV_PORT_DISP_H% R; v! G6 e( O+ \. t% i$ c: @2 o
- $ m b; |$ B9 y: R) [' n
- #ifdef __cplusplus) Y7 E" p: T4 `( g. \, W
- extern "C" {
& k$ Q% }0 h3 r2 q, k - #endif
` n% ]4 [3 Z7 j. j - " i; E6 X/ i( P
- #include "lvgl/lvgl.h"6 k& S! r! `% [+ A( y" j& I
0 p" {6 ^% T5 e% K! m' I- void lv_port_disp_init(void);
: T$ D7 D# B. ?6 V3 [$ X# v - . D' l, b1 \9 C0 m9 f5 n7 _
- #ifdef __cplusplus4 K8 }% _1 \$ o- X+ \ H
- } /* extern "C" */
^0 l9 b0 Q% t8 R$ S) p# F - #endif
* S4 s5 T7 o# p* y+ a - g$ U D0 e5 j; ^/ q
- #endif /*LV_PORT_DISP_TEMPL_H*/
& h$ J4 [' h. l - #endif /*Disable/Enable content*/
& x0 ~0 {) Y4 G! @1 K8 N* K h2 i
复制代码 lv_port_indev.c" y3 g4 Q( D+ [$ l) w; v
- #if 1
4 G' N% R0 c; d8 A1 ^ - #include "lv_port_indev.h"
/ p0 T9 F E( q - #include "Touch.h"
! S3 m( j, A& k. Q5 m
6 I! E; L) }. x2 j3 l" Z: b. X- static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);0 A" H$ d; o3 G1 r
- , ^' T/ e4 e0 u. c5 m- c
- void lv_port_indev_init(void)
1 h" I1 P8 \& P; y- F4 G - {
; _1 ]0 C. D" K$ d$ S - lv_indev_drv_t indev_drv;
1 V7 y8 }/ M- W8 g0 v R- r
0 l! F @4 H3 s4 E* ~- /*Register a touchpad input device*/
: f; u" U$ f! u - lv_indev_drv_init(&indev_drv);
" U1 S" w& {% B/ N' N - indev_drv.type = LV_INDEV_TYPE_POINTER;
7 L) o; Y! p3 I I9 w - indev_drv.read_cb = touchpad_read;) T! I- H2 f( G9 m
- lv_indev_drv_register(&indev_drv);
6 V# i6 `- C# J- p; U" v1 `) k8 K - }2 z% O( m! ?. j& m2 o
- # T3 n: c) J' v1 M* e7 D
- static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
% w9 e d: _0 `3 K6 X - {! Z7 R5 a# ?8 t# r- H
- static uint16_t last_x = 0;
6 O! Y/ S1 c. S) a - static uint16_t last_y = 0;; @0 z9 N: |# w
- Tp_dev.scan();/ U1 A4 D# r3 a
- if(Tp_dev.sta&0x80)//触摸按下了
4 G# A7 T( p9 V; C - {& u2 Y4 v0 g2 [% Y+ n
- last_x = Tp_dev.X[0];8 n6 I; x$ B& u& K( O
- last_y = Tp_dev.Y[0];//竖屏/ R* U6 h; P- E* Y% ?. D
- $ A: K5 L) W* n+ s0 U; o
- data->point.x = last_x;
+ @0 G: U0 F6 R; K; ^9 ^. V - data->point.y = last_y;
+ J; I5 J# d! y% o/ u/ f& y) l - data->state = LV_INDEV_STATE_PR;
+ N5 @/ I+ Z$ t* ] - }else{
# f: T4 G3 P H9 h$ K) d7 ^+ m - data->point.x = last_x;
5 S3 A9 m' j- \5 n - data->point.y = last_y;
' J0 |9 W D, d5 u7 P - data->state = LV_INDEV_STATE_REL; ]4 }5 o0 u3 B6 H9 }
- }: z3 b0 C1 [/ O. h
- return false;
" ~5 O/ U @% {5 K! s! u- V) ^ - }
5 }# p: Q- C0 i3 D' M( G
$ K5 q" D" N& O- #else /* Enable this file at the top */
3 p F6 Z: p' O( X. B
. d8 Z6 @# ^5 m- /* This dummy typedef exists purely to silence -Wpedantic. */* W% V/ s! x1 v0 k8 B( q$ `
- typedef int keep_pedantic_happy;
1 T2 Y4 f/ C; o2 m; W2 V' U - #endif
" E/ X8 e/ v& w Y9 x
复制代码 lv_port_indev.h6 U+ ~( l- z9 d+ `3 N: D
- #if 1
1 m/ M- r% [. N8 r/ I - #ifndef LV_PORT_INDEV_H1 i7 z1 j8 \! ?1 Z8 _4 U$ V$ C
- #define LV_PORT_INDEV_H: \" Y% V9 W$ D1 M
- 6 A; {7 T% S. y& r7 }, p( w
- #ifdef __cplusplus
1 e0 x& i" \1 Z7 v" _6 z$ x. N - extern "C" {
! w) {% p% X" `# D: h2 d( ` - #endif3 |+ R+ M0 f" i+ }9 D6 N
: y1 `) x6 i+ X- #include "lvgl/lvgl.h"+ @; U% k2 @" E( ]
- ! t9 T' p! `- H/ R z T
- void lv_port_indev_init(void);6 W* }3 Z- F5 u7 U B
- ' x' ?4 t7 G0 g, Y; j0 i S! y( Z
- #ifdef __cplusplus
# C0 d. R- j. H - } /* extern "C" */# U. F" t" l1 ~/ o0 W5 U
- #endif: W8 ~1 Q/ g4 y; n! p! ~1 H, W7 e
$ Q5 F$ ^' T! V4 b. K+ w0 h( Q- #endif /*LV_PORT_INDEV_TEMPL_H*/8 c7 S! a. H( I/ S6 Z1 n0 N
- #endif /*Disable/Enable content*/
- h) i- k2 d' S! q- H' Y
复制代码 ⑥完善main函数* A) I" c' G* Y4 w) V* L1 w
至此,移植工作基本完成,接下来只需要完善main函数,初始化LVGL,便可放心玩耍。
) l6 K2 Y" O- q修改main.c如下:* `, t2 j8 d* O
5 ^6 J9 Y+ ?2 a" n2 w- #include "include_config.h"! K- A/ B( I+ E) B( }5 H9 `
- #include "sys.h"
! q1 r' i0 b1 Q$ b5 z" n - #include "delay.h"
K2 l9 R( A6 s ~0 D/ ^( j - #include "dma.h"
5 x' I8 D+ A2 m& X/ Q( Q - #include "lvgl.h"$ i1 h0 ]$ o- [$ ]* j
- #include "lv_port_disp.h"
+ f" B) H4 D9 v8 I - #include "lv_port_indev.h"
8 e; G1 _5 v$ f" p( c$ V - - I$ c7 f/ J& ]& o
- void tim_lv_tick(void);
+ h$ F9 W% _ U0 D P - void lvgl_first_demo_start(void);
3 F; a1 }' o5 _, V- I( I - void setup()2 \& A9 k, k- F; ?3 t: ~0 E
- {
$ i( L2 u! l; w* ?& ?3 f* m - JTAG_Set(SWD_ENABLE); //关闭JTAG,只用SWD
) d( F7 k. L3 P# l$ { - delay_init(72);& W D5 ]- z5 q$ k: v9 O
- led_init();
8 g5 R$ n, c, n3 P" l5 b4 t - myuart_init(USART1,1152000,PA(10),PA(9));
. P) r8 ^5 w, d; A1 F+ v - Timer_InitDef TIM_2_conf=
% w- H: A d; _ - {/ a* x( ~6 d" [6 {& B
- .TIMx = TIM2,
9 S0 g' Y2 y- o) S - .ms = 1,1 @- F' N* n o; _( u
- .NVIC_Priority = 0,
! Z5 K5 o$ a% } - .event_handler = tim_lv_tick,1 ?9 v. l% i8 ~( m# @
- };% u( l6 Y/ k# |) F1 e: l
- timer_init(&TIM_2_conf);
8 v' G; ]4 _) N9 @. |/ g2 \7 Y -
6 ^( g o/ ^- G1 I9 t+ v2 u - LCD_init(BKOR); //默认竖屏
5 |- V0 D( w! B* D3 a6 ~ - if(Tp_dev.init())printf("Tp_dev failed!\r\n");
/ c8 `$ o9 z, B1 u! h, B! v - else printf("Tp_dev success!\r\n");6 V5 y* W+ R6 s2 q( {- F9 X' Q
-
6 i/ ^8 N5 O6 }0 l5 n# r, Z - FSMC_SRAM_Init();
6 y5 G2 ]# a7 U - DMA_fsmc_Init();3 @8 p& x7 h- v# J ~ [( t" ~
- }, W: Z8 f: K# D) D, g: d
- ! \' b B, B K6 K, D- t7 J
- int main(void)
0 P5 B& I2 o6 f) ?, P# N - {
/ ~; b; o$ S' [/ x - setup();- s( O# z2 c! i% ?8 Q
- printf("set up success!\r\n");
% j8 h4 J; x6 E4 R: f0 I- K( I
( n# E) K" \9 k2 ^! T; w$ T4 d V- lv_init(); //lvgl 系统初始化
, k+ F* C, V# f$ Q1 t( m; a5 T/ T - lv_port_disp_init(); //lvgl 显示接口初始化,放在 lv_init()的后面
5 w5 \# M, b! g - lv_port_indev_init(); //lvgl 输入接口初始化,放在 lv_init()的后面( r, x( X# m: B+ {9 X: q
- % j- v% y `$ p4 l
- # F2 {8 q1 f1 K, N
- lvgl_first_demo_start();: B! Z4 b: ?; R6 q+ d
- while(1)
$ F) }( D+ ]* } - {, }0 }& N5 d' t( H3 W* W
- lv_task_handler();! a! W7 c0 J# k2 ~: t
- }. ~9 | t) S3 o3 V7 x
- }
% Y5 R3 R5 A7 p$ n+ b - & z% i- |1 \+ c! l
- void tim_lv_tick()6 a; h. d' x3 o' K
- {- a4 a1 |1 o8 D! B
- lv_tick_inc(1);//lvgl 的 1ms 心跳
' ^- J1 a# [6 o9 `; G - }
! Q$ o/ c" }( {% c3 f
9 f @0 m3 h) |3 L* v) r
' S- B4 l [1 W: P, `5 Z% m- static void btn_event_cb(lv_obj_t * btn, lv_event_t event)
& B A! i9 `1 D2 w - {5 I: m! z8 D2 V k
- if(event == LV_EVENT_CLICKED) {. k8 @, j! a& L( G9 t
- static uint8_t cnt = 0;
0 k9 a3 ~ ?. P - cnt++;* t( ~% U, ~+ E
5 f7 e$ E6 I1 O8 p# t6 s. j) J1 ]- /*Get the first child of the button which is the label and change its text*/* p' J) r2 w! {, A# O
- lv_obj_t * label = lv_obj_get_child(btn, NULL);
( j- S/ a, |( F5 D/ d5 S+ G0 U. P - lv_label_set_text_fmt(label, "Button: %d", cnt);
5 a6 L$ L8 C4 p k% c0 Z0 Q - }
( ^! a( M2 b g% |4 O - }
0 z7 w" P" [* k6 k% `5 ]. \ - void lvgl_first_demo_start(void)1 x. I9 o l; B7 M
- {
# _( } q! l" |& [& o9 C0 g, R - lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL); /*Add a button the current screen*/. w B- F, H' f( c; k' P! i
- lv_obj_set_pos(btn, 10, 10); /*Set its position*/1 L, P. ?# C" d5 V* m7 ^
- lv_obj_set_size(btn, 120, 50); /*Set its size*/
# o! f+ y- D7 @& E- w; t9 U - lv_obj_set_event_cb(btn, btn_event_cb); /*Assign a callback to the button*/$ c D A% g+ T4 k
. @: L) N( F- {$ G- lv_obj_t * label = lv_label_create(btn, NULL); /*Add a label to the button*/
0 {2 |9 [- ]2 G: v* | - lv_label_set_text(label, "Button"); /*Set the labels text*/$ K1 l3 V2 P p, S+ l3 a; ^& v9 T6 P
- 1 v- h0 d E; P1 E: J
- 8 U( O- o1 ~5 O7 f- D1 ]: E
- lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);4 u* H: \& V4 z2 ]
- lv_label_set_text(label1, "Hello world!"); ! g* F1 u. h; ^, _5 w
- lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);
# R( X" O, s) d) e. O/ n' ` - lv_obj_align(btn, label1, LV_ALIGN_OUT_TOP_MID, 0, -10);0 k: V& R+ o+ Y
- }- h% d* W9 P4 ^& x" L2 b
复制代码 ⑦编译并运行
5 v {6 f6 N8 H4 M设置编译器版本
" T; [. w. r+ ~; v* U1 W' F
; N9 g1 t+ u2 u; G n注意:这一步应该在移植之前就应该执行,模板工程也使用V6编译器编译通过,免得移植后再编译各种报错。这一步非强制的。
1 `0 c. ]; P7 S6 k0 o3 \
2 f: g4 R$ T( l+ K( o. [设置编译参数
U% ~+ w7 N: ^C99:LVGL要求 C99 或更新的编译器,否则编译是会报错的7 ~+ T4 @& H# g' ?3 m) J
勾选Execute-only Code, 会自动去除未使用到的代码函数,大大减小了编译后的固件大小,建议勾选,其它默认即可。' {. F+ @) G$ i: Y7 Z9 G- \
- m2 [, f0 m- S& o3 h3 Z; S4 R9 Q6 p2 M6 @8 S4 Y+ c2 p+ T. S: q
编译 y1 \9 Z" H) A2 b4 k- k
烧写运行3 l4 |, Q! z6 u3 L
效果图:2 o3 O7 f) J! D( f" x6 Y
0 b5 \! m8 @7 L. X五、总结* b4 t) d9 d; K( }* }
到这里移植工作已经结束,接下来我会介绍一下移植需要修改的那几个重点文件,假如你要移植到其它平台,请看这里。
( Z& a- I- g6 Y8 Z+ q8 }! K1 }
2 _% t- p h7 b- j; \6 Q. T# G; v3 o+ F) a+ t* Z& b1 d+ f6 w- @
lv_port_disp.c8 ~+ e( b: @3 H! F* u
- //定义显存的大小
9 i3 l& U0 [* ^1 y' U, ^' @ - //我使用了刚好一整块屏幕的显存
3 X. L! q0 F% ?8 Z6 V T# B4 D i! N - //如果你的内存没有那么大,那么定义10行的显存也是可以的 LV_HOR_RES_MAX*10+ }; l" \. h; _5 S2 u
- //LV_HOR_RES_MAX,LV_VER_RES_MAX定义于lv_conf.h
u& h- j4 q+ s8 ?! \ - #define COLOR_BUF_SIZE (LV_HOR_RES_MAX*LV_VER_RES_MAX); l3 `6 n6 v M. h8 S
- * x7 R2 M4 H& l+ R5 Z
- static lv_color_t *color_buf; //定义显存指针" w! Z6 _' z5 |4 H: z5 R
- - ^# k, h1 w( s3 \" a
- 3 c/ l5 o' M* A3 h6 t0 V8 r* H
- //lv_port_disp_init函数
. s6 @% |9 ? f" D - //显存初始化,将显存指针指向我指定的内存啊区域
, P+ @5 E4 y) Q4 ?; Y - //0X68000000是我板子上外挂的SRAM启始地址
, I. b. Y( A6 e - //显存需要根据你自己的板子合理分配; F* V' z3 n7 l2 }7 @2 e& L
- color_buf = (lv_color_t *)0X68000000;
9 c$ m7 s3 t8 V' X4 t% E
. e- @' ~! a m0 E9 b8 q5 y, _( |- * X# }; l9 C$ r }% r; p( F2 v
- //把指定区域的显示缓冲区内容写入到屏幕上,你可以使用 DMA 或者其他的硬件加速器
! w; c2 U- z& m9 y4 ]1 d - //在后台去完成这个操作但是在完成之后,你必须得调用 lv_disp_flush_ready()7 c5 o. f. s/ u3 _& [! _1 [
- static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
0 b, x6 S' [7 Z* S - {! U$ d! a# V0 u' y) V
- //把指定区域的显示缓冲区内容写入到屏幕4 v) n1 j0 F, X4 T: l
- //LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);//TODO
- y: _. k8 c% n L4 j; W7 o - DMA_Fill_Color(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
/ t* }% k* f# [ - lv_disp_flush_ready(disp_drv);//最后必须得调用,通知 lvgl 库你已经 flushing 拷贝完成了
' K' ^7 ~" z5 j' f' B5 c _ - }
4 |. e, v5 G- v2 ] - //DMA_Fill_Color或者LCD_Color_Fill函数需要根据你自己的平台实现6 U- { J8 D6 Y- r7 V; T( H$ @; o/ M
复制代码 lv_port_indev.c
6 g- C4 P1 } B7 s: Y& d8 _
% n3 k% c g/ N- static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)" J# P: ` P3 k( i) G; P
- {
& R$ k7 x% m2 O! `7 w% L% \ - static uint16_t last_x = 0;/ d# `* {+ G) V8 Z+ k- a0 C
- static uint16_t last_y = 0;) z* `' H) h& W8 I! M
- Tp_dev.scan();
! U9 B( e G% z$ E4 X- x - if(Tp_dev.sta&0x80)//触摸按下了
# s5 A: f7 b" d% Z - {0 @3 Q' ~2 {4 S" T! a8 i! s) d
- last_x = Tp_dev.X[0];
6 A- _) @, V+ s: G" f - last_y = Tp_dev.Y[0];//竖屏
2 t+ L8 H3 A+ k; T @ -
. J! [/ V! A( H( m+ d Z+ k: T O - data->point.x = last_x;
7 H* n4 c7 l1 F# X& d% s - data->point.y = last_y;
3 H' p1 T& M y5 f2 N* j# p% j - data->state = LV_INDEV_STATE_PR;
) n0 \( L- a5 s, f% {, x4 R; O# } - }else{
4 [: u( g) \- m$ { ~* T - data->point.x = last_x;* z/ T9 c0 Y8 P5 `) H% b5 k
- data->point.y = last_y;
' |: H" H: D, P. O; j- }0 ]6 J - data->state = LV_INDEV_STATE_REL;
4 R t) w& x2 D% Z4 o - }: V3 N0 q% {+ G# R6 s
- return false;
& n9 \8 ], y$ T1 d* W - }
7 k+ K W N8 @- W+ ^+ H2 \ - //Tp_dev.scan() 是触摸屏驱动层的函数
3 Y5 ^, `7 D( c% a4 D. R2 b - //其作用是扫描整块儿屏幕,得到触摸状态写入Tp_dev.sta中,如果有触摸点按下,得到其坐标并存入Tp_dev.X和Tp_dev.Y数组中* g7 Y1 D$ a% v: t+ h2 [5 t5 ]8 Q
- //因此这个函数需要根据你自己的板子和屏幕提供
: u8 ^0 d) U3 b% X# Q
复制代码 六、源码
' N! C3 c7 s" k, L2 M9 Z本篇文章中移植的源码
3 {, R: G' w! V' U' E6 {移植前的工程模板:https://github.com/jesons007/LVGL_7.1.0_STM32/tree/Blank_Temp2 o9 z% o# _% p
移植好的代码:https://github.com/jesons007/LVGL_7.1.0_STM32
! L1 w8 o$ c! @
; x& o# b: ^8 z6 z# m6 g8 ]2 N' Y4 M9 q) G& Z6 |* s* G
+ R& I; o2 A' [
|