1.1 Echo的main函数
关于这里的/dev和framebuffer,可以参考韦东山的Linux应用内容,主要是读取内容,拿到对应的屏幕像素点所对应的数据位置信息。
#include "lvgl/lvgl.h" // 引入LVGL图形库核心头文件
#include "lvgl/demos/lv_demos.h" // 引入LVGL官方示例(当前未使用)
#include <unistd.h> // POSIX系统调用接口:usleep()
#include <pthread.h> // 线程库(预留多线程扩展)
#include <time.h> // 时间相关函数
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 标准库函数
#include "gui_app/common/lv_lib.h" // 项目自定义LVGL公共库(页面管理器、栈、动画)
#include "gui_app/ui.h" // UI应用层接口(ui_init、页面注册、系统参数)
// 【工具函数】获取环境变量,若不存在则返回默认值
// 数据处理:从进程环境变量中按name查找字符串,找不到则返回dflt兜底值
static const char *getenv_default(const char *name, const char *dflt)
{
return getenv(name) ? : dflt; // 三元运算符:getenv返回NULL时取dflt
}
#if LV_USE_EVDEV
// 【条件编译 - ARM设备模式】初始化Linux evdev输入设备(触摸屏)
static void lv_linux_indev_init(void)
{
lv_indev_t * touch; // 声明LVGL输入设备句柄
// 数据处理:创建一个指针类型的输入设备,绑定到/dev/input/event0(触摸屏设备节点)
// LV_INDEV_TYPE_POINTER 表示这是一个坐标指点类设备(触摸屏/鼠标)
touch = lv_evdev_create(LV_INDEV_TYPE_POINTER, "/dev/input/event0");
}
#endif
#if LV_USE_LINUX_FBDEV
// 【条件编译 - ARM设备Framebuffer模式】初始化Linux Framebuffer显示设备
static void lv_linux_disp_init(void)
{
// 数据处理:从环境变量读取framebuffer设备路径,默认为/dev/fb0(第一个帧缓冲设备)
const char *device = getenv_default("LV_LINUX_FBDEV_DEVICE", "/dev/fb0");
// 创建LVGL显示设备实例,返回lv_display_t*句柄
lv_display_t * disp = lv_linux_fbdev_create();
// 数据处理:将显示设备句柄与具体的framebuffer设备文件绑定
// 后续LVGL的渲染结果将通过该设备写入显存映射的内存区域
lv_linux_fbdev_set_file(disp, device);
}
#elif LV_USE_LINUX_DRM
// 【条件编译 - ARM设备DRM模式】初始化Linux DRM(Direct Rendering Manager)显示设备
static void lv_linux_disp_init(void)
{
// 数据处理:从环境变量读取DRM显卡设备路径,默认为/dev/dri/card0
const char *device = getenv_default("LV_LINUX_DRM_CARD", "/dev/dri/card0");
// 创建DRM显示设备实例
lv_display_t * disp = lv_linux_drm_create();
// 数据处理:将DRM显示设备与显卡设备文件及plane绑定(-1表示自动选择plane)
lv_linux_drm_set_file(disp, device, -1);
}
#elif LV_USE_SDL
// 【条件编译 - PC仿真模式】使用SDL2库创建窗口和鼠标输入
static void lv_linux_disp_init(void)
{
// 数据处理:从环境变量读取SDL窗口宽度,默认320像素(匹配目标屏幕分辨率)
const int width = atoi(getenv("LV_SDL_VIDEO_WIDTH") ? : "320");
// 数据处理:从环境变量读取SDL窗口高度,默认240像素
const int height = atoi(getenv("LV_SDL_VIDEO_HEIGHT") ? : "240");
// 创建SDL2窗口,尺寸为width×height,作为LVGL的显示输出目标
// 内部会创建SDL_Renderer用于硬件加速渲染
lv_sdl_window_create(width, height);
}
// PC仿真模式下使用SDL鼠标模拟触摸/点击输入
static void lv_linux_indev_init(void)
{
// 数据处理:创建一个基于SDL的鼠标输入设备
// SDL会将操作系统的鼠标事件(移动、点击)转换为LVGL可识别的输入事件
lv_sdl_mouse_create();
}
#else
#error Unsupported configuration // 如果以上三种模式都未启用,编译报错终止
#endif
// ==================== 程序入口函数 ====================
int main(void)
{
// ===== 第1步:LVGL核心库初始化 =====
// 数据处理:
// 1. 初始化LVGL内部全局状态机(内存管理器、对象链表、定时器列表等)
// 2. 分配LV_MEM_SIZE(1MB)大小的内存池给LVGL使用
// 3. 设置默认显示刷新周期LV_DEF_REFR_PERIOD=20ms
// 4. 此时还没有任何显示设备和输入设备,只是一个空壳框架
lv_init();
/*Linux display device init*/
// ===== 第2步:显示设备初始化 =====
// 根据dev_conf.h中的宏定义选择不同的显示驱动:
// - PC仿真模式(LV_USE_SDL):创建SDL窗口,像素数据通过SDL_Renderer输出到桌面窗口
// - ARM设备FB模式(LV_USE_LINUX_FBDEV):打开/dev/fb0,通过mmap映射显存直接写屏
// - ARM设备DRM模式(LV_USE_LINUX_DRM):通过DRM KMS接口控制GPU输出到屏幕
// 数据处理:创建并配置显示设备后,LVGL获得了一个可以输出像素的目标表面
lv_linux_disp_init();
// ===== 第3步:输入设备初始化 =====
// 根据平台选择不同的输入驱动:
// - PC仿真模式:SDL鼠标,捕获操作系统鼠标事件
// - ARM设备模式:evdev触摸屏,读取/dev/input/event0的原始触摸数据
// 数据处理:注册输入回调后,LVGL能够在每次lv_timer_handler()中轮询获取用户输入
lv_linux_indev_init();
/*Create a Demo*/
// lv_demo_widgets(); // 注释掉的LVGL官方控件演示
// lv_demo_widgets_start_slideshow(); // 注释掉的控件幻灯片演示
// ===== 第4步:应用层UI初始化 =====
// 数据处理(详见ui.c中的ui_init函数):
// 1. 加载/创建系统参数配置文件(时间、亮度、音量、WiFi、位置等)
// 2. 初始化GPIO硬件引脚(LED蓝灯、两个电机控制引脚)
// 3. 设置LVGL主题样式(蓝色主色调+红色辅助色)
// 4. 初始化页面管理器(分配页面栈内存,容量5层)
// 5. 注册11个应用页面到页面管理器(HomePage、SettingPage、WeatherPage等)
// 6. 打开HomePage主页(调用其init函数创建所有UI控件)
// 7. 创建1秒周期的定时器(_maintimer_cb),用于LED闪烁和参数保存
ui_init();
/*Handle LVGL tasks*/
// ===== 第5步:主事件循环(程序的核心执行引擎)=====
while(1) {
// ★★★ lv_timer_handler() - LVGL核心调度函数 ★★★
//
// 【执行频率】:约1000次/秒(每次循环1ms延时)
// 【内部数据处理流程】:
//
// ┌──────────────────────────────────────────────────────┐
// │ ① 系统时间更新 │
// │ 调用lv_tick_inc(1)递增LVGL内部时钟计数器 │
// │ 该计数器是所有LVGL定时器的基准时间源 │
// ├──────────────────────────────────────────────────────┤
// │ ② 定时器回调扫描与执行 │
// │ 遍历LVGL内部定时器链表,检查每个定时器是否到期 │
// │ 到期的定时器将被触发执行其回调函数: │
// │ - _maintimer_cb (每1000ms):LED闪烁 + 每5分钟存参数│
// │ - ui_home_timer_cb (每5000ms):更新时间标签文字 │
// │ - 动画定时器 (每~16ms):更新正在播放的动画属性值 │
// ├──────────────────────────────────────────────────────┤
// │ ③ 输入设备数据采集 │
// │ 调用已注册输入设备的read_cb函数: │
// │ - SDL模式:从SDL事件队列取鼠标状态 │
// │ - EVDEV模式:从/dev/input/event0读取触摸坐标 │
// │ 原始输入数据被转换为LVGL内部的indev_data结构 │
// ├──────────────────────────────────────────────────────┤
// │ ④ 输入事件分发与处理 │
// │ 将原始输入数据转换为高级事件: │
// │ - LV_EVENT_PRESSED(按下) │
// │ - LV_EVENT_PRESSING(持续按压/拖拽) │
// │ - LV_EVENT_RELEASED(释放) │
// │ - LV_EVENT_CLICKED(单击=按下+释放) │
// │ - LV_EVENT_GESTURE(滑动手势:左/右/上/下) │
// │ 匹配到事件目标对象后,调用其注册的事件回调函数 │
// │ 例如:按钮点击→ui_event_AppsBtn→lv_lib_pm_OpenPage │
// ├──────────────────────────────────────────────────────┤
// │ ⑤ 对象脏区标记与重绘计算 │
// │ 检查哪些对象的属性在上一周期发生了变化 │
// │ (位置、大小、颜色、透明度、文字内容等) │
// │ 将这些对象标记为"脏",计算需要重绘的最小矩形区域 │
// ├──────────────────────────────────────────────────────┤
// │ ⑥ 渲染输出 │
// │ 对脏区域进行软件光栅化渲染: │
// │ - 绘制背景色/背景图 │
// │ - 绘制边框 │
// │ - 绘制阴影 │
// │ - 绘制文字(使用FreeType/TinyTTF字体引擎) │
// │ - 绘制图片(PNG/JPG/BMP解码后绘制) │
// │ - 处理混合模式(Alpha合成) │
// │ 最终像素数据写入显示设备的flush buffer │
// ├──────────────────────────────────────────────────────┤
// │ ⑦ 显示刷新(Flush) │
// │ 调用显示设备的flush_cb函数: │
// │ - SDL模式:调用SDL_RenderPresent呈现到窗口 │
// │ - FB模式:mmap内存已写入,LCD控制器自动读取刷新 │
// │ - DRM模式:通过ioctl提交framebuffer到显示队列 │
// └──────────────────────────────────────────────────────┘
lv_timer_handler();
// 数据处理:让出CPU 1ms(1000微秒)
// 作用:
// 1. 防止while(1)死循环占用100% CPU导致系统卡顿
// 2. 控制lv_timer_handler的调用频率约为1000Hz
// 3. 给其他线程/进程(如网络请求线程)让出CPU时间片
// 注意:实际运行频率可能低于1000Hz,因为lv_timer_handler本身也有执行耗时
usleep(1000); // 1000微秒 = 1毫秒
}
return 0; // 理论上不可达(无限循环)
}
浙公网安备 33010602011771号