背景介绍

        依托现在炙手可热的大模型,越来越多的智能设备蓬勃发展,人机交互也是其中的重要的一个环节,本文抛砖引玉,简单的介绍一下依托GUI-Guider+LVGL8.3.0开发自己的屏幕UI程序。

硬件介绍

        Seeed Studio XIAO ESP32S3 Sense开发版,摄像头使用的是OV2610,显示采用的是GC9A01+240*240 TFT 圆形显示屏,加上几个物理按键,搭建一个简单的开发环境

GUI-Guider项目创建过程

1. 打开软件,创建一个空白的UI工程,如下图所示:

2. 编辑屏幕的信息,本项目使用的是GC9A01,只能支持RGB565格式,所以颜色深度16位,需要根据屏幕的信息具体进行设置,设置屏幕的分辨率,如果面板类型没有相应的屏幕,可以选择Custom选项,设置自己的屏幕分辨率,本项目就是使用的这一选项,分辨率设置成240*240

3.创建完项目之后,就可以进行UI编程,通过拖拽的方式,将要使用的屏幕控件放在屏幕的对应的位置上,如下图所示,软件的基本使用方法可以参考NXP官网的教程,使用起来还是比较方便的,官网的链接:GUI Guider | NXP 半导体

4.可以为每个图片按钮,添加具体的事件处理,事件的添加方式如下,单击图标,右键事件添加,本例程添加了三个事件,分别是focused代码聚焦到这按钮上时,图标的坐标向上移动到34,defocused事件代表离开这个图标时,坐标的值,这样当使用物理按键切换坐标的时候,图标有上下移动的效果,最后一个事件时点击,点击之后会跳转到一个新的页面。

5.软件自带模拟器,可以直接在软件上,调试效果,同时也可以自动生成代码,快速的部署到设备上。

6.工程的移植将自动生成的代码custom和generated目录移植到ESP-IDF框架中,如下所示

7.工程的cmake文件如下:要加上 这条宏 idf_build_set_property(COMPILE_OPTIONS "-DLV_LVGL_H_INCLUDE_SIMPLE=1" APPEND)

