LVGL嵌入式UI开发:手动编码、SquareLine Studio与GPT对比实践
2026/7/18 9:31:57 网站建设 项目流程

在嵌入式设备上构建用户界面时,LVGL(Light and Versatile Graphics Library)已经成为许多开发者的首选。它轻量、跨平台,并且提供了丰富的UI组件。但实际项目中,开发方式的选择直接影响开发效率、维护成本和最终用户体验。常见的三种方式包括:直接使用LVGL API手动编码、使用LVGL官方设计工具SquareLine Studio,以及结合AI工具如GPT生成界面代码。

本文将以一个具体的温湿度传感器数据上报前端为例,对比这三种开发方式。你将看到如何从零开始,使用DHT11或AHT20传感器采集数据,并通过LVGL在屏幕上显示实时读数,同时将数据上报到前端。我们将重点分析每种方式的实现步骤、代码结构、优缺点以及适用场景,并提供可复现的示例和常见问题排查方法。

1. LVGL 三种 UI 开发方式核心概念与适用场景

1.1 手动编码:最直接的控制力

手动编码是指直接调用LVGL提供的C语言API来创建和配置每一个UI组件。例如,要创建一个显示温度的标签(label),你需要手动创建对象、设置位置、大小、文本内容和样式。

这种方式适合对LVGL非常熟悉的开发者,或者UI逻辑复杂、需要高度自定义交互的场景。它的优点是灵活性极高,性能控制精准;缺点是代码量大,修改布局需要重新编译,不适合快速迭代。

1.2 SquareLine Studio:可视化设计工具

SquareLine Studio是LVGL官方推出的可视化UI设计工具。你可以在图形界面上拖拽组件,设置属性,然后导出C代码。工具会自动生成UI初始化代码,你只需要在项目中调用相应的初始化函数并编写业务逻辑。

这种方式极大地提高了布局效率,尤其适合界面复杂、组件众多的项目。缺点是生成的代码可能不够简洁,需要手动整合到现有项目中,并且高级动态效果仍需编码实现。

1.3 GPT 辅助生成:AI 驱动的代码助手

利用GPT(或其他大语言模型)生成LVGL代码是一种新兴方式。你可以用自然语言描述界面需求,例如“创建一个包含温度、湿度显示和上报按钮的界面”,GPT会生成对应的C代码片段。

这种方式在快速原型设计和学习阶段非常有用,能够减少初期的学习成本。但生成的代码可能需要调试,且无法保证最佳实践,适合作为起点而非最终方案。

1.4 为何选择温湿度传感器作为示例?

温湿度传感器(如DHT11、AHT20)是嵌入式项目中最常见的传感器之一,数据采集简单,显示需求明确(数值、单位、历史曲线)。这个场景涵盖了UI布局、数据绑定、事件处理等核心环节,非常适合对比不同开发方式的实际效果。

2. 环境准备与硬件选型

2.1 硬件清单

为了完成本实验,你需要准备以下硬件:

  • 主控板:ESP32开发板(具备Wi-Fi功能,便于数据上报)
  • 显示屏:SPI或I2C接口的TFT屏幕(如ILI9341、SSD1306)
  • 温湿度传感器:DHT11(基础款)或AHT20(更高精度)
  • 连接线:杜邦线若干

2.2 软件环境配置

开发环境以PlatformIO为例,它能够方便地管理依赖和编译配置。

首先,在PlatformIO中创建一个新项目,选择ESP32开发板。然后,在platformio.ini配置文件中添加必要的库依赖:

[env:esp32dev] platform = espressif32 board = esp32dev framework = arduino lib_deps = lvgl/lvgl@^8.3.11 adafruit/DHT sensor library@^1.4.6 adafruit/Adafruit AHTX0@^1.2.2

如果你的传感器是DHT11,使用DHT sensor library;如果是AHT20,则使用Adafruit AHTX0库。LVGL库用于界面渲染。

2.3 显示屏与传感器接线

以ESP32和SPI TFT屏、DHT11为例,接线参考如下:

ESP32引脚外设引脚说明
GPIO 18TFT SCKSPI时钟
GPIO 23TFT MOSISPI数据输出
GPIO 5TFT CS片选
GPIO 17TFT DC数据/命令选择
3.3VTFT VCC电源
GNDTFT GND地线
GPIO 4DHT11 DATA传感器数据线
3.3VDHT11 VCC传感器电源
GNDDHT11 GND传感器地线

接线后,建议先用简单测试程序验证屏幕和传感器是否工作正常,避免后续调试复杂化。

