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: pointersstructscallbacks.

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. springboot如何处理矩阵参数类型的url

    矩阵参数类型的url如何处理 首先要开启这个功能 在webconfig类中创建Webconfigurer类 并且设置 urlPathHelper类中的removeSemicolonContent 为f ...

  2. ThreadLocal的使用及原理解析

    # 基本使用 JDK的lang包下提供了ThreadLocal类,我们可以使用它创建一个线程变量,线程变量的作用域仅在于此线程内.<br />用2个示例来展示一下ThreadLocal的用 ...

  3. k8s 中的 ingress 使用细节

    k8s中的ingress 什么是ingress Ingress 如何使用 ingress 使用细节 参考 k8s中的ingress 什么是ingress k8s 中使用 Service 为相同业务的 ...

  4. SimpleDateFormat线程安全问题排查

    一. 问题现象 运营部门反馈使用小程序配置的拉新现金红包活动二维码,在扫码后跳转至404页面. 二. 原因排查 首先,检查扫码后的跳转链接地址不是对应二维码的实际URL,根据代码逻辑推测,可能是acc ...

  5. 云实例初始化工具cloud-init简介

    项目简介 cloud-init是一款用于初始化云服务器的工具,它拥有丰富的模块,能够为云服务器提供的能力有:初始化密码.扩容根分区.设置主机名.注入公钥.执行自定义脚本等等,功能十分强大. 目前为止c ...

  6. 第一章 计算机和C++简介

    1.1 简介 C++是一种强大的计算机面向对象编程的程序设计语言,它是制造软件的一种编程语言,适合程序员和刚接触编程的技术人员.当今智能手机销量爆炸式增长给移动应用程序的开发带来了很多机会,而C++就 ...

  7. 棋盘覆盖(java实现)

    棋盘覆盖 问题描述 在一个2k×2k 个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘.在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘 ...

  8. kubernetes笔记-3-快速入门

    一.增删改查 root@master:~# kubectl run ninig-deploy --image=nginx:1.14-alpine --port=80 --replicas=1 --dr ...

  9. pyinstaller打包TVM/RPC相关脚本及DSO文件

    0. 创建anaconda env numpy中MKL/BLAS库占用很大空间.使用如下命令创建新环境,并替换numpy. conda create -n extranumpy python=3.8. ...

  10. ST表优化区间gcd

    ST表的使用需要所求区间答案具有可重复性(询问时需要用到两个区间重叠来覆盖询问区间) 此题要求gcd为x的区间个数 可以用ST表处理出所有区间的\(gcd\) \(O(nlogn)\) 将区间的左端点 ...