LVGL 介绍

官方网站:LVGL - Light and Versatile Embedded Graphics Library

源码位置:GitHub - lvgl/lvgl: Powerful and easy-to-use embedded GUI library with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash).

官方文档:Introduction — LVGL documentation

LVGL 是一款使用 C 语言编写的非常精巧的开源 UI,拥有非常漂亮的视觉效果,对硬件资源要求很低,且移植非常简单。

以下位摘录则官方文档中对硬件最低要求说明:

  • 16, 32 or 64 bit microcontroller or processor
  • > 16 MHz clock speed is recommended
  • Flash/ROM: > 64 kB for the very essential components (> 180 kB is recommended)
  • RAM:
    • Static RAM usage: ~2 kB depending on the used features and objects types
    • Stack: > 2kB (> 8 kB is recommended)
    • Dynamic data (heap): > 4 KB (> 32 kB is recommended if using several objects).     Set by LV_MEM_SIZE in lv_conf.h.
    • Display buffer:  > "Horizontal resolution" pixels (> 10 × "Horizontal resolution" is recommended)
    • One frame buffer in the MCU or in external display controller
  • C99 or newer compiler
  • Basic C (or C++) knowledge: pointers, structs, callbacks.

LVGL 移植说明

LVGL 已将跟硬件相关的代码实现抽象出来,涉及到以下三个硬件模块,我们只需要按需选择实现即可:

一. 显示模块,模板文件为:lv_port_disp_template.h、lv_port_disp_template.c

实现回调接口:

void (*flush_cb)(struct _lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);

当需要绘制或更新图像时,LVGL会通过该接口传递一个指定矩形区域(area)和显示指定颜色值(color_p),我们需要实现在指定矩形区域内绘制指定颜色值。

LVGL 在刷动态效果是以固定的频率刷新(FPS),默认间隔时间为 30ms,通过配置文件中的宏决定 LV_DISP_DEF_REFR_PERIOD

2. 输入模块,模板文件为:lv_port_indev_template.h、lv_port_indev_template.c

输入设备支持四种类型:LV_INDEV_TYPE_POINTER(鼠标\触摸屏)、LV_INDEV_TYPE_KEYPAD(键盘)、LV_INDEV_TYPE_BUTTON(按钮)、LV_INDEV_TYPE_ENCODER(编码器)

实现回调接口:

void (*read_cb)(struct _lv_indev_drv_t * indev_drv, lv_indev_data_t * data);

LVGL 会间隔指定时间去调用此接口,来获取外部输入设备的输入事件,默认间隔时间为 30ms,通过配置文件中的宏决定 LV_INDEV_DEF_READ_PERIOD

3. 文件系统,模板文件为:lv_port_fs_template.h、lv_port_fs_template.c

实现回调接口:

bool (*ready_cb)(struct _lv_fs_drv_t * drv);

void * (*open_cb)(struct _lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode);
lv_fs_res_t (*close_cb)(struct _lv_fs_drv_t * drv, void * file_p);
lv_fs_res_t (*read_cb)(struct _lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br);
lv_fs_res_t (*write_cb)(struct _lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw);
lv_fs_res_t (*seek_cb)(struct _lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence);
lv_fs_res_t (*tell_cb)(struct _lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p); void * (*dir_open_cb)(struct _lv_fs_drv_t * drv, const char * path);
lv_fs_res_t (*dir_read_cb)(struct _lv_fs_drv_t * drv, void * rddir_p, char * fn);
lv_fs_res_t (*dir_close_cb)(struct _lv_fs_drv_t * drv, void * rddir_p);

对应API接口:

lv_fs_res_t lv_fs_open(lv_fs_file_t * file_p, const char * path, lv_fs_mode_t mode);
lv_fs_res_t lv_fs_close(lv_fs_file_t * file_p);
lv_fs_res_t lv_fs_read(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br);
lv_fs_res_t lv_fs_write(lv_fs_file_t * file_p, const void * buf, uint32_t btw, uint32_t * bw);
lv_fs_res_t lv_fs_seek(lv_fs_file_t * file_p, uint32_t pos, lv_fs_whence_t whence);
lv_fs_res_t lv_fs_tell(lv_fs_file_t * file_p, uint32_t * pos);
lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t * rddir_p, const char * path);
lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, char * fn);
lv_fs_res_t lv_fs_dir_close(lv_fs_dir_t * rddir_p);

移植前准备工作

一、建立新的工程

拷贝 xradio-skylark-sdk\project\demo\hello_demo 到 xradio-skylark-sdk\project\demo\lvgl_demo

二、LCD 驱动移植

需要提前准备好可以在 XR872 平台上使用的 LCD ,并且实现设定绘制区域接口和在区域内绘制指定颜色的接口。

我这里选择型号为 ZJY154S0800TG01,屏驱芯片 st7789,分辨率为 240x240,颜色格式为:RGB565,通信接口为 SPI(8BIT)

三、下载源代码:https://github.com/lvgl/lvgl/archive/refs/tags/v8.0.0.zip

将下载好的源代码解压到 xradio-skylark-sdk\project\demo\lvgl_demo\gui\ 并重命名为 lvgl

四、抽取配置文件

拷贝 xradio-skylark-sdk\project\demo\lvgl_demo\gui\lvgl\lv_conf_template.h 到  xradio-skylark-sdk\project\demo\lvgl_demo\gui\lv_conf.h

五、使能配置文件生效

xradio-skylark-sdk\project\demo\lvgl_demo\gui\lv_conf.h

