> 文档中心 > 【学习笔记】XR872 GUI Littlevgl 8.0 移植(显示部分)

【学习笔记】XR872 GUI Littlevgl 8.0 移植(显示部分)


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.h2021-06-01 15:48:03.000000000 +0800+++ xradio-skylark-sdk\project\demo\lvgl_demo\gui\lv_conf.h2021-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 

六、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 #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 errorsmake: *** [../../../../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: 0x000318E0bin 2 name:app_xip.bin    begin: 0x00012C00We've rearranged bin files and generated new cfg file 'image_auto_cal.cfg', the new one is recommended.Generate image file failedmake: *** [../../../../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 #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 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_tdisp;    lcd_info_tlcd_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.h2021-06-13 20:42:52.000000000 +0800+++ xradio-skylark-sdk\project\demo\lvgl_demo\gui\lv_conf.h2021-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_PORTSPI1+#define PRJCONF_DEVICE_LCD_BL_GPIO_PORTGPIO_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_PINGPIO_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 #include #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_tspi;// SPI 配置lcd_gpio_trst;// 复位引脚配置lcd_gpio_tblk;// 背光引脚控制lcd_gpio_tcmd;// 命令数据切换}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 #include #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_ & 0xff000000 ) >> 24))#define LITTLE_ENDIAN_16(_VAL_) (((_VAL_ & 0x00ff) <> 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 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 <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.35Vspi_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:0vspi_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;}