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 文件中的,实现了完全的自动化。

核心职责

  1. 平台硬件抽象层 (HAL) 初始化: 根据编译目标,选择性地初始化显示驱动(SDL 或 Framebuffer)和输入驱动(SDL Mouse/Keyboard 或 evdev)。
  2. LVGL 核心库初始化: 调用 lv_init() 启动 LVGL 引擎。
  3. 应用程序初始化: 创建 UI (ui_init()),并启动业务逻辑相关的模块,如开机自检、时间显示、UI 状态更新定时器等。
  4. 主事件循环: 运行一个 while(1) 循环,在其中周期性地调用 LVGL 的任务处理器,并处理来自控制台的输入。

代码执行流程详解

  1. Includes 阶段:

    • 代码首先根据 lv_drv_conf.hUSE_SDL 宏的值,决定包含哪一套头文件。
    • 如果 USE_SDL1,则包含 sdl.h
    • 如果 USE_SDL0,则包含 fbdev.hevdev.h,并声明 custom_tick_get() 函数。
  2. 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 上。
  3. 主循环 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 和业务逻辑)清晰地分离开来,是整个项目可移植性的基石。