Skip to content
Bjarne Hansen edited this page Aug 15, 2021 · 11 revisions

Using the Signal K Orientation Library

This guide supplements information available in the Orientation Sensor-Fusion library Wiki.

Below are details specific to using the Orientation library with Signal K / SensESP.

Magnetic Calibration

see also the Orientation Sensor-Fusion Library's Calibration wiki

Magnetic Calibration is needed before the sensor's heading is correct. After calibration, one can expect the magnetic heading to be within 2 degrees or better of the actual direction that the X-axis of the sensor is pointing.

When initially programmed, the sensor has a blank internal calibration table. A background magnetic calibration routing runs during regular operation, calculating the best hard- and soft-iron corrections to apply based on recent data samples. Therefore, after initial power-on, move the sensor through a series of rolls, pitches and yaws to cover a wide range of orientations. This will be best done in the same location that the sensor will be used, just prior to permanently mounting it. After enough readings have been collected (takes 30 - 60 seconds when rotating the sensor by hand) then the sensor should be calibrated. The Fit Error and Magnetic Solver parameters (see below) are useful in deciding if the calibration has been successful. A Magnetic Calibration can be saved in non-volatile memory so it will be loaded at the next power-up. To save a calibration, use the value_settings->Save_Mag_Cal entry in the sensor web interface. A calibration will be valid until the sensor's magnetic environment changes.

The orientation library provides several output parameters that are useful both when you are performing calibration, and also while using the orientation output. To make these parameters available, you need to define the Signal K paths they will output to and then create instances of the value producers. Here's a screenshot showing several of these additional parameters during calibration:

Signal K Instrument Panel

The parameters (and the OrientationValType providing them) are:

  • Fit kMagCalFitInUse reports the fit error of the currently in-use calibration. Lower numbers are better: it starts at 100% when uncalibrated, and you should aim for a value less than 3.5%. Note that Signal K displays this value as a ratio (i.e. a number between [0..1]); multiply by 100 to express it in percent.
  • Fit Trial kMagCalFitTrial reports fit error of the next prospective calibration (the fusion algorithm is always exploring whether a better calibration can be developed using the latest readings). It has the same units as Fit, and if a better fit is found that prospective calibration will replace the current calibration. It will only be saved to non-volatile memory if you explicitly do so in the sensor web interface.
  • Solver kMagCalAlgorithmSolver (called Order in the above screenshot) indicates the complexity of the calibration fitting algorithm, and increases as the number of readings used increases. Solver has possible values of 0 (uncalibrated), 4, 7, and 10 (most sophisticated algorithm).
  • Magnetic Inclination kMagInclination is the geomagnetic dip angle in radians below horizontal based on current readings
  • B Field Magnitude kMagFieldMagnitude is the geomagnetic field magnitude of the current calibration. It is provided in uT (average Earth magnetic field is about 50 uT) since sending it in T (Teslas) results in a value so small that the current Instrument panel options display it as 0.00
  • B Field Magnitude Trial kMagFieldMagnitudeTrial is the geomagnetic field magnitude based on current readings
  • Magnetic Noise Covariance kMagNoiseCovariance is the deviation of the current reading from the calibrated geomagnetic sphere. A lower number indicates more confidence in the current readings. If the noise value rises above about 0.0005, it suggests that the magnetic environment is no longer similar to what it was at the time of the most recent calibration.

To ensure a good calibration before saving, keep adding data points via manipulation of the sensor until the Fit-Error value drops below 3.5 and the Solver value is 10.

Saving Magnetic Calibration

Once a good calibration is generated, you may want to save it so it will be automatically loaded each time the microcontroller resets or powers on. Two options are available for permanently saving the magnetic calibration:

Via the Sensor's Web Interface

Access the sensor via WiFi and expand (click on) the entries: Configure device -> sensors -> Attitude or Heading -> settings. A "Save Magnetic Cal" field should appear:

Sensor's Web Interface

Enter the numeral 1 in the field, and click on Save.

If you wish to erase any stored calibration, enter the number -1 instead, and click Save.

Via a Physical Button

If a momentary switch is connected to a GPIO pin of the microcontroller, and the software is set up to monitor the switch, then pressing the switch will save the magnetic calibration. See the example main.cpp while referring to the following explanation.

A digital input pin is read periodically and, if its state changes, the next consumer (debounce transform) is notified. The example sets the switch read interval and debounce period fairly long, as we don't need quick response for this function and this way an accidental quick push on the switch won't cause one to lose an existing calibration. With the example settings, the switch needs to be held just under 500ms before it takes action.

