1. 项目概述
泰山派NAS-LVGL9监控器是一个基于LVGL图形库开发的嵌入式系统监控界面项目。作为一名长期从事嵌入式开发的工程师,我最近在泰山派开发板上实现了一个完整的系统监控解决方案,现在将详细的技术实现过程分享给大家。
这个项目最大的特点是将安卓Activity的组件化思想引入到嵌入式GUI开发中,配合LVGL9.1的强大功能,实现了模块化、可扩展的监控界面。整个项目从环境搭建到最终实现,涉及多个关键技术点,包括:
- LVGL在嵌入式Linux平台的移植
- FrameBuffer驱动适配
- 触摸输入系统集成
- 仿安卓Activity的UI框架设计
- 系统资源监控实现
- 开发调试技巧
下面我将从环境搭建开始,逐步讲解这个项目的完整实现过程。
2. 开发环境搭建
2.1 LVGL基础环境配置
LVGL(Light and Versatile Graphics Library)是一个专为嵌入式系统设计的轻量级图形库,支持多种显示设备和输入设备。在开始项目前,我们需要搭建好开发环境。
首先创建工作目录并获取LVGL源代码:
mkdir lvgl_blog_demo cd lvgl_blog_demo git clone https://github.com/lvgl/lvgl.git -b release/v9.1为了便于开发调试,我们还需要获取Visual Studio模拟器项目:
git clone --recurse-submodules https://github.com/lvgl/lv_port_pc_visual_studio.git以及Linux平台适配代码:
git clone https://github.com/lvgl/lv_port_linux.git -b release/v9.02.2 配置文件调整
将lv_port_linux目录下的关键文件复制到项目根目录:
cp lv_port_linux/{Makefile,main.c,CMakeLists.txt,.gitignore} .然后修改Makefile,注释掉mouse_cursor_icon.c的引用:
# CSRCS +=$(LVGL_DIR)/mouse_cursor_icon.c同样修改CMakeLists.txt,移除mouse_cursor_icon.c的编译:
add_executable(main main.c)2.3 屏幕参数配置
复制lvgl目录下的lv_conf_template.h到项目根目录,并重命名为lv_conf.h。然后进行以下关键配置:
#define DISP_WIDTH 320 // 设置屏幕宽度 #define DISP_HIGHT 240 // 设置屏幕高度 #define LV_MEM_SIZE (2048 * 1024U) // 分配2MB内存给LVGL #define LV_DEF_REFR_PERIOD 10 // 设置刷新周期为10ms #define LV_USE_OS LV_OS_PTHREAD // 使用Linux线程 #define LV_USE_LOG 1 // 启用日志 #define LV_USE_FLOAT 1 // 启用浮点支持 #define LV_USE_GIF 1 // 启用GIF支持 #define LV_GIF_CACHE_DECODE_DATA 1 // 启用GIF缓存 #define LV_USE_SYSMON 1 // 启用系统监控 #define LV_USE_LINUX_FBDEV 1 // 启用FrameBuffer支持 #define LV_LINUX_FBDEV_BUFFER_COUNT 2 // 启用双缓冲 #define LV_USE_EVDEV 1 // 启用输入设备支持这些配置为后续开发奠定了基础,特别是双缓冲和输入设备支持,对流畅的UI体验至关重要。
3. 显示与输入驱动适配
3.1 FrameBuffer驱动适配
为了让LVGL能够在泰山派开发板上正常显示,我们需要修改FrameBuffer驱动接口。主要修改位于lvgl/src/drivers/display/fb/lv_linux_fbdev.c:
lv_display_t * lv_linux_fbdev_create(int32_t hor_res, int32_t ver_res) { static bool inited = false; if(!inited) { lv_tick_set_cb(tick_get_cb); inited = true; } lv_linux_fb_t * dsc = lv_malloc_zeroed(sizeof(lv_linux_fb_t)); LV_ASSERT_MALLOC(dsc); if(dsc == NULL) return NULL; lv_display_t * disp = lv_display_create(hor_res, ver_res); if(disp == NULL) { lv_free(dsc); return NULL; } dsc->fbfd = -1; lv_display_set_driver_data(disp, dsc); lv_display_set_flush_cb(disp, flush_cb); return disp; }同时需要修改头文件中的函数声明:
lv_display_t * lv_linux_fbdev_create(int32_t hor_res, int32_t ver_res);在main.c中初始化显示设备:
lv_display_t * disp = lv_linux_fbdev_create(DISP_WIDTH, DISP_HIGHT); lv_linux_fbdev_set_file(disp, "/dev/fb0");3.2 触摸输入驱动适配
泰山派开发板通常使用Linux输入子系统(evdev)来处理触摸输入。在main.c中添加以下代码:
char evdev[41] = "/dev/input/event1"; lv_indev_t* cdev = lv_evdev_create(LV_INDEV_TYPE_POINTER, evdev); lv_evdev_set_swap_axes(cdev, true); // 交换XY轴 lv_evdev_set_calibration(cdev, 0, 0, DISP_WIDTH, DISP_HIGHT); // 设置触摸范围这里有几个关键点需要注意:
- 触摸设备节点通常是/dev/input/eventX,具体是哪个需要根据实际情况确定
- 某些屏幕可能需要交换XY轴坐标
- 校准参数需要根据实际触摸屏特性调整
4. 开发工具链配置
4.1 交叉编译环境
泰山派开发板使用ARM架构处理器,我们需要配置交叉编译工具链。假设SDK已经解压到~/share_file/tspi_linux/,可以这样设置环境变量:
export CC=~/share_file/tspi_linux/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc然后创建build目录并编译:
mkdir build cd build cmake .. make -j8编译完成后,可执行文件会生成在bin目录下。
4.2 VSCode开发环境
为了提高开发效率,建议使用VSCode进行开发。首先安装以下插件:
- Remote - SSH:用于远程连接开发主机
- CMake Tools:用于CMake项目管理
配置交叉编译工具链:
{ "name": "GCC 6.3.1 arm-rockchip-tspi", "compilers": { "C": "/home/chen/share_file/tspi_linux/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc", "CXX": "/home/chen/share_file/tspi_linux/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-g++" } }配置完成后,可以在VSCode中一键编译项目,大大提高开发效率。
4.3 Visual Studio模拟器
为了加快UI开发速度,我们可以使用Visual Studio模拟器进行前期开发:
- 下载安装Visual Studio
- 打开lv_port_pc_visual_studio/LVGL.sln
- 修改LvglWindowsSimulator.cpp中的屏幕尺寸:
lv_display_t* display = lv_windows_create_display( L"LVGL Windows Simulator Display 1", 350, // 宽度 200, // 高度 zoom_level, allow_dpi_override, simulator_mode);模拟器开发可以大大减少实际设备上的调试时间,特别适合UI布局和动画效果的调试。
5. Activity组件化框架设计
5.1 设计思想
借鉴安卓Activity的设计理念,我实现了一个轻量级的UI框架,主要特点包括:
- 页面组件化:每个界面作为独立组件
- 生命周期管理:onCreate、onDestroy等回调
- 三种压栈模式:STANDARD、SINGLE_TOP、SINGLE_TASK
- 消息总线:支持跨页面通信
- 动态启动页设置
这种设计使得UI开发更加模块化,便于维护和扩展。
5.2 核心实现
框架核心代码位于uikit目录,主要包含:
- ui.c:实现Activity管理
- data_bus.c:实现消息总线
页面组件的基本结构如下:
#include "application.h" static lv_obj_t * container; static ui_data_t * ui; LV_IMG_DECLARE(img_start_lunch) static void timer_cb(lv_timer_t * timer) { ui_intent_t intent; intent.anim = true; ui_fun_fast_create_intent(ui, PAGE_MONITOR_STYLE1, &intent); ui_fun_start_activity(&intent); ui_fun_finish(ui, false); } static void on_page_create(ui_data_t * ui_dat, void * params) { ui = ui_dat; container = lv_image_create(lv_screen_active()); lv_img_set_src(container, &img_start_lunch); lv_timer_t * timer = lv_timer_create(timer_cb, 3000, NULL); lv_timer_set_repeat_count(timer, 1); } static void on_page_destoy(void * params) {} static lv_obj_t * page_get_view() { return container; } ui_data_t page_lunch = { .id = PAGE_LUNCH, .launcher = true, .launcher_mode = SINGLE_TASK, .fun_get_view = page_get_view, .fun_on_create = on_page_create, .fun_on_destoy = on_page_destoy };5.3 页面管理与跳转
所有页面需要在application.h中注册:
#include "application.h" extern ui_data_t page_lunch; extern ui_data_t page_monitor; static ui_data_t* pages[] = {&page_lunch, &page_monitor}; void app_lunch() { ui_init_and_start(pages, sizeof(pages)); }页面跳转通过intent实现:
ui_intent_t intent; intent.anim = true; // 启用切换动画 ui_fun_fast_create_intent(ui, PAGE_MONITOR_STYLE1, &intent); ui_fun_start_activity(&intent); ui_fun_finish(ui, false); // 结束当前页面这种设计使得页面之间的跳转和通信变得非常清晰和可控。
6. GIF动画集成
6.1 准备工作
首先确保lv_conf.h中启用了GIF支持:
#define LV_USE_GIF 1 #define LV_GIF_CACHE_DECODE_DATA 1使用LVGL官方提供的在线图片转换工具将GIF转换为C数组: https://lvgl.io/tools/imageconverter
转换时注意选择正确的颜色格式:
- CF_RAW:无透明通道
- CF_RAW_ALPHA:带透明通道
6.2 GIF使用示例
声明GIF资源:
LV_IMG_DECLARE(gif_radiantboy)创建并显示GIF:
lv_obj_t * gif_obj = lv_gif_create(parent); lv_gif_set_src(gif_obj, &gif_radiantboy); lv_obj_set_pos(gif_obj, x, y);控制GIF播放:
lv_gif_pause(gif_obj); // 暂停 lv_gif_resume(gif_obj); // 恢复在实际项目中,GIF动画可以大大增强用户体验,但需要注意:
- 控制GIF大小,避免占用过多内存
- 复杂动画可能会影响性能
- 考虑提供暂停/播放控制接口
7. 项目构建系统
7.1 目录结构
项目采用模块化目录结构:
├── app/ # 页面组件 ├── assets/ # 原始资源 ├── font/ # 字体文件 ├── image/ # 图片资源 ├── monitor/ # 系统监控实现 ├── store/ # 存储相关 └── uikit/ # UI框架7.2 CMake配置
修改CMakeLists.txt包含所有模块:
include_directories( ${PROJECT_SOURCE_DIR}/app ${PROJECT_SOURCE_DIR}/monitor ${PROJECT_SOURCE_DIR}/uikit ${PROJECT_SOURCE_DIR}/store ) file(GLOB APP ${CMAKE_CURRENT_SOURCE_DIR}/app/*.c) file(GLOB MONITOR ${CMAKE_CURRENT_SOURCE_DIR}/monitor/*.c) file(GLOB UIKIT ${CMAKE_CURRENT_SOURCE_DIR}/uikit/*.c) file(GLOB STORE ${CMAKE_CURRENT_SOURCE_DIR}/store/*.c) file(GLOB FONT ${CMAKE_CURRENT_SOURCE_DIR}/font/*.c) file(GLOB IMG ${CMAKE_CURRENT_SOURCE_DIR}/image/*.c) add_executable(nas_monitor main.c ${STORE} ${UIKIT} ${MONITOR} ${FONT} ${IMG} ${APP}) target_link_libraries(nas_monitor lvgl lvgl::thorvg ${SDL2_LIBRARIES} m pthread)这种配置方式使得项目结构清晰,便于维护和扩展。
8. 启动参数解析
8.1 参数设计
程序支持以下启动参数:
./nas_monitor -fb 0 -ev 1 -disk /dev/sda1 -net eth0- -fb:指定FrameBuffer设备号
- -ev:指定输入设备号
- -disk:监控的磁盘设备
- -net:监控的网络接口
8.2 实现代码
参数解析工具函数:
int extractData(int argc, char* argv[], char* buf, const char* key) { for (int i = 1; i < argc; i++) { if (strncmp(argv[i], key, strlen(key)) == 0) { if (i + 1 < argc) { strcpy(buf, argv[i + 1]); return 0; } return -2; } } return -1; }在main函数中使用:
char flag_fb[2] = "0"; char flag_ev[2] = "1"; extractData(argc, argv, flag_fb, "-fb"); extractData(argc, argv, flag_ev, "-evdev"); extractData(argc, argv, monitor_dat.disk_name, "-disk"); extractData(argc, argv, monitor_dat.net_name, "-net"); if(extractData(argc, argv, NULL, "-h") == -2){ printf("\tNAS MONITOR Helper\n \ INPUT:-fb DESC: Display Output; Example: -fb 0\n \ INPUT:-evdev DESC: Touch the input subsystem used; Example:-evdev 1\n \ INPUT:-disk DESC: The hard drive to be monitored; Example: -disk /dev/sda1\n \ INPUT:-net DESC: The name of the NIC to which you want to monitor the network speed; Example: -net eth0\n"); return 0; } char fb[41] = "/dev/fb"; char evdev[41] = "/dev/input/event"; strcat(fb, flag_fb); strcat(evdev, flag_ev);这种设计使得程序更加灵活,可以适应不同的硬件配置。
9. 部署与测试
9.1 文件传输
编译完成后,使用scp将程序传输到开发板:
scp bin/nas_monitor user@192.168.110.167:~/9.2 运行测试
在开发板上运行程序:
./nas_monitor -fb 0 -ev 1 -disk /dev/sda1 -net eth09.3 性能优化建议
在实际部署时,可以考虑以下优化措施:
- 调整LVGL的内存配置
- 优化刷新频率
- 精简不必要的组件
- 使用更高效的绘图方式
- 合理使用双缓冲
10. 项目总结与扩展
通过这个项目,我们实现了一个完整的嵌入式系统监控界面,主要技术亮点包括:
- LVGL在嵌入式Linux平台的完整适配
- 创新的Activity组件化设计
- 灵活的参数配置系统
- 高效的开发调试流程
这个项目还可以进一步扩展:
- 添加更多监控指标
- 实现远程配置功能
- 增加告警通知机制
- 支持多语言
- 优化性能表现
在实际开发过程中,我总结了以下几点经验:
- 模拟器开发可以大大提高效率
- 组件化设计使项目更易维护
- 合理的日志系统对调试很有帮助
- 性能优化需要结合实际测试数据
- 良好的文档习惯很重要