【学习笔记】XR872 GUI Littlevgl 8.0 移植(显示部分)
LVGL 介绍
官方网站:LVGL - Light and Versatile Embedded Graphics Library
官方文档: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(®, 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, ¶m);
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, ¶m);
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, ¶m);
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 移植(显示部分)的更多相关文章
- Cocos2D-X2.2.3学习笔记9(处理重力感应事件,移植到Android加入两次返回退出游戏效果)
这节我们来学习Cocos2d-x的最后一节.怎样处理重力感应事件.移植到Android后加入再按一次返回键退出游戏等.我这里用的Android.IOS不会也没设备呃 效果图不好弄,由于是要移植到真机上 ...
- 【opencv学习笔记二】opencv3.4.0组件结构说明
在学习opencv使用之前我们先来看一下opencv有哪些组件结构.至于OpenCV组件结构的研究方法, 我们不妨管中窥豹,通过opencv安装路径下include目录里面头文件的分类存放,来一窥Op ...
- 【opencv学习笔记四】opencv3.4.0图形用户接口highgui函数解析
在笔记二中我们已经知道了,在highgui文件夹下的正是opencv图形用户接口功能结构,我们这篇博客所说的便是D:\Program Files\opencv340\opencv\build\incl ...
- AM335x(TQ335x)学习笔记——Nand&&网卡驱动移植
移植完毕声卡驱动之后本想再接再励,移植网卡驱动,但没想到的是TI维护的内核太健壮,移植网卡驱动跟之前移植按键驱动一样简单,Nand驱动也是如此,于是,本人将Nand和网卡放在同一篇文章中介绍.介绍之前 ...
- Java学习笔记:GUI基础
一:我们使用到的java GUI的API可以分为3种类: 组件类(component class) 容器类(container class) 辅助类(helper class) 1:组件类:组件类是用 ...
- MySQL学习笔记(六)MySQL8.0 配置笔记
今天把数据库配置文件修改了,结果重启不了了 需要使用 mysqld --initialize 或 mysqld --initialize-insecure 命令来初始化数据库 1.mysqld --i ...
- shell编程学习笔记之特殊变量($0、$1、$2、 $?、 $# 、$@、 $*)
特殊变量($0.$1.$2. $?. $# .$@. $*) shell编程中有一些特殊的变量可以使用.这些变量在脚本中可以作为全局变量来使用. 名称 说明 $0 脚本名称 $1-9 脚本执行时的参数 ...
- 接口与协议学习笔记-USB协议_USB2.0_USB3.0不同版本(三)
USB(Universal Serial Bus)全称通用串口总线,USB为解决即插即用需求而诞生,支持热插拔.USB协议版本有USB1.0.USB1.1.USB2.0.USB3.1等,USB2.0目 ...
- 【opencv学习笔记三】opencv3.4.0数据类型解释
opencv提供了多种基本数据类型,我们这里分析集中常见的类型.opencv的数据类型定义可以在D:\Program Files\opencv340\opencv\build\include\open ...
- java学习笔记_BeatBox(GUI部分)
import java.awt.*; import javax.swing.*; public class BeatBox { JFrame theFrame; JPanel mainPanel; S ...
随机推荐
- ES 客户端 RestHighLevelClient Connection reset by peer 亲测有效 2022-11-05
导读 最新公司ES集群老出现连接关闭,进而导致查询|写入ES时报错,报错日志显示如下 [2m2022-10-23 14:13:10.088[0;39m - [31mERROR[0;39m - [35m ...
- HTTPS 基础知识(密钥、对称加密、非对称加密、数字签名、数字证书)
HTTPS 概述 对称加密 非对称加密 非对称加密改良方案 非对称加密 + 对称加密 中间人攻击 数字证书 数字签名 HTTPS 工作原理 HTTPS 概述 HTTPS(全称:Hyper Text T ...
- 广州2022CCPC补题
I Infection 知识点: 树上背包 第一次写树上背包的题目,没想到就是在区域赛中 神奇的是树上背包的复杂度,看起来是\(O(n^3)\),但是实际计算只有\(O(n^2)\) 学会树上背包后可 ...
- c++题目:吃西瓜
吃西瓜 [问题描述] 老胡买了是长方体形的西瓜来犒劳大家.... 这块西瓜长m厘米,宽n厘米,高h厘米.他发现如果把这块西瓜平均地分成m*n*h块1立方厘米的小正方体,那么每一小块都会有一个营养值(可 ...
- CSP 记
csp 开考建好文件夹编译器不能用搞了半天换了台电脑 四道题看完一个小时过去了 第一题不会正解写了部分分还有点悬 第二题写暴力因为一个小错误调了半天 看时间不多了已经有点慌了 也没想正解直接开了下一题 ...
- 更换linux的开机启动图片, 启动主题
简述 之前就想更改开机的启动图片,但是后来简单查了一下,说要重新编译内核,听到编译我就望而却步了,今天发现只是个命令而已,注意这里我用的是 linux mint .这里更改不是 grub 主题, 是 ...
- 踩坑记录:Redis的lettuce连接池不生效
踩坑记录:Redis的lettuce连接池不生效 一.lettuce客户端 lettuce客户端 Lettuce 和 Jedis 的都是连接Redis Server的客户端程序.Jedis在实现上是直 ...
- dotnet new cli 以及Abp-cli命令的简单使用
1:要求 首先dotnet new 需要 .NET Core 3.1 SDK 以及更高版本 dotnet new - 根据指定的模板,创建新的项目.配置文件或解决方案 2:变化 从 .NET 7 S ...
- 跟我学Python图像处理丨图像分类原理与案例
摘要:本篇文章将分享图像分类原理,并介绍基于KNN.朴素贝叶斯算法的图像分类案例. 本文分享自华为云社区<[Python图像处理] 二十六.图像分类原理及基于KNN.朴素贝叶斯算法的图像分类案例 ...
- python基础语法&数据类型&运算符
1.标识符 # -*- coding:utf-8 -*- # @Time :2021/1/16 10:28 # @Author :QAbujiaban # @Email :wbxztoo@163.co ...