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