--- xradio-skylark-sdk\project\demo\lvgl_demo\gui\lvgl\lv_conf_template.h	2021-06-01 15:48:03.000000000 +0800
+++ xradio-skylark-sdk\project\demo\lvgl_demo\gui\lv_conf.h 2021-06-13 18:13:54.000000000 +0800
@@ -4,13 +4,13 @@
*/ /*
* COPY THIS FILE AS `lv_conf.h` NEXT TO the `lvgl` FOLDER
*/ -#if 0 /*Set it to "1" to enable content*/
+#if 1 /*Set it to "1" to enable content*/ #ifndef LV_CONF_H
#define LV_CONF_H
/*clang-format off*/ #include <stdint.h>

六、LVGL 时钟心跳和 LVGL 主循环

新增两个文件,填写以下内容,LVGL 内部有自己的定时器和绘图循环,因此需要定期调用 lv_tick_inc()、lv_task_handler() 两个接口

在这里我通过建立两个系统任务来调用,将 lv_tick_inc() 任务的优先级调至最高,也可以通过硬件定时器来调用该接口。

xradio-skylark-sdk\project\demo\lvgl_demo\gui\lvgl_driver\lv_tick_handle.h

/**
******************************************************************************
* @文件 lv_tick_handle.h
* @版本 V1.0.0
* @日期
* @概要 LVGL 时钟基准和主循环定期调用
* @作者 lmx
******************************************************************************
* @注意
*
*
******************************************************************************
*/
#ifndef _LV_TICK_HANDLE_H
#define _LV_TICK_HANDLE_H /*************************************************************
* 函数: lv_tick_handle_init
* 功能: 内部创建两个任务分别定时调用 LVGL 的时钟基准和主循环接口
* 参数:
* 返回值:
**************************************************************/
void lv_tick_handle_init(void); #endif

xradio-skylark-sdk\project\demo\lvgl_demo\gui\lvgl_driver\lv_tick_handle.c

/**
******************************************************************************
* @文件 lv_tick_handle.h
* @版本 V1.0.0
* @日期
* @概要 LVGL 时钟基准和主循环定期调用
* @作者 lmx
******************************************************************************
* @注意
*
*
******************************************************************************
*/
#include <stdio.h>
#include "lv_port_disp.h"
#include "kernel/os/os.h" /*************************************************************
* 函数: prvLvTickTask
* 功能: LVGL 时钟基准任务
* 参数:
* 返回值:
**************************************************************/
static void prvLvTickTask(void *pvParameters)
{
while(1){
lv_tick_inc(1);
OS_MSleep(1);
} return ;
} /*************************************************************
* 函数: prvLvHandlerTask
* 功能: LVGL 主循环任务
* 参数:
* 返回值:
**************************************************************/
static void prvLvHandlerTask(void *pvParameters)
{
while(1){
lv_task_handler();
OS_MSleep(5);
} return ;
} /*************************************************************
* 函数: lv_tick_handle_init
* 功能: 内部创建两个任务分别定时调用 LVGL 的时钟基准和主循环接口
* 参数:
* 返回值:
**************************************************************/
void lv_tick_handle_init(void)
{
printf("[lv_disp] OS_ThreadCreate: prvLvTickTask\n");
#define LVGL_TASK_TICK_PRIORITY (OS_PRIORITY_REAL_TIME)
#define LVGL_TASK_TICK_STACK_SIZE (4 * 1024)
static OS_Thread_t lv_task_tick_thread;
OS_ThreadCreate(&lv_task_tick_thread, "lvgl_task_tick", prvLvTickTask, (void *)NULL, LVGL_TASK_TICK_PRIORITY, LVGL_TASK_TICK_STACK_SIZE); printf("[lv_disp] OS_ThreadCreate: prvLvHandlerTask\n");
#define LVGL_TASK_HANDLER_PRIORITY (OS_PRIORITY_NORMAL)
#define LVGL_TASK_HANDLER_STACK_SIZE (4 * 1024)
static OS_Thread_t lv_task_handle_thread;
OS_ThreadCreate(&lv_task_handle_thread, "lvgl_task_handler", prvLvHandlerTask, (void *)NULL, LVGL_TASK_HANDLER_PRIORITY, LVGL_TASK_HANDLER_STACK_SIZE); return ;
}

七、编译工程排错

问题一:找不到头文件 lvgl.h

../../../../project/demo/lvgl_demo/gui/lvgl/examples/assets/animimg001.c:4:23: fatal error: lvgl/lvgl.h: No such file or directory
#include "lvgl/lvgl.h"
^
compilation terminated.
make: *** [../../../../gcc.mk:218: ../../../../project/demo/lvgl_demo/gui/lvgl/examples/assets/animimg001.o] Error 1

解决方法:设置好 lvgl 所在的路径作为 GCC 搜索路径

--- xradio-skylark-sdk\project\demo\lvgl_demo\gcc\Makefile
+++ xradio-skylark-sdk\project\demo\lvgl_demo\gcc\Makefile
@@ -53,13 +53,13 @@
# PRJ_EXTRA_LIBS_PATH := # extra libraries
# PRJ_EXTRA_LIBS := # extra header files searching path
-# PRJ_EXTRA_INC_PATH :=
+PRJ_EXTRA_INC_PATH := -I$(PRJ_ROOT_PATH)/gui # extra symbols (macros)
# PRJ_EXTRA_SYMBOLS := # ----------------------------------------------------------------------------
# override project variables

问题二:未使用的变量

