news 2026/4/15 8:05:35

LVGL与传感器数据可视化结合的实操指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LVGL与传感器数据可视化结合的实操指南

用LVGL把传感器数据“画”出来:从零打造嵌入式可视化仪表盘

你有没有遇到过这样的场景?手里的STM32板子正源源不断地读取温湿度、光照强度,但除了串口打印的一串数字,用户根本看不出趋势——昨天温度是不是突然飙升了?光照是否长期偏低影响植物生长?

这时候,一块小小的TFT屏配上动态图表,就能让设备“开口说话”。而LVGL + 传感器的组合,正是当前嵌入式系统实现专业级人机交互(HMI)最实用、性价比最高的方案之一。

本文不讲空泛理论,带你一步步把SHT30、BH1750这些常见传感器的数据,实时“搬”到屏幕上,变成会动的折线图和直观的状态标签。无论你是做智能温室、工业监控还是DIY项目,这套方法都能直接复用。


先看效果:我们要做出什么?

想象这样一个界面:

  • 屏幕顶部是深色背景的标题栏,写着“环境监测站”;
  • 中间是一块红色折线图,X轴代表时间,Y轴显示0~50°C,曲线随秒针般缓缓右移;
  • 图表下方并排两个标签:一个显示当前温度(如24.6 °C),另一个显示湿度(如58% RH);
  • 右上角还有一个小太阳图标,亮度随光照强度变化。

这并不是高端工控屏才有的功能。只要一块ESP32开发板、一个ST7789驱动的TFT彩屏、加上几行代码,就能在几十KB RAM的资源限制下跑起来。

接下来,我们就拆解这个系统的每一个环节,从初始化开始,一气呵成。


LVGL怎么“活”起来?三步启动流程

很多初学者卡在第一步:LVGL装上了,但屏幕黑着,或者只闪一下就死机。问题往往出在刷新机制没搞明白

LVGL不是传统GUI那种“我画一笔你就立刻显示”的模式。它采用异步刷新架构——你告诉它“某个按钮要变颜色”,它记下来;等到定时器触发时,再批量重绘变化区域,最后通过回调函数刷到屏幕上。

所以核心只有三步:

第一步:初始化LVGL内核

lv_init(); // 必须最先调用

第二步:配置显示缓冲区

这是最容易被忽略的关键点。LVGL需要至少一块内存来暂存待绘制的内容。对于320x240分辨率的屏幕,每像素占2字节(RGB565),一整帧就是320*240*2 = 153,600 字节 ≈ 150KB——这对MCU来说太奢侈了!

所以我们通常使用部分缓冲策略:

static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[1024]; // 仅分配1KB缓冲 lv_disp_draw_buf_init(&draw_buf, buf, NULL, 1024);

这意味着LVGL每次只会处理一小块区域(比如一行或一列),多次刷新拼成完整画面。虽然稍慢,但内存友好得多。

第三步:注册硬件驱动接口

显示刷新回调
static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.draw_buf = &draw_buf; disp_drv.flush_cb = my_flush_callback; // 关键! disp_drv.hor_res = 320; disp_drv.ver_res = 240; lv_disp_drv_register(&disp_drv); void my_flush_callback(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map) { int32_t w = (area->x2 - area->x1 + 1); int32_t h = (area->y2 - area->y1 + 1); // 假设使用SPI+DMA写入ST7789 lcd_set_window(area->x1, area->y1, area->x2, area->y2); spi_dma_send((uint8_t *)color_map, w * h * 2); lv_disp_flush_ready(drv); // 通知LVGL本次刷新完成 }

💡 小贴士:lv_disp_flush_ready()必须调用,否则LVGL会一直等待,界面卡住。

输入设备(可选)

如果你有触摸屏,也只需注册一个读取回调:

static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touch_read; lv_indev_drv_register(&indev_drv);

至此,LVGL已经“活”了。你可以创建第一个对象试试:

lv_obj_t * label = lv_label_create(lv_scr_act()); lv_label_set_text(label, "Hello World!"); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

