结论:common/ 里的时间常量、滤波器、PID、系统统计与 I2C 只是被复用的基础函数,它们不是独立进程,也不能单独证明端到端实时性。DT_CTRL 等定义的是名义时间步长,不是操作系统保障的周期;monitor_time() 只记录进度与超期,keep_time() 才在剩余时间为正时睡眠。因此「没有 lag 日志」并不能理解为满足控制时限。
一、时间常量、优先级与调度
| 原语 | 具体行为 | 调用边界 |
|---|---|---|
| 时间常量 | DT_CTRL 等定义 100/20/2/20 Hz 的名义时间步长。 |
控制器、模型、hardwared 和驾驶员监测用作状态更新或滤波参数;不是操作系统保障的周期。 |
Priority |
定义控制进程两级 FIFO 数值。 | controlsd、card 等选择 CPU/优先级;数值只对当前平台布局有意义。 |
config_realtime_process() |
禁用 GC;非 PC Linux 调用 SCHED_FIFO 并设 CPU 亲和性。 |
modeld、controlsd、selfdrived、card 等入口;PC 上仍禁用 GC,但跳过内核调度设置。 |
drop_realtime() / set_core_affinity() |
恢复 SCHED_OTHER;单独改 CPU 亲和性。 |
UI 部分操作、Athena、上传器等避免抢占高优先级控制任务。 |
Ratekeeper.monitor_time() / keep_time() |
前者用单调时间累积下一帧截止点、记录移动平均和超期;后者仅在剩余时间为正时睡眠。 | card、controlsd、selfdrived 调用 monitor_time(),不调用 keep_time();轮询传感器调用 keep_time()(sensord.py)。 |
MovingAverage.add_value()/get_average() 保留 100 个间隔的移动平均;Ratekeeper.lagging 在均值超过名义间隔约 11.1% 时为真——该布尔值不等于最坏延迟或丢消息率。
二、滤波与 PID
| 原语 | 关键公式 / 行为 | 使用注意 |
|---|---|---|
FirstOrderFilter.update() |
使用 alpha=dt/(rc+dt);initialized=False 时直接接受首样本。 |
调用方传固定 dt,无自动适配实际帧间隔;hardwared 温度链采用 initialized=False。 |
BounceFilter.update() |
在一阶位置滤波上加速度/阻尼状态,并按 60 FPS 基准缩放。 | 主要用于按钮、滚动、键盘等界面动画;不应误用作车辆动态滤波。 |
PIDController.update() |
按速度插值 P/I/D 增益,积分项按 rate 固定步长推进并做抗积分饱和,最后限幅。 |
横向/纵向控制和风扇;实际调用周期偏离配置速率时,积分动态会变化。 |
LinuxSystemStats |
cpu_usage_percent()/memory_usage_percent() 读 /proc/stat 与 /proc/meminfo。 |
CPU 新出现或计数回退时报告 0;缺字段会抛异常;它是遥测输入,不能代替控制循环的 99.9 分位时延。 |
三、运行时约束与故障边界
原生线程侧还有一组 C++ 入口:set_realtime_priority() 与 set_core_affinity() 对当前 Linux TID 设置 FIFO/CPU,非 Linux 返回 -1。源文档给出两种态度:pandad 在非 PC 上要求设置成功(使用断言),而 camerad 只设亲和性、驻车时允许失败。这也说明「能否设置实时优先级」是平台能力问题,普通插件不应默认拥有该权限。
Ratekeeper 的 _next_frame_time 按固定间隔递增,循环落后时不会跳帧重设截止点;连续超期可能积累负的 remaining。当 print_delay_threshold=None 时,它不会打印超期,也不会让 monitor_time() 返回 True,但仍计算 remaining 和窗口均值——所以不能把「无 lag 日志」理解为满足控制时限。config_realtime_process() 的内核调用失败会抛异常;C++ 侧对 pandad 使用断言要求成功,而 camerad 只设亲和性、驻车时允许失败。
源文档的六项原函数/假时钟与调度探针给出一个直观例子:10 Hz 下首次正余量约 0.1 秒,后续某轮 0.25 秒的迟到仍让下一截止点只加 0.1 秒,连续轮次继续报迟到;keep_time() 只为正余量调用睡眠。config_realtime_process() 先禁用 GC,再尝试 FIFO,最后设置 CPU 亲和性——若 FIFO 调用抛错,GC 已禁用而亲和性尚未设置。探针不使用真实内核优先级或时钟,因此不证明车端尾延迟。
FirstOrderFilter 默认把 x0 当有效初值,固定 dt 意味着跳帧/超时后仍按一次名义更新,不会自动按真实经过时间补偿。PIDController 的积分步长也在构造时固定为 1/rate,所以硬件移植和插件执行线程必须记录实际间隔,再用回放/故障注入验证误差与饱和恢复。SMBus.read_i2c_block_data() 在发 ioctl 前限制请求为 0–32 字节,回读长度上限为请求长度;底层没有统一重试、设备健康位或锁,共享总线上的驱动冲突与电气隔离由上层/平台负责。
四、插件化落点与验证
新增可运行插件应在 manifest 中声明输入频率、最大处理时长、消息最大年龄、CPU/内存上限和故障处理;受信任调度器保留唯一控制权。SCHED_FIFO/亲和性仅供经过基线测试的核心进程申请,普通插件默认低优先级、进程隔离。传感器插件必须明确总线地址、单位、时间基准、错误/重试语义,并在断连时发布无效/超时状态,不能把 0 遥测值当作健康证据。
现有 common/tests/ 未找到专门覆盖这几个调度/滤波/I2C 原语的测试文件;本篇已对 Python 调度分支做隔离探针,未测真实内核调度权限、总线时序或车辆级截止时间。建议先在台架采集控制和传感器消息年龄、循环延迟分布、CPU 热限流与故障恢复,再考虑调整优先级或将插件接入热路径。若确实要把这类原语接入热路径,应记录实际调用间隔并据此校准滤波与 PID 的 dt,而不是沿用名义值。
常见问题
标称 100 Hz 是不是操作系统保障的周期?
不是。DT_CTRL 等只是名义时间步长。控制类循环调用 monitor_time() 而非 keep_time(),受输入/工作负载驱动;源文档明确 100 Hz 是期望节拍,而非主动节流,更不是操作系统保障。
没有 lag 日志是否说明满足控制时限?
不能说明。当 print_delay_threshold=None 时既不打印超期,也不让 monitor_time() 返回 True,但仍计算 remaining 和窗口均值。因此「无 lag 日志」不能理解为满足控制时限。
FirstOrderFilter 会在跳帧后按真实时间补偿吗?
不会。调用方传固定 dt,alpha=dt/(rc+dt),跳帧/超时后仍按一次名义更新,不会自动按真实经过时间补偿。同理 PIDController 的积分步长在构造时固定为 1/rate。
I2C 一次最多能读多少字节?有统一重试吗?
SMBus.read_i2c_block_data() 在发 ioctl 前限制请求为 0–32 字节,回读长度上限为请求长度;SMBus 打开 /dev/i2c-N 后经 ioctl 读写,force=True 选 I2C_SLAVE_FORCE。底层没有统一重试、设备健康位或锁,错误透传给调用方。
延伸阅读
- openpilot 的 100 Hz 控制回路:开源 ADAS 能给车载设备什么 —— 承接时间常量与循环节拍的整体语境。
- 控制算法:PID 与控制回路的分层 —— 展开
PIDController的控制器语义。 - 纵向规划 MPC:目标速度如何被裁剪与跟踪 —— 说明滤波/限幅在纵向链上的落点。
- 监控与定位 —— 承接系统统计与消息年龄观测的实践。
- 热区与启动/退出契约 —— 承接 CPU 热限流与设备状态的关系。
参考资料
- 项目 02 ——
openpilot-research/COMMON_RUNTIME_PRIMITIVES_DEEP_DIVE.md(取用了时间常量与调度、滤波与 PID、系统统计与 I2C 的函数级契约及插件化要求)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复