../../../../project/demo/lvgl_demo/gui/lvgl/examples/widgets/btnmatrix/lv_example_btnmatrix_1.c: In function 'event_handler':
../../../../project/demo/lvgl_demo/gui/lvgl/examples/widgets/btnmatrix/lv_example_btnmatrix_1.c:10:22: error: unused variable 'txt' [-Werror=unused-variable]
const char * txt = lv_btnmatrix_get_btn_text(obj, id);
^
cc1.exe: all warnings being treated as errors
make: *** [../../../../gcc.mk:218: ../../../../project/demo/lvgl_demo/gui/lvgl/examples/widgets/btnmatrix/lv_example_btnmatrix_1.o] Error 1

解决方法:最快的解决方法就是设置编译器不将未使用的变量视为错误,也可以去修改源代码,把响应未使用的变量屏蔽掉,但不建议这么去做。

--- xradio-skylark-sdk\project\demo\lvgl_demo\gcc\Makefile
+++ xradio-skylark-sdk\project\demo\lvgl_demo\gcc\Makefile
@@ -46,12 +46,14 @@
DIRS += $(PRJ_BOARD) SRCS := $(basename $(foreach dir,$(DIRS),$(wildcard $(dir)/*.[csS]))) OBJS := $(addsuffix .o,$(SRCS)) +CC_FLAGS += -Wno-error
+
# extra libraries searching path
# PRJ_EXTRA_LIBS_PATH := # extra libraries
# PRJ_EXTRA_LIBS :=

问题三:提示空间不足

err: bin 1 and bin 2 were overlaped!
Overlapped size: 126176 Byte(124kB)
bin 1 name:app.bin begin: 0x00008000 end: 0x000318E0
bin 2 name:app_xip.bin begin: 0x00012C00 We've rearranged bin files and generated new cfg file 'image_auto_cal.cfg', the new one is recommended.
Generate image file failed
make: *** [../../../../project/project.mk:352: image] Error 127

解决方法:按照编译提示信息,将 image_auto_cal.cfg 文件重命名为 image.cfg 文件,同时修改 Makefile

--- xradio-skylark-sdk\project\demo\lvgl_demo\gcc\Makefile
+++ xradio-skylark-sdk\project\demo\lvgl_demo\gcc\Makefile
@@ -48,12 +48,14 @@
SRCS := $(basename $(foreach dir,$(DIRS),$(wildcard $(dir)/*.[csS]))) OBJS := $(addsuffix .o,$(SRCS)) CC_FLAGS += -Wno-error +IMAGE_CFG := ./image.cfg
+
# extra header files searching path
PRJ_EXTRA_INC_PATH := -I$(PRJ_ROOT_PATH)/gui # extra symbols (macros)
# PRJ_EXTRA_SYMBOLS :=

显示驱动实现

主要参考 lvgl_demo\lvgl\examples\porting 中的  lv_port_disp_template.h、lv_port_disp_template.c 两个文件即可。

我这边基于多屏兼容性考虑,为了提高可读性和灵活性,不固定分辨率和位宽,做了精简和调整,完整实现文件如下:

 xradio-skylark-sdk\project\demo\lvgl_demo\gui\lvgl_driver\lv_port_disp.h

/**
******************************************************************************
* @文件 lv_port_disp.h
* @版本 V1.0.0
* @日期
* @概要 LVGL 显示驱动
* @作者 lmx
******************************************************************************
* @注意
*
*
******************************************************************************
*/
#ifndef LV_PORT_DISP_H
#define LV_PORT_DISP_H #include "lvgl/lvgl.h"
#include "library/inc/lcd_device.h" /*************************************************************
* 函数: lv_port_disp_init
* 功能: 初始化显示设备
* 参数:
* 返回值: -1:失败 0:成功
**************************************************************/
int lv_port_disp_init(lcd_device_t *lcd); #endif

xradio-skylark-sdk\project\demo\lvgl_demo\gui\lvgl_driver\lv_port_disp.c

/**
******************************************************************************
* @文件 lv_port_disp.c
* @版本 V1.0.0
* @日期
* @概要 LVGL 显示驱动
* @作者 lmx
******************************************************************************
* @注意
*
*
******************************************************************************
*/
#include <stdio.h>
#include "lv_port_disp.h"
#include "kernel/os/os.h" typedef struct{
lcd_device_t *lcd;
unsigned short *frame;
}disp_device_t; /*************************************************************
* 函数: disp_flush
* 功能: 绘制颜色点回调
* 参数:
* 返回值:
**************************************************************/
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
disp_device_t *disp = (disp_device_t *)disp_drv->user_data;
lv_color_t *buf = (lv_color_t *)disp->frame;
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
uint32_t i, len = w * h; // 对颜色值进行排序
for(i = 0; i < len; i++){
buf[i] = (lv_color_t)color_p->full;
color_p++;
} // 设置显示区域, 并一次性更新数据, 可以提升刷图速度
disp->lcd->set_window(area->x1, area->y1, area->x2, area->y2);
disp->lcd->brush(buf, len * sizeof(lv_color_t));
lv_disp_flush_ready(disp_drv);
} /*************************************************************
* 函数: lv_port_disp_init
* 功能: 初始化显示设备
* 参数:
* 返回值: -1:失败 0:成功
**************************************************************/
int lv_port_disp_init(lcd_device_t *lcd)
{
static disp_device_t disp;
lcd_info_t lcd_info;
unsigned int buflen; disp.lcd = lcd;
lcd->get_info(&lcd_info);
printf("[lv_disp] lcd:[%s]:[%dx%d] %d\n", lcd_info.name, lcd_info.hor, lcd_info.ver, sizeof(lv_color_t)); // LVGL 内部所需要的显存缓冲区
static lv_disp_draw_buf_t draw_buf_dsc_1;
buflen = lcd_info.hor * 10 * sizeof(lv_color_t);
lv_color_t *buf_1 = lv_mem_alloc(buflen);
if(NULL == buf_1){
printf("[lv_disp] draw lv_mem_alloc failed...\n");
return -1;
}
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, lcd_info.hor * 10); // 申请缓存空间, 用于存储排序后的颜色数据
disp.frame = lv_mem_alloc(buflen);
if(NULL == disp.frame){
printf("[lv_disp] frame lv_mem_alloc failed...\n");
lv_mem_free(buf_1);
return -1;
} // 将此显示驱动注册到 LVGL 中
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = lcd_info.hor;
disp_drv.ver_res = lcd_info.ver;
disp_drv.flush_cb = disp_flush;
disp_drv.draw_buf = &draw_buf_dsc_1;
disp_drv.user_data = &disp;
lv_disp_drv_register(&disp_drv); return ;
}

