1 // mag_compass.cxx - a magnetic compass.
2 // Written by David Megginson, started 2003.
4 // This file is in the Public Domain and comes with no warranty.
6 // This implementation is derived from an earlier one by Alex Perry,
7 // which appeared in src/Cockpit/steam.cxx
14 #include <simgear/sg_inlines.h>
16 #include "mag_compass.hxx"
17 #include <Main/fg_props.hxx>
18 #include <Main/util.hxx>
21 MagCompass::MagCompass ( SGPropertyNode *node )
24 name("magnetic-compass"),
28 for ( i = 0; i < node->nChildren(); ++i ) {
29 SGPropertyNode *child = node->getChild(i);
30 string cname = child->getName();
31 string cval = child->getStringValue();
32 if ( cname == "name" ) {
34 } else if ( cname == "number" ) {
35 num = child->getIntValue();
37 SG_LOG( SG_INSTR, SG_WARN, "Error in magnetic-compass config logic" );
38 if ( name.length() ) {
39 SG_LOG( SG_INSTR, SG_WARN, "Section = " << name );
45 MagCompass::MagCompass ()
51 MagCompass::~MagCompass ()
59 branch = "/instrumentation/" + name;
61 SGPropertyNode *node = fgGetNode(branch.c_str(), num, true );
62 _serviceable_node = node->getChild("serviceable", 0, true);
64 fgGetNode("/orientation/roll-deg", true);
66 fgGetNode("/orientation/pitch-deg", true);
68 fgGetNode("/orientation/heading-magnetic-deg", true);
70 fgGetNode("/orientation/side-slip-deg", true);
72 fgGetNode("/environment/magnetic-dip-deg", true);
74 fgGetNode("/accelerations/pilot/x-accel-fps_sec", true);
76 fgGetNode("/accelerations/pilot/y-accel-fps_sec", true);
78 fgGetNode("/accelerations/pilot/z-accel-fps_sec", true);
79 _out_node = node->getChild("indicated-heading-deg", 0, true);
84 MagCompass::update (double delta_time_sec)
86 // This is the real magnetic
87 // which would be displayed
88 // if the compass had no errors.
89 //double heading_mag_deg = _heading_node->getDoubleValue();
92 // don't update if the compass
94 if (!_serviceable_node->getBoolValue())
98 * Vassilii: commented out because this way, even when parked,
99 * w/o any accelerations and level, the compass is jammed.
100 * If somebody wants to model jamming, real forces (i.e. accelerations)
101 * and not sideslip angle must be considered.
104 // jam on excessive sideslip
105 if (fabs(_beta_node->getDoubleValue()) > 12.0) {
113 Formula for northernly turning error from
114 http://williams.best.vwh.net/compass/node4.html:
117 psi: magnetic heading
118 theta: bank angle (right positive; should be phi here)
119 mu: dip angle (down positive)
121 Hc = atan2(sin(Hm)cos(theta)-tan(mu)sin(theta), cos(Hm))
123 This function changes the variable names to the more common psi
124 for the heading, theta for the pitch, and phi for the roll (and
125 target_deg for Hc). It also modifies the equation to
126 incorporate pitch as well as roll, as suggested by Chris
130 // bank angle (radians)
131 double phi = _roll_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
133 // pitch angle (radians)
134 double theta = _pitch_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
136 // magnetic heading (radians)
137 double psi = _heading_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
139 // magnetic dip (radians)
140 double mu = _dip_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
144 Tilt adjustments for accelerations.
146 The magnitudes of these are totally made up, but in real life,
147 they would depend on the fluid level, the amount of friction,
148 etc. anyway. Basically, the compass float tilts forward for
149 acceleration and backward for deceleration. Tilt about 4
150 degrees (0.07 radians) for every G (32 fps/sec) of
153 TODO: do something with the vertical acceleration.
155 double x_accel_g = _x_accel_node->getDoubleValue() / 32;
156 double y_accel_g = _y_accel_node->getDoubleValue() / 32;
157 //double z_accel_g = _z_accel_node->getDoubleValue() / 32;
159 theta -= 0.07 * x_accel_g;
160 phi -= 0.07 * y_accel_g;
162 ////////////////////////////////////////////////////////////////////
163 // calculate target compass heading degrees
164 ////////////////////////////////////////////////////////////////////
166 // these are expensive: don't repeat
167 double sin_phi = sin(phi);
168 double sin_theta = sin(theta);
169 double sin_mu = sin(mu);
170 double cos_theta = cos(theta);
171 double cos_psi = cos(psi);
172 double cos_mu = cos(mu);
174 double a = cos(phi) * sin(psi) * cos_mu
175 - sin_phi * cos_theta * sin_mu
176 - sin_phi* sin_theta * cos_mu * cos_psi;
178 double b = cos_theta * cos_psi * cos(mu)
179 - sin_theta * sin_mu;
181 // This is the value that the compass
182 // is *trying* to display.
183 double target_deg = atan2(a, b) * SGD_RADIANS_TO_DEGREES;
184 double old_deg = _out_node->getDoubleValue();
186 while ((target_deg - old_deg) > 180.0)
188 while ((target_deg - old_deg) < -180.0)
191 // The compass has a current rate of
192 // rotation -- move the rate of rotation
193 // towards one that will turn the compass
194 // to the correct heading, but lag a bit.
195 // (so that the compass can keep overshooting
197 double error = target_deg - old_deg;
198 _rate_degps = fgGetLowPass(_rate_degps, error, delta_time_sec / 5.0);
199 double indicated_deg = old_deg + _rate_degps * delta_time_sec;
200 SG_NORMALIZE_RANGE(indicated_deg, 0.0, 360.0);
202 // That's it -- set the messed-up heading.
203 _out_node->setDoubleValue(indicated_deg);
206 // end of altimeter.cxx