ESP32 基于GUI-Guider屏幕UI开发教程
ESP32 基于GUI-Guider屏幕UI开发教程
背景介绍
依托现在炙手可热的大模型,越来越多的智能设备蓬勃发展,人机交互也是其中的重要的一个环节,本文抛砖引玉,简单的介绍一下依托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;
}
}
最终的效果
更多推荐



所有评论(0)