骑行码表项目:项目架构与面试总览

1. 一句话定义

这是一个运行在 ESP32-P4 上的圆形 LVGL 骑行终端。P4 负责业务计算、SD 地图、显示、音频和协议栈;ESP32-C6 只提供 Bluetooth Controller,二者通过 ESP-Hosted SDIO 交换 HCI。设备侧把 BLE 路线、手机定位、GNSS、传感器和 DEMO 数据汇聚到统一运动模型,再由 UI、历史记录和主动播报分别消费快照。

2. 分层边界

1
2
3
4
5
6
7
8
9
10
11
12
板级层 components/board_peripherals
PMIC / LCD / Touch / SDMMC / GNSS UART / I2S 音频 / IMU
|
业务模型层 main/app_model + map
motion snapshot / weather / history / system time
map_store SD 数据 + map_route 路线投影
|
协议接入层 bt + app_net + ai_voice
NimBLE GATT / BLE route / Wi-Fi STA / Xiaozhi WebSocket
|
视图层 main/ui_app
ui_page_manager / 页面 / draw callback / 低频 timer

底层任务不能调用 lv_...。UI 只在 LVGL 线程内读取快照并更新控件。跨任务传递使用 FreeRTOS queue、mutex、event group 或“带 generation 的快照”。

3. 启动顺序及原因

main/app_main() 的顺序是:NVS -> board_peripherals_init() -> 运动模型/天气任务 -> LCD -> LVGL adapter -> 输入设备 -> UI -> 按键 -> BLE -> 手动 Xiaozhi controller -> 延迟挂载 SD。

先初始化 NVS 是因为 Wi-Fi profile、天气、骑行历史都依赖 NVS。板级初始化必须早于 UI,确保电源、显示和音频设备状态可用。LVGL 启动后才允许创建页面。BLE 在 UI 之后启动,失败只记录 warning,不阻塞骑行 UI。SD 卡挂载被放到延迟任务,避免启动阶段和 ESP-Hosted/显示 DMA 同时争用内部 SRAM。

LVGL adapter 任务 priority=24、core=1、最大延迟 5 ms、栈在 PSRAM;这个优先级高于 TCP/IP 线程,目的不是让业务跑得更快,而是避免联网时 LVGL worker 被饿死。

4. 数据流:从手机路线到导航

1
2
3
4
5
6
7
8
9
10
11
手机规划 GCJ-02 polyline
-> Command Characteristic 0x22 BEGIN
-> 0x23 按 offset 分块
-> 0x24 COMMIT + CRC32
-> ble_route task 形成 LOADED 快照
-> map_route task 复制点列
-> GCJ-02 迭代反算为 WGS84
-> map_store 读取中心 3x3 个 2 km 网格
-> 等距投影为 dm 世界坐标
-> 累计距离/高程/包围盒
-> UI 复制自己的 PSRAM 几何缓存并绘制

PHONE 定位走另一条链:0x30 在 GATT 回调中只做协议解析,然后入队;phone_location_task 做精度/时间/跳点过滤,调用运动模型更新距离,并由 map_route_project_gcj02() 投影到路线。路线状态和定位来源是两个独立状态机。

5. 面试中需要主动说清楚的取舍

为什么大数据放 PSRAM,音频小块放内部 RAM

地图网格、路线点列、历史图表和 Wi-Fi 扫描记录体积不可预测,使用 heap_caps_malloc(..., MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT),避免挤占 DMA 所需的内部 SRAM。SDMMC、I2S 和 OPUS 热路径要求可寻址且对齐的内部 8-bit 缓冲,所以音频 raw/PCM/OPUS packet 和 MAX98357A 写入缓冲使用 MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT

为什么不在 LVGL 绘制回调里访问 SD 卡

SD 读是阻塞 IO,且绘制回调运行在 UI 关键路径;把它放到 map_store task 后,UI 只看到旧快照或新快照,避免卡顿和并发破坏。

为什么有 generation

地图、路线、运动快照都可能在不同任务更新。generation 让 UI 判断“缓存是否仍对应当前源数据”,避免路线替换后旧屏幕点集继续被使用。generation 回绕到 0 时立即改为 1,避免 0 被当作“未初始化”。

6. 当前边界

当前没有 OTA、地图瓦片 BLE 传输、离线唤醒词、ESP-SR、MCP 设备控制工具。Xiaozhi 强制选 WebSocket,MQTT 配置即使返回也不使用。Live Metrics 只包含时间、距离、当前/平均速度和保留的电池字段;完整历史记录通过独立 Ride Record characteristic 同步。

7. 代码入口

  • 启动:main/main.c
  • 页面路由:main/ui_app/core/ui_page_manager.c
  • 统一数据:main/app_model/app_motion_model.c
  • BLE:bt/app_ble.cbt/app_ble_gatt.cbt/app_ble_proto.c
  • 完整路线:bt/app_ble_route.cmap/map_route.c
  • 地图:map/map_store.c
  • Wi-Fi/Xiaozhi:main/app_net/app_wifi.cmain/ai_voice/app_ai_voice.c
1