main.c
文件是整个项目的Orchestrator (编排器) 和 Bootstrap Loader (引导加载程序)。它负责在程序启动时进行所有必要的初始化,然后进入一个无限循环来驱动整个应用程序的运行。
其最核心的设计是利用 C 语言的预处理器 (Preprocessor),特别是 #if USE_SDL ... #else ... #endif
这样的条件编译 (Conditional Compilation) 块,来实现平台代码的抽象。这意味着同一份 main.c
源代码,无需任何修改,就可以被编译成面向完全不同硬件平台的两个版本:一个用于 PC/SDL 仿真,另一个用于嵌入式 ARM/Framebuffer 运行。
关键点:
USE_SDL
这个宏的值并不是由开发者手动修改的。它是由CMakeLists.txt
在执行cmake
配置阶段时,根据目标平台自动写入到lv_drv_conf.h
文件中的,实现了完全的自动化。
核心职责
- 平台硬件抽象层 (HAL) 初始化: 根据编译目标,选择性地初始化显示驱动(SDL 或 Framebuffer)和输入驱动(SDL Mouse/Keyboard 或 evdev)。
- LVGL 核心库初始化: 调用
lv_init()
启动 LVGL 引擎。 - 应用程序初始化: 创建 UI (
ui_init()
),并启动业务逻辑相关的模块,如开机自检、时间显示、UI 状态更新定时器等。 - 主事件循环: 运行一个
while(1)
循环,在其中周期性地调用 LVGL 的任务处理器,并处理来自控制台的输入。
代码执行流程详解
Includes 阶段:
- 代码首先根据
lv_drv_conf.h
中USE_SDL
宏的值,决定包含哪一套头文件。 - 如果
USE_SDL
为1
,则包含sdl.h
。 - 如果
USE_SDL
为0
,则包含fbdev.h
和evdev.h
,并声明custom_tick_get()
函数。
- 代码首先根据
main()
函数入口:lv_init()
: 初始化 LVGL 的所有内部机制。hal_init()
: 这是平台抽象的核心所在。这个函数内部包含了完整的条件编译块,用于执行与平台相关的硬件初始化(详见下文)。ui_init()
: 调用 UI 模块的入口函数,创建所有屏幕和控件。- 业务逻辑启动:
init_all_lights_test()
: 启动开机自检,短暂点亮所有指示灯。init_time_display()
: 创建一个定时器,用于更新时间显示。init_roller_event_handler()
: 为驾驶模式选择器绑定事件。lv_timer_create(update_ui_from_state, 100, NULL)
: 创建数据驱动 UI 的核心定时器。它会每隔 100 毫秒调用一次obj/
目录中的update_ui_from_state
函数,将车辆状态数据渲染到 UI 上。
主循环
while(1)
:handle_console_input()
: 调用obj/
模块的函数,非阻塞地检查并处理来自用户终端的命令。lv_timer_handler()
: 这是 LVGL 的“心跳”。它负责处理所有任务,包括重绘屏幕、执行动画、调用定时器回调等。该函数会返回下一次需要被调用之前可以休眠的毫秒数。usleep(time_to_wait_ms * 1000)
: 根据lv_timer_handler()
的返回值进行智能休眠。这极大地降低了 CPU 占用率,避免了空转,对于 PC 和嵌入式设备都至关重要。
hal_init()
函数:平台抽象的核心
这个静态函数是实现跨平台的关键。它内部完全根据 USE_SDL
宏分成了两个独立的世界。
功能 | PC / SDL 平台 (#if USE_SDL ) | 嵌入式 ARM 平台 (#else ) |
---|---|---|
显示初始化 | 调用 sdl_init() | 调用 fbdev_init() |
显示缓冲区 | 创建一个较小的缓冲区 (SDL_HOR_RES * 100 ) | 创建两个全尺寸的缓冲区 (800*480 ) 以支持双缓冲 |
显示刷新回调 | 注册 sdl_display_flush 函数 | 注册 fbdev_flush 函数,将像素数据写入 /dev/fb0 |
输入设备 | 调用 evdev_init() | |
指针设备 | 注册 sdl_mouse_read 作为鼠标输入 | 注册 evdev_read 作为触摸屏或鼠标输入 |
其他输入 | 额外注册了键盘 (sdl_keyboard_read ) 和鼠标滚轮 (sdl_mousewheel_read ) | 通常只有一个 evdev 设备 |
custom_tick_get()
函数:嵌入式平台的“时钟”
PC / SDL 平台 | 嵌入式 ARM 平台 | |
---|---|---|
Tick 来源 | SDL 库内部提供了 SDL_GetTicks() ,它会自动为 LVGL 提供时间流逝信息。 | Linux Framebuffer 本身不提供 Tick 功能。 |
实现方式 | 无需额外代码。 | 当 CMakeLists.txt 检测到交叉编译时,它会自动在 lv_conf.h 中启用 #define LV_TICK_CUSTOM 1 。这会告诉 LVGL 去调用一个名为 custom_tick_get() 的函数来获取时间。main.c 中的这个函数使用 gettimeofday() 系统调用来计算从程序启动开始经过的毫秒数,从而为 LVGL 提供了必需的“时钟”。 |
通过这种精巧的设计,main.c
成功地将平台相关的底层代码与平台无关的上层应用代码(UI 和业务逻辑)清晰地分离开来,是整个项目可移植性的基石。