最后一步还需要根据实际显示屏的能力来调整

1. LV_COLOR_DEPTH:颜色位深 ,我现在使用的屏是 RGB565,因此需要设定为 16 位宽

2. LV_COLOR_16_SWAP:同时由于 XR872 只支持 SPI 8BIT 传输方式,涉及高低字节传输问题,需要使能 LV_COLOR_16_SWAP

3. LV_USE_PERF_MONITOR:打开此宏可以显示当前 CPU 使用率和帧率,便于优化和显示

4. LV_MEM_CUSTOM:设置为 1 打开此宏,使用动态内存分配,LVGL 实际运行需要多少就申请多少,0 则静态内存。

备注说明:

LVGL 内存分为静态内存和动态内存,静态内存是事先占用一大块内存,动态内存则是 LVGL 按需申请。

LV_USE_MEM_MONITOR:打开此宏可以显示当前所使用的内存大小和占用比例,以及产生多少碎片。(选择静态内存才会显示信息)

注意,这里的内存占用比例指的是 LV_MEM_CUSTOM 宏为 0 的情况下,LV_MEM_SIZE 所定义的大小除于 LVGL 内部实际占用内存大小之比。

LVGL 有自己的内存管理机制,LV_MEM_SIZE 为一个静态数组的大小,这样就可以给 LVGL 设限,只允许它只使用指定已经分配好多大的内存区域。

xradio-skylark-sdk\project\demo\lvgl_demo\gui\lv_conf.h

--- xradio-skylark-sdk\project\demo\lvgl_demo\gui\lv_conf.h		2021-06-13 20:42:52.000000000 +0800
+++ xradio-skylark-sdk\project\demo\lvgl_demo\gui\lv_conf.h 2021-06-13 20:29:53.000000000 +0800
@@ -18,16 +18,16 @@ /*====================
COLOR SETTINGS
*====================*/ /*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
-#define LV_COLOR_DEPTH 32
+#define LV_COLOR_DEPTH 16 /*Swap the 2 bytes of RGB565 color. Useful if the display has a 8 bit interface (e.g. SPI)*/
-#define LV_COLOR_16_SWAP 0
+#define LV_COLOR_16_SWAP 1 /*Enable more complex drawing routines to manage screens transparency.
*Can be used if the UI is above an other layer, e.g. an OSD menu or video player.
*Requires `LV_COLOR_DEPTH = 32` colors and the screen's `bg_opa` should be set to non LV_OPA_COVER value*/
#define LV_COLOR_SCREEN_TRANSP 0 @@ -36,13 +36,13 @@ /*=========================
MEMORY SETTINGS
*=========================*/ /*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
-#define LV_MEM_CUSTOM 0
+#define LV_MEM_CUSTOM 1
#if LV_MEM_CUSTOM == 0
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
# define LV_MEM_SIZE (32U * 1024U) /*[bytes]*/ /*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
# define LV_MEM_ADR 0 /*0: unused*/
@@ -183,13 +183,13 @@ /*-------------
* Others
*-----------*/ /*1: Show CPU usage and FPS count in the right bottom corner*/
-#define LV_USE_PERF_MONITOR 0
+#define LV_USE_PERF_MONITOR 1 /*1: Show the used memory and the memory fragmentation in the left bottom corner
* Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 0 /*1: Draw random colored rectangles over the redrawn areas*/

完整示例代码

xradio-skylark-sdk\project\demo\lvgl_demo\prj_config.h

---
+++
@@ -137,11 +137,26 @@
/* net pm mode enable/disable */
#define PRJCONF_NET_PM_EN 1 /* environment variable "TZ" for time zone setting */
#define PRJCONF_ENV_TZ "TZ=GMT-8" +/*
+ * project gpio config
+ */
+
+/* lcd device config */
+#define PRJCONF_DEVICE_LCD_SPI_PORT SPI1
+#define PRJCONF_DEVICE_LCD_BL_GPIO_PORT GPIO_PORT_A
+#define PRJCONF_DEVICE_LCD_BL_GPIO_PIN GPIO_PIN_22
+
+#define PRJCONF_DEVICE_LCD_RST_GPIO_PORT GPIO_PORT_A
+#define PRJCONF_DEVICE_LCD_RST_GPIO_PIN GPIO_PIN_19
+
+#define PRJCONF_DEVICE_LCD_MODE_GPIO_PORT GPIO_PORT_A
+#define PRJCONF_DEVICE_LCD_MODE_GPIO_PIN GPIO_PIN_20
+
#ifdef __cplusplus
}
#endif #endif /* _PRJ_CONFIG_H_ */

