265 lines
11 KiB
Text
265 lines
11 KiB
Text
|
# wingflexer.nas - A simple wing flex model.
|
||
|
#
|
||
|
# Copyright (C) 2014 Thomas Albrecht
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or
|
||
|
# modify it under the terms of the GNU General Public License as
|
||
|
# published by the Free Software Foundation; either version 2 of the
|
||
|
# License, or (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful, but
|
||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
# General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program; if not, write to the Free Software
|
||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
|
||
|
#
|
||
|
# -->
|
||
|
# g
|
||
|
# +-----+ +-----+
|
||
|
# <--- | m_w |---/\/\/\---| |
|
||
|
# +-----+ +-----+
|
||
|
# Lift wing spring fuselage
|
||
|
# force mass
|
||
|
#
|
||
|
# We integrate
|
||
|
#
|
||
|
# .. k d . 0.5*F_L ..
|
||
|
# 0 = -z + --- z + ---- z - ------- - g - z_f
|
||
|
# m_w m_w m_w
|
||
|
#
|
||
|
# where
|
||
|
#
|
||
|
# z : deflection
|
||
|
# k : wing stiffness
|
||
|
# d : damping
|
||
|
# m_w = m_dw + fuel_frac * m_fuel
|
||
|
# Total wing mass. Because the fuel is distributed over the wing, we use
|
||
|
# a fraction of the fuel mass in the calculation.
|
||
|
# 0.5*F_L : lift force/2 (we look at one wing only)
|
||
|
# ..
|
||
|
# z_f : acceleration of the frame of reference (fuselage)
|
||
|
#
|
||
|
# and write the deflection (z + z_ofs) in meters to /sim/model/wing-flex/z-m.
|
||
|
# The offset z_ofs is calculated automatically and ensures that the dry wing
|
||
|
# (which still has a non-zero mass) creates neutral deflection.
|
||
|
#
|
||
|
# Discretisation by first order finite differences:
|
||
|
#
|
||
|
# z_0 - 2 z_1 + z_2 k d (z_0 - z_1) 1/2 F_L ..
|
||
|
# ----------------- + --- z_1 + --- ----------- - ------- - g - z_f = 0
|
||
|
# dt^2 m_w m_w dt m_w
|
||
|
#
|
||
|
# It is convenient to divide k and d by a (constant) reference mass:
|
||
|
#
|
||
|
# K = k / m_dw
|
||
|
# D = d / m_dw
|
||
|
#
|
||
|
# To adapt this to your aircraft, you need m_w, K, D.
|
||
|
# How to estimate these?
|
||
|
#
|
||
|
# 1. Assume a dry wing mass m_dw. Research the wing fuel mass m_fuel.
|
||
|
#
|
||
|
# 2. Obtain estimates of
|
||
|
# - the deflection z_flight in level flight, e.g by comparing photos
|
||
|
# of the real aircraft on ground and in air,
|
||
|
# - the wing's eigenfrequency, perhaps from videos of the wing's oscillation in
|
||
|
# turbulence,
|
||
|
# - the deflection with full and empty tanks while sitting on the ground.
|
||
|
#
|
||
|
# 3. Compute K to match in flight deflection with full tanks:
|
||
|
# K = g * (m_ac / 2 - (fuel_frac * m_fuel)) / (z_in_flight / z_fac) / m_dw
|
||
|
#
|
||
|
# where
|
||
|
# m_ac : aircraft mass
|
||
|
# g : 9.81 m/s^2
|
||
|
# z_fac: scaling factor for the deflection, start with 1.
|
||
|
#
|
||
|
# 4. Compute the eigenfrequency of this system for full and empty wing tanks:
|
||
|
# f_full = sqrt(K * m_dw / (m_dw + fuel_frac * m_fuel)) / (2 pi)
|
||
|
# f_empty = sqrt(K) / (2 pi)
|
||
|
#
|
||
|
# Ideally we want our model to match the eigenfrequency, the deflection
|
||
|
# while sitting on the ground with full or empty tanks, and the deflection
|
||
|
# during a hard landing. Getting real-world data for the latter is difficult.
|
||
|
#
|
||
|
# There's a python script wingflexer.py which assists you in tuning the parameters.
|
||
|
#
|
||
|
# Here are some relations:
|
||
|
# - a lower wing mass increases the eigenfrequency, and weakens the touchdown bounce
|
||
|
# - a higher stiffness K reduces the deflection and increases the eigenfrequency
|
||
|
#
|
||
|
# The 787 is known for its very flexible wings; the deflection in
|
||
|
# unaccelerated flight is quoted as z = 3 m. One wing tank of FG's 787-8 holds
|
||
|
# 23,000 kg of fuel. Because the fuel is distributed over the wing, we use a
|
||
|
# fraction of the fuel mass in the calculation: fuel_frac = 0.75. For the same reason
|
||
|
# we don't use the true wing mass, but rather something that makes our model look
|
||
|
# plausible.
|
||
|
#
|
||
|
# So assuming a wing mass of 12000 kg, we get K=25.9 and f_empty = 0.5 Hz.
|
||
|
# That frequency might be a bit low, videos of a 777 wing in turbulence show about
|
||
|
# 2-3 Hz. (I didn't research 787 videos).
|
||
|
#
|
||
|
# To increase it, we could either reduce m_dw or increase K. A lower m_dw results
|
||
|
# in a rather weak bounce on touchdown which might look odd. A higher K reduces
|
||
|
# the deflection z_flight, but we can simply scale the animation to account for
|
||
|
# that. We'll multiply the deflection z by a factor z_fac to get an angle for the
|
||
|
# <rotate> animation later on anyway. So repeat 3. and 4. using e.g. z_fac = 10.
|
||
|
# Now K = 259 and f_empty=2.6 Hz. While our model spring now only deflects
|
||
|
# to 0.3 m instead of 3 m, the animation scale factor will make sure the wing
|
||
|
# bends to 3 m. This way, we can match both eigenfrequency and observed deflection,
|
||
|
# and still get a realistic bounce on touch down. Finally, adjust D such that an
|
||
|
# impulse is damped out after about one or two oscillations; D = 12 seems to work
|
||
|
# OK in our example.
|
||
|
#
|
||
|
# It's difficult to get real-world data on the deflection during touchdown.
|
||
|
# Touchdown at more than 10 ft/s is considered a hard landing. There's a video of
|
||
|
# a hard landing of an A346 (http://avherald.com/h?article=471e70e9), showing the
|
||
|
# wings bend perhaps 1 m. But I couldn't find any data for the acceleration over
|
||
|
# time during a hard landing.
|
||
|
#
|
||
|
# To assist you in tuning parameters for the touchdown bounce we can give our
|
||
|
# wing mass the touchdown vertical speed via /sim/model/wing-flex/sink-rate_fps.
|
||
|
#
|
||
|
# Our model outputs the deflection in meters, but the <rotate> animation expects an
|
||
|
# angle. It is up to you calculate an appropriate factor, depending on your wing
|
||
|
# span and number of segments in the animation. Also don't forget to include z_fac.
|
||
|
#
|
||
|
# To use this with your JSBSim aircraft, use
|
||
|
#
|
||
|
# io.include("Aircraft/Generic/wingflexer.nas");
|
||
|
# WingFlexer.new(1, K, D, mass_dry_wing_kg,
|
||
|
# fuel_fraction, fuel_node_left, fuel_node_right);
|
||
|
#
|
||
|
# with apropriate parameters.
|
||
|
#
|
||
|
# Yasim does not write the lift to the property tree. But you can create a helper
|
||
|
# function which computes the lift as
|
||
|
# lift_force_lbs = aircraft_weight_lbs * load_factor - total_weight_on_wheels_lbs
|
||
|
# and write lift_force_lbs to /fdm/jsbsim/forces/fbz-aero-lbs (or another location
|
||
|
# passed to WingFlexer.new() as lift_node).
|
||
|
#
|
||
|
# TODO
|
||
|
# - write Yasim helper
|
||
|
# - perhaps use analytical solution of ODE
|
||
|
# - input for fuselage acceleration should rather be acceleration at CG -- find property
|
||
|
|
||
|
io.include("Aircraft/Generic/updateloop.nas");
|
||
|
|
||
|
var WingFlexer = {
|
||
|
parents: [Updatable],
|
||
|
|
||
|
# FIXME: these defaults make the 787-8 wing flex look realistic, which is certainly not
|
||
|
# the most generic airliner wing. Once someone obtains a set of parameters for e.g.
|
||
|
# the 777, use them here.
|
||
|
|
||
|
new: func(enable = 1, K=259., D=12., mass_dry_wing_kg = 12000., fuel_fraction = 0.75,
|
||
|
fuel_node_left = "consumables/fuel/tank/level-kg",
|
||
|
fuel_node_right = "consumables/fuel/tank[1]/level-kg",
|
||
|
node = "sim/model/wing-flex/", lift_node = "fdm/jsbsim/forces/fbz-aero-lbs") {
|
||
|
var m = { parents: [WingFlexer] };
|
||
|
m.node = node;
|
||
|
m.m_dw = mass_dry_wing_kg;
|
||
|
m.k = K * m.m_dw;
|
||
|
m.d = D * m.m_dw;
|
||
|
m.fuel_frac_on_2 = fuel_fraction / 2.; # so we don't have to divide each frame
|
||
|
m.fuel_node_left = fuel_node_left;
|
||
|
m.fuel_node_right = fuel_node_right;
|
||
|
m.lift_node = lift_node;
|
||
|
m.loop = UpdateLoop.new(components: [m], enable: enable);
|
||
|
return m;
|
||
|
},
|
||
|
|
||
|
reset: func {
|
||
|
|
||
|
me.z = 0.;
|
||
|
me.z1 = 0.;
|
||
|
me.z2 = 0.;
|
||
|
|
||
|
setprop(me.node ~ "z-m", 0.);
|
||
|
setprop(me.node ~ "mass-wing-kg", me.m_dw);
|
||
|
setprop(me.node ~ "K", me.k/me.m_dw);
|
||
|
setprop(me.node ~ "D", me.d/me.m_dw);
|
||
|
setprop(me.node ~ "fuel-fac", me.fuel_frac_on_2 * 2);
|
||
|
setprop(me.node ~ "sink-rate_fps", 0.);
|
||
|
me.g_on_2_times_LB2KG = getprop("/environment/gravitational-acceleration-mps2") / 2. * globals.LB2KG;
|
||
|
me.calc_z_ofs();
|
||
|
|
||
|
setlistener(me.node ~ "mass-wing-kg", func(the_node) {
|
||
|
me.m_dw = the_node.getValue();
|
||
|
me.calc_z_ofs();
|
||
|
}, 0, 0);
|
||
|
setlistener(me.node ~ "K", func(the_node) {
|
||
|
me.k = the_node.getValue() * me.m_dw;
|
||
|
me.calc_z_ofs();
|
||
|
}, 0, 0);
|
||
|
setlistener(me.node ~ "D", func(the_node) { me.d = the_node.getValue() * me.m_dw; }, 0, 0);
|
||
|
setlistener(me.node ~ "fuel-fac", func(the_node) { me.fuel_frac_on_2 = the_node.getValue() / 2.; }, 0, 0);
|
||
|
|
||
|
# The following helped me getting wing flex look OK. It's no longer
|
||
|
# needed once you get the parameters right, so it's disabled by default.
|
||
|
# Look for DEV to re-enable.
|
||
|
# Include z-fac here, so you don't have to adjust the animation .xml
|
||
|
# setprop(me.node ~ "z-fac", 3.);
|
||
|
# me.last_dt = 1/30.;
|
||
|
# me.max_z = 0.;
|
||
|
# setlistener(me.node ~ "sink-rate_fps", func(the_node) {
|
||
|
# var dz = me.last_dt * the_node.getValue() * globals.FT2M;
|
||
|
# me.z0 = me.z1 - dz;
|
||
|
# me.z2 = me.z1 + dz;
|
||
|
# me.max_z = 0.;
|
||
|
# }, 1, 0);
|
||
|
},
|
||
|
|
||
|
calc_z_ofs: func() {
|
||
|
print ("wingflex: calc z_ofs");
|
||
|
me.z_ofs = getprop("/environment/gravitational-acceleration-mps2") * me.m_dw / me.k;
|
||
|
},
|
||
|
|
||
|
update: func(dt) {
|
||
|
# limit time step to avoid numerical instability
|
||
|
if (dt > 0.2) dt = 0.2;
|
||
|
|
||
|
# DEV:
|
||
|
# me.last_dt = dt;
|
||
|
|
||
|
# fuselage z (up) acceleration in m/s^2
|
||
|
# we get -g in unaccelerated flight, and large negative numbers on touchdown
|
||
|
var a_f = getprop("accelerations/pilot/z-accel-fps_sec") * globals.FT2M;
|
||
|
|
||
|
# lift force. Convert to N and use 1/2 (one wing only)
|
||
|
var F_l = getprop(me.lift_node) * me.g_on_2_times_LB2KG;
|
||
|
|
||
|
# compute total mass of one wing, using the average fuel mass in both wing tanks.
|
||
|
# The averaging factor 0.5 is lumped into fuel_frac_on_2
|
||
|
me.m = me.m_dw + me.fuel_frac_on_2 * (getprop(me.fuel_node_left) + getprop(me.fuel_node_right));
|
||
|
|
||
|
# integrate discretised equation of motion
|
||
|
# reverse sign of F_l because z in JSBsim body coordinate system points down
|
||
|
me.z = (2.*me.z1 - me.z2 + dt * ((me.d * me.z1 + dt * (-F_l - me.k * me.z1))/me.m + dt *
|
||
|
a_f)) / (1. + me.d * dt / me.m);
|
||
|
me.z2 = me.z1;
|
||
|
me.z1 = me.z;
|
||
|
|
||
|
me.z += me.z_ofs;
|
||
|
|
||
|
# output to property
|
||
|
setprop(me.node ~ "z-m", me.z);
|
||
|
|
||
|
# DEV: scale output and log max deflection
|
||
|
# var z_fac = getprop(me.node ~ "z-fac");
|
||
|
# if (me.z * z_fac < me.max_z) me.max_z = me.z * z_fac;
|
||
|
# print (sprintf(" z %4.2f max %4.2f m %7.1f", me.z * z_fac, me.max_z, me.m));
|
||
|
# setprop(me.node ~ "z-m", me.z * z_fac);
|
||
|
},
|
||
|
|
||
|
enable: func { me.loop.enable() },
|
||
|
disable: func { me.loop.disable() },
|
||
|
};
|
||
|
|
||
|
|
||
|
|