set(APP_DIR ./app)
file(GLOB_RECURSE APP_SRCS ${APP_DIR}/*.c)
set(UI_DIR ./ui)
file(GLOB_RECURSE UI_SRCS ${UI_DIR}/*.c  ${UI_DIR}/generated/images/*.c ${UI_DIR}/generated/guider_fonts/*.c ${UI_DIR}/custom/*.c)
set(UI_INCLUDES ${UI_DIR}/generated ${UI_DIR}/generated/guider_fonts ${UI_DIR}/generated/guider_customer_fonts ${UI_DIR}/generated/guider_fonts ${UI_DIR}/custom)

idf_component_register(
        SRCS
            "main.c"
            ${APP_SRCS}
            ${UI_SRCS}
        INCLUDE_DIRS
            "."
            ${APP_DIR}
            ${UI_INCLUDES}

)
idf_build_set_property(COMPILE_OPTIONS "-DLV_LVGL_H_INCLUDE_SIMPLE=1" APPEND)

物理按键的添加过程

        物理按键与屏幕的关联,使用到了lvgl group的概念,如果要了解lvgl的相关知识点可以参考韦东山老师的lvgl的教程。具体物理按键的管理过程如下所示:

        1. 编写物理按键的驱动,本例子使用了三个物理按键,两个方向键,一个确认键,具体的驱动代码如下:

#define BUTTON_LEFT_GPIO_PIN    1   // 假设左箭头键的GPIO引脚
#define BUTTON_RIGHT_GPIO_PIN   3   // 假设右箭头键的GPIO引脚
#define BUTTON_SELECT_GPIO_PIN  2   // 假设选择键的GPIO引脚

static uint8_t button_left_state = 0;
static uint8_t button_right_state = 0;
static uint8_t button_select_state = 0;

static lv_indev_t *indev;
lv_indev_drv_t indev_drv;

bool read_button_state(uint8_t button_gpio_pin) {
    return gpio_get_level(button_gpio_pin) == 0;   // 按键按下时GPIO为低电平
}

// 按键驱动函数实现
void key_input_driver(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) {
    // 获取按键的状态
    button_left_state = read_button_state(BUTTON_LEFT_GPIO_PIN);
    button_right_state = read_button_state(BUTTON_RIGHT_GPIO_PIN);
    button_select_state = read_button_state(BUTTON_SELECT_GPIO_PIN);

    // 判断按键状态并生成LVGL的输入事件
    if (button_left_state) {
        data->key = LV_KEY_NEXT;  // 设置为左箭头键
        data->state = LV_INDEV_STATE_PRESSED;  // 按下状态
    } else if (button_right_state) {
        data->key = LV_KEY_PREV;  // 设置为右箭头键
        data->state = LV_INDEV_STATE_PRESSED;  // 按下状态
    } else if (button_select_state) {
        data->key = LV_KEY_ENTER;  // 设置为选择键
        data->state = LV_INDEV_STATE_PRESSED;  // 按下状态
    }else {
        data->key = LV_INDEV_TYPE_NONE;  // 没有按键按下时为无效键
        data->state = LV_INDEV_STATE_RELEASED;  // 释放状态
    }
}

void esp32s3_bsp_button_input_init(void)
{
    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << BUTTON_LEFT_GPIO_PIN) | (1ULL << BUTTON_RIGHT_GPIO_PIN) |
                        (1ULL << BUTTON_SELECT_GPIO_PIN),
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .intr_type = GPIO_INTR_DISABLE  // 不启用中断,使用轮询
    };
    gpio_config(&io_conf);
    lv_indev_drv_init(&indev_drv);  // 初始化驱动结构体
    indev_drv.type = LV_INDEV_TYPE_KEYPAD;  // 设置输入设备类型为键盘
    indev_drv.read_cb = key_input_driver;  // 设置按键驱动函数
    indev = lv_indev_drv_register(&indev_drv);  // 注册驱动
}

void * esp32s3_bsp_get_indev()
{
    return indev;
}

 2. 创建 lvgl group,并且将lvgl的图片按钮控件添加到group中,同时关联按键驱动,group的创使用:lv_group_create(); 只有将控件添加到group中,才能使用物理按键关联控件。控件的添加如下:

void add_lvgl_obj_to_group(lv_obj_t *obj)
{
    printf("add_lvgl_obj_to_group\n");
    lv_group_add_obj(g_lvgl_group, obj);
    lv_indev_set_group(g_button_indev, g_lvgl_group);
}

3.做了一个简单的demo,将摄像头的采集的图片数据,通过屏幕显示出来,达到实时预览的效果,主要的业务代码如下:

#include "app_display.h"

#include <esp32s3_bsp_board.h>
#include <esp_heap_caps.h>
#include <esp_log.h>
#include <gui_guider.h>

#define IMAGE_MAX_SIZE  (240*176*3)
#define UI_TASK_STOP         BIT0
#define UI_TASK_FINISH       BIT1


static uint8_t *s_image_buffer;
lv_ui guider_ui;
lv_group_t *g_lvgl_group;
static lv_indev_t * g_button_indev;
static lv_img_dsc_t camera_image_dsc;
static char TAG [] = "app_display";
static ST_UI_EVENT s_ui_event;

void add_lvgl_obj_to_group(lv_obj_t *obj)
{
    printf("add_lvgl_obj_to_group\n");
    lv_group_add_obj(g_lvgl_group, obj);
    lv_indev_set_group(g_button_indev, g_lvgl_group);
}

void remove_lvgl_obj_to_group(lv_obj_t *obj)
{
    if (NULL != obj) {
        lv_group_remove_obj(obj);
    }
}

void setup_camera_image(uint8_t *image_data, uint32_t width, uint32_t height) {
    // 设置图片描述符
    camera_image_dsc.header.always_zero = 0;
    camera_image_dsc.header.w = width;
    camera_image_dsc.header.h = height;
    camera_image_dsc.data_size = width * height * 2; // RGB565: 每像素2字节
    camera_image_dsc.header.cf = LV_IMG_CF_TRUE_COLOR; // 颜色格式
    camera_image_dsc.data = image_data; // 指向图像数据
}

static void camera_play_task(void *arg)
{
    while (true) {
        memset(s_image_buffer, 0, IMAGE_MAX_SIZE);
        int len  = get_pic_from_camera(s_image_buffer);
        if (len > 0) {
             setup_camera_image(s_image_buffer,240,176);
             esp_lvgl_lock(0);
             lv_img_set_src(guider_ui.src_camera_camera_play, &camera_image_dsc); // 设置图片源
             lv_obj_align(guider_ui.src_camera_camera_play, LV_ALIGN_CENTER, 0, 0);
             esp_lvgl_unlock();
        }
        if (UI_TASK_STOP & xEventGroupGetBits(s_ui_event.event_group)) {
            ESP_LOGI(TAG, "Camera TASK FINISH");
            xEventGroupClearBits(s_ui_event.event_group, UI_TASK_STOP);
            xEventGroupSetBits(s_ui_event.event_group, UI_TASK_FINISH);
            vTaskDelete(NULL);
        }
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

void handle_ui_event(EN_UI_EVENT event)
{
    switch (event) {
        case APP_UI_EVENT_START_CAMERA:
            xTaskCreate(&camera_play_task, "Camera Task", 4 * 1024,NULL, 5,NULL);
            break;
        case APP_UI_EVENT_STOP_CAMERA:
            xEventGroupSetBits(s_ui_event.event_group, UI_TASK_STOP);
            xEventGroupWaitBits(s_ui_event.event_group,UI_TASK_FINISH, 1, 1, portMAX_DELAY);
            ESP_LOGI(TAG, "UI_TASK_FINISH");
            break;
        default:
            ESP_LOGE(TAG,"UNHANDLED EVENT");
    }
}

void app_display_start()
{
    g_lvgl_group = lv_group_create();
    assert(g_lvgl_group);
    g_button_indev = esp32s3_bsp_get_indev();
    s_ui_event.event_group = xEventGroupCreate();
    assert(s_ui_event.event_group);
    s_image_buffer = heap_caps_calloc(1, IMAGE_MAX_SIZE, MALLOC_CAP_SPIRAM);
    assert(s_image_buffer);
    esp_lvgl_lock(0);
    setup_ui(&guider_ui);
    esp_lvgl_unlock();
}

4.lvgl事件梳理的函数如下,当按下按键时,启动摄像头数据采集,当选择退出时,取消摄像头数据采集

static void main_camera_btn_event_handler (lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    switch (code) {
    case LV_EVENT_CLICKED:
    {
        remove_lvgl_obj_to_group(guider_ui.main_camera_btn);
        ui_load_scr_animation(&guider_ui, &guider_ui.src_camera, guider_ui.src_camera_del, &guider_ui.main_del, setup_scr_src_camera, LV_SCR_LOAD_ANIM_MOVE_RIGHT, 100, 100, true, true);
        handle_ui_event(APP_UI_EVENT_START_CAMERA);
        break;
    }
    default:
        break;
    }
}

void events_init_main (lv_ui *ui)
{
    lv_obj_add_event_cb(ui->main, main_event_handler, LV_EVENT_ALL, ui);
    lv_obj_add_event_cb(ui->main_camera_btn, main_camera_btn_event_handler, LV_EVENT_ALL, ui);
}

static void src_camera_btn_cam_back_event_handler (lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    switch (code) {
    case LV_EVENT_CLICKED:
    {
        remove_lvgl_obj_to_group(guider_ui.src_camera_btn_cam_back);
        handle_ui_event(APP_UI_EVENT_STOP_CAMERA);
        ui_load_scr_animation(&guider_ui, &guider_ui.main, guider_ui.main_del, &guider_ui.src_camera_del, setup_scr_main, LV_SCR_LOAD_ANIM_MOVE_LEFT, 100, 100, true, true);
        break;
    }
    default:
        break;
    }
}

最终的效果

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