The debounce transform sends its output to a lambda consumer function, which is defined as a call to the orientation library's SaveMagneticCalibration() method. That saves the current magnetic calibration to EEPROM. There's also a debugI() print to the serial stream (unless DEBUG_DISABLED is defined) which indicates the save function has been called. It's a bit hard to pick out from all the other serial traffic, unless one turns the reporting rate for the other sensors way down...

The example code is appropriate for a physical button that grounds a GPIO pin when pushed, so the GPIO has its pull-up resistor enabled. One can use a switch tied to logic high instead, with a pull-down on the GPIO - just adjust the code that configures the digital input.

Bear in mind for good noise immunity and CPU protection of a device to be used in the field, I recommend putting a few passive components on the switch circuit. This is particularly a good idea if the wire length between the CPU and the switch is longer than, say, 10 cm or there's a chance of zapping the switch with static electricity. There's quite a few configurations commonly used for input protection - for example a series resistor between the switch and the GPIO pin of about 1000 Ohms (the pull-up/pull-down resistance for ESP32 pins is 45 kOhm, so 1 kOhm will overcome that nicely), and then an ESD protection diode rated between 3V3 and 5V0 between the GPIO pin and board ground. An alternative is to use a small-value capacitor instead of the ESD diode - a 100 nF cap should be fine, as the RC time constant together with the 1 kOhm resistor will be plenty short.

Deviation Corrections

Once the magnetic calibration is performed, the sensor should output reasonably accurate Compass Headings. There will be some residual magnetic environmental distortions, and there likely will be mounting or mechanical offsets as well. Collectively, these Deviation errors cause the Compass Heading to differ from the actual Magnetic Heading of the bow of your vessel. Correcting the sensor reading for Deviation will convert the Compass Heading to the Magnetic Heading of the vessel.

One way of determining Deviation is to point your vessel at a selection of targets with known bearings (determined for example via a chartplotter and GPS and the local Magnetic Variation). By recording the difference between the eCompass' reported Magnetic Heading, and the actual known Magnetic bearing for a variety of objects throughout a 360-degree span, you will create a Deviation table. A graphical plot of the deviations against the bearings will show either a constant offset, or more likely, a varying error that changes smoothly through the full circle.

If the Deviation can be approximated as a constant offset, then the easiest way to convert the Compass Heading to a Magnetic Heading is by passing the value through the AngleCorrection transform:

main.cpp [...]
  sensor_heading
        // Correct for mounting offsets - Pi/2 rotation in my case.
        ->connect_to(new AngleCorrection((PI/2.0), 0.0, kConfigPathHeadingOffset))
        ->connect_to(
            new SKOutputNumber(kSKPathHeadingCompass, kConfigPathHeading_SKC))
        //pass to simple deviation transform. Set initial offset to 0.0 radians.
        ->connect_to( new AngleCorrection( 0.0, 0.0, kConfigPathHeadingDev) )     //<<<< Angle Correction transform
        ->connect_to(
            new SKOutputNumber(kSKPathHeadingMagnetic, kConfigPathHeading_SKM));

If the Deviation value varies significantly over the circle, then a more accurate way of producing the Magnetic Heading is by using the CurveInterpolator transform:

main.cpp:[...]
        //an optional, more complex transform is a CurveInterpolator
        // CurveInterpolator applies deviation corrections
        ->connect_to( new CurveInterpolator( NULL,kConfigPathHeadingDev) )
        // AngleCorrection normalizes to [0..2Pi] range, when CurveInterpolator output < 0 or > 2*Pi
        ->connect_to(new AngleCorrection(0.0, 0.0, ""))
        ->connect_to(
            new SKOutputNumber(kSKPathHeadingMagnetic, kConfigPathHeading_SKM));

The CurveInterpolator uses pairs of (input,output) values stored in non-volatile memory as a lookup-table to convert incoming values into outgoing values. Linear Interpolation is used for input values that are not explicitly listed in the table. By using a dozen or so value pairs, one can approximate a fairly complex Deviation curve. Entering the (input,output) pairs is easily done using the web-interface to the sensor via the kConfigPathHeadingDev menu path provided when instantiating the CurveInterpolator.

