资讯详情 小型无人机飞控分层设计:从坐标系、配平到逐次闭环
📅 2026/11/3 15:32:47
简介《Small Unmanned Aircraft: Theory and Practice》英文原版教材PDF面向无人机、自动控制与机器人方向的高年级本科生、研究生及工程技术人员系统梳理小型无人机从理论建模到飞行控制的完整知识框架。压缩包内仅含1个PDF文件大小约4.89MB正文围绕系统架构、坐标系与旋转矩阵、运动学与刚体动力学、力与力矩、线性设计模型展开并延伸到逐次闭环回路的自动驾驶仪设计、机载传感器、状态估计与GPS平滑、制导设计等关键环节。各章配有设计项目读者可据此把建模、控制与估计方法落到具体无人机方案亦可对照公式与实例理解小型无人机的稳定性分析思路。原书由普林斯顿大学出版社出版并附参考文献与索引已有309人学习或下载适合作为课程教材、科研入门参考及工程实践查阅资料。1. 小型无人机的理论与实践之间隔着的是接口而不是公式小型无人机的理论与实践之间隔着的不是公式而是一层层的接口。资料开篇就摆出一个 12 状态的六自由度非线性模型看起来像全篇的主角但真正落到飞控代码里的是一条被切成五六层的控制链每一层只看得见自己那一小段状态也只被允许输出一个有界的指令。这个分工不是工程习惯是被带宽、饱和与噪声三件事逼出来的传感器给不了姿态只给角速率舵机接不了大指令只能在小角度内线性工作气动模型只在配平点附近才敢用。搞懂这条链为什么这么分层、层与层之间靠什么信号握手比记住任何一个 PID 参数都值——换一架机体、换一套传感器参数全变分层的理由不变。2. 坐标系与风三角为什么速度在这套系统里有四个名字一架固定翼在天上飞同一个速度会被不同模块叫成四个名字空速、地速、风速、航迹速度。新手最容易在这里翻车因为它们在数学上是三个不同的矢量在代码里却常常共用一个变量名。2.1 三个坐标系与旋转矩阵的传递链整条控制链里有三套语言同时在说话。惯性系负责任务——盘旋某个坐标、飞到某个航点这些指令全是在地面坐标系下表达的机体系负责传感器与舵面——陀螺测的是绕机体三轴的角速率副翼产生的是绕机体纵轴的力矩气流系负责气动——升力垂直于空速方向阻力平行于空速方向这两个定义只有在气流系里才是干净的。坐标系原点轴向约定谁在这个系里说话惯性系地面某点北-东-地航点、盘旋中心、任务指令机体系重心前-右-下陀螺、加速度计、舵面力矩气流系重心沿空速、垂直空速升力、阻力、迎角、侧滑角把机体坐标表示成一个矢量要转到惯性系需要乘一个旋转矩阵。这个矩阵通常按偏航-俯仰-滚转的顺序连乘也就是先绕竖直轴转航向再绕新的横轴转俯仰最后绕新的纵轴转滚转。顺序不能换换了得到的姿态完全不同这也是无数仿真里飞得好好的装机就翻事故的第一嫌疑人。旋转矩阵还有一个常被忽略的性质它是正交的逆等于转置。这一点在实时系统里很实在——从惯性系往机体系转不需要求逆一次转置就够在几十赫兹的控制循环里省下的不只是算力还有数值误差累积。2.2 风三角空速、风速、地速为什么不能直接相加空速管测的是动压换算出来的是飞机相对于空气的速度大小GPS 测的是飞机相对于地面的速度矢量。两者之间的差值就是风。写成矢量关系就是地速等于空速加风速但注意这是一个矢量加法而且必须统一在同一个坐标系里做——因为空速管只给大小方向要另外从迎角和侧滑角推出来。这里有一个很反直觉的推论单靠一个时刻的测量你解不出风。飞机匀速直线平飞时风的存在会被空速的变化吸收掉观测上无法区分空气在动和飞机在动。要估计风必须做机动——盘旋一圈或者飞一段 S 形让空速矢量在惯性系里的方向发生变化风矢量才会在数据里留下可辨识的痕迹。资料的 GPS 平滑部分正是干这件事把一段机动里的地速与空速序列拿来做拟合平滑出慢变的风估计再反过来修正空速与航迹的偏差。import numpy as np def wind_triangle(ground_vel, airspeed, heading): ground_vel: GPS 给的地速矢量 (north, east)单位 m/s airspeed: 空速管测到的空速大小标量单位 m/s heading: 机头指向弧度仅在没有侧滑时才能当作空速方向 返回: 风矢量 (north, east)单位 m/s # 无侧滑假设下空速矢量沿机头方向有侧滑时必须用气流角修正 air_vec airspeed * np.array([np.cos(heading), np.sin(heading)]) # 矢量相减不是大小相减 return np.array(ground_vel) - air_vec这段代码只有两行有效逻辑但踩过的坑不少。第一传入的 heading 必须是真航向而不是磁航向磁偏角没修正的话风估计会系统性偏几度。第二这个解算只在无侧滑时成立转弯时侧滑角不为零直接套用会把侧滑误判成侧风。第三风是慢变量单点解算出来的结果噪声极大实际系统里必须做低通或者放进卡尔曼滤波的状态里一起估计。2.3 在旋转坐标系里求导为什么会多出一项这是整本书里最容易被跳过去、又最不能跳的一步。一个矢量在惯性系下的变化率等于它在机体系下的变化率加上角速度与它本身的叉乘。多出来的这一项不是数学技巧是物理事实即使飞机在机体系里没有加速只要它在转惯性系看它就是有加速度的。这一项直接解释了为什么力矩方程里会出现 p、q、r 的乘积项。滚转角速率 p 和偏航角速率 r 同时不为零时会产生所谓的交叉耦合——飞机在盘旋时会自己抬头或者低头这不是气动问题是刚体运动学。如果不把这一项写进模型仿真里的盘旋会是一条完美的水平圆真机却会缓慢爬升或者掉高度而且越盘越明显。角速率的积分也有坑。用欧拉角做积分时俯仰角接近 ±90 度会出现除零发散import numpy as np def euler_rate(phi, theta, p, q, r): 由机体角速率求欧拉角速率theta 接近 ±90° 时 1/cos(theta) 发散 s, c np.sin(phi), np.cos(phi) t np.tan(theta) phi_dot p (q * s r * c) * t theta_dot q * c - r * s psi_dot (q * s r * c) / np.cos(theta) return phi_dot, theta_dot, psi_dot固定翼正常盘旋时俯仰角不大这个奇异点碰不到所以很多简化实现就这么用了但一旦要做大迎角机动或者垂直爬升就必须换成四元数用四个参数加一个归一化约束来避开三角函数奇异。资料附录里专门留了一节讲四元数以及欧拉角与四元数之间的互相转换原因就在这里。注意坐标系转换里最贵的不是算力是符号约定。北东地与东北天、弧度与角度、顺时针与逆时针这三组约定只要有一处没对齐现象往往不是完全不飞而是飞得怪排查成本极高。3. 六自由度与配平12 个状态怎么变成 4 个传递函数仿真里那个看起来很唬人的六自由度模型本质上就是 12 个一阶微分方程。它的作用是扮演真实世界而不是用来设计控制器——这两件事在方法上是分开的。3.1 状态变量12 个数字各自负责什么把状态分成三组看就清楚了位置与速度是平动姿态与角速率是转动两组之间通过力和力矩耦合。状态含义谁来测谁来控北、东、地位置惯性系位置GPS制导外环u、v、w机体三轴速度空速管 迎角/侧滑估计油门、俯仰滚转、俯仰、航向欧拉角姿态加速度计 磁力计 陀螺融合副翼、升降舵、方向舵p、q、r机体角速率陀螺三个舵面的最内环这张表最有价值的一列是谁来测。你会发现几乎没有哪个状态是被直接测到的位置靠 GPS 且有延迟姿态靠多个传感器融合速度里只有空速大小是直接量。控制器的输入从头到尾都是估计值估计器的动态特性因此会直接进入闭环——这也是为什么估计器和控制器不能分开调。3.2 气动力和力矩为什么要写成无量纲系数升力的标准写法是动压乘以机翼面积再乘以升力系数而升力系数通常被展开成若干项之和零迎角项、迎角项、俯仰角速率项、升降舵项。这样写有三个好处。动压里含速度平方符合气动力随速度平方增长的物理规律系数是无量纲的同一套系数可以按相似准则迁移到不同尺寸的机体上不必为每一架飞机重新吹风每一项都对应一个明确的物理来源调试时能一眼看出是哪一项在作怪。常见的实现思路是把这个展开写成一张系数表每一行对应一个气动导数配平、线性化、仿真三处共用同一张表。这样改一个参数三处同步生效不会出现仿真用一套、线性化用另一套的分裂。3.3 配平线性化只在一点附近成立配平就是找一个状态与输入的组合让所有导数同时为零。方程数是 12 个未知数更多所以配平解是一族而不是一个。工程上的做法是固定两个量——通常是空速和高度——然后只解三个未知数迎角、升降舵、油门。import numpy as np def solve_trim(residual, x0, tol1e-9, max_iter100): residual(z) - 三个残差轴向加速度、法向加速度、俯仰角加速度 z [alpha, delta_e, delta_t]即迎角、升降舵、油门 数值雅可比 牛顿迭代和资料附录里的数值配平思路一致 z np.array(x0, dtypefloat) for _ in range(max_iter): r residual(z) if np.linalg.norm(r) tol: break J np.zeros((3, 3)) h 1e-6 for i in range(3): dz np.zeros(3) dz[i] h # 中心差分比前向差分精度高一阶代价是多算一次 J[:, i] (residual(z dz) - residual(z - dz)) / (2 * h) z z - np.linalg.solve(J, r) return z三个残差对应三个方程正好解三个未知数。迭代的初值很关键迎角给个小的正数升降舵给零油门给一个能抵消阻力的粗估值收敛一般只要五六步。如果不收敛八成是初值跑到了失速区气动系数在那里是非线性的雅可比会失真。转弯状态下配平要复杂一层盘旋时升力的竖直分量要平衡重力水平分量提供向心力所以滚转角、迎角、油门三者是联立的。资料里把这一步叫协调转弯配平它是后面所有横向控制律设计的工作点。3.4 四个模态与降阶把 12 阶模型在配平点做数值线性化得到 A、B 两个矩阵再对 A 做特征值分解会看到几组时间尺度差异极大的模态。这组时间尺度差异就是逐次闭环分层设计的全部依据。模态特征时间主要涉及状态由哪一层管滚转收敛零点几秒p滚转角速率内环短周期一到两秒迎角、俯仰角速率俯仰角速率内环荷兰滚一到三秒侧滑、滚转、偏航角速率方向舵阻尼环长周期几十秒空速、俯仰角空速与高度外环螺旋几十秒滚转角、航向航向外环短周期和长周期差了将近两个数量级这是好事意味着你可以先关掉长周期不管专心把短周期阻尼调好再回过头处理高度和空速。分层不是人为切割是模型自己长出来的结构。降阶的做法就是把不关心的模态对应的状态冻结掉得到二阶或者三阶的传递函数用频域方法设计增益。提示特征值实部为正的模态是不稳定的。螺旋模态在不少机体上就是弱不稳定或者中立稳定这也是为什么航向环必须存在——没有它飞机自己会慢慢偏出去。4. 从传感器到舵面一个闭环里到底套了几层整条链从上往下数是任务与路径规划给航点制导层把航点变成航向与高度指令自动驾驶仪把指令变成舵量估计器把传感器原始数据变成状态。每往下一层带宽就高一个档次。4.1 传感器给的是带噪的间接量传感器直接测什么想要的量缺什么加速度计比力三轴分量姿态、速度含重力投影无法分辨重力与加速度陀螺三轴角速率姿态有零偏积分后漂移静压传感器静压高度受气流与温度影响有延迟动压传感器动压空速大迎角时失真侧滑时有误差磁力计地磁分量航向受机体电流与外部磁干扰GPS位置与地速位置、地速频率低、有延迟、遮挡时丢星这张表的最后一列才是重点每个传感器都缺一块而它们缺的恰好互补。加速度计长期漂但短期准陀螺短期准但长期漂磁力计无漂但噪声大——融合的本质就是用各自的强项补对方的弱项。4.2 连续-离散卡尔曼预测用模型更新用测量飞机的动力学是连续的而 GPS、磁力计的读数是一拍一拍来的频率还不一样。所以滤波器必须写成连续-离散混合形式两次测量之间用连续模型积分测量到达的那一刻做一次离散更新。import numpy as np class ContinuousDiscreteKF: def __init__(self, A, B, C, Q, R, dt): self.A, self.B, self.C A, B, C self.Q, self.R, self.dt Q, R, dt self.x np.zeros(A.shape[0]) self.P np.eye(A.shape[0]) def propagate(self, u): # 采样间隔内用一阶保持积分状态与协方差一起往前推 self.x self.x self.dt * (self.A self.x self.B u) Ad np.eye(len(self.x)) self.dt * self.A # Q 乘 dt 保证过程噪声的量纲与协方差一致少了这一步滤波会过于自信 self.P Ad self.P Ad.T self.Q * self.dt def update(self, y): # 测量更新只在测量到达时刻执行增益由先验协方差决定 S self.C self.P self.C.T self.R K self.P self.C.T np.linalg.inv(S) self.x self.x K (y - self.C self.x) self.P (np.eye(len(self.x)) - K self.C) self.P两个参数最需要经验。Q 描述你对模型的信任程度机体参数准、风小Q 就可以给小R 描述你对测量的信任程度GPS 在开阔地可以给小进了树林就得加大。Q 乘 dt 这一步常被漏掉漏掉的后果是采样频率一改滤波器行为就全变了而你会以为是自己调参调坏了。还有一点卡尔曼滤波假设噪声是高斯白噪声而实际传感器有偏置、有色噪声、还有偶尔的野值。所以工程上通常会在更新前加一道野值检测——新息超过若干倍标准差就直接丢弃这一帧宁可少更新一次也不让一个跳点污染整个状态。4.3 逐次闭环带宽分离与饱和约束逐次闭环的核心思想只有两条。第一条是带宽分离内环必须比外环快足够多通常五到十倍这样外环设计时可以把内环近似成一个增益为一的快速环节忽略它的动态。第二条是饱和约束每一层的输出都必须限幅因为舵面有物理行程滚转角有限制过载有结构上限。第二条比第一条更容易被忽视。如果外环不限幅航向误差一大算出来的滚转指令可能是六十度飞机滚到六十度升力的竖直分量只剩一半高度掉下去高度环又把俯仰拉起来速度掉下去最后进螺旋。整条链上没有任何一个环节错但飞机就是掉了——这是典型的层间耦合事故。4.4 横向通道航向到滚转到滚转率横向通道是三层结构从慢到快依次是航向环、滚转环、滚转角速率环。import math def lateral_autopilot(chi_c, chi, phi, p, gains, limits): chi_c: 制导层给的航向指令弧度 chi: 当前航向估计弧度 phi: 当前滚转角弧度 p: 当前滚转角速率弧度/秒 返回: 副翼舵量已限幅 # 第一层航向误差 - 滚转指令用 tanh 限幅大误差时指令不会爆掉 e_chi math.atan2(math.sin(chi_c - chi), math.cos(chi_c - chi)) phi_c limits[phi_max] * math.tanh(gains[k_chi] * e_chi / limits[phi_max]) # 第二层滚转误差 - 滚转角速率指令这一层比上一层快约五倍 p_c gains[k_phi] * (phi_c - phi) # 第三层角速率误差 - 舵面带宽最高直接对抗阵风 delta_a gains[k_p] * (p_c - p) return max(-limits[da_max], min(limits[da_max], delta_a))三个细节值得说清楚。atan2 那一步是为了把航向误差归一化到正负一百八十度之间不做的话从三百五十度转到十度会被算成三百四十度的误差飞机直接掉头。tanh 那一步是外环限幅斜率在小误差时约等于增益 k_chi大误差时饱和到滚转上限形状比硬限幅平滑不会在限幅边界上来回抖。第三层的 k_p 最大它决定了抵御阵风的能力但也最容易被舵机延迟拖到振荡——舵机带宽是这条链的硬上限。纵向通道结构对称高度误差给俯仰角指令俯仰误差给俯仰角速率指令角速率误差给升降舵空速则单独走一条路用油门控制。为什么空速不用升降舵控因为在固定翼上俯仰同时影响高度和速度两个变量一个执行器是控不住的必须把油门拉进来解耦——这也是为什么纵向通道看起来比横向复杂。4.5 最外环制导直线与盘旋的矢量场制导层的任务是把飞到那条线上去翻译成航向应该是多少。资料里用的办法是矢量场在路径周围构造一个方向场飞机顺着场的方向走自然会收敛到路径上。import math def line_following_course(pn, pe, p0, p1, k, chi_inf): 直线路径跟随用矢量场生成航向指令 p0/p1: 路径起点、终点 (north, east) k: 收敛速度增益越大贴合越快过大会振荡 chi_inf: 远离路径时相对路径方向的极限偏角弧度 d (p1[0] - p0[0], p1[1] - p0[1]) chi_q math.atan2(d[1], d[0]) # 路径方向 r (pn - p0[0], pe - p0[1]) # 横向偏差把相对位置投影到路径法向上 e math.cos(chi_q) * r[1] - math.sin(chi_q) * r[0] # 偏差越大航向越偏但被 atan 钳在 ±chi_inf 内 chi_c chi_q - chi_inf * (2.0 / math.pi) * math.atan(k * e) return chi_c, e用反正切而不是线性比例是这个设计里最关键的一处取舍。线性比例在偏差变大时指令会无限增长最后撞上滚转限制反正切把指令天然钳在极限偏角以内从制导层就满足了饱和约束内环永远拿不到一个它执行不了的指令。盘旋跟随的思路一样只是把到直线的横向距离换成到圆心的距离与期望半径之差方向场绕圆心旋转。多条路径拼起来就是路径管理直线接圆弧、圆弧接圆弧。资料里用 Dubins 路径来保证拼接处曲率连续因为最小转弯半径是由最大滚转角决定的指令一个比它还小的半径飞机根本转不过来轨迹会外扩越飞越偏。5. 排查这些坑几乎都长在层与层的接缝上单看每一层都没问题问题全出在交接处。下面五条是从仿真到试飞最常见的翻车现场现象、原因、处理都按实际排查顺序写。5.1 坐标系与单位一次乘错矩阵的代价现象仿真里姿态曲线漂亮实际飞行一进入转弯就侧翻或者定高环越飞越低。原因通常有两个。一是旋转矩阵的连乘顺序写反了代码按滚转-俯仰-偏航乘文档按偏航-俯仰-滚转写小角度时两者差别很小看不出问题一到大滚转角就完全对不上。二是高度的符号约定混用状态方程里用地坐标系向下为正气压高度向上为正接口处少乘了一个负号。解决内部计算统一用国际单位制和弧度只在与人交互的边界做一次转换。另外在初始化时加一条断言检查旋转矩阵正交且行列式为一这一行代码能省掉几天的排查。5.2 线性化模型被用在它不成立的区间现象小扰动下控制器表现很好一旦做较大机动响应开始发散增益怎么调都压不住。原因线性模型是在配平点做一阶泰勒展开得到的只在配平点邻域成立。大迎角时升力系数曲线开始弯曲大滚转角时耦合项变强这些非线性效应在 A、B 矩阵里根本不存在。解决给线性模型加一个有效性检查把当前状态与配平点的距离算出来超过阈值就切回非线性模型或者用增益调度——按空速和高度把配平点分成网格每个格点一套增益中间插值。代价是标定工作量翻几倍收益是飞行包线能拓宽不少。5.3 环与环的接缝饱和与时序现象一航向误差很大时飞机滚到极限然后过冲来回摆几次才稳下来。原因外环没有限幅内环的积分项在饱和期间还在累积等误差反号了积分项还挂着一大坨必须反向饱和很久才能吐出来这就是积分饱和。解决外环输出用平滑限幅内环积分加抗饱和逻辑——检测到执行器饱和就停止积分或者用反算法把超出的部分反馈回去。两条都做效果比只做一条稳得多。现象二飞得越快位置估计越拖盘旋轨迹呈螺旋状缓慢漂移。原因GPS 解算有延迟融合时用了数据到达的时刻而不是采样时刻。延迟在低速时看不出速度一快几十毫秒的延迟就是几米的偏差而控制器会把这个偏差当成真实误差去修正。解决给每一帧测量打上采样时间戳在融合前把状态回推到采样时刻或者把延迟显式建模进测量方程。这件事在仿真里几乎不可能发现因为仿真里没有延迟。5.4 制导切换点不是随便定的现象过航点时航迹画龙或者在该转弯的地方直接冲过头。原因航点切换半径设得太小小于当前空速下的最小转弯半径。最小转弯半径由空速和最大滚转角共同决定空速越大半径越大固定一个切换距离在某些速度下必然不够用。解决按当前空速实时计算最小转弯半径切换距离取一点五倍左右。如果航线用 Dubins 拼接还要检查圆弧段的曲率有没有超过这个上限——超了就把半径放大重规划而不是硬飞。注意所有仿真里好好的、实飞就不对的问题先怀疑延迟和饱和再怀疑模型。这两件事在理想仿真里通常都被省略了而它们恰恰是真实系统里最先咬人的。6. 设计取舍本文还有配套的精品资源点击获取