简介:本资源面向航空航天领域研究人员、高海拔太阳能飞机设计工程师及相关专业研究生,围绕基于多飞行阶段的能量管理策略展开论文复现。内容将飞行路径划分为夜间低空平飞、最大功率爬升、任务高度平飞、继续爬升与最长滑翔续航五个阶段,结合重力储能与任务高度优化,构建包含气动、运动学、太阳辐射、电池及能量管理的综合模型,并以Zephyr7为对象完成仿真验证,结果显示可节省约22.9%能源,电池重量由16.0kg降至12.3kg。资源包共1个PDF文件,约704KB,内含论文复现说明与Python代码实现,便于读者理解五阶段策略的建模思路、能量消耗评估方法与可视化流程。目前已有110人学习,适合需要评估不同飞行阶段能耗、改进太阳能飞机设计或研究任务模式下性能表现的中高级读者参考。
1. 高海拔太阳能飞机能量管理:为什么多飞行阶段建模是续航优化的分水岭
做高海拔长航时太阳能飞机的人,迟早会撞上同一个问题:同样一块电池、同样一片光伏,为什么仿真里能飞三天,实际连一个昼夜都撑不过去。答案往往不在电池本身,而在能量管理策略把整段飞行当成了一个阶段来处理。爬升、巡航、下滑、夜间盘旋,这四个阶段的能量收支曲线完全不同,用一套固定规则去调度,等于拿巡航的省电逻辑去管爬升的功耗峰值,翻车是必然的。
这个标题讲的就是把飞行剖面拆成多个阶段,针对每个阶段单独设计能量分配与切换逻辑,最终让续航和能源效率同时往上走。它适合做无人机总体设计、能源系统仿真、以及飞控策略验证的从业者。核心不是某个玄学公式,而是一套可复现的建模流程:阶段怎么划分、功率怎么平衡、电池荷电状态怎么在昼夜循环里守住底线。下面按我实际复现时的顺序,把每一步拆开讲。
2. 多飞行阶段能量模型:从功率平衡方程到状态机设计
2.1 为什么单阶段模型在跨昼夜场景下必然失效
单阶段模型通常假设飞机全程处于同一高度和同一速度,光伏输出用一条平滑曲线近似,电池放电用恒定功率估算。这在短时飞行里误差还能忍,一旦跨昼夜,问题就暴露了。白天爬升阶段光伏输出随高度增加而提升,但电机功耗也随爬升率增大;夜间巡航时没有光伏输入,电池要同时供推进和航电,放电倍率远高于白天。把这两个阶段混在一起做平均,电池的荷电状态曲线会被严重平滑,仿真看起来能撑到天亮,实际到凌晨三点就触底。
我一般会把飞行剖面按能量特征切成四段:爬升段、日间巡航段、下滑段、夜间巡航段。每段的输入变量不同,爬升段关注光伏随高度的增益与电机功耗的权衡,日间巡航段关注多余能量往电池里充的速率,下滑段关注重力势能能不能回收,夜间巡航段关注放电深度和截止电压。切分之后,每个阶段单独写功率平衡方程,再用一个状态机在阶段之间切换。
功率平衡的基本形式是:光伏输出功率减去推进功率再减去航电功率,等于电池充放电功率。爬升段推进功率最大,光伏输出随高度上升,净功率可能先负后正;夜间巡航段光伏为零,净功率恒为负。这个符号变化就是状态机切换的依据。
2.2 状态机与功率分配逻辑的代码实现
下面这段是我复现时用的核心状态机,用 Python 写,输入是当前高度、时间、电池荷电状态,输出是当前阶段和电池目标功率。注释里标了每个判断的依据。
# 多飞行阶段能量管理状态机 # 输入:altitude 当前高度(m),t 当前时刻(s),soc 电池荷电状态(0-1) # 输出:phase 当前阶段,p_batt_target 电池目标功率(W),正为充电负为放电 def energy_management_state_machine(altitude, t, soc, params): # params 包含:alt_cruise 巡航高度,t_climb_end 爬升结束时刻, # t_day_end 日间结束时刻,soc_min 最低荷电状态,soc_max 最高荷电状态 alt_cruise = params['alt_cruise'] t_climb_end = params['t_climb_end'] t_day_end = params['t_day_end'] soc_min = params['soc_min'] soc_max = params['soc_max'] # 阶段判断:先看时间,再看高度 if t < t_climb_end and altitude < alt_cruise: phase = 'climb' # 爬升段优先保证推进,电池按需放电,但不超过 soc_min p_batt_target = -params['p_climb_discharge'] if soc <= soc_min: p_batt_target = 0.0 # 触底则限制放电,靠光伏硬撑 elif t < t_day_end and altitude >= alt_cruise * 0.95: phase = 'day_cruise' # 日间巡航:光伏有余则充电,充电功率受 soc_max 限制 p_pv = params['p_pv_day'] p_prop = params['p_prop_cruise'] p_avionics = params['p_avionics'] p_net = p_pv - p_prop - p_avionics if soc >= soc_max and p_net > 0: p_batt_target = 0.0 # 充满则不再充,多余能量舍弃或降高 else: p_batt_target = p_net elif t >= t_day_end and altitude >= alt_cruise * 0.95: phase = 'night_cruise' # 夜间巡航:光伏为零,电池放电供推进和航电 p_batt_target = -(params['p_prop_cruise'] + params['p_avionics']) if soc <= soc_min: p_batt_target = 0.0 # 触底保护 else: phase = 'descent' # 下滑段:降低推进功率,利用势能,电池可小幅充电 p_batt_target = params['p_descent_charge'] return phase, p_batt_target逻辑说明:状态机先按时间切分昼夜,再按高度确认是否进入巡航。爬升段电池放电有下限保护,日间巡航段充电有上限保护,夜间巡航段放电同样有下限保护。参数说明里,p_climb_discharge是爬升段允许的放电功率,通常取电机峰值功率的 60% 到 80%;p_pv_day是日间光伏平均输出,按高度和光照模型算;soc_min和soc_max一般设 0.2 和 0.95,留出电池寿命余量。
这个状态机的关键不是代码多复杂,而是每个阶段的功率目标都单独可调。你可以把爬升段的放电功率调大来缩短爬升时间,但代价是夜间可用能量减少。这种权衡就是多阶段建模的价值所在。
2.3 光伏与电池模型的参数怎么标定
光伏模型我一般用简化形式:输出功率等于太阳辐照度乘以电池板面积乘以转换效率,再乘以一个随高度变化的增益因子。高海拔的优势在于辐照度更高、温度更低,转换效率比海平面好,但增益因子不要拍脑袋,我通常按每千米 3% 到 5% 的增益来估,超过这个范围就要查实际大气模型。
电池模型用一阶等效电路就够,重点是放电倍率对可用容量的影响。高海拔夜间巡航的放电倍率如果超过 0.5C,实际可用容量会掉到标称的 80% 左右。这个衰减在单阶段模型里经常被忽略,导致仿真偏乐观。我一般会在电池模型里加一个倍率修正系数,放电倍率越高,修正系数越小。
参数标定的顺序是:先定光伏面积和效率,再定电池标称容量和倍率修正,最后用状态机跑一个完整昼夜,看荷电状态曲线是否在 soc_min 和 soc_max 之间闭合。如果夜间结束时 soc 低于 soc_min,要么减小夜间放电功率,要么增大电池容量,要么调整爬升段策略让白天充得更满。
3. 复现流程:从飞行剖面到能量调度仿真
3.1 飞行剖面生成与阶段时间戳计算
复现的第一步是生成一条合理的飞行剖面。我一般用分段线性高度曲线:爬升段从起飞高度线性升到巡航高度,日间巡航保持高度,下滑段线性下降,夜间巡航保持高度。时间戳根据爬升率和巡航速度反推。
# 生成飞行剖面:高度随时间变化 import numpy as np def generate_flight_profile(params): # 时间轴:0 到 t_total,步长 dt dt = params['dt'] t_total = params['t_total'] t = np.arange(0, t_total, dt) altitude = np.zeros_like(t) alt_cruise = params['alt_cruise'] t_climb_end = params['t_climb_end'] t_day_end = params['t_day_end'] t_descent_start = params['t_descent_start'] for i, ti in enumerate(t): if ti < t_climb_end: # 爬升段:线性上升 altitude[i] = alt_cruise * (ti / t_climb_end) elif ti < t_descent_start: # 日间巡航和夜间巡航:保持高度 altitude[i] = alt_cruise elif ti < t_day_end: # 下滑段:线性下降 frac = (ti - t_descent_start) / (t_day_end - t_descent_start) altitude[i] = alt_cruise * (1 - frac) else: # 下滑后到夜间巡航:保持低高度或再爬升,按需调整 altitude[i] = alt_cruise * 0.3 return t, altitude逻辑说明:这个函数只负责生成高度曲线,时间戳参数需要根据实际飞行能力设定。t_climb_end是爬升结束时刻,t_descent_start是下滑开始时刻,t_day_end是日间结束时刻。参数说明里,dt取 10 秒到 60 秒都行,太大会漏掉阶段切换的瞬态;alt_cruise按设计巡航高度填,高海拔场景一般在一万五到两万米之间。
生成剖面后,把高度和时间喂给状态机,同时计算每个时刻的光伏输出和电池荷电状态。光伏输出用高度查表或公式算,电池荷电状态用安时积分更新。
3.2 能量调度仿真主循环与结果验证
主循环的结构是:每个时间步,先算光伏输出,再调状态机拿电池目标功率,然后更新电池荷电状态,最后记录数据。下面这段是主循环的骨架。
# 能量调度仿真主循环 def simulate_energy_management(params): t, altitude = generate_flight_profile(params) soc = params['soc_init'] # 初始荷电状态 soc_list = [] phase_list = [] p_batt_list = [] for i in range(len(t)): # 1. 计算当前光伏输出 p_pv = compute_pv_power(altitude[i], t[i], params) # 2. 状态机决策 phase, p_batt_target = energy_management_state_machine( altitude[i], t[i], soc, params) # 3. 电池荷电状态更新:安时积分 # 电池容量单位 Wh,p_batt_target 单位 W,dt 单位 s delta_energy = p_batt_target * params['dt'] / 3600.0 soc = soc + delta_energy / params['batt_capacity_wh'] soc = max(0.0, min(1.0, soc)) # 限幅 # 4. 记录 soc_list.append(soc) phase_list.append(phase) p_batt_list.append(p_batt_target) return t, altitude, soc_list, phase_list, p_batt_list逻辑说明:光伏输出函数compute_pv_power按高度和光照模型算,简化版可以用p_pv = p_pv_sea * (1 + gain_per_km * altitude / 1000),其中p_pv_sea是海平面等效输出。电池荷电状态更新用安时积分,注意单位换算,功率乘时间除以 3600 得到瓦时,再除以电池容量得到荷电状态变化量。限幅是为了防止数值溢出。
验证方法是看荷电状态曲线是否在一天结束时回到初始值附近。如果夜间结束时 soc 明显低于初始值,说明能量不够,需要调整参数。我一般会跑三个昼夜,看曲线是否收敛到稳定循环。如果第一个昼夜就触底,后面不用看了,直接改设计。
3.3 续航与能源效率的量化对比方法
复现的最终目的是量化多阶段策略比单阶段好多少。我一般用两个指标:续航时间和能源效率。续航时间就是荷电状态从初始值到 soc_min 所经历的时间,能源效率是总推进能量除以总光伏能量。
对比时,单阶段策略用固定放电功率和固定充电功率,多阶段策略用状态机。跑同一个飞行剖面,记录两个策略的续航时间和能源效率。我复现时的经验是,多阶段策略在跨昼夜场景下续航能提升 15% 到 30%,能源效率提升 5% 到 10%。提升幅度取决于阶段划分的粒度和参数调优程度。
注意:对比时飞行剖面必须完全一致,否则续航差异可能来自剖面本身而不是策略。我一般会把两个策略跑在同一组高度和时间数据上,只换能量管理逻辑。
4. 避坑与排查:复现时最容易翻车的五个地方
4.1 荷电状态曲线在夜间突然跳水
现象:仿真跑到夜间巡航段,荷电状态曲线从平缓下降突然变成陡降,几分钟内触底。
原因:夜间巡航的放电功率参数设得太大,或者电池容量单位搞错了。常见错误是把瓦时当成安时,导致容量少算一个电压倍数。另一个原因是状态机在阶段切换时没有平滑过渡,夜间巡航一开始就按最大放电功率跑。
解决:先检查电池容量单位,确认是瓦时还是安时。如果是安时,要乘以标称电压再参与荷电状态计算。然后在状态机里加一个过渡段,夜间巡航前几分钟逐步增加放电功率,避免阶跃。我一般会把夜间放电功率设成推进功率加航电功率的 1.1 倍,留一点余量,不要按峰值设。
4.2 光伏输出在爬升段反而下降
现象:爬升段高度增加,但计算出的光伏输出不升反降。
原因:高度增益因子设成了负数,或者光照模型里把高度和辐照度的关系搞反了。另一个可能是温度修正项写错,高海拔温度低,光伏效率应该上升,如果代码里写成温度升高效率上升,就会在高海拔低温时算出低效率。
解决:检查增益因子的符号,确保高度增加时输出增加。温度修正项一般写成效率乘以一加温度系数乘以实际温度减参考温度,温度系数对晶体硅是负值,温度低于参考温度时效率提升。我一般会把增益因子和温度修正分开写,方便单独调试。
4.3 状态机在阶段边界反复切换
现象:仿真日志里阶段名称在边界附近来回跳,比如爬升和巡航之间反复切换。
原因:阶段判断条件用了硬阈值,高度在阈值附近波动时就会反复触发。另一个原因是时间戳和高度判断的逻辑顺序不对,先判断高度再判断时间,导致时间还没到但高度已经到了就提前切换。
解决:在状态机里加滞回区间,比如高度超过巡航高度的 95% 才算进入巡航,低于 90% 才算退出。时间判断放在高度判断之前,确保阶段切换按时间轴走。我一般会把阶段判断写成一个独立函数,输入当前状态和条件,输出下一个状态,这样调试时只看状态转移表就行。
4.4 能源效率算出来大于 1
现象:能源效率指标超过 100%,明显不合理。
原因:分子分母的单位不一致,或者把光伏输出和推进功率的积分区间搞错了。常见错误是分子用了瓦时,分母用了焦耳,或者积分时间步长不一致。
解决:统一用瓦时做能量单位,积分时确保分子分母用同一个时间轴和同一个步长。我一般会在积分前先把功率序列转成能量序列,再分别求和。如果效率还是大于 1,检查光伏输出是否包含了电池放电的能量,光伏和电池是两条独立的能量来源,不能混在一起算。
4.5 仿真跑得完但结果不可复现
现象:同一组参数跑两次,荷电状态曲线不一样。
原因:代码里有随机数或者未初始化的变量。另一个可能是浮点数累积误差,时间步长太小导致舍入误差放大。
解决:检查代码里有没有用随机数生成光照或风速,如果有,固定随机种子。浮点误差方面,我一般会把时间步长设在 10 秒以上,太小没必要,反而增加累积误差。另外,状态机里的参数最好用字典传入,避免全局变量被意外修改。
5. 进阶技巧:用参数扫描找到续航与效率的帕累托前沿
复现完基础版本后,真正有意思的是调参。多阶段能量管理的参数不少:爬升段放电功率、日间充电上限、夜间放电功率、阶段切换阈值。这些参数互相耦合,手动调很容易顾此失彼。我一般用参数扫描加帕累托前沿的方法,把续航和能源效率同时作为目标,找一组非支配解。
具体做法是:先定参数范围,比如爬升段放电功率从电机峰值的 50% 到 90%,步长 5%;夜间放电功率从推进功率的 1.0 倍到 1.3 倍,步长 0.05。然后写一个循环,每组参数跑一次仿真,记录续航时间和能源效率。最后用帕累托筛选,留下那些在续航和效率上都不差的参数组合。
# 参数扫描与帕累托前沿筛选 import itertools def parameter_sweep(params_base): results = [] # 定义扫描范围 p_climb_range = [0.5, 0.6, 0.7, 0.8, 0.9] p_night_range = [1.0, 1.1, 1.2, 1.3] for p_climb, p_night in itertools.product(p_climb_range, p_night_range): params = params_base.copy() params['p_climb_discharge'] = params['p_motor_peak'] * p_climb params['p_night_discharge'] = params['p_prop_cruise'] * p_night # 跑仿真 t, alt, soc, phase, p_batt = simulate_energy_management(params) # 计算续航:soc 降到 soc_min 的时间 endurance = compute_endurance(soc, t, params['soc_min']) # 计算能源效率 efficiency = compute_energy_efficiency(p_batt, params) results.append({ 'p_climb': p_climb, 'p_night': p_night, 'endurance': endurance, 'efficiency': efficiency }) # 帕累托筛选:续航和效率都不差的解 pareto = [] for r in results: dominated = False for other in results: if (other['endurance'] >= r['endurance'] and other['efficiency'] >= r['efficiency'] and (other['endurance'] > r['endurance'] or other['efficiency'] > r['efficiency'])): dominated = True break if not dominated: pareto.append(r) return pareto逻辑说明:参数扫描用笛卡尔积遍历所有组合,每组跑一次完整仿真。续航计算找荷电状态首次低于 soc_min 的时间点,能源效率用总推进能量除以总光伏能量。帕累托筛选的逻辑是,如果存在另一组参数在续航和效率上都不差且至少一项更好,当前组就被支配,不进入前沿。
参数说明:p_motor_peak是电机峰值功率,p_prop_cruise是巡航推进功率。扫描范围不要设太宽,否则计算量大且很多组合明显不可行。我一般先粗扫,找到大致区域后再细扫。
跑完参数扫描后,我会把帕累托前沿画出来,横轴续航,纵轴效率,每个点标注对应的参数组合。然后根据实际需求选点:如果任务要求长续航,选续航最长的点;如果能源紧张,选效率最高的点。多数情况下,前沿中间的点比两端更实用,因为两端往往牺牲了另一项指标。
我自己的习惯是,每次改完参数都跑一遍三个昼夜的仿真,看荷电状态曲线是否稳定。如果第三个昼夜和第二个昼夜的曲线几乎重合,说明参数收敛了。如果还在漂移,说明能量收支没平衡,得继续调。这个习惯帮我省了很多次返工,也让我对每个参数的影响有了直觉。希望帮到你。
本文还有配套的精品资源,点击获取