xradio-skylark-sdk\project\demo\lvgl_demo\main.c

/**
******************************************************************************
* @文件 main.c
* @版本 V1.0.0
* @日期
* @概要
* @作者 lmx
******************************************************************************
* @注意
*
*
******************************************************************************
*/
#include "common/framework/platform_init.h"
#include <string.h>
#include <stdio.h>
#include "prj_config.h"
#include "kernel/os/os.h"
#include "lvgl/lvgl.h"
#include "lvgl-drivers/lv_port_disp.h"
#include "library/inc/lcd_st7789.h" int main(void)
{
static lcd_device_t lcd;
lcd_para_t lcd_para; platform_init(); memset(&lcd_para, 0, sizeof(lcd_para_t));
lcd_para.dir = 0;
lcd_para.spi.ssel = SPI_TCTRL_SS_SEL_SS0;
lcd_para.spi.port = PRJCONF_DEVICE_LCD_SPI_PORT;
lcd_para.blk.port = PRJCONF_DEVICE_LCD_BL_GPIO_PORT;
lcd_para.blk.pin = PRJCONF_DEVICE_LCD_BL_GPIO_PIN;
lcd_para.rst.port = PRJCONF_DEVICE_LCD_RST_GPIO_PORT;
lcd_para.rst.pin = PRJCONF_DEVICE_LCD_RST_GPIO_PIN;
lcd_para.cmd.port = PRJCONF_DEVICE_LCD_MODE_GPIO_PORT;
lcd_para.cmd.pin = PRJCONF_DEVICE_LCD_MODE_GPIO_PIN;
register_operations_st7789(&lcd); lcd.init(&lcd_para);
lcd.set_brightness(255); lv_init();
lv_port_disp_init(&lcd);
lv_tick_handle_init(); lv_example_bar_6();
lv_example_btn_1(); while(1)
{
OS_Sleep(3);
} return 0;
}

附带 LCD 驱动文件