3. 手动编码实现温湿度显示界面

3.1 LVGL 初始化与显示驱动设置

在Arduino框架下,首先初始化LVGL并绑定显示驱动。以下代码完成了基础设置:

#include <lvgl.h> #include <TFT_eSPI.h> TFT_eSPI tft = TFT_eSPI(); // 假设使用TFT_eSPI库驱动屏幕 void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = (area->x2 - area->x1 + 1); uint32_t h = (area->y2 - area->y1 + 1); tft.startWrite(); tft.setAddrWindow(area->x1, area->y1, w, h); tft.pushColors(&color_p->full, w * h, true); tft.endWrite(); lv_disp_flush_ready(disp); } void setup() { Serial.begin(115200); tft.begin(); tft.setRotation(3); lv_init(); static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[TFT_WIDTH * 10]; lv_disp_draw_buf_init(&draw_buf, buf, NULL, TFT_WIDTH * 10); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = TFT_WIDTH; disp_drv.ver_res = TFT_HEIGHT; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); }

这段代码初始化了TFT屏幕,并设置了LVGL的显示缓冲区和刷新回调。my_disp_flush函数负责将LVGL绘制的颜色数据推送到实际屏幕。

3.2 创建温湿度显示组件

接下来,我们手动创建两个标签用于显示温度和湿度读数:

