当前位置: 首页 > news >正文

ESP32-LVGL 开发笔记(二):设备注册

目标

本次的任务是

  1. 运行 lvgl demo
  2. 注册输入设备
    1. 注册按键
    2. 注册编码器(按键模拟)
  3. 开启性能检测

准备工作

运行 lvgl demo 之前,先来对 main.c 代码处理一下

  1. 改成横屏显示
    1. 分辨率,EXAMPLE_LCD_H_RES、EXAMPLE_LCD_V_RES
    2. 偏移,esp_lcd_panel_set_gap
    3. 反转 xy 坐标,swap_xy
  2. 删除 _app_button_cb 函数
  3. 删除 app_main_display 函数内的 ui 代码
static void app_main_display(void)
{/* Task lock */lvgl_port_lock(0);/* Your LVGL objects code here .... *//* Task unlock */lvgl_port_unlock();
}

1. 运行 lvgl demo

  1. 根目录创建 components/ui 组件
  2. ui 组件下创建 01_demo.c01_demo.hCMakeLists.txt
    image

CMakeLists.txt:

idf_component_register(SRCS "01_demo.c"INCLUDE_DIRS "."REQUIRES espressif__esp_lvgl_port lvgl__lvgl)

相关说明下方补充

  1. 复制官方demo
    复制 demo 代码到 01_demo.c,在 app_main_display 调用,完整代码如下
    01_demo.c
#include "01_demo.c"void lv_example_get_started_1(void)
{/*Change the active screen's background color*/lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x003a57), LV_PART_MAIN);/*Create a white label, set its text and align it to the center*/lv_obj_t * label = lv_label_create(lv_screen_active());lv_label_set_text(label, "Hello world");lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0xffffff), LV_PART_MAIN);lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}

01_demo.h

#pragma once#include "lvgl.h"void lv_example_get_started_1(void);

main.c,不要忘了引入头文件 和 CMakeLists 注册组件

static void app_main_display(void)
{/* Task lock */lvgl_port_lock(0);/* Your LVGL objects code here .... */lv_example_get_started_1();/* Task unlock */lvgl_port_unlock();
}

显示效果如下:
image

2. 注册输入设备

这里只写了 ButtonEncoder 设备驱动,编码器使用按键模拟实现

2.1 按键设备

复制demo2
Example_2 A button with a label and react on click event
demo2是有关按键的,通过这个示例可以简单了解 输入设备事件 的作用

static void btn_event_cb(lv_event_t * e)
{lv_event_code_t code = lv_event_get_code(e);lv_obj_t * btn = lv_event_get_target_obj(e);if(code == LV_EVENT_CLICKED) {static uint8_t cnt = 0;cnt++;/*Get the first child of the button which is the label and change its text*/lv_obj_t * label = lv_obj_get_child(btn, 0);lv_label_set_text_fmt(label, "Button: %d", cnt);}
}/*** Create a button with a label and react on click event.*/
void lv_example_get_started_2(void)
{lv_obj_t * btn = lv_button_create(lv_screen_active());     /*Add a button the current screen*/lv_obj_set_pos(btn, 10, 10);                            /*Set its position*/lv_obj_set_size(btn, 120, 50);                          /*Set its size*/lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL);           /*Assign a callback to the button*/lv_obj_t * label = lv_label_create(btn);          /*Add a label to the button*/lv_label_set_text(label, "Button");                     /*Set the labels text*/lv_obj_center(label);
}

不要忘了在头文件、CMakeLists引入

创建 Indev 组件

  1. 创建 indev 组件
  2. 复制 lv_port_indev_template 设备驱动模板复制到 indev 组件,并重命名为 lv_port_indev
    模板路径:
    managed_components\lvgl__lvgl\examples\porting
  3. 修改头文件,见下方

按键驱动实现:

  1. 添加头文件
/**********************      INCLUDES*********************/
#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
#include "lvgl.h"
#else
// #include "lvgl/lvgl.h"            <<------------- 改这里
#endif
#include "esp_lvgl_port.h"
#include "driver/gpio.h"
  1. 完成 button_init 初始化