Note that the default SensESP CurveInterpolator code only allows for a maximum of about 11 value pairs (due to limits on the length of the JSON object used by the HTTP GET and PUT methods). If this is too low for your purposes, the SensESP code at https://github.com/BjarneBitscrambler/SensESP.git#IncreaseCurveIntPoints modifies the files http.cpp, configurable.cpp, and curveinterpolator.cpp to allow at least 37 value pairs, which is enough for 10-degree spacing through the full circle.

Manually entering a large set of deviation value pairs can be tedious and error-prone. Instead of using the sensor's web interface, one can export the (input,output) pairs as a .CSV file (since the deviation plot was likely created in a spreadsheet anyway), then process the .CSV file using an AWK script to create a command line that uses curl to transfer the data directly to the sensor. An example command line is:

curl -X PUT -H "Content-Type: application/json" -d '{"samples":[{"input":0.0000,"output":0.0873},{"input":0.1745,"output":0.2793},{"input":0.3491,"output":0.4800},{"input":0.5236,"output":0.7330},{"input":0.6981,"output":0.9163}]}' http://192.168.1.10/config/sensors/heading/deviation > result

where the IP address for this sensor was 192.168.1.10 and the config path was /config/sensors/heading/deviation (modify as needed). A sample AWK script for performing the conversion is in this repository's /examples folder.

Relationship of the Axes and Terminology

X,Y,Z axes are orthogonal, in a Right-handed coordinate system. On the Adafruit FXOS8700/FXAS21002 sensor PCB, the axes are printed on the top-side silkscreen.

If the sensor is mounted with the X-axis pointing to the bow of the boat and the Y-axis pointing to Port, then Z points up and the following applies:

  • Heading is rotation about the Z-axis. It increases with rotation to starboard.
  • Pitch is rotation about the Y-axis. Positive is when the bow points up.
  • Roll is rotation about the X-axis. Positive is rolling to starboard.
  • Turn-rate is rotation about the Z-axis. Positive is increasing bearing.
  • Roll-rate is rotation about the X-axis. Positive is rolling to starboard.
  • Pitch-rate is rotation about the Y-axis. Positive is bow rising.
  • Acceleration is measured in the direction of the corresponding axis.

If the sensor is mounted differently, or you prefer an alternate nomenclature, the Get___() methods in sensor_fusion_class.cpp can be adjusted.

Output to NMEA2000

Here are some unformatted notes for getting the orientation data output on a NMEA2000 (N2K) network.

Signal K Server

Install Plugin signalk-to-nmea2000

  • use menu Appstore -> Available and Appstore -> Installed to locate and install the plugin. A reboot is needed after installation.
  • use menu Server -> Plugin Config to configure the Signal K to NMEA 2000 plugin. Click on Enabled. Scroll down the list of PGNs to locate the ones you want (e.g. Attitude 127257 and Heading 127250) and click their Enabled checkboxes. Add any optional resend or sources details. Click Submit.

Configure Output

  • Use menu item Server -> Data Connections and add the N2Knetwork connection.
  • Click on the Enabled switch. Choose a NMEA 2000 Source: I use the Canbus (canboatjs) drop-down menu entry.
  • Specify the Interface: I used can0.
  • Add any optional filters you want.
  • Click on Apply.

Restart` the Signal K server (it doesn't say to do so, but I found changes made in Data Connections don't take effect until the server is restarted.

You should now have a NMEA2000 packet emitted each time the Signal K server receives updated data for the PGN(s) you enabled. If, for example, the ESP sensor is reporting Magnetic Heading every 100 ms then you should see NMEA 2000 packets with PGN 127250 at 10 Hz.

Updating Firmware

Once you are happy with the performance of your eCompass, you will probably want to securely mount it in a location that is far from magnetic disturbances. This may not be an easy-to-access location, so if you ever wish to update the firmware remember that the ESP devices can be updated via WiFi in addition to the usual USB cable. The command within platformio to do this is:

pio run --target upload --upload-port <your_device's_IP-address>

The IP address can be determined from your Signal K server. Assuming you haven't disabled the standard SensESP sensors (one of which is the sensor's IP address), you can use the Signal K Instrument Panel to look at the path <your_device_name>.ipaddr

Performance Testing

Since heading information is time-sensitive (e.g. if it is being used to overlay a radar plot on a chart), it is important the the Signal K server receive frequent updates and and process them quickly. Many eCompasses operate at an update rate of 10 Hz or faster. With that in mind, I tested the performance of the Orientation sensor for max update rate and latency before readings appear on the NMEA 2000 bus.

Details are in the wiki page SignalK-Orientation Performance Testing