/**
******************************************************************************
* @文件 lcd_device.h
* @版本 V1.0.0
* @日期
* @概要 lcd device interface definition
* @作者 lmx
******************************************************************************
* @注意
*
*
*
******************************************************************************
*/
#ifndef __LCD_DEVICE_H__
#define __LCD_DEVICE_H__ typedef struct{
unsigned int ssel; // SPI 片选号
unsigned int port; // SPI 端口号
}lcd_spi_t; typedef struct{
unsigned int port; // GPIO 端口号
unsigned int pin; // GPIO 引脚号
}lcd_gpio_t; typedef struct{
unsigned char dir; // 显示方向:横屏\竖屏
lcd_spi_t spi; // SPI 配置
lcd_gpio_t rst; // 复位引脚配置
lcd_gpio_t blk; // 背光引脚控制
lcd_gpio_t cmd; // 命令数据切换
}lcd_para_t; typedef struct{
const char *name; // 显示屏名称
unsigned int hor; // 水平长度
unsigned int ver; // 垂直长度
}lcd_info_t; typedef struct{
int (*init)(lcd_para_t *para); // 初始化
int (*release)(); // 释放设备
int (*get_info)(lcd_info_t *info); // 获取信息
int (*set_brightness)(unsigned char val); // 背光设置
int (*set_rotation)(unsigned char angle); // 设置方向
int (*set_window)(unsigned short x1, unsigned short x2, unsigned short y1, unsigned short y2);// 设置窗口
int (*brush)(void *pdata, unsigned int size); // 绘制矩形
}lcd_device_t; #endif
/**
******************************************************************************
* @文件 lcd_st7789.h
* @版本 V1.0.0
* @日期
* @概要 st7789 driver
* @作者 lmx
******************************************************************************
* @注意
*
*
*
******************************************************************************
*/
#ifndef __LCD_ST7789_H__
#define __LCD_ST7789_H__ #include "lcd_device.h"
#include "driver/chip/hal_spi.h" int register_operations_st7789(lcd_device_t *device); #endif
/**
******************************************************************************
* @文件 lcd_st7789.h
* @版本 V1.0.0
* @日期
* @概要 st7789 driver
* @作者 lmx
******************************************************************************
* @注意
*
*
*
******************************************************************************
*/
#include <stdio.h>
#include <string.h>
#include "lcd_st7789.h"
#include "kernel/os/os.h"
#include "driver/chip/hal_clock.h" #define iprintf(fmt, arg...) printf("[st7789] [inf] "fmt, ##arg)
#define eprintf(fmt, arg...) printf("[st7789] [err] "fmt, ##arg) // 大小端转换
#define LITTLE_ENDIAN_32(_VAL_) (((_VAL_ & 0x000000ff) << 24 ) | ((_VAL_ & 0x0000ff00) << 8) | ((_VAL_ & 0x00ff0000) >> 8) | ((_VAL_ & 0xff000000 ) >> 24))
#define LITTLE_ENDIAN_16(_VAL_) (((_VAL_ & 0x00ff) << 8 ) | ((_VAL_ & 0xff00) >> 8)) // 复位控制
#define RESET_ENABLE() HAL_GPIO_WritePin(lcd_para.rst.port, lcd_para.rst.pin, GPIO_PIN_LOW)
#define RESET_DISABLE() HAL_GPIO_WritePin(lcd_para.rst.port, lcd_para.rst.pin, GPIO_PIN_HIGH) // 背光控制
#define BRIGHTNESS_ENABLE() HAL_GPIO_WritePin(lcd_para.blk.port, lcd_para.blk.pin, GPIO_PIN_HIGH)
#define BRIGHTNESS_DISABLE() HAL_GPIO_WritePin(lcd_para.blk.port, lcd_para.blk.pin, GPIO_PIN_LOW) // 命令模式
#define ENTER_COMMAND_MODE() HAL_GPIO_WritePin(lcd_para.cmd.port, lcd_para.cmd.pin, GPIO_PIN_LOW)
#define ENTER_DATA_MODE() HAL_GPIO_WritePin(lcd_para.cmd.port, lcd_para.cmd.pin, GPIO_PIN_HIGH) // 参数配置
static lcd_para_t lcd_para; static void dump_hex(char *table, void *data, unsigned int length)
{
printf("dump:begin->table:[%s][%d]---------------\n", table, length);
length = length > 16 ? 16 : length;
for(unsigned int i = 0; i < length; i++){
printf("0x%.2X ", ((unsigned char*)data)[i]);
}
printf("\ndump:end ->table:[%s][%d]---------------\n", table, length);
} // 传输命令
static int spi_transmit_command(void *command, unsigned int length)
{
ENTER_COMMAND_MODE();
// dump_hex("command", command, length);
HAL_SPI_CS(lcd_para.spi.port, 1);
if (HAL_SPI_Transmit(lcd_para.spi.port, command, length) != HAL_OK) {
HAL_SPI_CS(lcd_para.spi.port, 0);
return -1;
}
HAL_SPI_CS(lcd_para.spi.port, 0); return 0;
} // 传输数据
static int spi_transmit_data(void *data, unsigned int length)
{
ENTER_DATA_MODE();
// dump_hex("data", data, length);
HAL_SPI_CS(lcd_para.spi.port, 1);
if (HAL_SPI_Transmit(lcd_para.spi.port, data, length) != HAL_OK) {
HAL_SPI_CS(lcd_para.spi.port, 0);
return -1;
}
HAL_SPI_CS(lcd_para.spi.port, 0); return 0;
} // 传输单个命令
static int spi_transmit_reg(unsigned char reg)
{
return spi_transmit_command(&reg, sizeof(unsigned char));
} // 传输单个数据
static int spi_transmit_val(unsigned char val)
{
return spi_transmit_data(&val, sizeof(unsigned char));
} // 背光控制
static int lcd_set_brightness(unsigned char val)
{
val ? BRIGHTNESS_ENABLE() : BRIGHTNESS_DISABLE();
return 0;
} // 显示方向
static int lcd_set_rotation(unsigned char angle)
{
lcd_para.dir = angle;
spi_transmit_reg(0x36);
switch(lcd_para.dir){
case 0: spi_transmit_val(0x00); break;
case 1: spi_transmit_val(0xC0); break;
case 2: spi_transmit_val(0x70); break;
case 3: spi_transmit_val(0xA0); break;
default: spi_transmit_val(0x00); break;
}
return 0;
} // 设置绘制的窗口
static int lcd_set_window(unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2)
{
unsigned int x = 0;
unsigned int y = 0; switch(lcd_para.dir)
{
case 0: // 竖屏
x = x1 << 16 | x2;
y = y1 << 16 | y2;
break; case 1: // 竖屏
x = x1 << 16 | x2;
y = (y1 + 80) << 16 | (y2 + 80);
break; case 2: // 横屏
x = x1 << 16 | x2;
y = y1 << 16 | y2;
break; case 3: // 横屏
x = (x1 + 80) << 16 | (x2 + 80);
y = y1 << 16 | y2;
break; default:
x = x1 << 16 | x2;
y = y1 << 16 | y2;
break;
} x = LITTLE_ENDIAN_32(x);
y = LITTLE_ENDIAN_32(y); spi_transmit_reg(0x2a);//列地址设置
spi_transmit_data(&x, sizeof(unsigned int));
spi_transmit_reg(0x2b);//行地址设置
spi_transmit_data(&y, sizeof(unsigned int));
spi_transmit_reg(0x2c);//储存器写 return 0;
} // 获取显示屏信息
static int lcd_get_info(lcd_info_t *info)
{
info->name = "ST7789";
info->hor = 240;
info->ver = 240; return 0;
} // 刷入显存(需要调整高低字节)
static int lcd_brush(void *pdata, unsigned int size)
{
return spi_transmit_data(pdata, size);
} // 引脚初始化
static int gpio_init()
{
GPIO_InitParam param;
param.driving = GPIO_DRIVING_LEVEL_1;
param.mode = GPIOx_Pn_F1_OUTPUT;
param.pull = GPIO_PULL_NONE; HAL_GPIO_Init(lcd_para.blk.port, lcd_para.blk.pin, &param);
HAL_GPIO_WritePin(lcd_para.blk.port, lcd_para.blk.pin, GPIO_PIN_LOW); HAL_GPIO_Init(lcd_para.rst.port, lcd_para.rst.pin, &param);
HAL_GPIO_WritePin(lcd_para.rst.port, lcd_para.rst.pin, GPIO_PIN_LOW); HAL_GPIO_Init(lcd_para.cmd.port, lcd_para.cmd.pin, &param);
HAL_GPIO_WritePin(lcd_para.cmd.port, lcd_para.cmd.pin, GPIO_PIN_LOW); return 0;
} // 引脚释放
static int gpio_deinit()
{
HAL_GPIO_WritePin(lcd_para.blk.port, lcd_para.blk.pin, GPIO_PIN_LOW);
HAL_GPIO_WritePin(lcd_para.cmd.port, lcd_para.cmd.pin, GPIO_PIN_LOW);
HAL_GPIO_WritePin(lcd_para.rst.port, lcd_para.rst.pin, GPIO_PIN_LOW); HAL_GPIO_DeInit(lcd_para.blk.port, lcd_para.blk.pin);
HAL_GPIO_DeInit(lcd_para.rst.port, lcd_para.rst.pin);
HAL_GPIO_DeInit(lcd_para.cmd.port, lcd_para.cmd.pin);
return 0;
} // 通信初始化
static int spi_init()
{
SPI_Global_Config spi_param;
spi_param.cs_level = 0;
spi_param.mclk = HAL_GetCPUClock();
if(HAL_SPI_Init(lcd_para.spi.port, &spi_param) != HAL_OK){
return -1;
} SPI_Config spi_Config;
spi_Config.firstBit = SPI_TCTRL_FBS_MSB;
spi_Config.mode = SPI_CTRL_MODE_MASTER;
spi_Config.opMode = SPI_OPERATION_MODE_DMA;
spi_Config.sclk = HAL_GetCPUClock() / 4;
spi_Config.sclkMode = SPI_SCLK_Mode0;
if (HAL_SPI_Open(lcd_para.spi.port, lcd_para.spi.ssel, &spi_Config, 1000) != HAL_OK) {
return -1;
} HAL_SPI_Config(lcd_para.spi.port, SPI_ATTRIBUTION_IO_MODE, SPI_IO_MODE_NORMAL);
HAL_SPI_CS(lcd_para.spi.port, 0); return 0;
} // 通信释放
static int spi_deinit()
{
HAL_SPI_CS(lcd_para.spi.port, 0);
HAL_SPI_Close(lcd_para.spi.port);
HAL_SPI_Deinit(lcd_para.spi.port); return 0;
} // 屏驱初始化
static int st7789_init()
{
BRIGHTNESS_ENABLE();
OS_MSleep(100); RESET_ENABLE();
OS_MSleep(100);
RESET_DISABLE();
OS_MSleep(100); spi_transmit_reg(0x11); //Sleep out
OS_MSleep(120); //Delay 120ms spi_transmit_reg(0x36);
switch(lcd_para.dir){
case 0: spi_transmit_val(0x00); break;
case 1: spi_transmit_val(0xC0); break;
case 2: spi_transmit_val(0x70); break;
case 3: spi_transmit_val(0xA0); break;
default: spi_transmit_val(0x00); break;
} spi_transmit_reg(0x3A);
spi_transmit_val(0x05); spi_transmit_reg(0xB2);
spi_transmit_val(0x0C);
spi_transmit_val(0x0C);
spi_transmit_val(0x00);
spi_transmit_val(0x33);
spi_transmit_val(0x33); spi_transmit_reg(0xB7);
spi_transmit_val(0x35); spi_transmit_reg(0xBB);
spi_transmit_val(0x32); //Vcom=1.35V spi_transmit_reg(0xC2);
spi_transmit_val(0x01); spi_transmit_reg(0xC3);
spi_transmit_val(0x15); //GVDD=4.8V 颜色深度 spi_transmit_reg(0xC4);
spi_transmit_val(0x20); //VDV, 0x20:0v spi_transmit_reg(0xC6);
spi_transmit_val(0x0F); //0x0F:60Hz spi_transmit_reg(0xD0);
spi_transmit_val(0xA4);
spi_transmit_val(0xA1); spi_transmit_reg(0xE0);
spi_transmit_val(0xD0);
spi_transmit_val(0x08);
spi_transmit_val(0x0E);
spi_transmit_val(0x09);
spi_transmit_val(0x09);
spi_transmit_val(0x05);
spi_transmit_val(0x31);
spi_transmit_val(0x33);
spi_transmit_val(0x48);
spi_transmit_val(0x17);
spi_transmit_val(0x14);
spi_transmit_val(0x15);
spi_transmit_val(0x31);
spi_transmit_val(0x34); spi_transmit_reg(0xE1);
spi_transmit_val(0xD0);
spi_transmit_val(0x08);
spi_transmit_val(0x0E);
spi_transmit_val(0x09);
spi_transmit_val(0x09);
spi_transmit_val(0x15);
spi_transmit_val(0x31);
spi_transmit_val(0x33);
spi_transmit_val(0x48);
spi_transmit_val(0x17);
spi_transmit_val(0x14);
spi_transmit_val(0x15);
spi_transmit_val(0x31);
spi_transmit_val(0x34);
spi_transmit_reg(0x21); spi_transmit_reg(0x29); return 0;
} // 初始化设备
static int lcd_init(lcd_para_t *para)
{
memcpy(&lcd_para, para, sizeof(lcd_para_t)); if(spi_init()){
eprintf("spi interface failed...");
return -1;
} if(gpio_init()){
eprintf("gpio interface failed...");
return -1;
} st7789_init();
return 0;
} // 释放设备
static int lcd_release()
{
spi_deinit();
gpio_deinit();
return 0;
} // 注册回调
int register_operations_st7789(lcd_device_t *device)
{
memset(device, 0, sizeof(lcd_device_t));
device->init = lcd_init;
device->get_info = lcd_get_info;
device->set_brightness = lcd_set_brightness;
device->set_rotation = lcd_set_rotation;
device->set_window = lcd_set_window;
device->brush = lcd_brush;
device->release = lcd_release;
return 0;
}

