随着物联网技术的快速发展,高性能、低功耗、多协议的无线通信芯片成为智能设备的核心组件。海思 WS63 芯片(Hi3863V100)作为一款集成了 Wi-Fi 6、星闪 SLE 1.0 和 BLE 5.2 三模通信协议的物联网 SoC 芯片,凭借其出色的性能和丰富的接口资源,为开发者提供了强大的硬件平台。
一、项目背景
LVGL(Light and Versatile Graphics Library)是一个免费、开源的嵌入式图形库,支持多种显示控制器和输入设备。在海思WS63嵌入式平台上,通过移植LVGL UI框架,可以实现丰富的图形用户界面,提升用户体验。
本文档详细介绍如何将LVGL UI框架9.4版本的底层显示驱动移植到海思WS63平台,包括硬件驱动层、LVGL适配层、定时器配置等关键内容。
在线文档地址 :https://docs.hisilicon.com/repos/fbb_ws63/zh-CN/master/
论坛地址 :https://developers.hisilicon.com/forum/0133146886267870001
SDK代码仓地址 :https://gitcode.com/HiSpark/fbb_ws63

二、LVGL简介
LVGL(Light and Versatile Graphics Library)是一个免费的轻量级开源图形库。LVGL 是一款具有丰富部件,具备高级图形特性,支持多种输入设备和多国语言,独立于硬件之外的开源图形库。LVGL 官方网址为:https://lvgl.io/。LVGL 源代码网址为:https://github.com/lvgl/lvgl/。
图形用户界面(GUI)是指采用图形方式显示的计算机操作用户界面,允许用户使用鼠标等输入设备操纵屏幕上的图标或菜单选项。图形用户界面由多种控件及其相应的控制机制构成,在各种新式应用程序中都是标准化的,即相同的操作总是以同样的方式来完成,在图形用户界面,用户看到和操作的都是图形对象,应用的是计算机图形学的技术。
2.1 LVGL特性
LVGL是一个功能强大的嵌入式图形库,具有以下特点:
- 轻量级:代码体积小,内存占用低,适合资源受限的嵌入式系统
- 跨平台:支持多种MCU平台和操作系统
- 丰富的控件:提供按钮、标签、图表、列表等30+种内置控件
- 强大的样式系统:支持类似CSS的样式设置
- 多种输入设备:支持触摸屏、鼠标、键盘、编码器等
- 动画效果:内置平滑的动画引擎
- 文件系统支持:支持FATFS、LittleFS等文件系统
- 多语言支持:支持Unicode和多种字体
- 双缓冲:支持双缓冲渲染,避免闪烁
2.2 适用场景
- 智能家居设备界面
- 工业控制面板
- 医疗设备显示
- 可穿戴设备
- 智能仪表盘
- 物联网设备界面
三、硬件平台
3.1 显示屏规格
本项目使用的显示屏为ST7789S驱动的240x240像素TFT LCD:
| 参数 | 值 |
|---|---|
| 分辨率 | 240 x 240 |
| 颜色深度 | RGB565(16位色) |
| 接口 | SPI |
| 驱动芯片 | ST7789S |
| 触摸 | 无(本项目未使用触摸) |
3.2 硬件连接
ST7789S显示屏通过SPI接口连接到海思WS63平台:
| LCD引脚 | WS63引脚 | 功能说明 |
|---|---|---|
| CS | GPIO_XX | 片选信号 |
| SCK | SPI_CLK | 时钟信号 |
| SDA | SPI_MOSI | 数据输出 |
| DC | GPIO_XX | 数据/命令选择 |
| RST | GPIO_XX | 复位信号 |
| BLK | PWM_2 | 背光控制 |
3.3 引脚定义
c
/* board_config.h - 引脚定义 */
#define LCD_CS_PIN GPIO_PIN_XX
#define LCD_DC_PIN GPIO_PIN_XX
#define LCD_RST_PIN GPIO_PIN_XX
#define LCD_BLK_PWM 2
#define SPI_IDX 0 /* SPI控制器索引 */
四、驱动层架构
LVGL显示驱动移植采用四层架构:
┌─────────────────────────────────────┐
│ 应用层(Application) │ UI布局、事件处理
├─────────────────────────────────────┤
│ LVGL适配层(LVGL Port) │ 显示刷新、定时器
├─────────────────────────────────────┤
│ 驱动抽象层(Driver) │ 显示驱动抽象接口
├─────────────────────────────────────┤
│ 硬件驱动层(Hardware) │ LCD驱动、SPI通信
└─────────────────────────────────────┘
4.1 硬件驱动层
负责底层硬件操作,包括LCD初始化、SPI通信等。
4.1.1 LCD驱动头文件
c
/* lcd_st7789_driver.h */
#ifndef __LCD_ST7789_DRIVER_H__
#define __LCD_ST7789_DRIVER_H__
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include "board_config.h"
#include "disp_driver.h"
/* 屏幕分辨率定义 */
#define LCD_PANEL_PIXEL_X (240)
#define LCD_PANEL_PIXEL_Y (240)
/* 颜色定义(RGB565格式) */
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE 0x001F
#define WHITE 0xFFFF
#define BLACK 0x0000
#define YELLOW 0xFFE0
#define GRAY0 0xEF7D
#define GRAY1 0x8410
#define GRAY2 0x4208
/* LCD驱动接口 */
void lcd_init(void);
void lcd_set_region(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void lcd_set_point(uint16_t x, uint16_t y);
void lcd_draw_region(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color);
void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color);
void lcd_clear(uint16_t color);
#endif /* __LCD_ST7789_DRIVER_H__ */
4.1.2 LCD驱动实现
c
/* lcd_st7789_driver.c */
#include <unistd.h>
#include "disp_driver.h"
#include "lcd_st7789_driver.h"
void lcd_init(void)
{
uint8_t buff[16] = {0};
printf("[lcd_init] st7789\r\n");
/* 退出睡眠模式 */
disp_write_byte(LCD_CMD, 0x11);
usleep(100*1000);
/* 设置列地址 */
disp_write_byte(LCD_CMD, 0x2A);
buff[0] = 0x00;
buff[1] = 0x00; /* 起始列 0x0000 */
buff[2] = 0x00;
buff[3] = 0xEF; /* 结束列 0x00EF = 239 */
disp_write_buff(LCD_DAT, buff, 4);
/* 设置行地址 */
disp_write_byte(LCD_CMD, 0x2B);
buff[0] = 0x00;
buff[1] = 0x00; /* 起始行 0x0000 */
buff[2] = 0x00;
buff[3] = 0xEF; /* 结束行 0x00EF = 239 */
disp_write_buff(LCD_DAT, buff, 4);
/* 门廊控制 */
disp_write_byte(LCD_CMD, 0xB2);
buff[0] = 0x0C;
buff[1] = 0x0C;
buff[2] = 0x00;
buff[3] = 0x33;
buff[4] = 0x33;
disp_write_buff(LCD_DAT, buff, 5);
/* 显示反转关闭 */
disp_write_byte(LCD_CMD, 0x20);
/* 门控制 */
disp_write_byte(LCD_CMD, 0xB7);
disp_write_byte(LCD_DAT, 0x56);
/* VCOMS设置 */
disp_write_byte(LCD_CMD, 0xBB);
disp_write_byte(LCD_DAT, 0x18);
/* LCM控制 */
disp_write_byte(LCD_CMD, 0xC0);
disp_write_byte(LCD_DAT, 0x2C);
/* VDV和VRH命令使能 */
disp_write_byte(LCD_CMD, 0xC2);
disp_write_byte(LCD_DAT, 0x01);
/* VRH设置 */
disp_write_byte(LCD_CMD, 0xC3);
disp_write_byte(LCD_DAT, 0x1F);
/* VDV设置 */
disp_write_byte(LCD_CMD, 0xC4);
disp_write_byte(LCD_DAT, 0x20);
/* FR控制2 */
disp_write_byte(LCD_CMD, 0xC6);
disp_write_byte(LCD_DAT, 0x0F);
/* 电源控制1 */
disp_write_byte(LCD_CMD, 0xD0);
disp_write_byte(LCD_DAT, 0xA6);
disp_write_byte(LCD_DAT, 0xA1);
/* 正电压Gamma控制 */
disp_write_byte(LCD_CMD, 0xE0);
buff[0] = 0xD0;
buff[1] = 0x0D;
buff[2] = 0x14;
buff[3] = 0x0B;
buff[4] = 0x0B;
buff[5] = 0x07;
buff[6] = 0x3A;
buff[7] = 0x44;
buff[8] = 0x50;
buff[9] = 0x08;
buff[10] = 0x13;
buff[11] = 0x13;
buff[12] = 0x2D;
buff[13] = 0x32;
disp_write_buff(LCD_DAT, buff, 14);
/* 负电压Gamma控制 */
disp_write_byte(LCD_CMD, 0xE1);
disp_write_buff(LCD_DAT, buff, 14);
/* 存储器数据访问控制 */
disp_write_byte(LCD_CMD, 0x36);
disp_write_byte(LCD_DAT, 0x00);
/* 列模式/接口像素格式 */
disp_write_byte(LCD_CMD, 0x3A);
disp_write_byte(LCD_DAT, 0x55); /* 65K: 0x55[565] */
/* 使能数据通道2 */
disp_write_byte(LCD_CMD, 0xE7);
disp_write_byte(LCD_DAT, 0x00);
/* 显示反转开启 */
disp_write_byte(LCD_CMD, 0x21);
/* 显示开启 */
disp_write_byte(LCD_CMD, 0x29);
}
void lcd_set_region(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
uint8_t buff[4] = {0};
/* 设置列地址 */
buff[0] = (x0 & 0xFF00) >> 8;
buff[1] = (x0 & 0x00FF) >> 0;
buff[2] = (x1 & 0xFF00) >> 8;
buff[3] = (x1 & 0x00FF) >> 0;
disp_write_byte(LCD_CMD, 0x2A);
disp_write_buff(LCD_DAT, buff, 4);
/* 设置行地址 */
buff[0] = (y0 & 0xFF00) >> 8;
buff[1] = (y0 & 0x00FF) >> 0;
buff[2] = (y1 & 0xFF00) >> 8;
buff[3] = (y1 & 0x00FF) >> 0;
disp_write_byte(LCD_CMD, 0x2B);
disp_write_buff(LCD_DAT, buff, 4);
/* 内存写入命令 */
disp_write_byte(LCD_CMD, 0x2C);
}
void lcd_set_point(uint16_t x, uint16_t y)
{
lcd_set_region(x, y, x, y);
}
void lcd_draw_region(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
{
uint16_t i, j;
uint8_t buff[2] = {0};
buff[0] = (color & 0xFF00) >> 8;
buff[1] = (color & 0x00FF) >> 0;
lcd_set_region(x0, y0, x1-1, y1-1);
disp_write_byte(LCD_CMD, 0x2C);
for(i = x0; i < x1; i++) {
for(j = y0; j < y1; j++) {
disp_write_buff(LCD_DAT, buff, 2);
}
}
}
void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color)
{
uint8_t buff[2] = {0};
buff[0] = (color & 0xFF00) >> 8;
buff[1] = (color & 0x00FF) >> 0;
lcd_set_region(x, y, x+1, y+1);
disp_write_buff(LCD_DAT, buff, 2);
}
void lcd_clear(uint16_t color)
{
lcd_draw_region(0, 0, LCD_PANEL_PIXEL_X, LCD_PANEL_PIXEL_Y, color);
}
4.2 驱动抽象层
提供统一的显示驱动接口,屏蔽底层硬件差异。
4.2.1 驱动抽象头文件
c
/* disp_driver.h */
#ifndef __DISP_DRIVER_H__
#define __DISP_DRIVER_H__
#include "board_config.h"
#if defined(LCD_ST7789S_240x240)
#include "lcd_st7789_driver.h"
#endif
/* 显示分辨率定义 */
#if defined(LCD_PANEL_PIXEL_X)
#define DISP_WIDTH LCD_PANEL_PIXEL_X
#else
#error LCD_PANEL_PIXEL_X unknown
#endif
#if defined(LCD_PANEL_PIXEL_Y)
#define DISP_HEIGHT LCD_PANEL_PIXEL_Y
#else
#error LCD_PANEL_PIXEL_Y unknown
#endif
/* UI相关定义 */
#define UI_SHOW_EMOJI (1)
#define UI_SAFE_BORDER (6) /* 安全边界宽度(像素) */
#define UI_STATUS_HEIGHT (18) /* 状态栏高度(像素) */
#define UI_EMOJI_WIDTH (72) /* emoji图标宽度 */
#define UI_EMOJI_HEIGHT (72) /* emoji图标高度 */
/* LCD命令/数据选择 */
#define LCD_CMD (0) /* 命令模式 */
#define LCD_DAT (1) /* 数据模式 */
/* 背光PWM配置 */
#define LCD_BLK_PWM_PORT (2) /* 背光PWM端口 */
#define LCD_BLK_PWM_MAX_DIV_NUM_MAX (8)
#define LCD_BLK_PWM_DUTY_MIN (0) /* 最小占空比 */
#define LCD_BLK_PWM_DUTY_MAX (100) /* 最大占空比 */
#define LCD_BLK_PWM_DUTY_DEF (40) /* 默认占空比 */
#define LCD_BLK_PWM_DUTY_STEP (2) /* 占空比步进 */
#define LCD_BLK_PWM_FREQ_STEP (10) /* 频率步进 */
#define LCD_BLK_PWM_FREQ_DEF (1230) /* 默认频率 */
/* GPIO控制函数 */
static inline void lcd_set_reset_pin(uint8_t level)
{
uapi_gpio_set_val(LCD_RST_PIN, level);
}
static inline void lcd_set_dc_pin(uint8_t level)
{
uapi_gpio_set_val(LCD_DC_PIN, level);
}
/* 片选控制函数 */
errcode_t lcd_set_cs_pin(uint8_t level);
/* 显示驱动接口 */
errcode_t disp_write_buff(uint8_t cmd, uint8_t* buff, uint32_t len);
errcode_t disp_write_byte(uint8_t cmd, uint8_t data);
/* 背光控制接口 */
errcode_t disp_set_backlight_level(uint8_t level); /* level[0, 100] */
errcode_t disp_set_backlight_default(void);
errcode_t disp_set_backlight_off(void);
/* 面板控制接口 */
void disp_init_panel(void);
void disp_reset_panel(void);
/* 绘图接口 */
void disp_set_point(uint16_t x, uint16_t y);
void disp_set_region(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void disp_draw_point(uint16_t x, uint16_t y, uint16_t color);
void disp_clear(uint16_t color);
#endif /* __DISP_DRIVER_H__ */
4.2.2 驱动抽象实现
c
/* disp_driver.c */
#include "disp_driver.h"
/* 片选控制 */
errcode_t lcd_set_cs_pin(uint8_t level)
{
uint16_t timeout = 0;
errcode_t ret = spi_cs_pin_race(LCD_CS_PIN, level);
while (ERRCODE_SUCC != ret) {
usleep(1000); /* 等待1ms */
timeout++;
if (timeout >= SPI_CS_PIN_RACE_TIMEOUT) { /* 超时 */
printf("lcd_set_cs_pin: spi_cs_pin_race ==>> NG, timeout\r\n");
return ERRCODE_FAIL;
}
ret = spi_cs_pin_race(LCD_CS_PIN, level);
}
return ret;
}
/* 写缓冲区 */
errcode_t disp_write_buff(uint8_t cmd, uint8_t* buff, uint32_t len)
{
spi_xfer_data_t spi_data = {
.tx_buff = buff,
.tx_bytes = len,
};
if (ERRCODE_SUCC != lcd_set_cs_pin(GPIO_LEVEL_LOW)) {
return ERRCODE_FAIL;
}
lcd_set_dc_pin(cmd); /* DC(cmd): [Lo-Cmd, Hi-Data] */
errcode_t ret = uapi_spi_master_write(SPI_IDX, &spi_data, 0xFFFF);
lcd_set_cs_pin(GPIO_LEVEL_HIGH);
return ret;
}
/* 写单字节 */
errcode_t disp_write_byte(uint8_t cmd, uint8_t data)
{
return disp_write_buff(cmd, &data, 1);
}
/* 复位面板 */
void disp_reset_panel(void)
{
lcd_set_cs_pin(GPIO_LEVEL_HIGH);
lcd_set_reset_pin(GPIO_LEVEL_LOW);
usleep(20*1000);
lcd_set_reset_pin(GPIO_LEVEL_HIGH);
usleep(10*1000);
}
/* 初始化面板 */
void disp_init_panel(void)
{
disp_reset_panel();
lcd_init();
}
/* 设置绘图点 */
void disp_set_point(uint16_t x, uint16_t y)
{
lcd_set_point(x, y);
}
/* 设置绘图区域 */
void disp_set_region(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
lcd_set_region(x0, y0, x1, y1);
}
/* 绘制点 */
void disp_draw_point(uint16_t x, uint16_t y, uint16_t color)
{
lcd_draw_point(x, y, color);
}
/* 清屏 */
void disp_clear(uint16_t color)
{
lcd_clear(color);
}
/* 设置背光 */
static errcode_t disp_set_backlight(uint8_t port, uint8_t in_duty, unsigned int in_freq)
{
uint8_t duty = in_duty;
unsigned int freq = in_freq;
/* 占空比限制 */
if (duty > LCD_BLK_PWM_DUTY_MAX) {
duty = LCD_BLK_PWM_DUTY_MAX;
} else if (duty <= LCD_BLK_PWM_DUTY_MIN) {
duty = LCD_BLK_PWM_DUTY_MIN;
}
/* 频率限制 */
if (freq < LCD_BLK_PWM_FREQ_DEF) {
freq = LCD_BLK_PWM_FREQ_DEF;
}
/* 计算分频系数 */
uint32_t clk_freq = uapi_pwm_get_frequency((uint8_t)port);
uint32_t div_num = (uint32_t)(clk_freq / freq);
if (div_num < LCD_BLK_PWM_MAX_DIV_NUM_MAX) {
return ERRCODE_FAIL;
}
/* 计算高电平时间 */
uint32_t high_time = div_num * duty / LCD_BLK_PWM_DUTY_MAX;
if (high_time * LCD_BLK_PWM_DUTY_MAX / div_num >=
(uint32_t)(duty - duty * LCD_BLK_PWM_DUTY_STEP / LCD_BLK_PWM_DUTY_MAX)) {
/* OK */
} else if ((high_time + 1) * LCD_BLK_PWM_DUTY_MAX / div_num <=
(uint32_t)(duty + duty * LCD_BLK_PWM_DUTY_STEP / LCD_BLK_PWM_DUTY_MAX)) {
high_time++;
} else {
return ERRCODE_FAIL;
}
uint32_t low_time = div_num - high_time;
/* 配置PWM */
pwm_config_t cfg = {
.low_time = low_time,
.high_time = high_time,
.offset_time = 0,
.cycles = 0,
.repeat = true
};
uapi_pwm_close(port);
if (uapi_pwm_open((uint8_t)port, &cfg) != ERRCODE_SUCC) {
return ERRCODE_FAIL;
}
/* 设置PWM组 */
uint8_t group_id = 0;
switch (port) {
case 0:
case 1:
group_id = 0;
break;
case 2:
case 3:
group_id = 1;
break;
case 4:
case 5:
group_id = 2;
break;
case 6:
case 7:
group_id = 3;
break;
default:
break;
}
uapi_pwm_clear_group(group_id);
if (uapi_pwm_set_group(group_id, &port, 1) != ERRCODE_SUCC) {
return ERRCODE_FAIL;
}
if (uapi_pwm_start((uint8_t)port) != ERRCODE_SUCC) {
return ERRCODE_FAIL;
}
return ERRCODE_SUCC;
}
/* 设置背光等级 */
errcode_t disp_set_backlight_level(uint8_t level)
{
return disp_set_backlight(LCD_BLK_PWM_PORT, level, LCD_BLK_PWM_FREQ_DEF);
}
/* 设置默认背光 */
errcode_t disp_set_backlight_default(void)
{
return disp_set_backlight(LCD_BLK_PWM_PORT, LCD_BLK_PWM_DUTY_DEF, LCD_BLK_PWM_FREQ_DEF);
}
/* 关闭背光 */
errcode_t disp_set_backlight_off(void)
{
return disp_set_backlight(LCD_BLK_PWM_PORT, 0, LCD_BLK_PWM_FREQ_DEF);
}
4.3 LVGL适配层
将LVGL的显示接口与底层驱动连接起来。
4.3.1 LVGL显示刷新回调
c
/* lvgl_task.c - LVGL任务和显示适配 */
#include "timer.h"
#include "chip_core_irq.h"
#include "board_config.h"
#include "disp_driver.h"
#include "task_entry.h"
#include "net_wifi_config.h"
#include "settings.h"
#include "lvgl.h"
#include "lvgl_ui_layout.h"
/* 显示缓冲区 */
static uint8_t disp_buffer[DISP_WIDTH * UI_SAFE_BORDER * 2];
/* 定时器相关变量 */
static timer_handle_t timerHandle = NULL;
static timer_index_t timerIndex = 1;
static uint32_t timerIrqId = TIMER_1_IRQN;
static uint32_t timerDelay = 1000;
static uint16_t timerPriority = 1;
/* LVGL tick定时器回调 */
void disp_timer_cb(uint32_t data)
{
unused(data);
lv_tick_inc(1); /* 增加LVGL tick计数 */
uapi_timer_start(timerHandle, timerDelay, disp_timer_cb, 0);
}
/* 配置LVGL定时器 */
void disp_timer_config(void)
{
uapi_timer_init();
uapi_timer_adapter(timerIndex, timerIrqId, timerPriority);
uapi_timer_create(timerIndex, &timerHandle);
uapi_timer_start(timerHandle, timerDelay, disp_timer_cb, 0);
}
/* LVGL显示刷新回调 */
void disp_flush_cb(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *px_map)
{
/* 计算要发送的数据长度 */
uint32_t len = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 2;
/* 设置显示区域 */
disp_set_region(area->x1, area->y1, area->x2, area->y2);
/* 发送像素数据 */
disp_write_buff(LCD_DAT, px_map, len);
/* 通知LVGL刷新完成 */
lv_display_flush_ready(disp_drv);
}
/* LVGL任务主函数 */
void LvglTask(void)
{
osStatus_t ret = osError;
uint8_t wifi_st = eWifi_St_Disconnected;
uint8_t msg_recv = 0;
uint8_t dbg_emj = 0;
uint8_t dbg_txt = 0;
/* 初始化显示 */
disp_set_backlight_off(); /* 初始化时关闭背光 */
disp_init_panel();
disp_timer_config();
/* 初始化LVGL */
lv_init();
/* 创建显示对象 */
lv_display_t* disp_obj = lv_display_create(DISP_WIDTH, DISP_HEIGHT);
lv_display_set_flush_cb(disp_obj, disp_flush_cb);
lv_display_set_buffers(disp_obj, disp_buffer, NULL,
sizeof(disp_buffer),
LV_DISPLAY_RENDER_MODE_PARTIAL);
/* 创建UI对象 */
lv_obj_t* status_bar = lv_label_create(lv_scr_act());
lv_obj_t* img_emoji = lv_image_create(lv_scr_act());
lv_obj_t* text_half = lv_label_create(lv_scr_act());
lv_obj_t* text_full = lv_label_create(lv_scr_act());
/* 配置UI区域 */
ui_status_area_config(status_bar);
ui_emoji_area_config(img_emoji);
ui_text_area_config(text_half, 0);
ui_text_area_config(text_full, 1);
lv_obj_add_flag(text_full, LV_OBJ_FLAG_HIDDEN);
/* 清屏并设置背光 */
disp_clear(WHITE);
disp_set_backlight_level(get_blk_level());
/* 主循环 */
while (1) {
/* LVGL任务处理 */
lv_timer_handler();
ui_status_area_update(status_bar);
/* 接收显示事件 */
ret = osMessageQueueGet(g_disp_event_qid, (void *)&msg_recv, NULL, 30);
if (ret != osOK) {
msg_recv = 0;
continue;
}
printf("[LvglTask] osMessageQueueGet: msg[%d]\r\n", msg_recv);
/* 处理显示事件 */
switch (msg_recv) {
case eDisp_Bar_Update:
ui_status_area_update(status_bar);
break;
case eDisp_Text_Update:
lv_obj_remove_flag(img_emoji, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(text_half, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(text_full, LV_OBJ_FLAG_HIDDEN);
dbg_txt = (dbg_txt+1)&0x7F;
if (dbg_txt&0x01) {
lv_label_set_text_fmt(text_half,
"OpenAtom Foundation\n孵化及运营开源项目的平台\nBlk:%d",
get_blk_level());
} else {
lv_label_set_text_fmt(text_half,
"Hello OpenHarmony\n开源操作系统项目\nVol:%d",
get_volume());
}
break;
case eDisp_Emoji_Update:
lv_obj_remove_flag(img_emoji, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(text_half, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(text_full, LV_OBJ_FLAG_HIDDEN);
dbg_emj = (dbg_emj+1)&0x7F;
if (dbg_emj&0x01) {
lv_image_set_src(img_emoji, &img_emoji_0xF617);
} else {
lv_image_set_src(img_emoji, &img_emoji_0xF600);
}
break;
case eDisp_WiFi_St_Update:
ui_status_area_update(status_bar);
wifi_st = get_wifi_status();
if (wifi_st == eWifi_St_Connecting) {
lv_label_set_text_fmt(text_full,
"手机搜索并连接WiFi热点:%s,密码是:12345678,连接成功后,手机打开浏览器输入网址\"http://192.168.1.1\",在登录页面输入待连接的热点名称和密码,点击开始配网即可。",
hotspot_name);
lv_obj_add_flag(img_emoji, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(text_half, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(text_full, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_remove_flag(img_emoji, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(text_half, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(text_full, LV_OBJ_FLAG_HIDDEN);
if (wifi_st == eWifi_St_Connected) {
lv_image_set_src(img_emoji, &img_emoji_0xF600);
lv_label_set_text_fmt(text_half, "WiFi 连接成功!");
} else if (wifi_st == eWifi_St_AutoConnecting) {
lv_image_set_src(img_emoji, &img_emoji_0xF600);
lv_label_set_text_fmt(text_half, "WiFi 连接中...");
} else {
lv_image_set_src(img_emoji, &img_emoji_0xF617);
lv_label_set_text_fmt(text_half, "WiFi 连接失败,请重新尝试连接...");
}
}
break;
case eDisp_Blk_On:
disp_set_backlight_level(get_blk_level());
break;
case eDisp_Blk_Off:
disp_set_backlight_off();
break;
case eDisp_Font_Dl_Progress:
lv_obj_add_flag(img_emoji, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(text_half, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(text_full, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(text_full, "Font downloading, please wait...");
break;
case eDisp_Font_Dl_Complete:
lv_label_set_text(text_full, "Font download done.");
break;
case eDisp_Font_Dl_Already:
lv_obj_add_flag(img_emoji, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(text_half, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(text_full, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(text_full, "Font already on device.");
break;
case eDisp_Font_Dl_Restore: {
uint8_t st = get_wifi_status();
lv_obj_remove_flag(img_emoji, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(text_half, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(text_full, LV_OBJ_FLAG_HIDDEN);
if (st == eWifi_St_Connected) {
lv_image_set_src(img_emoji, &img_emoji_0xF600);
lv_label_set_text_fmt(text_half, "WiFi 连接成功!");
} else if (st == eWifi_St_AutoConnecting) {
lv_image_set_src(img_emoji, &img_emoji_0xF600);
lv_label_set_text_fmt(text_half, "WiFi 连接中...");
} else {
lv_image_set_src(img_emoji, &img_emoji_0xF617);
lv_label_set_text_fmt(text_half, "WiFi 连接失败,请重新尝试连接...");
}
break;
}
default:
break;
}
}
}
/* LVGL任务入口 */
uint32_t LvglTaskEntry(void)
{
task_config_t task_conf = {
"LvglTask", /* 任务名称 */
1024*4, /* 栈大小 */
24, /* 优先级 */
(void*)LvglTask /* 任务函数 */
};
return create_task(&task_conf);
}
五、关键移植步骤
5.1 LVGL初始化
c
/* 1. 初始化LVGL库 */
lv_init();
/* 2. 创建显示对象 */
lv_display_t* disp_obj = lv_display_create(DISP_WIDTH, DISP_HEIGHT);
/* 3. 设置刷新回调 */
lv_display_set_flush_cb(disp_obj, disp_flush_cb);
/* 4. 设置显示缓冲区 */
lv_display_set_buffers(disp_obj, disp_buffer, NULL,
sizeof(disp_buffer),
LV_DISPLAY_RENDER_MODE_PARTIAL);
5.2 显示刷新回调实现
显示刷新回调是LVGL与底层驱动的关键接口:
c
void disp_flush_cb(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *px_map)
{
/* area: 要刷新的区域 */
/* px_map: 像素数据指针 */
/* 1. 计算数据长度 */
uint32_t len = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 2;
/* 2. 设置显示区域 */
disp_set_region(area->x1, area->y1, area->x2, area->y2);
/* 3. 发送像素数据 */
disp_write_buff(LCD_DAT, px_map, len);
/* 4. 通知LVGL刷新完成(必须调用) */
lv_display_flush_ready(disp_drv);
}
5.3 LVGL定时器配置
LVGL需要一个周期性的tick来驱动内部计时器:
c
/* 1. 配置硬件定时器 */
void disp_timer_config(void)
{
uapi_timer_init();
uapi_timer_adapter(timerIndex, timerIrqId, timerPriority);
uapi_timer_create(timerIndex, &timerHandle);
uapi_timer_start(timerHandle, timerDelay, disp_timer_cb, 0);
}
/* 2. 定时器回调函数 */
void disp_timer_cb(uint32_t data)
{
unused(data);
lv_tick_inc(1); /* 每次调用增加1ms */
uapi_timer_start(timerHandle, timerDelay, disp_timer_cb, 0);
}
5.4 LVGL任务循环
c
void LvglTask(void)
{
/* 初始化显示和LVGL */
disp_init_panel();
disp_timer_config();
lv_init();
lv_display_create(...);
/* 主循环 */
while (1) {
/* LVGL任务处理 */
lv_timer_handler();
/* 处理其他任务 */
/* ... */
/* 适当延时 */
osDelay(1);
}
}
六、性能优化
6.1 显示缓冲区优化
c
/* 使用部分渲染模式 */
#define LV_DISPLAY_RENDER_MODE_PARTIAL
/* 缓冲区大小优化 */
static uint8_t disp_buffer[DISP_WIDTH * UI_SAFE_BORDER * 2];
/* 或者使用双缓冲 */
static uint8_t disp_buf1[DISP_WIDTH * UI_SAFE_BORDER * 2];
static uint8_t disp_buf2[DISP_WIDTH * UI_SAFE_BORDER * 2];
lv_display_set_buffers(disp_obj, disp_buf1, disp_buf2,
sizeof(disp_buf1),
LV_DISPLAY_RENDER_MODE_PARTIAL);
6.2 SPI传输优化
c
/* 增加SPI时钟频率 */
/* 在SPI初始化时设置更高的时钟频率 */
/* 使用DMA传输(如果硬件支持) */
spi_xfer_data_t spi_data = {
.tx_buff = buff,
.tx_bytes = len,
.dma_enable = 1, /* 启用DMA */
};
6.3 刷新区域优化
c
/* 只刷新变化的区域 */
void partial_flush(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
disp_set_region(x0, y0, x1, y1);
/* 只刷新指定区域 */
}
七、常见问题与解决方案
7.1 显示闪烁
问题:屏幕刷新时出现闪烁。
原因:单缓冲渲染,刷新过程中可见。
解决方案:
c
/* 使用双缓冲 */
static uint8_t disp_buf1[DISP_WIDTH * DISP_HEIGHT * 2];
static uint8_t disp_buf2[DISP_WIDTH * DISP_HEIGHT * 2];
lv_display_set_buffers(disp_obj, disp_buf1, disp_buf2,
sizeof(disp_buf1),
LV_DISPLAY_RENDER_MODE_DIRECT);
7.2 刷新不完整
问题:屏幕刷新不完整,部分区域未更新。
原因:缓冲区大小不足或刷新回调未正确调用lv_display_flush_ready()。
解决方案:
c
/* 确保调用lv_display_flush_ready() */
void disp_flush_cb(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *px_map)
{
disp_set_region(area->x1, area->y1, area->x2, area->y2);
disp_write_buff(LCD_DAT, px_map, len);
/* 必须调用,否则LVGL认为刷新未完成 */
lv_display_flush_ready(disp_drv);
}
7.3 颜色显示异常
问题:颜色显示不正确。
原因:颜色格式不匹配(RGB565 vs RGB888)。
解决方案:
c
/* 确保LVGL配置为RGB565格式 */
#define LV_COLOR_FORMAT LV_COLOR_FORMAT_RGB565
/* 或者在lv_conf.h中设置 */
#define LV_COLOR_DEPTH 16
7.4 性能问题
问题:刷新速度慢,UI响应延迟。
解决方案:
c
/* 1. 增加SPI时钟频率 */
/* 2. 使用DMA传输 */
/* 3. 优化刷新区域 */
/* 4. 减少不必要的重绘 */
/* 示例:减少重绘 */
lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN); /* 隐藏不需要显示的对象 */
7.5 内存不足
问题:内存不足导致LVGL初始化失败。
解决方案:
c
/* 1. 减小缓冲区大小 */
static uint8_t disp_buffer[DISP_WIDTH * 10 * 2]; /* 只缓冲10行 */
/* 2. 使用部分渲染模式 */
lv_display_set_buffers(disp_obj, disp_buffer, NULL,
sizeof(disp_buffer),
LV_DISPLAY_RENDER_MODE_PARTIAL);
/* 3. 减少LVGL内存池大小 */
/* 在lv_conf.h中调整 */
#define LV_MEM_SIZE (32U * 1024U) /* 减小到32KB */
八、调试技巧
8.1 启用LVGL日志
c
/* 在lv_conf.h中启用日志 */
#define LV_USE_LOG 1
#define LV_LOG_LEVEL LV_LOG_LEVEL_INFO
/* 在代码中设置日志级别 */
lv_log_set_level(LV_LOG_LEVEL_DEBUG);
8.2 性能监控
c
/* 监控刷新时间 */
void disp_flush_cb(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *px_map)
{
uint32_t start = osKernelGetTickCount();
disp_set_region(area->x1, area->y1, area->x2, area->y2);
disp_write_buff(LCD_DAT, px_map, len);
uint32_t elapsed = osKernelGetTickCount() - start;
printf("Flush time: %lu ms\n", elapsed);
lv_display_flush_ready(disp_drv);
}
8.3 内存监控
c
/* 监控LVGL内存使用 */
void monitor_memory(void)
{
lv_mem_monitor_t mon;
lv_mem_monitor(&mon);
printf("LVGL Memory:\n");
printf(" Total: %d bytes\n", mon.total_size);
printf(" Free: %d bytes\n", mon.free_size);
printf(" Used: %d%%\n", mon.used_pct);
}
九、总结
本文档详细介绍了LVGL UI框架底层显示驱动在海思WS63平台上的移植过程,包括:
- 硬件平台:ST7789S显示屏规格和硬件连接
- 驱动架构:四层架构设计(硬件层、驱动层、适配层、应用层)
- 驱动实现:LCD驱动、SPI通信、背光控制
- LVGL适配:显示刷新回调、定时器配置、任务循环
- 性能优化:缓冲区优化、SPI传输优化、刷新区域优化
- 问题解决:显示闪烁、刷新不完整、颜色异常等常见问题
- 调试技巧:日志启用、性能监控、内存监控
通过本次移植,WS63平台获得了强大的图形用户界面能力,可以方便地创建丰富的UI界面。LVGL的轻量级设计和强大的功能,使其非常适合在资源受限的嵌入式系统中使用。
十、参考资料
- LVGL官方文档:https://docs.lvgl.io/
- LVGL GitHub仓库:https://github.com/lvgl/lvgl
- ST7789S数据手册
- 海思WS63开发指南
- CMSIS-RTOS2 API文档
- https://www.cnblogs.com/star-light-glimmer/p/18104596