static void button_init(void)
{/*Your code comes here*/gpio_config_t button_gpio_config = {.pin_bit_mask = (1ULL<<12),.mode = GPIO_MODE_INPUT,.pull_up_en = GPIO_PULLUP_ENABLE,.pull_down_en = GPIO_PULLDOWN_DISABLE,.intr_type = GPIO_INTR_DISABLE,};gpio_config(&button_gpio_config);
}
  1. 完善 button_is_pressed 函数
    主要操作还是编码器完成的,所以这里只设置了一个按键,多个按键可以自行修改代码。
static bool button_is_pressed(uint8_t id)
{// 过于简陋🙄,但是没事,这不是重点/*Your code comes here*/if(id == 0) {return gpio_get_level(12) == 0;}return false;
}
  1. 调用 lv_port_indev_init
void app_main(void)
{/* LCD HW initialization */ESP_ERROR_CHECK(app_lcd_init());/* LVGL initialization */ESP_ERROR_CHECK(app_lvgl_init());// 一定是在 app_lvgl_init 之后调用/* input device initialization */lv_port_indev_init();/* Show LVGL objects */app_main_display();
}

效果展示
ESP32-LVGL 开发笔记(二):注册设备与性能监控-1762860031649

2.2 编码器设备

复制 demo4
Examples 4 - Create a slider and write its value on a label

编码器驱动实现:
编码器使用三个按键模拟实现

  1. encoder_init 初始化
#define ENCODER_LEFT   GPIO_NUM_1
#define ENCODER_RIGHT  GPIO_NUM_2
#define ENCODER_ENTER  GPIO_NUM_42static void encoder_init(void)
{/*Your code comes here*/gpio_config_t io_conf = {.pin_bit_mask = (1ULL << ENCODER_LEFT) | (1ULL << ENCODER_RIGHT) | (1ULL << ENCODER_ENTER),.mode = GPIO_MODE_INPUT,.pull_up_en = GPIO_PULLUP_ENABLE,.pull_down_en = GPIO_PULLDOWN_DISABLE,.intr_type = GPIO_INTR_DISABLE};gpio_config(&io_conf);
}
  1. 实现 encoder_read 方法
/*Will be called by the library to read the encoder*/
static void encoder_read(lv_indev_t * indev_drv, lv_indev_data_t * data)
{int left = gpio_get_level(ENCODER_LEFT);int right = gpio_get_level(ENCODER_RIGHT);int enter = gpio_get_level(ENCODER_ENTER);// 左右旋转if (!left) encoder_diff--;   // 按下低电平if (!right) encoder_diff++;  // 按下低电平// 按压状态if (!enter)encoder_state = LV_INDEV_STATE_PRESSED;elseencoder_state = LV_INDEV_STATE_RELEASED;// 传递给 LVGLdata->enc_diff = encoder_diff;data->state = encoder_state;// LVGL 会读取完后清空这个 diff,下次重新计算encoder_diff = 0;
}

有关 编码器 如何控制 组件,这就要引入几个概念

  1. Group
    1. 页面可能会包含很多组件,为了保证 导航模式 下在有效的组件中切换,就需要创建一个 group,把能够交互的组件添加进去
  2. 编辑模式导航模式
    1. 导航模式,就像上面提到的,导航模式下前进或后退会在绑定在 group 的组件切换
    2. 编辑模式,遇到可以交互的组件,如滑块,点击 enter 按键会从 导航模式 切换至 编辑模式,此时前进或后退就会改变滑块的值

完整 demo4 代码如下

void lv_example_get_started_4(void)
{/*Create a slider in the center of the display*/lv_obj_t * slider = lv_slider_create(lv_screen_active());lv_obj_set_width(slider, 200);                          /*Set the width*/lv_obj_center(slider);                                  /*Align to the center of the parent (screen)*/lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);     /*Assign an event function*//*Create a label above the slider*/label = lv_label_create(lv_screen_active());lv_label_set_text(label, "0");lv_obj_align_to(label, slider, LV_ALIGN_OUT_TOP_MID, 0, -15);    /*Align top of the slider*/// 👉 创建 group , 并添加 sliderlv_group_t * g = lv_group_create();lv_group_add_obj(g, slider);lv_indev_t * indev_encoder = lv_indev_get_next(NULL);while(indev_encoder) {if(lv_indev_get_type(indev_encoder) == LV_INDEV_TYPE_ENCODER) {lv_indev_set_group(indev_encoder, g);break;}indev_encoder = lv_indev_get_next(indev_encoder);}
}