【学习笔记】XR872 GUI Littlevgl 8.0 移植(显示部分)的更多相关文章

  1. Cocos2D-X2.2.3学习笔记9(处理重力感应事件,移植到Android加入两次返回退出游戏效果)

    这节我们来学习Cocos2d-x的最后一节.怎样处理重力感应事件.移植到Android后加入再按一次返回键退出游戏等.我这里用的Android.IOS不会也没设备呃 效果图不好弄,由于是要移植到真机上 ...

  2. 【opencv学习笔记二】opencv3.4.0组件结构说明

    在学习opencv使用之前我们先来看一下opencv有哪些组件结构.至于OpenCV组件结构的研究方法, 我们不妨管中窥豹,通过opencv安装路径下include目录里面头文件的分类存放,来一窥Op ...

  3. 【opencv学习笔记四】opencv3.4.0图形用户接口highgui函数解析

    在笔记二中我们已经知道了,在highgui文件夹下的正是opencv图形用户接口功能结构,我们这篇博客所说的便是D:\Program Files\opencv340\opencv\build\incl ...

  4. AM335x(TQ335x)学习笔记——Nand&amp;&amp;网卡驱动移植

    移植完毕声卡驱动之后本想再接再励,移植网卡驱动,但没想到的是TI维护的内核太健壮,移植网卡驱动跟之前移植按键驱动一样简单,Nand驱动也是如此,于是,本人将Nand和网卡放在同一篇文章中介绍.介绍之前 ...

  5. Java学习笔记:GUI基础

    一:我们使用到的java GUI的API可以分为3种类: 组件类(component class) 容器类(container class) 辅助类(helper class) 1:组件类:组件类是用 ...

  6. MySQL学习笔记(六)MySQL8.0 配置笔记

    今天把数据库配置文件修改了,结果重启不了了 需要使用 mysqld --initialize 或 mysqld --initialize-insecure 命令来初始化数据库 1.mysqld --i ...

  7. shell编程学习笔记之特殊变量($0、$1、$2、 $?、 $# 、$@、 $*)

    特殊变量($0.$1.$2. $?. $# .$@. $*) shell编程中有一些特殊的变量可以使用.这些变量在脚本中可以作为全局变量来使用. 名称 说明 $0 脚本名称 $1-9 脚本执行时的参数 ...

  8. 接口与协议学习笔记-USB协议_USB2.0_USB3.0不同版本(三)

    USB(Universal Serial Bus)全称通用串口总线,USB为解决即插即用需求而诞生,支持热插拔.USB协议版本有USB1.0.USB1.1.USB2.0.USB3.1等,USB2.0目 ...

  9. 【opencv学习笔记三】opencv3.4.0数据类型解释

    opencv提供了多种基本数据类型,我们这里分析集中常见的类型.opencv的数据类型定义可以在D:\Program Files\opencv340\opencv\build\include\open ...

  10. java学习笔记_BeatBox(GUI部分)

    import java.awt.*; import javax.swing.*; public class BeatBox { JFrame theFrame; JPanel mainPanel; S ...

随机推荐

  1. mysql 子查询 联结 组合查询

    子查询 SELECT cust_id FROM orders WHERE order_num IN (SELECT order_num FROM orderitems WHERE prod_id='T ...

  2. 记一次HTTPClient模拟登录获取Cookie的开发历程

    记一次HTTPClient模拟登录获取Cookie的开发历程 环境: ​ springboot : 2.7 ​ jdk: 1.8 ​ httpClient : 4.5.13 设计方案 ​ 通过新建一个 ...

  3. spring运行报500 bean不存在

    spring运行报500 bean不存在 bean不存在 步骤: ​ 查看bean是否注入成功 ​ junit单元测试 ​ 问题,不一定在我们底层,是spring出现了问题 ​ SpringMVC整合 ...

  4. 数学建模经验分享会For浙商大管工学院

    数学建模介绍 数学建模与数学 数学建模和数学没有必然的关系 一个队里至少有一个数学较好 注重运筹学.统计学这两门课 数学建模做什么 时间:一般每年9月开学三周内 时长:3天3夜 目标:完成一份排版精美 ...

  5. [排序算法] 直接/折半插入排序 (C++)

    插入排序解释 插入排序很好理解,其步骤是 :先将第一个数据元素看作是一个有序序列,后面的 n-1 个数据元素看作是未排序序列.对后面未排序序列中的第一个数据元素在这个有序序列中进行从后往前扫描,找到合 ...

  6. AArch32/AArch64系统级内存模型(三)

    1. 内存系统架构 1.1 系统级存储系统体系结构的形式   Armv8的a -profile体系结构包括一个虚拟内存系统体系结构(Virtual Memory System Architecture ...

  7. i春秋who are you

    打开题目网页,抓包,查源码都一无所获,然后就去看cookie,发现一个role变量,刚开始也不知道这个变量是什么,其值也是没有规律的一串字符串.看了别人源码才知道这是base64加密后的字符串,将其用 ...

  8. Web Api出现500 Internal Server Error 错误

    在测试环境一切正常,但是部署到了生产环境发现一直报错.查询网上的方法设置了权限等等.都没有解决 原来发现是数据库连接字符串的问题.只需要把数据库连接字符串修改正确即可!

  9. Referenced file contains errors (http://mybatis.org/dtd/mybatis-3-config.dtd). For more information, right click on the message in the Problems View and select "Show Details..."

    mybatis配置文件报错Referenced file contains errors mybatis的配置文件报错 The errors below were detected when vali ...

  10. win10中jupyter notebook设置conda虚拟环境全流程及问题汇总

    正常安装流程 1.安装anaconda 必备条件 2.安装jupyter notebook 一般anaconda自带安装 如没有,则在终端安装 conda install jupyter notebo ...