OpenHarmony海思WS63星闪平台:LVGL UI框架底层显示驱动移植指南

随着物联网技术的快速发展,高性能、低功耗、多协议的无线通信芯片成为智能设备的核心组件。海思 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平台上的移植过程,包括:

  1. 硬件平台:ST7789S显示屏规格和硬件连接
  2. 驱动架构:四层架构设计(硬件层、驱动层、适配层、应用层)
  3. 驱动实现:LCD驱动、SPI通信、背光控制
  4. LVGL适配:显示刷新回调、定时器配置、任务循环
  5. 性能优化:缓冲区优化、SPI传输优化、刷新区域优化
  6. 问题解决:显示闪烁、刷新不完整、颜色异常等常见问题
  7. 调试技巧:日志启用、性能监控、内存监控

通过本次移植,WS63平台获得了强大的图形用户界面能力,可以方便地创建丰富的UI界面。LVGL的轻量级设计和强大的功能,使其非常适合在资源受限的嵌入式系统中使用。

十、参考资料

相关推荐
国医中兴3 小时前
ClickHouse数据导入导出最佳实践:从性能到可靠性
flutter·harmonyos·鸿蒙·openharmony
国医中兴4 小时前
大数据处理的性能优化技巧:从理论到实践
flutter·harmonyos·鸿蒙·openharmony
qq_4924484467 小时前
AirTest APP UI自动化测试框架
macos·ui·cocoa
雪域迷影7 小时前
OpenHarmony 电源管理模块状态转换分析
c++·openharmony·电源管理部件
国医中兴7 小时前
ClickHouse集群部署与管理:从0到1的实战指南
flutter·harmonyos·鸿蒙·openharmony
文人sec7 小时前
抛弃 Postman!用 Pytest+Requests+Allure+Playwright+Minium 搭建高逼格接口+UI自动化测试平台
自动化测试·python·测试工具·ui·pytest·playwright
特立独行的猫a8 小时前
OpenHarmony海思WS63星闪平台:LVGL 9 + LittleFS:字库文件按需流式加载,减少内存占用的实践笔记
lvgl·openharmony·海思·littlefs·流式加载·ws63·hi3863
长安第一美人1 天前
AI辅助下的嵌入式UI系统设计与实践(二)[代码阅读理解]
c++·嵌入式硬件·ui·显示屏·工业应用
HwJack201 天前
HarmonyOS应用开发中EmbeddedUIExtensionAbility:跨进程 UI 嵌入的“幕后导演“
ui·华为·harmonyos