效果展示
ESP32-LVGL 开发笔记(二):注册设备与性能监控-1762864537823

编码器驱动完善
现在的问题是:
导航模式下,按下左右键会持续触发,导致无法准确切换组件
所以想要设计成点击第一次的一定时间内不会再次触发,之后持续触发

基本解决思路是:

  1. 记录第一次按下按键的时间
  2. 持续检测按下的时间
  3. 判断按下事件是否超过设置的延迟(500ms)
  4. 超过的话持续触发,每次 input device read 轮询就会 "前加" 或 "后退"
  5. 松开重置

完整代码如下,可以自行参考:

/*------------------* Encoder* -----------------*/// 按键触发参数
#define INITIAL_DELAY_MS 500 // 初次触发后的等待时间 500ms
// LV_DEF_REFR_PERIOD:Default display refresh, input device read and animation step period
#define REPEAT_INTERVAL_MS 40    // 初始重复触发间隔 LV_DEF_REFR_PERIOD(33) + 40ms
#define MIN_REPEAT_INTERVAL_MS 0 // 最小重复触发间隔 LV_DEF_REFR_PERIOD + 0mstypedef struct
{int gpio;bool last_state;int64_t press_time;int64_t last_repeat_time;int16_t repeat_interval_ms;
} key_state_t;static key_state_t key_left = {.last_state = false,.gpio = ENCODER_LEFT,.repeat_interval_ms = REPEAT_INTERVAL_MS
};
static key_state_t key_right = {.last_state = false,.gpio = ENCODER_RIGHT,.repeat_interval_ms = REPEAT_INTERVAL_MS
};/*Initialize your encoder*/
static void encoder_init(void)
{/*Your code comes here*/gpio_config_t io_conf = {.pin_bit_mask = (1ULL << ENCODER_LEFT) | (1ULL << ENCODER_RIGHT) | (1ULL << ENCODER_ENTER),.mode = GPIO_MODE_INPUT,.pull_up_en = GPIO_PULLUP_ENABLE,.pull_down_en = GPIO_PULLDOWN_DISABLE,.intr_type = GPIO_INTR_DISABLE};gpio_config(&io_conf);
}// 检查单个方向键是否该触发
static bool check_key_trigger(key_state_t *key)
{bool level = !gpio_get_level(key->gpio);   // 低电平为按下int64_t now = esp_timer_get_time() / 1000; // 当前时间 msif (level){if (!key->last_state){// 首次按下key->press_time = now;key->last_repeat_time = now;key->last_state = true;return true; // 立即触发一次}else{int64_t held_time = now - key->press_time;if (held_time < INITIAL_DELAY_MS){// 在首次延迟阶段,不触发return false;}else{// 超过延迟,开始周期触发if (now - key->last_repeat_time >= key->repeat_interval_ms){key->last_repeat_time = now;key->repeat_interval_ms -= 2; // 长按后加速if (key->repeat_interval_ms < MIN_REPEAT_INTERVAL_MS)key->repeat_interval_ms = MIN_REPEAT_INTERVAL_MS; // 最小间隔 10msreturn true;}}}}else{// 松开key->last_state = false;key->repeat_interval_ms = REPEAT_INTERVAL_MS; // 松开后重置间隔}return false;
}/*Will be called by the library to read the encoder*/
static void encoder_read(lv_indev_t *indev_drv, lv_indev_data_t *data)
{encoder_diff = 0;// 左右按键:控制旋转if (check_key_trigger(&key_left))encoder_diff--;if (check_key_trigger(&key_right))encoder_diff++;// 中键:控制按压状态bool enter_pressed = !gpio_get_level(ENCODER_ENTER);encoder_state = enter_pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;// 输出给 LVGLdata->enc_diff = encoder_diff;data->state = encoder_state;
}

功能验证
为了验证导航模式下不同组件之间可以正常切换,我们需要再次添加一个组件来测试

	// 创建一个 button 组件lv_obj_t* button = lv_button_create(lv_screen_active());lv_obj_set_width(button, LV_SIZE_CONTENT);lv_obj_set_height(button, LV_SIZE_CONTENT);lv_obj_align(button, LV_ALIGN_BOTTOM_MID, 0, -15);    /*Align bottom */lv_obj_t* button_label = lv_label_create(button);lv_label_set_text(button_label, "Button");lv_obj_center(button_label);lv_obj_align_to(button_label, button, LV_ALIGN_CENTER, 0, 0);// 添加进 group    lv_group_add_obj(g, button);

