一、dt 的意义与限幅
1.1 为什么飞控算法强依赖 dt
飞控算法是连续时间模型的离散实现,几乎所有关键计算都依赖两次执行之间的时间间隔 dt(单位秒):
| 算法环节 |
dt 的作用 |
| 积分(I 项) |
integral += error * dt,dt 大则积分快 |
| 微分(D 项) |
derivative = (error - last_error) / dt,dt 小则放大噪声 |
| 低通滤波器 |
滤波器系数 α 由 dt 与截止频率共同决定 |
| 状态传播 |
v += a * dt,x += v * dt,dt 错则状态估计发散 |
计算方式:
1 2 3
| const uint64_t now_us = hrt_absolute_time(); float dt = (now_us - _last_run_us) * 1e-6f; _last_run_us = now_us;
|
1.2 为什么必须对 dt 限幅
实际中 dt 会因 CPU 忙、队列阻塞、线程调度抖动、任务被长时间抢占而波动,出现 0.001 s、0.004 s、0.020 s 的跳变。不限幅时:
- 积分项被偶发大 dt 破坏,产生积分爆炸
- 滤波器有效带宽被改变,响应不一致
- 状态预测产生误差积累
标准做法:
1 2
| if (dt < 0.0005f) { dt = 0.0005f; } if (dt > 0.02f) { dt = 0.02f; }
|
二、HRT 的作用
HRT(High Resolution Timer)是 PX4 自建的统一微秒时基系统,提供三个接口:
1 2 3
| hrt_absolute_time() hrt_call_every(...) hrt_call_after(...)
|
所有传感器时间戳、EKF 时间对齐、控制 dt 都基于同一个单调微秒时钟,保证系统内时间域一致。1 kHz 级别任务可靠,抖动远低于 OS 调度时钟。
相关知识:uORB 通信机制(上)
三、WorkQueue 实例模板
以下是 WorkQueue 模式的完整代码模板,集成了 WorkQueue + uORB 回调 + HRT dt + 参数订阅的标准范式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| #include <px4_platform_common/module.h> #include <px4_platform_common/module_params.h> #include <px4_platform_common/log.h>
#include <uORB/SubscriptionCallbackWorkItem.hpp> #include <uORB/Publication.hpp>
#include <uORB/topics/vehicle_attitude.h> #include <uORB/topics/vehicle_rates_setpoint.h> #include <uORB/topics/parameter_update.h> #include <drivers/drv_hrt.h>
using namespace time_literals;
class UorbWqExample final : public ModuleBase<UorbWqExample> , public ModuleParams , public px4::WorkItem { public: UorbWqExample() : ModuleParams(nullptr), WorkItem(MODULE_NAME, px4::wq_configurations::hp_default) {}
~UorbWqExample() override { _att_sub.unregisterCallback(); }
static int task_spawn(int argc, char *argv[]) { UorbWqExample *inst = new UorbWqExample(); if (!inst) { return -1; }
_object.store(inst); _task_id = task_id_is_work_queue;
if (inst->init()) { return 0; }
delete inst; _object.store(nullptr); _task_id = -1; return -1; }
static UorbWqExample *instantiate(int argc, char *argv[]) { return nullptr; }
bool init() { if (!_att_sub.registerCallback()) { PX4_ERR("attitude callback registration failed"); return false; } ScheduleDelayed(10_ms); _last_run_us = hrt_absolute_time(); return true; }
void Run() override { if (should_exit()) { _att_sub.unregisterCallback(); exit_and_cleanup(); return; }
if (_param_update_sub.updated()) { parameter_update_s p{}; _param_update_sub.copy(&p); updateParams(); }
const uint64_t now_us = hrt_absolute_time(); float dt = (now_us - _last_run_us) * 1e-6f; _last_run_us = now_us; if (dt < 0.0005f) { dt = 0.0005f; } if (dt > 0.02f) { dt = 0.02f; }
vehicle_attitude_s att{}; if (_att_sub.update(&att)) {
if (_param_enable.get() == 0) { return; }
vehicle_rates_setpoint_s rates_sp{}; rates_sp.timestamp = now_us; rates_sp.roll = _param_p.get() * 0.0f; rates_sp.pitch = _param_p.get() * 0.0f; rates_sp.yaw = _param_p.get() * 0.0f;
_rates_sp_pub.publish(rates_sp); }
ScheduleDelayed(10_ms); }
int print_status() override { PX4_INFO("running"); PX4_INFO("enable=%d p=%.3f", _param_enable.get(), (double)_param_p.get()); return 0; }
private: uORB::SubscriptionCallbackWorkItem _att_sub{this, ORB_ID(vehicle_attitude)}; uORB::Subscription _param_update_sub{ORB_ID(parameter_update)}; uORB::Publication<vehicle_rates_setpoint_s> _rates_sp_pub{ORB_ID(vehicle_rates_setpoint)}; uint64_t _last_run_us{0};
DEFINE_PARAMETERS( (ParamInt<px4::params::SYS_AUTOSTART>) _param_enable, (ParamFloat<px4::params::MPC_XY_P>) _param_p ) };
extern "C" __EXPORT int uorb_wq_example_main(int argc, char *argv[]) { return UorbWqExample::main(argc, argv); }
|
四、WorkQueue 模式关键约束
| 约束 |
原因 |
Run() 内禁止 px4_poll |
WorkQueue 线程是共享线程,阻塞会卡住整个队列 |
| Run() 内禁止阻塞 I/O |
同上 |
| 不要打印高频日志 |
串口/文件 I/O 有延迟,高频调用会导致 WorkQueue 积压 |
| 回调 + ScheduleDelayed 同时存在时 |
频率由兜底周期决定,两者会叠加触发 |
五、三种订阅方式的选型建议
| 方式 |
类型 |
适用场景 |
uORB::Subscription::update() |
轮询式 |
简单低频模块,独立线程循环 |
px4_poll() + orb_copy() |
阻塞式 |
独立线程,等待多个 topic 中任意一个更新 |
SubscriptionCallbackWorkItem |
回调式 |
WorkQueue 模式,高频控制算法(姿态、位置环) |
上一篇:uORB 通信机制(上)
下一篇:DShot 电机转向修改