lv_obj_t *temp_label; lv_obj_t *humi_label; void create_ui() { // 创建温度标签 temp_label = lv_label_create(lv_scr_act()); lv_obj_align(temp_label, LV_ALIGN_TOP_LEFT, 10, 10); lv_label_set_text(temp_label, "Temperature: --°C"); lv_obj_set_style_text_font(temp_label, &lv_font_montserrat_24, 0); // 创建湿度标签 humi_label = lv_label_create(lv_scr_act()); lv_obj_align(humi_label, LV_ALIGN_TOP_LEFT, 10, 50); lv_label_set_text(humi_label, "Humidity: --%"); lv_obj_set_style_text_font(humi_label, &lv_font_montserrat_24, 0); // 创建上报按钮 lv_obj_t *btn = lv_btn_create(lv_scr_act()); lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -20); lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); lv_obj_t *btn_label = lv_label_create(btn); lv_label_set_text(btn_label, "Report Data"); lv_obj_center(btn_label); } void btn_event_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_CLICKED) { // 触发数据上报逻辑 Serial.println("Button clicked - report data"); } }

手动编码的优势在这里体现:你可以精确控制每个组件的位置、样式和事件回调。但缺点是,如果界面复杂,代码会变得冗长且难以维护。

3.3 集成传感器数据读取

在循环中定期读取传感器数据并更新UI:

#include <DHT.h> #define DHT_PIN 4 #define DHT_TYPE DHT11 DHT dht(DHT_PIN, DHT_TYPE); void loop() { lv_timer_handler(); // 必须定期调用LVGL任务处理器 delay(5); static uint32_t last_read = 0; if (millis() - last_read > 2000) { // 每2秒读取一次 float temp = dht.readTemperature(); float humi = dht.readHumidity(); if (!isnan(temp) && !isnan(humi)) { char temp_str[20]; char humi_str[20]; snprintf(temp_str, sizeof(temp_str), "Temperature: %.1f°C", temp); snprintf(humi_str, sizeof(humi_str), "Humidity: %.1f%%", humi); lv_label_set_text(temp_label, temp_str); lv_label_set_text(humi_label, humi_str); } last_read = millis(); } }

这段代码每2秒读取一次传感器数据,并更新标签文本。注意要检查读数是否有效(非NaN)。

4. 使用 SquareLine Studio 设计相同界面

4.1 界面设计与组件布局

在SquareLine Studio中,你可以通过拖拽方式创建界面:

  1. 新建项目,选择ESP32和你的屏幕分辨率。
  2. 从组件库中拖拽两个Label组件到画布,分别命名为temp_labelhumi_label
  3. 设置它们的字体、大小和位置。
  4. 拖拽一个Button组件,设置文本为"Report Data"。
  5. 调整对齐和间距,使布局美观。

4.2 导出代码与项目集成

设计完成后,导出C代码。你会得到两个主要文件:ui.cui.h。将它们添加到你的PlatformIO项目中。

在主程序中,包含头文件并调用初始化函数:

#include "ui.h" void setup() { // ... 其他初始化代码 ui_init(); // 初始化SquareLine Studio生成的界面 } void loop() { lv_timer_handler(); delay(5); // 更新数据的部分需要手动编写 update_sensor_data(); }

4.3 数据绑定与事件处理

SquareLine Studio生成的代码提供了全局变量来访问UI组件,例如ui_temp_labelui_humi_label。你可以在自己的函数中更新它们:

void update_sensor_data() { static uint32_t last_read = 0; if (millis() - last_read > 2000) { float temp = dht.readTemperature(); float humi = dht.readHumidity(); if (!isnan(temp) && !isnan(humi)) { lv_label_set_text(ui_temp_label, String("Temperature: " + String(temp, 1) + "°C").c_str()); lv_label_set_text(ui_humi_label, String("Humidity: " + String(humi, 1) + "%").c_str()); } last_read = millis(); } }

按钮事件需要在SquareLine Studio中设置回调函数名,或者在导出后手动添加:

// 在ui.c中找到按钮的事件回调设置,或手动添加 lv_obj_add_event_cb(ui_report_btn, report_btn_cb, LV_EVENT_CLICKED, NULL); void report_btn_cb(lv_event_t *e) { // 处理上报逻辑 }

可视化工具节省了布局时间,但动态数据和复杂交互仍需编码实现。

5. 利用 GPT 生成 LVGL 界面代码

5.1 有效的提示词设计

向GPT描述需求时,要具体且包含技术细节。一个好的提示词示例:

"请生成LVGL 8.x的C代码,创建一个嵌入式设备的温湿度监控界面。要求:

  • 使用320x240分辨率
  • 顶部显示大字体温度读数,格式'温度:25.0°C'
  • 中间显示湿度读数,格式'湿度:50.0%'
  • 底部有一个上报按钮,点击时串口打印'Report data'
  • 使用lv_label和lv_button组件
  • 包含必要的对象变量定义和事件回调函数框架"

5.2 GPT 生成代码示例与分析

GPT可能会生成类似下面的代码:

#include <lvgl.h> lv_obj_t *temp_label; lv_obj_t *humi_label; lv_obj_t *report_btn; void create_gpt_ui() { // 创建温度标签 temp_label = lv_label_create(lv_scr_act()); lv_obj_set_pos(temp_label, 10, 10); lv_label_set_text(temp_label, "温度:--°C"); lv_obj_set_style_text_font(temp_label, &lv_font_montserrat_28, 0); // 创建湿度标签 humi_label = lv_label_create(lv_scr_act()); lv_obj_set_pos(humi_label, 10, 80); lv_label_set_text(humi_label, "湿度:--%"); lv_obj_set_style_text_font(humi_label, &lv_font_montserrat_28, 0); // 创建上报按钮 report_btn = lv_btn_create(lv_scr_act()); lv_obj_set_size(report_btn, 120, 40); lv_obj_align(report_btn, LV_ALIGN_BOTTOM_MID, 0, -20); lv_obj_add_event_cb(report_btn, gpt_btn_cb, LV_EVENT_CLICKED, NULL); lv_obj_t *btn_label = lv_label_create(report_btn); lv_label_set_text(btn_label, "上报数据"); lv_obj_center(btn_label); } void gpt_btn_cb(lv_event_t *e) { if (lv_event_get_code(e) == LV_EVENT_CLICKED) { printf("Report data\n"); } }

5.3 生成代码的整合与调试

GPT生成的代码通常能运行,但可能需要调整:

  • 检查字体引用是否正确(你的项目可能没有montserrat_28)
  • 确认坐标布局在不同分辨率下的适应性
  • 添加错误处理和数据验证
  • 将printf改为具体的上报逻辑

这种方式适合快速验证想法,但需要开发者具备调试和优化能力。

6. 三种方式对比与选型建议

6.1 开发效率对比

开发方式布局效率代码控制力学习成本适合场景
手动编码最高复杂交互、性能敏感项目
SquareLine Studio中等复杂界面、团队协作
GPT生成快速原型、学习参考

6.2 维护成本考量

手动编码的维护成本最高,因为布局修改需要直接改代码。SquareLine Studio可视化修改后重新导出即可,但要注意自定义代码的保留。GPT生成的代码缺乏结构一致性,长期维护困难。

6.3 性能与资源占用

手动编码通常性能最优,因为可以精确控制内存分配和渲染流程。工具生成的代码可能包含冗余设置,但影响通常不大。在资源紧张的嵌入式设备上,手动优化仍有价值。

6.4 项目阶段选型建议

  • 原型阶段:使用GPT快速生成基础界面,验证需求
  • 开发阶段:复杂界面用SquareLine Studio,关键交互部分手动编码
  • 维护阶段:根据团队技能选择,保持一致性更重要

7. 温湿度数据上报前端实现

7.1 数据采集与格式化

无论采用哪种UI开发方式,数据采集逻辑是通用的。为了提高可靠性,建议添加读取重试机制:

bool read_sensor_data(float *temp, float *humi) { for (int i = 0; i < 3; i++) { // 重试3次 *temp = dht.readTemperature(); *humi = dht.readHumidity(); if (!isnan(*temp) && !isnan(*humi)) { return true; } delay(100); } return false; }

7.2 网络上报协议设计

通过ESP32的Wi-Fi功能,可以将数据上报到服务器。以下示例使用HTTP POST请求:

#include <WiFi.h> #include <HTTPClient.h> const char* ssid = "your_SSID"; const char* password = "your_PASSWORD"; const char* server_url = "http://your-server.com/api/data"; void report_data(float temp, float humi) { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; http.begin(server_url); http.addHeader("Content-Type", "application/json"); String json_data = "{\"temperature\":" + String(temp, 1) + ",\"humidity\":" + String(humi, 1) + "}"; int http_code = http.POST(json_data); if (http_code > 0) { Serial.printf("HTTP response code: %d\n", http_code); } else { Serial.printf("HTTP error: %s\n", http.errorToString(http_code).c_str()); } http.end(); } }

7.3 上报状态UI反馈

在界面上添加状态指示,让用户知道数据上报结果:

lv_obj_t *status_label; // 在create_ui函数中添加状态标签 status_label = lv_label_create(lv_scr_act()); lv_obj_align(status_label, LV_ALIGN_BOTTOM_LEFT, 10, -10); lv_label_set_text(status_label, "Status: Ready"); // 上报时更新状态 void update_status(const char* status) { lv_label_set_text(status_label, status); // 可以添加颜色变化提示重要状态 }

8. 常见问题与排查方法

8.1 LVGL 显示问题排查

问题现象可能原因检查方法解决方案
屏幕白屏显示驱动未正确初始化检查引脚定义、SPI设置确认接线,验证初始化顺序
画面撕裂缓冲区大小不足检查draw_buf大小增加缓冲区或使用双缓冲
组件不显示对象创建后未设置父对象确认lv_scr_act()可用确保在lvgl初始化后创建对象

8.2 传感器数据异常处理

DHT11等传感器容易受干扰,建议添加数据滤波:

#define SAMPLE_SIZE 5 float read_filtered_temperature() { float samples[SAMPLE_SIZE]; int valid_samples = 0; for (int i = 0; i < SAMPLE_SIZE; i++) { float temp = dht.readTemperature(); if (!isnan(temp)) { samples[valid_samples++] = temp; } delay(50); } if (valid_samples > 0) { // 使用中值或平均值 float sum = 0; for (int i = 0; i < valid_samples; i++) { sum += samples[i]; } return sum / valid_samples; } return NAN; }

8.3 内存不足问题优化

嵌入式设备内存有限,LVGL使用中要注意:

  • 避免频繁创建销毁对象,重用现有对象
  • 使用LVGL内存监控函数检查使用情况
  • 精简字体和图片资源
  • 适当减少动画和复杂效果

9. 生产环境最佳实践

9.1 代码结构组织

将UI逻辑、传感器逻辑和网络逻辑分离到不同文件:

src/ main.cpp ui_manager.cpp/h // UI相关代码 sensor_reader.cpp/h // 传感器读取 network_client.cpp/h // 网络上报

9.2 错误处理与恢复

添加看门狗和异常恢复机制:

void setup() { esp_task_wdt_init(10, true); // 10秒看门狗 // ... 其他初始化 } void loop() { esp_task_wdt_reset(); // 喂狗 try { // 主循环逻辑 } catch (...) { // 异常处理,记录日志并尝试恢复 ESP.restart(); } }

9.3 功耗优化建议

对于电池供电设备:

  • 在不操作时降低屏幕亮度或关闭显示
  • 调整数据采集和上报频率
  • 使用深度睡眠模式
  • 优化Wi-Fi连接策略(连接后立即发送数据,然后断开)

三种UI开发方式各有适用场景,选择的关键是匹配项目需求和团队能力。从手动编码开始学习LVGL基础,再用工具提高效率,最后根据项目特点制定长期技术方案。温湿度监控示例虽然简单,但涵盖了嵌入式UI开发的核心环节,可以作为更复杂项目的基础模板。

实际项目中,建议先明确性能要求、团队技能和维护计划,再决定主要开发方式。无论选择哪种方式,良好的代码结构、错误处理和文档都是项目成功的关键。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询