效果展示
ESP32-LVGL 开发笔记(二):注册设备与性能监控-1762953029979

现在已经算的上是相当丝滑了🥳🥳

其他、补充

CMakeLists 相关参数的简单说明:

  • SRCS 源文件列表,添加需要编译的文件
  • INCLUDE_DIRS 目录列表,头文件搜索路径
  • REQUIRES 不是必需的,声明该组件依赖的组件组件所在的文件夹名 即为 组件名。如 ui 组件依赖的 espressif__esp_lvgl_port,还有 mian.c 要使用到 ui 组件的代码,也需要在 REQUIRES 添加 ui 组件的依赖(不过好像会自动依赖 components 下的组件
    相关文档说明:
  • 最小组件 CMakeLists 文件
  • 组件 CMakeLists 文件

group 说明:

  • Groups - LVGL 9.4 documentation

测试过程中发现按下按键频率过快的话,可能会栈溢出
当然栈空间给大点就没事了

http://www.jsqmd.com/news/45707/

相关文章:

  • 2025年成都火锅必吃榜TOP10,本地人强推!美食/地摊火锅/附近火锅/重庆火锅/牛肉火锅/成都火锅/老火锅/社区火锅/火锅品牌排行榜单
  • 高松灯和大石头的故事
  • 2025 11月 易上手建站工具指南:实用性和难点解决分析
  • 同样都是36岁,同样都是面临人生的抉择,《岁月》中的梁志远放下清高觉醒了,我呢,如何在社会这个大染缸里面混呢?
  • 2025 年 11 月冲压机械手厂家推荐排行榜,冲床机械手/摆臂机械手/二次元拉伸/三次元冲压/模内平移/多工位冲压/四轴上下料/自动拆垛/新能源电池壳拉伸/双臂机械手/全自动码垛机厂家精选
  • 2025年EVA废料优质厂家权威推荐榜单:EVA造粒/EVA颗粒/EVA再生造粒料源头厂家精选
  • 2025 初中一对一教育机构口碑排名:高性价比靠谱名单 + 权威测评排行榜
  • C#AI系列(1):深度学习项目构建及实战TensorFlow准备篇
  • 详细介绍:2026计算机毕业设计课题推荐
  • 【PCIE725G 】基于 PCIe x16 总线架构的 JFM9VU9P FPGA 高性能数据预处理平台(100%国产化)
  • 2025年疏浚船优质厂家权威推荐榜单:绞吸船/挖沙船/清淤船源头厂家精选
  • 2025 年 11 月高温老化房厂家推荐排行榜,老化室/高温房/熟化房/固化房/恒温恒湿室/恒温房,专业定制与稳定性能深度解析
  • PG优化系列:Oracle迁移到PG中性能下降1000倍续集
  • ORACLE故障恢复:启用与禁用事务的并行恢复
  • 基于SIC8F1233开发智能充气泵方案
  • ESD整改核心思路:堵、防、疏的实践平衡-ASIM阿赛姆
  • 2025 最新瓷砖品牌权威推荐:经国际协会测评认证,精选品质与创新兼具的优质品牌
  • Qiling使用速记
  • 保温杯LED屏幕驱动和语音播报二合一芯片方案
  • B端界面设计之流程页设计——从“能用”到“好用”的边界重构
  • 2025 靠谱初中一对一辅导机构排行榜:权威评价 + 真实口碑排名推荐
  • 什么是I2C通信协议
  • 视频汇聚平台EasyCVR服务器使用WiFi网卡时,为何无法向级联平台发送注册?
  • ai-answer
  • 2025 年 11 月纯化水设备厂家推荐排行榜,生物制药纯化水设备,医疗器械纯化水设备,食品纯化水设备,化妆品纯化水设备,制药纯化水设备公司推荐
  • 火山引擎多模态数据湖,破解智能驾驶数据处理瓶颈
  • The 2025 ICPC Asia Shenyang Regional Contest
  • 2025年交通安全国际学术会议(ICTS 2025)
  • 2025一对一教育机构口碑排名:高性价比靠谱名单 + 权威测评排行榜
  • 11.19题解