你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】移植LVGL到STM32F103

[复制链接]
STMCU-管管 发布时间:2021-6-7 17:39
一、前言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 F
4 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
1.png ; u$ P/ w; ~2 [9 z- ]) [
打开下载好的文件夹8 E( j( I+ c2 \: A# L. o8 x
2.png
& 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
3.png
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
  1. //从GUI\lvgl\examples\porting\目录拷贝:
    0 \, ^- m0 ]) t* ^* P
  2. lv_port_disp_template.c
    * O% N2 c) }+ z& B
  3. lv_port_disp_template.h
    $ }6 n$ `9 O: H; h: E
  4. lv_port_indev_template.c
    ( l4 K! t9 k+ j/ x
  5. lv_port_indev_template.h" G; _( h) H, ~
  6. // 到GUI/lvgl_driver/目录下并分别改名为:& l* A$ H' M  ?$ M
  7. lv_port_disp.c
    " n- o% w! Y# R- ^/ L5 N6 b5 E
  8. lv_port_disp.h$ C7 x7 d2 g5 u2 N
  9. lv_port_indev.c
    % @  h1 c9 N  `& Q0 ?2 f3 I
  10. lv_port_indev.h. }8 E( S5 |+ B; G6 F& d
  11. 1 o3 N: m; X% n' y% w2 L" S: ~3 K( W
  12. //添加:2 R* A8 W' A, k& w& `
  13. dma.c
    / l! |& n; B! j  c. d
  14. dma.h; |. H  U4 g; \% h& S
  15. //这两个文件时我编写的使用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
4.png , Z) T2 n! C2 Q7 Q, w4 L0 n
5.png 6 M: P* ^$ h5 t) X1 ~3 Z
6.png ) 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: ]
  1. GUI/lvgl/src/lv_core
    4 o6 I2 i/ B, |( |' z2 g* r
  2. GUI/lvgl/src/lv_draw
    2 i& n: D$ v' E  v
  3. GUI/lvgl/src/lv_font
    5 D* y, I! J8 N; Y
  4. GUI/lvgl/src/lv_hal7 u2 V' S7 O# @
  5. GUI/lvgl/src/lv_misc
    0 S6 N7 w5 T9 P: Z# ^
  6. GUI/lvgl/src/lv_themes
    # b( E/ n9 `2 v; o8 p" D' K
  7. GUI/lvgl/src/lv_widgets; P4 L9 z' E: {4 z$ D. I) `
  8. //注意不要添加 GUI/lvgl/src/lv_gpu中的文件,除非你用到了相关功能1 v8 H: B' k( J, o8 `1 j
复制代码
在GUI_drv组中添加以下.c文件:
0 W- a! x+ x! ?9 ]+ M
  1. GUI/lvgl_driver/dma.c  P. O7 w: k8 I  g, B0 K: ]9 X
  2. GUI/lvgl_driver/lv_port_disp.c
    4 N7 i4 [+ _( q' {: y0 B
  3. GUI/lvgl_driver/lv_port_indev.c
复制代码
④添加头文件路径
. r0 A. H- b, W2 |; C 7.png 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( ]
  1. #define LV_HOR_RES_MAX          (480)//定义屏幕的最大水平分辨率
    7 K1 W* J. Q% x6 ]% M
  2. #define LV_VER_RES_MAX          (800)//定义屏幕的最大垂直分辨率& K5 L! r/ t  y
  3. //修改为你的屏幕分辨率即可
    ' s+ [; w  F' K, L% T% L

  4.   J5 C! t/ e7 q% F( B! V
  5. /* Color depth:
    $ g3 F, Y, w1 s, v$ U) Z0 {
  6. * - 1:  1 byte per pixel
    2 S  T% k' p& u5 {( H
  7. * - 8:  RGB332! ~' `) I% w% k; o3 z" C
  8. * - 16: RGB565
    8 ?# \3 X. T' g( @, v
  9. * - 32: ARGB8888
    ; U( @8 s, e/ G5 A6 V+ y; R
  10. */% q4 g* }' X: Z# w2 `! K
  11. #define LV_COLOR_DEPTH     16
    5 M1 P6 t7 H: p$ K
  12. //定义颜色深度,如果是单色屏的话就改为17 d+ M3 |2 `# a

  13. 2 h, V3 @- m7 M" b4 D
  14. #  define LV_MEM_SIZE    (30U * 1024U)
    1 g4 R! Q( l( x( @
  15. //给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
  1. #if 1
    $ q9 r4 u  f0 H# W6 \3 z( T9 S* A; t* E
  2. #include "lv_port_disp.h"
    % c( p& i' {! t7 W
  3. #include "LCD.h"
    , g. W. \5 v9 a2 y' U# {1 }
  4. #include "dma.h"5 S) R5 c! F+ f4 F- T, z5 [

  5. ' z/ `* ~- e' R. o
  6. #define COLOR_BUF_SIZE (LV_HOR_RES_MAX*LV_VER_RES_MAX)
    " G/ a2 \* v# b: |# f
  7. //分配到外部 1MB sram 的最起始处
    . }4 ]! k7 E$ v% R  V+ o% D
  8. static lv_color_t *color_buf;
    ' |9 D3 h# u; M# M4 L6 l6 X
  9. 2 @# V+ j& c5 D
  10. 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

  11. 6 p' K7 z, G; f/ u, ?" X
  12. void lv_port_disp_init(void)
    % r5 K" {9 Y! p5 ^1 `
  13. {/ Q3 k( z! B# }; C0 w, J/ z- ]+ P4 `
  14.     static lv_disp_buf_t disp_buf;
    2 G/ c; ?' r4 ^- }3 \6 k. X
  15.     color_buf = (lv_color_t *)0X68000000;//显示缓冲区初始化, N9 C* u6 j+ ^, m' F
  16.     lv_disp_buf_init(&disp_buf, color_buf, NULL,COLOR_BUF_SIZE);
    / }$ A& y. ?, I# `
  17.     lv_disp_drv_t disp_drv;" h! \8 w0 ~; m& X% a! X9 p; y
  18.     lv_disp_drv_init(&disp_drv);4 V& s1 ~; H5 w: M3 l
  19. $ ]* r- C0 v' o# g/ \
  20.     //注册显示驱动回调
    4 a: s' m# b6 P$ E
  21.     disp_drv.flush_cb = disp_flush;
    0 c: d( S, }# b& \. x
  22.    
    " w$ i- v) T9 h6 @: Y  X! ?
  23.     //注册显示缓冲区9 A" N( p* B1 i! h+ T1 ]
  24.     disp_drv.buffer = &disp_buf;4 ?* J2 t4 I, J% g9 o
  25.     9 E3 T: `( U. R
  26.     //注册显示驱动到 lvgl 中
    4 s& {$ \/ O+ @* P, K
  27.     lv_disp_drv_register(&disp_drv);3 p4 g: s" Z; w
  28. }. s( G$ \( d0 q
  29. & [) t5 x4 N7 I$ ?! l' z6 t
  30. //把指定区域的显示缓冲区内容写入到屏幕上,你可以使用 DMA 或者其他的硬件加速器
    + z" V/ i, L. L
  31. //在后台去完成这个操作但是在完成之后,你必须得调用 lv_disp_flush_ready()
    & B- K: p- G5 Y* ?
  32. static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)' g8 F4 g0 w( {
  33. {
    $ ~( g( O+ s' G
  34.     //把指定区域的显示缓冲区内容写入到屏幕
    4 b7 b' d% W, n1 ~3 \
  35.     //LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);//TODO# \1 j6 ^; F/ D. \, W$ z
  36.     DMA_Fill_Color(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
    % I) g- \8 ^5 @! i
  37.     lv_disp_flush_ready(disp_drv);//最后必须得调用,通知 lvgl 库你已经 flushing 拷贝完成了
    1 C. @' g4 |9 _
  38. }
    0 H; Z' A/ k5 t3 f

  39. 5 n% P8 z8 f9 W% j6 A6 n& r
  40. #else /* Enable this file at the top */  S$ c$ n1 F+ k, q9 o% {  a
  41. /* This dummy typedef exists purely to silence -Wpedantic. */
    : c$ Q; p' e7 u/ A8 t# A
  42. typedef int keep_pedantic_happy;
    . r" l" J/ w2 c1 c$ q
  43. #endif
    * b7 l! B* @7 L7 a. B* `
复制代码
lv_port_disp.h) Y# R1 x; C& d
" E8 y1 _' `. _/ H! x6 f
  1. #if 1
    2 k( Z* @9 m% n/ N7 L) [. w: K
  2. #ifndef LV_PORT_DISP_H
    $ j& f3 |, h( O  g
  3. #define LV_PORT_DISP_H
    + |2 I; u( X; N! H
  4. ) f" d: X& b+ |" W0 ~
  5. #ifdef __cplusplus
    # X* X) H1 i2 ^4 P" p
  6. extern "C" {9 `- y; b: s8 e, A
  7. #endif
    ) g! r6 Y: w, X. Y: Z! z' F
  8. 8 o' t0 q$ `+ S0 i
  9. #include "lvgl/lvgl.h"
    $ V( c5 M9 D: R2 g! P- e
  10. 4 l' R& i5 Q" U0 N. b/ G# [
  11. void lv_port_disp_init(void);
    % `/ p/ c4 R# v+ y: P
  12. ) c+ U: i6 g- \; b4 z+ u
  13. #ifdef __cplusplus" e- ]3 C+ F7 A. l
  14. } /* extern "C" */) s! \: B, ~; m; U* q
  15. #endif
    ; l7 p5 C, H* a# [

  16. : x- J- o( _/ ^' q* U5 o- c/ Z
  17. #endif /*LV_PORT_DISP_TEMPL_H*/
    . S4 ?: L/ Y; X2 \
  18. #endif /*Disable/Enable content*/7 h& u3 I( o0 ^2 V. x* X
复制代码
lv_port_indev.c
! R  k: ?! q! d% z! t
  1. #if 1
    ) j. }$ u8 O! u% T. T* ^0 M, ~: K
  2. #include "lv_port_indev.h"
    0 _* _% [( x+ ?, S2 v- r
  3. #include "Touch.h"
    1 O9 b( }: C; f4 C/ k. _: d0 A+ k

  4. # }" I; ~8 L& {! {" ^; D7 ]$ G
  5. static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
    - j; H& ^" G. n* q
  6. ; i" r0 `  _3 S# C  P% L! y5 n9 g
  7. void lv_port_indev_init(void)
    2 G: C: c5 \& A: l6 F
  8. {) j1 o" L8 a6 ^. \7 l
  9.     lv_indev_drv_t indev_drv;
    4 d2 A% G0 L1 Y- Y) p' Y1 ?

  10. ) i( e! u, N6 ?3 w- h
  11.     /*Register a touchpad input device*/
    0 H. @" e2 \- G. }: B8 b
  12.     lv_indev_drv_init(&indev_drv);( y  }; P0 G+ Z. K
  13.     indev_drv.type = LV_INDEV_TYPE_POINTER;
    ( M7 V* `7 b# \
  14.     indev_drv.read_cb = touchpad_read;) E' w; V- o3 V( A3 P; l9 A
  15.     lv_indev_drv_register(&indev_drv);! G% S; t; D7 v. w7 x4 g) f% K$ Q
  16. }
    , Y. l9 d6 G7 @, O. L5 v4 K

  17. 4 j8 q3 u/ D5 e5 }" |+ c6 ?
  18. 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; ?
  19. {+ z% o9 F6 ~  {% N
  20.                 static uint16_t last_x = 0;8 D- o& @  c* n2 F+ H; l
  21.                 static uint16_t last_y = 0;& w; ?& \5 }7 k  U' S6 w9 \
  22.                 Tp_dev.scan();" Y& q5 n' X! x) U% G: S
  23.                 if(Tp_dev.sta&0x80)//触摸按下了
    $ V" l3 V" n; t5 C2 L$ r
  24.                 {! S5 E% r$ \+ Z; R* |" q' F2 P
  25.                         last_x = Tp_dev.X[0];3 P- `8 s* R! ~7 d5 t
  26.                         last_y = Tp_dev.Y[0];//竖屏0 y7 z- s% X5 ]# _( A; E, s
  27.                        
      L7 U* [; ]5 _& C6 j; z
  28.                         data->point.x = last_x;; o+ k& [0 L( G. b* D! C$ z7 a
  29.                         data->point.y = last_y;
    5 Y; ^4 `4 w- f4 `
  30.                         data->state = LV_INDEV_STATE_PR;
    9 j. j# U$ o! _' f% l: m9 {7 m2 P
  31.                 }else{
    . z3 F9 L. u: S/ e
  32.                         data->point.x = last_x;$ W/ X3 c  I$ D; U1 e% W8 p; g( t: m
  33.                         data->point.y = last_y;: @5 u/ g% \9 f" N  }' H/ I7 h# v+ P
  34.                         data->state = LV_INDEV_STATE_REL;
    6 @7 n* @% X: V" G8 _' Q1 [! x
  35.                 }
    0 K* m# o! e; w" B6 `8 i
  36.     return false;6 k  w# j+ r0 S4 U) K7 W
  37. }( L# f% A2 m* W' V

  38. 6 Z: W8 e5 ?+ s! o2 N
  39. #else /* Enable this file at the top */- J$ G% ?0 t" G0 \6 }: p- C

  40. 5 |$ C7 L& e  o% W8 c! [
  41. /* This dummy typedef exists purely to silence -Wpedantic. */
    - F; C& }+ P+ a- p
  42. typedef int keep_pedantic_happy;
    / r5 r3 V# A4 Z
  43. #endif
    2 h- x* W. i5 w1 _. j2 I0 i* S
复制代码
lv_port_indev.h
( Q2 }. X& c9 y' M
  1. #if 1
    # H% m& x8 g; T5 i
  2. #ifndef LV_PORT_INDEV_H- s+ \; v& m& g
  3. #define LV_PORT_INDEV_H
    * L( {% f( Y9 u4 p  B" P
  4. $ V( H! }5 `' g
  5. #ifdef __cplusplus
    2 f6 K: G# [& k
  6. extern "C" {
    , S- H9 p9 z7 C
  7. #endif7 s6 Z, u- ?& R* C0 q0 x
  8. & S$ u$ X- c$ p; A0 q9 w
  9. #include "lvgl/lvgl.h"
    % O0 F# l' t0 Z$ w

  10. & G) ^  s. m( j5 s( p/ g* {8 A% u
  11. void lv_port_indev_init(void);9 C) f  f/ e) S4 w: o
  12. 0 K) n5 i1 d( O; ]  @, A& P1 y
  13. #ifdef __cplusplus
    " @5 E  J/ j% E5 \0 N
  14. } /* extern "C" */1 B" E5 K& p! I7 _
  15. #endif% i% Y2 R. K# v" s  x- v, p

  16. 1 v/ L  i7 m+ `% j2 x5 I! i1 \
  17. #endif /*LV_PORT_INDEV_TEMPL_H*/
    3 l: v$ Q, Q3 T/ d0 L2 ^$ Y, I
  18. #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
  1. #include "include_config.h"9 `7 R- i. {" c( x0 i  z
  2. #include "sys.h"
    7 |* d, E* D# g. T+ d) ?; L
  3. #include "delay.h"
    ; L' Z' X' Z0 e& q" y. l/ {$ X
  4. #include "dma.h"
    9 y" M! C: M4 t' h, ~5 x8 \, w1 u
  5. #include "lvgl.h"5 T& N) [& ?. s$ h
  6. #include "lv_port_disp.h"
    + ~$ K% j& J1 d# M6 {1 L9 }# z
  7. #include "lv_port_indev.h"6 Z( C( z8 u$ S7 I. J

  8. 6 x* o( L" k7 T; T5 G9 I9 l% v
  9. void tim_lv_tick(void);0 r: Z9 ^, K/ m8 S
  10. void lvgl_first_demo_start(void);
    2 y: I0 y, k" N* z: ~* ~3 ^+ z+ i
  11. void setup(): q: M/ M, i5 K" @0 r
  12. {
    4 \- B* r% Z; k8 l5 ]4 r
  13.         JTAG_Set(SWD_ENABLE);   //关闭JTAG,只用SWD& O1 W' j& |. s  |* l0 F3 {
  14.         delay_init(72);
    , `0 h, J. _7 P
  15.         led_init();
    8 H" Y5 Q* M( C0 z2 k
  16.         myuart_init(USART1,1152000,PA(10),PA(9));
    9 d  |. o) {2 T& d$ c! ?
  17.         Timer_InitDef TIM_2_conf=. v3 S1 C& x' o( [) n8 c
  18.         {
    0 L) Q: H+ s* c8 ^5 P
  19.                 .TIMx = TIM2,
    & F. w* p' l3 B+ u. v) z! f
  20.                 .ms   = 1," h; d9 O0 G& U8 \3 J
  21.                 .NVIC_Priority = 0,
    % {3 [$ n6 W4 m2 f
  22.                 .event_handler = tim_lv_tick,
    " U7 F5 x' {/ s  A$ a
  23.         };
    $ t  v  b! M+ m7 `6 \& ^
  24.         timer_init(&TIM_2_conf);  z! P9 @( U$ y$ L9 ~
  25.         # C! d: q( d4 q' b9 m# U
  26.         LCD_init(BKOR); //默认竖屏+ H7 L7 K7 Y( z
  27.         if(Tp_dev.init())printf("Tp_dev failed!\r\n");7 M% V% f8 l* D; Q. t
  28.         else printf("Tp_dev success!\r\n");
    1 p+ q3 W" m- \
  29.         4 f9 M" J0 L) N6 e) J+ k( J1 P, H2 G
  30.         FSMC_SRAM_Init();
    . e* \2 @' P2 j- x$ @( }; F3 I2 X
  31.         DMA_fsmc_Init();/ e+ u$ _, f% e) W  K( [- ^
  32. }
    ) B6 Y) K: e8 F) Q5 S( r

  33. 8 c& {3 m5 m" k5 F8 }+ E! M
  34. int main(void)
    ' a  m0 i4 K- t% `
  35. {                       
    2 j( G4 G! C* D4 T: Q8 h8 B6 v
  36.         setup();" w  t- Y; q3 [' v* w3 t
  37.         printf("set up success!\r\n");
    2 i2 I. f2 P6 x: k! L- l" M- K
  38. 4 b- Z8 z: Q2 V! x4 [& R9 U
  39.         lv_init(); //lvgl 系统初始化
    # a1 H+ G( O$ K  v/ B8 w; e" i7 Y
  40.         lv_port_disp_init(); //lvgl 显示接口初始化,放在 lv_init()的后面
    : v* V6 s* L8 Z) f
  41.         lv_port_indev_init(); //lvgl 输入接口初始化,放在 lv_init()的后面; K" Z! j9 v$ h4 u. g
  42. & [- E  o2 W7 o- z/ m3 I

  43. ) ]% `! _, J% P; P
  44.         lvgl_first_demo_start();, S# T& }2 U2 R' ^0 p, Y0 c8 t$ S
  45.         while(1)7 k8 M% O. P/ _) l& x, u& J6 @
  46.         {
    - C, U7 q8 u8 K1 D8 m# F
  47.                 lv_task_handler();9 {% [+ S5 k3 r% L, Q; s
  48.         }
    ! t! o! Y2 o4 x! {+ B
  49. }. {  N: K2 g% [# P3 c# e$ I  v; O

  50. ! h3 Z) d% t, T4 S3 D* j# F: z7 e5 H" u
  51. void tim_lv_tick()
    2 u$ u* }2 P( i  }4 V
  52. {! H' ^$ d5 _6 T; b- P6 \$ T
  53.         lv_tick_inc(1);//lvgl 的 1ms 心跳, g$ l  U% I, R
  54. }. G, i- j% T4 E, o8 W# F' b. U
  55. . }3 s. S1 |* O2 Z' J2 @

  56. 6 p% q) y8 k  \' }% @
  57. static void btn_event_cb(lv_obj_t * btn, lv_event_t event): ?! k& }( h  j$ [1 C& m: ]: i+ v
  58. {) L) v3 [& |* q+ u( y
  59.     if(event == LV_EVENT_CLICKED) {
    ) ~* r; l: |5 [6 m2 |3 S
  60.         static uint8_t cnt = 0;$ }6 V' E' L$ J8 @
  61.         cnt++;
    2 l3 w& w/ @- X, b" f

  62. 1 u  {# o7 o/ A8 ^% K' J  S
  63.         /*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
  64.         lv_obj_t * label = lv_obj_get_child(btn, NULL);
    9 d/ n. o6 k1 J- B
  65.         lv_label_set_text_fmt(label, "Button: %d", cnt);
      u* e/ ]. o) T4 E
  66.     }4 [2 G, H: X" N% d
  67. }( H, c9 O2 N! d( Y" Z
  68. void lvgl_first_demo_start(void)
    ) C# u  N% W8 R0 U/ z5 ^
  69. {4 k3 @% Y3 S4 B; [. j) p
  70.     lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL);     /*Add a button the current screen*/
    , Y5 H3 x7 _- n( g
  71.     lv_obj_set_pos(btn, 10, 10);                            /*Set its position*/# P6 h2 B4 y9 Y! o8 t7 |
  72.     lv_obj_set_size(btn, 120, 50);                          /*Set its size*/
    ! w7 O1 o# U$ Q
  73.     lv_obj_set_event_cb(btn, btn_event_cb);                 /*Assign a callback to the button*/
    5 Q, z0 }7 Q! ~: d
  74. + q( p* p6 b7 Y. e- n
  75.     lv_obj_t * label = lv_label_create(btn, NULL);          /*Add a label to the button*/
    5 K' w$ l3 L0 q; B) h5 C
  76.     lv_label_set_text(label, "Button");                     /*Set the labels text*/8 ?8 r& ~2 W( @

  77. " y/ w+ D' i  @) m6 p. o
  78. ! K/ E/ N: t/ M, F0 a! V! I
  79.         lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
    , m- x; a0 S7 D& T- S, o/ u* g) F' h
  80.         lv_label_set_text(label1, "Hello world!");
    ! }/ K. Z; {; a+ ^* S$ D$ Q
  81.         lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);- M1 L4 g3 F8 s; G
  82.         lv_obj_align(btn, label1, LV_ALIGN_OUT_TOP_MID, 0, -10);8 }4 F0 E+ h* v& D5 Y0 B) v
  83. }+ |5 Z* O: x: j4 J5 g; A! j
复制代码
⑦编译并运行2 u; U2 m* Y/ J' l
设置编译器版本' G% C# c$ \( a9 s
8.png : y& S: }6 `+ @* o& a
注意:这一步应该在移植之前就应该执行,模板工程也使用V6编译器编译通过,免得移植后再编译各种报错。这一步非强制的。7 T/ w( U( E: W4 U' \. h7 T4 o9 ?
9.png
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
10.png ) 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
  1. //定义显存的大小
    & @' f# [, @9 E3 y( R3 D* M
  2. //我使用了刚好一整块屏幕的显存
    8 b- e- E, b0 A
  3. //如果你的内存没有那么大,那么定义10行的显存也是可以的   LV_HOR_RES_MAX*10
    4 N. q! U3 c# L7 A7 E/ ~
  4. //LV_HOR_RES_MAX,LV_VER_RES_MAX定义于lv_conf.h
    & q& }( F) u$ E5 i# x9 d
  5. #define COLOR_BUF_SIZE (LV_HOR_RES_MAX*LV_VER_RES_MAX)
    ) a+ `) B. }' ~$ Z1 }' _

  6. & B; a; a+ T( y, V" ^4 T6 S- r
  7. static lv_color_t *color_buf;   //定义显存指针* y$ d3 V) ~; q7 R1 |- S

  8. 6 I, Q) [8 L8 }+ T. M) d

  9. 8 F: M' E% _) F/ {5 J
  10. //lv_port_disp_init函数( O1 L/ x; Z9 x5 y' p1 T2 v
  11. //显存初始化,将显存指针指向我指定的内存啊区域  _3 \* i8 R# i
  12. //0X68000000是我板子上外挂的SRAM启始地址, z; F" ]! F/ X' t/ }
  13. //显存需要根据你自己的板子合理分配
      K1 A  T% ~7 A1 _
  14. color_buf = (lv_color_t *)0X68000000;; v' B5 O* z# l- B
  15. ! F5 s5 |* z5 \  k9 v, Y/ c: F
  16. 8 ]$ p2 s) J: m
  17. //把指定区域的显示缓冲区内容写入到屏幕上,你可以使用 DMA 或者其他的硬件加速器+ b( ~, ?3 j1 Q2 e5 d/ z
  18. //在后台去完成这个操作但是在完成之后,你必须得调用 lv_disp_flush_ready()
    ' ~: r; ~! f0 [( c- C& l
  19. 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( ?
  20. {
    3 _$ ?, J2 R* {) a7 Z. [* a7 j2 t
  21.     //把指定区域的显示缓冲区内容写入到屏幕7 I' S# o$ W) G. k& E1 e1 s3 I0 H
  22.     //LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);//TODO
    6 f' \, x) O' |5 }
  23.     DMA_Fill_Color(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);  t9 w$ Y0 S7 t! i* W
  24.     lv_disp_flush_ready(disp_drv);//最后必须得调用,通知 lvgl 库你已经 flushing 拷贝完成了
    % Q, O9 d$ D+ `5 _/ r, B9 R7 A
  25. }/ n2 T7 z7 G& K3 v! v+ R" o
  26. //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
  1. static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)9 N1 S, ]! V9 D# ~/ @  L) t
  2. {4 b5 j1 m$ _; `3 R9 k( L7 a$ C
  3.                 static uint16_t last_x = 0;) D9 i& z- B; w4 O
  4.                 static uint16_t last_y = 0;
    4 r. r5 o7 o" A+ Y9 [% F
  5.                 Tp_dev.scan();* W7 V& T1 D6 X' J' j5 j
  6.                 if(Tp_dev.sta&0x80)//触摸按下了
    : y# d) ^, z7 c
  7.                 {6 N7 `9 r: \# i0 D) I6 w6 K
  8.                         last_x = Tp_dev.X[0];
    * t( o4 s. a; `& s
  9.                         last_y = Tp_dev.Y[0];//竖屏8 s" B$ k1 t% l" R+ q3 k
  10.                         , n# X. K( _- w1 k  j( B1 C9 X* t; @
  11.                         data->point.x = last_x;
    * {, X8 Y7 I3 e$ m' i) [$ P
  12.                         data->point.y = last_y;
    - H; E# @3 s0 ]
  13.                         data->state = LV_INDEV_STATE_PR;, P7 d8 M  ^8 _0 k- A$ c8 u8 V; X* U
  14.                 }else{
      [0 Y6 A/ k6 F" ~9 V' q- [
  15.                         data->point.x = last_x;
    * k' Z7 f! Y" O' `8 c( d7 R# J
  16.                         data->point.y = last_y;
    1 a8 ?. P) C9 K; @4 Z% q+ E
  17.                         data->state = LV_INDEV_STATE_REL;/ Y8 }) R, j1 l
  18.                 }
    6 }, U+ a( S- ^: Y" x, L% o
  19.     return false;% C+ \3 R) |. A: m0 R
  20. }/ ]+ z  S, t" b2 s+ X
  21. //Tp_dev.scan() 是触摸屏驱动层的函数4 B) P6 O. w; ]9 C# N
  22. //其作用是扫描整块儿屏幕,得到触摸状态写入Tp_dev.sta中,如果有触摸点按下,得到其坐标并存入Tp_dev.X和Tp_dev.Y数组中
    ) n3 M+ T: d: `! L/ Z
  23. //因此这个函数需要根据你自己的板子和屏幕提供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
收藏 1 评论1 发布时间:2021-6-7 17:39

举报

1个回答
moticsoft 回答时间:2021-6-9 15:36:02
流畅度怎么样啊,有没有效果视频看?9 p8 O2 e( j1 S8 ~" T/ @2 m

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版