阶段1: 将 dzbj 项目的 LVGL 8.3.11 LCD 显示集成到 AI小智 主项目, 开机显示 ScreenHome 界面,同时优化配网模式下的内存使用, 确保 WiFi+BLE+LVGL 三者共存运行。 ## 新增功能 ### dzbj 显示模块集成 - 新增 main/dzbj/ 目录,移植 LCD 驱动(ST77916 QSPI)、触摸驱动(CST816S)、 LVGL 初始化和 SquareLine Studio UI 界面 - I2C 总线共享:dzbj 触摸控制器复用主项目的 I2C_NUM_1 总线 - GPIO 冲突解决:LED(GPIO21)、Touch1(GPIO1)、Touch4(GPIO7) 改为 NC, 电池 ADC 从 GPIO6 改为 GPIO3 - 添加 LVGL、esp_lcd_st77916、esp_lcd_touch_cst816s 等组件依赖 - managed_components 纳入版本管理 ### 配网模式轻量化启动 - BoxAudioCodec: 新增 output_only 模式,仅创建 I2S TX 通道(省 ~13KB DMA) 跳过 ES7210 ADC 初始化(省 ~2-4KB) - AudioCodec: 新增 StartOutputOnly() 方法,仅启用扬声器输出 - Application: 配网模式跳过 Opus 编码器、输入重采样器、协议初始化、 天气位置检测等网络业务 - 板级构造函数: 配网模式跳过电池检测、IMU传感器、PowerSaveTimer ### WifiBoard 配网流程修复 - NeedsProvisioning() 静态方法: 读取 NVS force_ap 和 SSID 列表, 用于提前判断配网模式 - force_ap 竞态修复: 构造函数不再清零 force_ap,改在 StartNetwork() 清零, 确保 NeedsProvisioning() 能正确读到 force_ap=1 - Application 缓存 provisioning_mode_ 成员变量,避免重复读 NVS ### BLE 配网重启修复 - 配网成功后用 esp_timer 延迟重启替代 xTaskCreate, 避免内存紧张时任务创建失败导致设备不重启 - 注释掉 WiFi 连接成功后的 MAC 地址发送步骤 ### sdkconfig 内存优化 - BT_ALLOCATION_FROM_SPIRAM_FIRST=y (BLE 动态分配优先 PSRAM) - SPIRAM_MALLOC_RESERVE_INTERNAL=32768 - NVS_ALLOCATE_CACHE_IN_SPIRAM=y - WiFi 静态缓冲区数量优化 (RX=10, TX=8) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.4 KiB
Displays
Multiple display support
In LVGL you can have multiple displays, each with their own driver and objects. The only limitation is that every display needs to have the same color depth (as defined in LV_COLOR_DEPTH).
If the displays are different in this regard the rendered image can be converted to the correct format in the drivers flush_cb.
Creating more displays is easy: just initialize more display buffers and register another driver for every display.
When you create the UI, use lv_disp_set_default(disp) to tell the library on which display to create objects.
Why would you want multi-display support? Here are some examples:
- Have a "normal" TFT display with local UI and create "virtual" screens on VNC on demand. (You need to add your VNC driver).
- Have a large TFT display and a small monochrome display.
- Have some smaller and simple displays in a large instrument or technology.
- Have two large TFT displays: one for a customer and one for the shop assistant.
Using only one display
Using more displays can be useful but in most cases it's not required. Therefore, the whole concept of multi-display handling is completely hidden if you register only one display. By default, the last created (and only) display is used.
lv_scr_act(), lv_scr_load(scr), lv_layer_top(), lv_layer_sys(), LV_HOR_RES and LV_VER_RES are always applied on the most recently created (default) display.
If you pass NULL as disp parameter to display related functions the default display will usually be used.
E.g. lv_disp_trig_activity(NULL) will trigger a user activity on the default display. (See below in Inactivity).
Mirror display
To mirror the image of a display to another display, you don't need to use multi-display support. Just transfer the buffer received in drv.flush_cb to the other display too.
Split image
You can create a larger virtual display from an array of smaller ones. You can create it as below:
- Set the resolution of the displays to the large display's resolution.
- In
drv.flush_cb, truncate and modify theareaparameter for each display. - Send the buffer's content to each real display with the truncated area.
Screens
Every display has its own set of screens and the objects on each screen.
Be sure not to confuse displays and screens:
- Displays are the physical hardware drawing the pixels.
- Screens are the high-level root objects associated with a particular display. One display can have multiple screens associated with it, but not vice versa.
Screens can be considered the highest level containers which have no parent.
A screen's size is always equal to its display and their origin is (0;0). Therefore, a screen's coordinates can't be changed, i.e. lv_obj_set_pos(), lv_obj_set_size() or similar functions can't be used on screens.
A screen can be created from any object type but the two most typical types are Base object and Image (to create a wallpaper).
To create a screen, use lv_obj_t * scr = lv_<type>_create(NULL, copy). copy can be an existing screen copied into the new screen.
To load a screen, use lv_scr_load(scr). To get the active screen, use lv_scr_act(). These functions work on the default display. If you want to specify which display to work on, use lv_disp_get_scr_act(disp) and lv_disp_load_scr(disp, scr). A screen can be loaded with animations too. Read more here.
Screens can be deleted with lv_obj_del(scr), but ensure that you do not delete the currently loaded screen.
Transparent screens
Usually, the opacity of the screen is LV_OPA_COVER to provide a solid background for its children. If this is not the case (opacity < 100%) the display's background color or image will be visible.
See the Display background section for more details. If the display's background opacity is also not LV_OPA_COVER LVGL has no solid background to draw.
This configuration (transparent screen and display) could be used to create for example OSD menus where a video is played on a lower layer, and a menu is overlayed on an upper layer.
To handle transparent displays, special (slower) color mixing algorithms need to be used by LVGL so this feature needs to enabled with LV_COLOR_SCREEN_TRANSP in lv_conf.h.
The Alpha channel of 32-bit colors will be 0 where there are no objects and 255 where there are solid objects.
In summary, to enable transparent screens and displays for OSD menu-like UIs:
- Enable
LV_COLOR_SCREEN_TRANSPinlv_conf.h - Set the screen's opacity to
LV_OPA_TRANSPe.g. withlv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_TRANSP, LV_PART_MAIN) - Set the display opacity to
LV_OPA_TRANSPwithlv_disp_set_bg_opa(NULL, LV_OPA_TRANSP);
Features of displays
Inactivity
A user's inactivity time is measured on each display. Every use of an Input device (if associated with the display) counts as an activity.
To get time elapsed since the last activity, use lv_disp_get_inactive_time(disp). If NULL is passed, the lowest inactivity time among all displays will be returned (NULL isn't just the default display).
You can manually trigger an activity using lv_disp_trig_activity(disp). If disp is NULL, the default screen will be used (and not all displays).
Background
Every display has a background color, background image and background opacity properties. They become visible when the current screen is transparent or not positioned to cover the whole display.
The background color is a simple color to fill the display. It can be adjusted with lv_disp_set_bg_color(disp, color);
The display background image is a path to a file or a pointer to an lv_img_dsc_t variable (converted image data) to be used as wallpaper. It can be set with lv_disp_set_bg_image(disp, &my_img);
If a background image is configured the background won't be filled with bg_color.
The opacity of the background color or image can be adjusted with lv_disp_set_bg_opa(disp, opa).
The disp parameter of these functions can be NULL to select the default display.
API
.. doxygenfile:: lv_disp.h
:project: lvgl