RTOS 任务、并发与内存模型深挖

1. 任务清单

任务 优先级/栈 触发或周期 主要职责
LVGL adapter 24,PSRAM 最长延迟 5 ms 唯一 UI 线程
app_button 5/4 KiB 队列事件 按键去抖、单击/双击/长按分发
ble_route 4/4 KiB PSRAM BLE 命令队列 路线接收、CRC、快照发布
phone_location 4/3 KiB 定位队列/超时 过滤定位、更新运动模型
map_store 4/4 KiB 地图请求 SD 读取、校验、解析、快照交换
map_route 4/4 KiB PSRAM 路线 generation 坐标转换、投影、高程预处理
app_wifi 4/6 KiB PSRAM 命令队列 扫描、连接、超时、重试、NVS
ai_voice 4/8 KiB PSRAM event group Xiaozhi 生命周期
ai_audio 4/12 KiB OPUS 队列 下行 OPUS 解码和 I2S 播放
ai_mic 4/32 KiB PSRAM I2S 连续采样 PCM 静音门、OPUS 编码、上传
broadcast detect/play 3/4-6 KiB 1 s/队列 主动播报事件和 PCM 播放
motion/demo/history/weather 3-4 各自队列或 500 ms 中央模型、模拟、持久化
board_gnss/attitude 5/Kconfig UART/I2C 传感器采集与融合

优先级数字越大越优先。LVGL=24、ESP-Hosted transport 约 23、TCP/IP 约 18,业务 task 维持在 3~5,避免音频或 SD 任务压住 UI/网络底层。

2. 事件驱动而不是 UI 阻塞

BLE GATT 回调只解析包并入队;Wi-Fi event handler 只把事件转换为命令;地图和路线 task 做文件/浮点计算;音频回调只复制包并入 OPUS 队列。任何需要等待、读 SD、解码或 NVS commit 的动作都不在 ISR、GATT callback、LVGL callback 中执行。

3. 快照一致性

运动模型维护 app_motion_snapshot_t,读写由模型内部同步保护。快照带 versionsource_timestamp_msvalid_flags。UI 每秒读取一次,按 valid flag 更新字段;没有有效心率/功率/踏频时显式显示离线,而不是保留上一场骑行的旧值。

地图/路线使用 mutex 或临界区保护指针交换。读侧先复制结构体和 generation,再复制到页面私有缓存;如果 source generation 变化,页面丢弃本轮缓存并重建。

4. 定位与运动状态机

定位模式枚举为 AUTO/DEMO/PHONE/GNSS,来源为 NONE/DEMO/PHONE/GNSS。模式选择不改变路线快照;路线 LOADED 也不自动切 DEMO。运动状态为 IDLE/RUNNING/PAUSED/FINISHED,仅 RUNNING 时累计距离、运动时间、爬升下降。

PHONE 任务拒绝精度大于 50 m、时间戳倒退、异常跳点;超过 3 s 没有新包就标记 timeout,断 BLE 后不跨断连区间累计距离。DEMO 每 500 ms 更新,以当前完整路线总长度作为模拟终点。

5. 队列满和背压

BLE PHONE 队列长度 4,满时丢最旧样本,保证最新位置优先。路线命令队列长度 8,写入失败返回 ESP_ERR_TIMEOUT,小程序依据 Route Status 重试。OPUS 下行队列深度 12,满时释放最旧包后接收新包,不无限增大。

主动播报 P0/P1 队列深度均 4:P0 转向/到达可打断 P1 和 Xiaozhi TTS;P1 里程/时间/速度变化在 TTS 时等待,满了丢新事件并记录 warning。

6. 内存策略

PSRAM

地图 cell、路线接收缓冲(最大 24576 B)、路线点、累计距离、高程数组、Wi-Fi AP 记录、历史明细、AI voice 主任务栈和本地 PCM 读取缓冲使用 PSRAM。申请失败必须发布错误状态或跳过可选功能。

内部 SRAM

SDMMC DMA、I2S raw/PCM/OPUS packet、libopus decoder/encoder 需要内部 8-bit。禁止用普通 malloc 在 UI 初始化阶段申请大块缓冲,也不通过无上限增大 task stack 解决溢出。

7. 典型并发风险及现有处理

  • Wi-Fi GOT_IP 不在事件回调里启动 TLS,而是投递给 ai_voice,并分段等待 1 s/1 s/0.5 s。
  • Xiaozhi 断线不依赖 websocket 内部 5 s 重连,应用层释放 chat/MCP,延迟 3 s 重建。
  • 本地播报开始时停止 TTS 下行、暂停麦克风上传;结束后按会话状态恢复。
  • 地图新快照发布前保留旧页面几何,避免 SD 慢读导致 UI 空白。
  • 页面退出时删除 timer、注销按键 handler、删除 LVGL root 和释放 PSRAM 缓存。

8. 面试回答模板

当被问“为什么这么多任务”时,回答重点是阻塞边界而非任务数量:SD、网络、音频编码、BLE 解析都有不同实时性和阻塞特征,拆分后用小消息在任务间传递;UI 只消费快照,保证最长路径可控。被问“如何证明没有竞态”时,指出所有共享对象都有 mutex/临界区/generation,callback 不持有 UI 指针,页面只拥有自己的缓存。