news 2026/4/18 9:34:42

LVGL9 双物理屏幕驱动入门教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LVGL9 双物理屏幕驱动入门教程

LVGL9 双物理屏幕驱动入门教程

下面以C + LVGL v9为例,介绍如何在一个 MCU 上同时驱动两个独立的物理屏幕(两个lv_display_t),并在每个屏上加载自己的界面。示例代码严格按照工程中lvgl__lvgl组件(LVGL v9 原生 API,例如lv_display_createlv_display_set_bufferslv_screen_load等)来写,再按实际硬件做适配。


一、总体思路(LVGL v9 原生 API)

  • 每块物理屏 = 一个显示控制器 (lv_display_t)
    • 使用lv_display_create(hor_res, ver_res)创建显示对象
    • 使用lv_display_set_flush_cb(disp, my_flush_cb)绑定刷新回调
    • 使用lv_display_set_buffers(disp, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_*)绑定帧缓冲
  • 每个显示器有自己的根 Screen
    • lv_obj_create(NULL)创建 Screen(根对象)
    • lv_screen_load(screen)把 Screen 设置为当前显示器的活动 Screen(与lv_display_set_default(disp)配合使用)
  • 一个lv_timer_handler()轮询即可,所有显示器共用一套 LVGL 任务处理。

二、初始化两个显示器(Display)

假设有两块 240×320 的屏幕,各自有独立的刷新函数my_flush_cb1/my_flush_cb2

#include"lvgl.h"#defineSCREEN1_HOR_RES240#defineSCREEN1_VER_RES320#defineSCREEN2_HOR_RES240#defineSCREEN2_VER_RES320#defineDISP_BUF_LINES20staticlv_display_t*disp1;staticlv_display_t*disp2;/* 屏幕1 刷新回调(LVGL v9:px_map 是 uint8_t* 原始像素) */staticvoidmy_flush_cb1(lv_display_t*disp,constlv_area_t*area,uint8_t*px_map){LV_UNUSED(disp);/* TODO: 把 px_map 中的像素,按 area->x1..x2, area->y1..y2 写入屏幕1 *//* 刷新结束后必须调用:*/lv_display_flush_ready(disp);}/* 屏幕2 刷新回调 */staticvoidmy_flush_cb2(lv_display_t*disp,constlv_area_t*area,uint8_t*px_map){LV_UNUSED(disp);/* TODO: 把 px_map 写入屏幕2 */lv_display_flush_ready(disp);}voidgui_dual_disp_init(void){lv_init();/* -------- 显示器1:创建 display 并绑定缓冲和刷新回调 -------- */disp1=lv_display_create(SCREEN1_HOR_RES,SCREEN1_VER_RES);/* 计算缓冲区大小(PARTIAL 渲染模式,按行数 * color_size) */uint32_tbuf1_size=SCREEN1_HOR_RES*DISP_BUF_LINES*lv_color_format_get_size(lv_display_get_color_format(disp1));staticuint8_tbuf1_a[SCREEN1_HOR_RES*DISP_BUF_LINES*4];// 预留足够字节,可按 buf1_size 调整staticuint8_tbuf1_b[SCREEN1_HOR_RES*DISP_BUF_LINES*4];lv_display_set_flush_cb(disp1,my_flush_cb1);lv_display_set_buffers(disp1,buf1_a,buf1_b,buf1_size,LV_DISPLAY_RENDER_MODE_PARTIAL);/* -------- 显示器2:同样方式创建 -------- */disp2=lv_display_create(SCREEN2_HOR_RES,SCREEN2_VER_RES);uint32_tbuf2_size=SCREEN2_HOR_RES*DISP_BUF_LINES*lv_color_format_get_size(lv_display_get_color_format(disp2));staticuint8_tbuf2_a[SCREEN2_HOR_RES*DISP_BUF_LINES*4];staticuint8_tbuf2_b[SCREEN2_HOR_RES*DISP_BUF_LINES*4];lv_display_set_flush_cb(disp2,my_flush_cb2);lv_display_set_buffers(disp2,buf2_a,buf2_b,buf2_size,LV_DISPLAY_RENDER_MODE_PARTIAL);}

提示:工程里的lvgl__lvgl组件使用的就是这套 v9 API(见lv_display.hlcd_stm32_guide.rst等),lv_disp_drv_t/lv_disp_draw_buf_t已被新的lv_display_*API 取代,老名字只是通过lv_api_map_v8.h做了兼容映射。


三、(可选)为每块屏单独配置触摸输入

如果两块屏都要触控,需要两个lv_indev_t,并分别绑定到disp1/disp2(v9 原生用lv_indev_set_display,你工程里也可以继续用兼容宏)。