传感器数据怎么安全又高效地拿?

我们以I²C接口的SHT30温湿度传感器为例。这类器件看似简单,但在多任务环境下极易因总线冲突导致死锁。

正确打开方式:封装带超时的读写函数

别再裸奔调用HAL_I2C_Master_Transmit()了!必须加超时保护:

bool i2c_write_timeout(uint8_t addr, uint8_t *data, uint8_t len, uint32_t timeout_ms) { uint32_t start = millis(); while (HAL_I2C_IsActiveFlag(I2C_HANDLE)) { if (millis() - start > timeout_ms) return false; osDelay(1); } return HAL_I2C_Master_Transmit(&hi2c1, addr << 1, data, len, timeout_ms) == HAL_OK; } bool i2c_read_timeout(uint8_t addr, uint8_t *buf, uint8_t len, uint32_t timeout_ms) { uint32_t start = millis(); while (HAL_I2C_IsActiveFlag(I2C_HANDLE)) { if (millis() - start > timeout_ms) return false; osDelay(1); } return HAL_I2C_Master_Receive(&hi2c1, addr << 1, buf, len, timeout_ms) == HAL_OK; }

完整的SHT30读取函数(含CRC校验)

float read_sht30_temperature(void) { uint8_t cmd[] = {0x2C, 0x06}; // 高重复性测量命令 uint8_t data[6]; if (!i2c_write_timeout(SHT30_ADDR, cmd, 2, 10)) return NAN; osDelay(20); // 等待转换完成 if (!i2c_read_timeout(SHT30_ADDR, data, 6, 20)) return NAN; // CRC校验(重要!防止误码) if (crc8(data, 2) != data[2] || crc8(data+3, 2) != data[5]) { return NAN; // 校验失败 } uint16_t raw_temp = (data[0] << 8) | data[1]; float temp_c = -45.0f + 175.0f * raw_temp / 65535.0f; return temp_c; }

🔍为什么强调CRC?
I²C总线在电磁干扰强的环境中容易出错。一次错误的温度假数据可能引发空调误动作。加入CRC后,可靠性提升一个数量级。


让数据“动”起来:lv_chart实战技巧

lv_chart是LVGL中最适合传感器可视化的控件。但它默认行为有点反直觉:新数据是从左边挤进去的

也就是说,调用一次lv_chart_set_next_value(chart, series, val),新的点会被插入最右边,原来的点集体左移一位,最左边的老数据消失——完美模拟“时间轴滚动”。

创建一个专业的温度曲线图

lv_obj_t *chart; lv_chart_series_t *ser_temp; void create_temp_chart(void) { chart = lv_chart_create(lv_scr_act()); lv_obj_set_size(chart, 300, 180); lv_obj_align(chart, LV_ALIGN_TOP_MID, 0, 10); lv_chart_set_type(chart, LV_CHART_TYPE_LINE); lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 50); // °C范围 lv_chart_set_point_count(chart, 60); // 缓存60个点 → 每秒更新则显示1分钟趋势 // 设置样式 lv_obj_set_style_line_width(chart, 2, 0); lv_obj_set_style_radius(chart, 5, 0); // 添加红色系列 ser_temp = lv_chart_add_series(chart, lv_color_red(), LV_CHART_AXIS_PRIMARY_Y); }

如何避免图表疯狂抖动?

如果你每秒采样5次,但UI每500ms才更新一次,直接塞原始值会导致图表剧烈跳变。

解决办法:应用层做滑动平均滤波

#define FILTER_SIZE 5 float temp_filter[FILTER_SIZE]; int filter_idx = 0; float apply_filter(float new_val) { temp_filter[filter_idx] = new_val; filter_idx = (filter_idx + 1) % FILTER_SIZE; float sum = 0; for (int i = 0; i < FILTER_SIZE; i++) sum += temp_filter[i]; return sum / FILTER_SIZE; }

然后这样更新图表:

float filtered_temp = apply_filter(raw_temp); lv_chart_set_next_value(chart, ser_temp, (lv_coord_t)filtered_temp);

多任务协同:FreeRTOS下的黄金搭档

真正稳定的系统,必须将数据采集UI更新分离。

推荐架构:双任务 + 队列通信

// 定义消息结构 typedef struct { float temp; float humidity; float light; } sensor_data_t; QueueHandle_t sensor_queue; // 任务1:传感器采集 void sensor_task(void *pvParameters) { while (1) { sensor_data_t data; data.temp = read_sht30_temperature(); data.humidity = read_sht30_humidity(); data.light = read_bh1750_lux(); xQueueSend(sensor_queue, &data, 0); // 发送给UI任务 vTaskDelay(pdMS_TO_TICKS(500)); // 每500ms采样一次 } } // 任务2:UI更新 void ui_task(void *pvParameters) { sensor_data_t data; while (1) { if (xQueueReceive(sensor_queue, &data, portMAX_DELAY)) { // 更新图表 lv_chart_set_next_value(chart, ser_temp, (lv_coord_t)data.temp); // 更新数字标签 lv_label_set_text_fmt(temp_label, "%.1f °C", data.temp); lv_label_set_text_fmt(humi_label, "%.1f %%", data.humidity); } } }

✅ 这种设计的好处:
- 采集失败不影响UI刷新;
- 不同传感器可以有不同的采样周期;
- 即使UI卡顿,也不会阻塞数据采集;
- 易于扩展为多屏或多数据显示。


踩坑避雷指南:那些手册不会写的真相

❌ 陷阱1:用vTaskDelay()刷LVGL

很多人在main循环里这么写:

while(1) { lv_timer_handler(); // 处理LVGL内部逻辑 vTaskDelay(5); // 错!这会让整个任务挂起 }

如果这是唯一任务,倒没问题。但如果还有别的工作要做,其他任务会被严重延迟

✅ 正确做法:创建独立的LVGL任务,优先级低于关键控制任务:

void lvgl_tick_task(void *pvParameter) { while(1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); // 每5ms执行一次 } }

❌ 陷阱2:图表点数设太多导致内存溢出

每个点占2字节(lv_coord_t),100个点×4条曲线 = 800字节看着不多。但LVGL还要为每个series分配额外内存,再加上动画缓存,很容易撑破栈空间。

✅ 建议:
- 默认设为30~60点足够观察趋势;
- 使用LV_MEM_CUSTOM 1启用外部SRAM或PSRAM(ESP32可用);
- 关闭不用的动画:lv_obj_set_style_anim_time(obj, 0, 0);

❌ 陷阱3:字体模糊像马赛克

默认启用的是小字号点阵字体。想清晰显示数字?换矢量字体!

LV_FONT_DECLARE(lv_font_montserrat_20) lv_obj_set_style_text_font(label, &lv_font_montserrat_20, 0);

并在lv_conf.h中开启对应字体支持。


最终整合:你的第一个环境监测仪表盘

现在你已经掌握了所有关键技术模块。完整的工程结构建议如下:

/project ├── main.c // 主程序入口 ├── lvgl_init.c // LVGL初始化与驱动绑定 ├── sensor/ │ ├── sht30.c // 温湿度驱动 │ ├── bh1750.c // 光照驱动 │ └── sensor_task.c // 数据采集任务 ├── ui/ │ ├── dashboard.c // 界面布局 │ └── chart_utils.c // 图表辅助函数 └── drivers/ ├── st7789.c // 屏幕驱动 └── touch_xpt2046.c // 触摸驱动(如有)

当你看到那条平滑滚动的红色曲线,伴随着准确跳动的数字标签时,你会意识到:这不是玩具,而是真正可用的产品级HMI原型


写在最后:下一步往哪走?

完成了基础可视化之后,还有很多方向可以拓展:

  • 加入报警机制:当温度超过阈值时,图表边框变红并弹出提示;
  • 支持触控操作:点击图表切换显示历史/实时模式;
  • 远程查看:通过WiFi上传数据到Blynk或ThingsBoard;
  • 低功耗优化:无操作5分钟后自动进入休眠,仅保留RTC唤醒;
  • 主题切换:白天/夜间模式一键切换。

LVGL的强大之处就在于,它不是一个静态库,而是一个可塑性极强的图形引擎。只要你能想到的交互形式,几乎都可以用它的对象系统构建出来。

如果你正在做一个需要“看得见”的嵌入式项目,不妨今天就试着点亮第一行文字、画出第一个点。也许下一个惊艳的设计,就始于这一小步。

🛠️动手提示:官方GitHub仓库提供了数十个开箱即用的例子。搜索lv_examples,找到lv_example_chart_1,改造成你的传感器数据源,最快10分钟就能看到成果。

有什么具体问题?欢迎留言讨论。我们一起把冷冰冰的数据,变成会呼吸的界面。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/13 7:44:47

搜狗站长平台备案:争取在搜狗搜索获得良好展现位置

搜狗站长平台备案&#xff1a;争取在搜狗搜索获得良好展现位置 在数字内容爆炸式增长的今天&#xff0c;一张修复如初的老照片不仅能唤醒个体记忆&#xff0c;也可能成为社交媒体上的传播热点。而背后支撑这类“时光重生”的&#xff0c;往往是深度学习驱动的AI图像修复技术。…

作者头像 李华
网站建设 2026/4/15 15:22:25

IBM Cloud Code Engine:全自动管理DDColor运行环境

IBM Cloud Code Engine&#xff1a;全自动管理DDColor运行环境 在老照片泛黄褪色的今天&#xff0c;我们比以往任何时候都更渴望重现历史的色彩。一张黑白的家庭合影、一段尘封的街景影像&#xff0c;背后承载的是记忆与情感。而如今&#xff0c;借助AI技术&#xff0c;这些画…

作者头像 李华
网站建设 2026/4/15 15:20:17

Let‘s Encrypt泛域名证书:统一保护www/api/admin等子域

Let’s Encrypt泛域名证书&#xff1a;统一保护www/api/admin等子域 在今天的Web世界里&#xff0c;一个网站背后往往不是单一服务&#xff0c;而是由数十甚至上百个子域构成的复杂系统。www.example.com 展示页面&#xff0c;api.example.com 提供数据接口&#xff0c;admin.e…

作者头像 李华
网站建设 2026/4/15 8:09:38

如何快速配置SteamVR Unity插件:面向新手的终极指南

如何快速配置SteamVR Unity插件&#xff1a;面向新手的终极指南 【免费下载链接】steamvr_unity_plugin SteamVR Unity Plugin - Documentation at: https://valvesoftware.github.io/steamvr_unity_plugin/ 项目地址: https://gitcode.com/gh_mirrors/st/steamvr_unity_plug…

作者头像 李华
网站建设 2026/4/14 6:26:01

搜狗输入法词库优化:加入‘ddcolor’提升技术人群打字效率

搜狗输入法词库优化&#xff1a;加入‘ddcolor’提升技术人群打字效率 在AI工具快速渗透创作与办公场景的今天&#xff0c;一个看似微小的输入体验改进&#xff0c;往往能撬动巨大的效率杠杆。比如&#xff0c;当你在调试图像修复流程时&#xff0c;只需敲下“ddc”三个字母&am…

作者头像 李华
网站建设 2026/4/12 21:17:57

Maccy:终极macOS剪贴板管理器完整使用指南

你是不是也经常遇到这样的困扰&#xff1f;刚复制了一段重要信息&#xff0c;不小心又被新的内容覆盖了&#xff1b;或者需要频繁在多个应用之间切换&#xff0c;反复复制粘贴相同的内容&#xff1f;&#x1f914; 如果你正在寻找一款轻量级、功能强大的macOS剪贴板管理工具&am…

作者头像 李华