转向执行器滞后核算
这一页解决什么问题
上层规划给出的转角是"瞬间到位"的理想值,真实的转向执行器不是:
- 电机、减速器、液压助力都有惯性,打一把方向要 0.3~1 s 才跟上;
- 齿条与连杆之间存在间隙和虚位,指令只差一点点时执行器根本不动;
- 电机有转速上限,大角度指令会被"限速";
- 回正往往靠回位弹簧或独立回正电机,比打角慢。
判断"这条转向曲线行不行"不能靠假定,要把它算出来。本页对应的
src/ground/steering_actuator_lag 包就是做这件事的:给执行器的物理特性,
算上升时间、稳态误差、角速度上限与回正时间,并给出 PASS / FAIL 结论。
纯 Python 标准库实现,不需要 CARLA、不需要 GPU、不需要显示器。
模型
每一帧先算误差,再按一阶惯性 + 死区 + 速率限制推进:
误差 e = command − angle
死区 |e| < dead_band → 不动
期望速率 v = e / τ
限速 v = clamp(v, ±cap) cap = center_rate(回正段)或 max_rate(打角段)
推进 angle += v · dt,angle = clamp(angle, ±max_angle)
指令在 [0, dwell) 内是阶跃幅值,之后归零(相当于松开方向盘回正)。
一阶惯性意味着没有超调,这是本包实测 超调 0.000 度 的原因。
四条断言
| 断言 | 判据 | 默认阈值 |
|---|---|---|
| 上升时间 | 角度到阶跃的 90% 所需时间 | ≤ 1.00 s |
| 稳态误差 | 保持段末端的 \|command − angle\| |
≤ 0.50 度 |
| 速率上限 | 全程实测最大角速度 | ≤ max(打角, 回正) 上限 |
| 回正时间 | 指令归零后回到 0.50 度以内所需时间 | ≤ 1.50 s |
四条断言各自独立:三个反例分别只翻其中一条,说明它们没有被同一个量牵着走。
参数
| 参数 | 默认 | 说明 |
|---|---|---|
actuator/time_constant |
0.25 | 一阶惯性时间常数 (s) |
actuator/dead_band_deg |
0.30 | 死区半宽 (度) |
actuator/max_rate_deg |
60.0 | 打角速率上限 (度/s) |
actuator/center_rate_deg |
40.0 | 回正速率上限 (度/s) |
actuator/max_angle_deg |
35.0 | 机械最大转角 (度) |
test/step_deg |
20.0 | 阶跃指令幅值 (度) |
test/dwell_seconds |
5.0 | 阶跃保持时长 (s) |
test/steady_tolerance_deg |
0.50 | 稳态误差容差 (度) |
test/rise_limit_seconds |
1.00 | 上升时间上限 (s) |
test/center_limit_seconds |
1.50 | 回正时间上限 (s) |
test/center_tolerance_deg |
0.50 | 回正判定容差 (度) |
sim/step |
0.01 | 积分步长 (s) |
rate/publish_hz |
10.0 | 保活频率 (Hz) |
全部参数在 config/steering_actuator_lag.yaml 里有默认值,launch/main.launch
把它们逐个暴露成同名 <arg>,所以命令行可以覆盖:
roslaunch steering_actuator_lag main.launch time_constant:=1.0 dead_band_deg:=2.0
话题
| 话题 | 类型 | 说明 |
|---|---|---|
/steering/report |
std_msgs/String |
完整报告(latched) |
/steering/curve |
std_msgs/String |
指令/实际角度的字符曲线(latched) |
/steering/rise_time |
std_msgs/Float64 |
上升时间;未达到 90% 时发 −1.0(latched) |
节点算完一次就常驻(按 rate/publish_hz 保活),Ctrl+C 退出,不需要额外的定时器。
运行与实际输出
roslaunch steering_actuator_lag main.launch
Ubuntu 18.04 + ROS Melodic 实测:
转向执行器节点已启动:时间常数 0.25 s,死区 ±0.30 度,打角上限 60.0 度/s,回正上限 40.0 度/s
工况:阶跃 20.00 度,保持 5.00 s,时间常数 0.25 s
死区:±0.30 度,打角上限 60.0 度/s,回正上限 40.0 度/s
---
到 90% 用时:0.580 s,超调 0.000 度
稳态角度:19.310 度,稳态误差 0.290 度
最大角速度:60.00 度/s
回正:归零后 0.970 s 回到容差内,最终 0.007 度
断言:4 通过 / 0 失败(共 4 条)
[PASS] 上升时间 到 90% 用时 0.580 s,上限 1.00 s
[PASS] 稳态误差 稳态误差 0.290 度(死区 0.30 度),容差 0.50 度
[PASS] 速率上限 最大角速度 60.00 度/s,上限 60.00 度/s(打角 60.0、回正 40.0)
[PASS] 回正时间 回正到 0.50 度以内用时 0.970 s,上限 1.50 s
结论:PASSED
稳态角度是 19.31 而非 20.00,差值 0.29 度正是死区的宽度 —— 误差小到 0.30 度以内 执行器就不再动作。这是真实系统里最容易忽略、也最容易被误判成"标定不准"的现象。
三个反例
| 参数 | 结果 | 说明 |
|---|---|---|
time_constant:=1.0 |
2 通过 / 2 失败 |
上升 2.30 s(超 1.00)、回正 3.65 s(超 1.50) |
dead_band_deg:=2.0 |
3 通过 / 1 失败 |
稳态误差 1.98 度(超 0.50) |
center_rate_deg:=5.0 |
3 通过 / 1 失败 |
回正段在保活时间内没回到 0.50 度以内 |
离线测试
python3 tests/test_steering_actuator_lag_local.py
不需要 ROS,兼容 Python 2.7 / 3.x。也可以用 pytest:
python3 -m pytest tests/test_steering_actuator_lag_local.py -q
每个分组都是真断言,失败会抛 AssertionError,不会出现"pytest 全绿但脚本非零退出"的假通过。
与已有模块的分工
| 模块 | 算的是 |
|---|---|
twist_to_control |
Twist 速度指令 → 转角与轮速(几何换算) |
turning_sweep_check |
转弯时车身的横向包络(空间) |
trailer_kinematics |
牵引车-挂车的折刀与稳定性(双状态量) |
reverse_parking_planner |
倒车入库的轨迹几何 |
| 本页对应模块 | 执行器的时间响应:滞后、死区、回正 |
前者回答"方向盘该打多少度",本包回答"这 20 度要多久才能到位、松手多久能回正"。
常见问题
为什么稳态角度不等于阶跃幅值? 死区。误差进入 ±dead_band 后执行器不再动作,
这是齿条间隙/连杆虚位的数学表达。想让稳态更准,就调小 actuator/dead_band_deg。
max_rate_deg 调小以后,为什么速率上限仍能到 40 度/s? 因为回正段用的是
center_rate_deg,而"速率上限"断言取两者较大值。要整体限速需同时调小两个。
为什么上升时间断言用 90% 而不是 100%? 一阶惯性理论上永远到不了目标值 (指数逼近),工程上统一用 90% 作为上升时间定义。
Melodic 上运行要注意什么? 发给 rospy 的日志消息必须是 byte str。本包所有日志
都经过 _log(),在 Python 2.7 下先把 unicode 编码成 utf-8 再交给 rospy,否则
rosgraph 的格式化会对 str(unicode) 抛 UnicodeEncodeError。
ROS 参数为什么会被当成字符串? rosparam 读出来都是 str。本包在
ActuatorParams.__init__ 里统一经 _as_float 转换,避免 Python 2.7 下
max(1.0, "10.0") 直接 TypeError。
所使用具体的大模型声明:使用大模型辅助代码编写、测试编写与文档整理,提交内容由本人负责。