staticlv_indev_t*indev1;staticlv_indev_t*indev2;/* 触摸读取回调1 */staticvoidmy_touch_read1(lv_indev_drv_t*drv,lv_indev_data_t*data){// TODO: 读取触摸屏1坐标,填充>}/* 触摸读取回调2 */staticvoidmy_touch_read2(lv_indev_drv_t*drv,lv_indev_data_t*data){// TODO: 读取触摸屏2坐标}voidgui_dual_input_init(void){/* 屏幕1输入 */staticlv_indev_drv_tindev_drv1;lv_indev_drv_init(&indev_drv1);indev_drv1.type=LV_INDEV_TYPE_POINTER;indev_drv1.read_cb=my_touch_read1;indev1=lv_indev_drv_register(&indev_drv1);lv_indev_set_display(indev1,disp1);// 绑定到显示器1/* 屏幕2输入 */staticlv_indev_drv_tindev_drv2;lv_indev_drv_init(&indev_drv2);indev_drv2.type=LV_INDEV_TYPE_POINTER;indev_drv2.read_cb=my_touch_read2;indev2=lv_indev_drv_register(&indev_drv2);lv_indev_set_display(indev2,disp2);// 绑定到显示器2}

四、为每个显示器创建并加载 Screen

关键点是:在创建 Screen 或控件前先用lv_disp_set_default(dispX)切换当前显示器,或者用lv_disp_get_scr_act(disp)拿到该显示器的根对象。

staticlv_obj_t*scr1;staticlv_obj_t*scr2;staticvoidcreate_screens_for_dual_display(void){/* ==== 显示器1的界面 ==== */lv_disp_set_default(disp1);// 切换当前默认显示器scr1=lv_obj_create(NULL);// 新建一个 Screen(根对象)lv_obj_set_style_bg_color(scr1,lv_color_hex(0x000000),0);lv_scr_load(scr1);// 或:lv_disp_load_scr(disp1, scr1);// 在屏幕1上创建控件lv_obj_t*label1=lv_label_create(scr1);lv_label_set_text(label1,"Hello, Display 1");lv_obj_align(label1,LV_ALIGN_CENTER,0,0);/* ==== 显示器2的界面 ==== */lv_disp_set_default(disp2);// 切换到第二个显示器scr2=lv_obj_create(NULL);lv_obj_set_style_bg_color(scr2,lv_color_hex(0x202020),0);lv_scr_load(scr2);// 或:lv_disp_load_scr(disp2, scr2);lv_obj_t*label2=lv_label_create(scr2);lv_label_set_text(label2,"Hello, Display 2");lv_obj_align(label2,LV_ALIGN_CENTER,0,0);}

也可以不调用lv_scr_load(),而是使用:

lv_disp_load_scr(disp1,scr1);lv_disp_load_scr(disp2,scr2);

效果是一样的,只是更明确指定了要加载到哪个显示器。


五、在某个屏上切换界面(可选,带动画)

如果某个物理屏需要在多个界面之间切换,比如副屏加载不同的菜单,可以在对应的disp上用lv_scr_loadlv_scr_load_anim

// 为屏幕2预先创建另一个界面staticlv_obj_t*scr2_alt=NULL;staticvoidcreate_alt_screen_for_disp2(void){lv_disp_set_default(disp2);scr2_alt=lv_obj_create(NULL);lv_obj_set_style_bg_color(scr2_alt,lv_color_hex(0x003366),0);lv_obj_t*label=lv_label_create(scr2_alt);lv_label_set_text(label,"Display 2 - ALT");lv_obj_align(label,LV_ALIGN_CENTER,0,0);}/* 在某个事件中调用这个函数 */voidswitch_disp2_to_alt(void){if(scr2_alt==NULL){create_alt_screen_for_disp2();}lv_disp_set_default(disp2);// 确保当前默认显示器是第二个// 直接切换:// lv_scr_load(scr2_alt);// 或使用带动画的切换:lv_scr_load_anim(scr2_alt,LV_SCR_LOAD_ANIM_MOVE_LEFT,300,0,false);}

如果你已经有自己的封装(比如SwitchToScreenWithAnim),只要在调用前先lv_disp_set_default(dispX),再调用你自己的封装即可。


六、主循环(共享lv_timer_handler

无论有几个显示器、多少个 Screen,主循环只需要一个lv_timer_handler()

voidapp_main(void){gui_dual_disp_init();gui_dual_input_init();// 如有触摸create_screens_for_dual_display();while(1){lv_timer_handler();// LVGL 9 对应的处理函数delay_ms(5);// 根据系统换成 vTaskDelay / HAL_Delay}}

七、常见注意事项

  • 内存占用

    • 两块 240×320×16bpp 的屏,如果每块屏用 1 个SCREEN_HOR_RES * 20的缓冲区,大约:
      • 每块缓冲区:240 * 20 * 2 byte ≈ 9.6 KB
      • 两块 ≈ 19 KB(不含 LVGL 内部堆和控件开销)
    • 如果内存紧张,可以:
      • 减少缓冲区高度(例如*10行)
      • 使用单缓冲(second buffer = NULL
  • 多线程 / RTOS

    • 在 FreeRTOS / ESP-IDF 下,通常会用一个 GUI 任务:
      • 任务里循环调用lv_timer_handler()
      • 其他任务通过消息队列或事件向 GUI 任务请求界面更新(避免多任务同时操作 LVGL)
  • 不同分辨率 / 方向

    • 每个显示器可以有自己的hor_res/ver_res
    • 需要旋转时,可使用disp_drv.sw_rotatedisp_drv.rotated(若硬件不支持旋转)。
  • 输入设备绑定

    • 一个输入设备可以指定绑定到某个lv_disp_t,这样同一个坐标系只作用在对应的屏幕上(lv_indev_set_disp(indev, disp))。

通过以上步骤,就可以在 LVGL9 中同时驱动两个物理屏幕,并在每个屏上加载不同的界面、独立处理输入。如果你已经有单屏工程,只需要:

  1. 再注册一个lv_disp_drv_tflush_cb作为第二块屏;
  2. 为第二块屏创建自己的 Screen 并lv_disp_load_scr(disp2, scr2)
  3. 保持原有lv_timer_handler()循环不变,即可完成双屏扩展。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 6:28:58

MQTT网络传输协议巩固知识基础题(1)

1. MQTT 是什么类型的协议? A. 请求-响应协议 B. 发布-订阅协议 C. 点对点协议 D. 广播协议 答案:B 解析: MQTT(Message Queuing Telemetry Transport)是基于发布-订阅模式的轻量级消息传输协议。 2. MQTT 主要设计用于哪种场景? A. 高带宽网络环境 B. 低带宽、高延迟…

作者头像 李华
网站建设 2026/4/16 18:00:19

AI绘画不止“出图快”:它正让文化活起来、让普通人赚到钱

前几天在南宁举办的中国—东盟博览会上,一个“AI非遗服饰体验区”被围得水泄不通。外国游客对着屏幕输入自己的照片,一键选择印尼纱笼、泰国宋干节服饰或是广西壮锦盛装,十秒不到,一张融合了个人特质与传统纹样的合影就生成了。我…

作者头像 李华
网站建设 2026/4/16 17:25:56

LobeChat能否用于创建交互式教程?教育内容动态生成

LobeChat能否用于创建交互式教程?教育内容动态生成 在智能教育工具日益普及的今天,越来越多的学习者不再满足于“点击播放”的录播课或静态PDF讲义。他们渴望的是能即时回应、按需讲解、甚至主动引导学习路径的“AI导师”。这种需求背后,是对…

作者头像 李华
网站建设 2026/4/18 8:16:46

【Android饮食健康管理系统】(免费领源码+演示录像)|可做计算机毕设Java、Python、PHP、小程序APP、C#、爬虫大数据、单片机、文案

摘 要 科学技术日新月异,人们的生活都发生了翻天覆地的变化,饮食健康管理系统当然也不例外。过去的信息管理都使用传统的方式实行,既花费了时间,又浪费了精力。在信息如此发达的今天,我们可以通过网络这个媒介&#x…

作者头像 李华
网站建设 2026/4/16 9:29:15

LobeChat能否集成珊瑚礁健康数据?海洋生态系统保护行动指南

LobeChat能否集成珊瑚礁健康数据?海洋生态系统保护行动指南 在太平洋偏远环礁的监测站里,一位生态保护工作者正顶着烈日整理传感器数据。她最担心的不是设备故障,而是如何让这些枯燥的温度曲线和白化率数字被地方政府真正“看见”并采取行动。…

作者头像 李华
网站建设 2026/4/16 19:30:29

LobeChat能否导出聊天记录为PDF或文本文件?

LobeChat 能否导出聊天记录为 PDF 或文本文件? 在构建 AI 助手的实践中,一个常被忽视但极其关键的问题浮出水面:我们如何真正拥有自己的对话数据? 许多用户在使用像 ChatGPT 这类主流 AI 对话工具时,常常发现一旦关闭…

作者头像 李华