骑行码表项目:项目架构与面试总览
骑行码表项目:项目架构与面试总览
为巽1. 一句话定义
这是一个运行在 ESP32-P4 上的圆形 LVGL 骑行终端。P4 负责业务计算、SD 地图、显示、音频和协议栈;ESP32-C6 只提供 Bluetooth Controller,二者通过 ESP-Hosted SDIO 交换 HCI。设备侧把 BLE 路线、手机定位、GNSS、传感器和 DEMO 数据汇聚到统一运动模型,再由 UI、历史记录和主动播报分别消费快照。
2. 分层边界
1 | 板级层 components/board_peripherals |
底层任务不能调用 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 | 手机规划 GCJ-02 polyline |
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.c、bt/app_ble_gatt.c、bt/app_ble_proto.c - 完整路线:
bt/app_ble_route.c、map/map_route.c - 地图:
map/map_store.c - Wi-Fi/Xiaozhi:
main/app_net/app_wifi.c、main/ai_voice/app_ai_voice.c
1 |



