Tern

Log ·

Surprise: Temperature Matters

I quietly assumed that temperature wasn't a significant impact. Boy did that turn out to be wrong.

One Heading Is Not a Measurement

Seeking a reason for a 17 degree heading error on a recent dive, I decided to test for a temperature impact on indicated heading. After a cursory test (heat gun and ice packs), I came up with a conclusion that was interesting, and which failed to give me the smoking gun I sought. The magnetometer drifts with temperature, but carefully measured it came to about 0.43° of heading per °C. On a typical summer lake dive, where the temperature goes from around 21 °C at the surface to around 10 °C at depth, that’s about 5° of heading error. Annoying and worth compensating for, not dangerous, and a long way from the ~17° error that sent me looking in the first place. Temperature was a smaller suspect than I’d feared. Action to take, but nothing too significant.

The number was measured correctly. It was also measured at exactly one heading, and that turned out to matter.

Over the next couple of days I ran the same heat-gun test at north, east, south and west. At one of those headings the compass moved about 2° per °C. So much for case closed.

Setup with scooter on the bench, heating with a well-worn heat gun
Sometimes science looks like beakers and lab coats. Sometimes it's a heat gun and a bubble level on a stool with a lot of waiting.

The test

The setup is dull, which is what you want from a test:

  1. Park the scooter level on a non-magnetic stand, pointed at a cardinal heading by an ordinary magnetic compass. Not by the unit’s own display, since that’s the thing under test.
  2. Start logging. Let it sit for a few seconds to get a baseline.
  3. Heat the nav board with a heat gun for 45–60 seconds.
  4. Walk away. Let it cool back toward room temperature for 45–60 minutes without touching it.
  5. Rotate 90° and do it again.

One obvious thing: measure temperature on the IMU, not from the depth sensor. In code, they’re both reported as temperature, source ambiguous, so it’s pretty easy to log the wrong one and do a bunch of math based on a number that absolutely is not correlated with the die temp. The LIS3MDL magnetometer has a temperature sensor on its own die, so that’s the one I used. Measure with the sensor that actually feels the heat.

What the data says

The heat gun took the magnetometer die from about 20 °C up to 45–69 °C, depending on how enthusiastic I was that run. Heading swung hard:

PointedPeak die tempHeading swing at peak
North53 °C+41°
East45 °C−49°
South69 °C−93°
West55 °C+65°

Now, you’re unlikely to see 70C water on a typical dive, and similarly unlikely to see 50C temperature swings in either direction. So 90 degree heading swings are unlikely. But it’s good data to understand what’s going on.

But heading isn’t what the sensor measures. The magnetometer measures a magnetic field in three axes, and heading is calculated from that. So I looked at the raw field instead. That’s where things got simple:

Pointedx (µT/°C)y (µT/°C)z (µT/°C)
North0.480.400.29
East0.490.450.26
South0.510.420.23
West (1)0.510.430.32
West (2)0.500.420.29

The field reading drifts the same amount no matter which way the scooter points. Every °C adds roughly the same small vector to the reading: about half a microtesla on each horizontal axis. For scale, the horizontal part of Earth’s field here is about 17–19 µT. It’s the same at every heading, on both heating and cooling, and close to linear all the way up to 69 °C. It isn’t the scooter’s steel or the building. It’s the sensor, and the error travels with it.

A few things I checked before believing any of this:

Why one heading lied

If the sensor error is the same at every heading, why does the heading error change so much?

A compass works out heading from the direction of the horizontal field. Add a small fixed offset to that field and it tips the arrow sideways, but only the part of the offset that’s perpendicular to the field does any tipping. Point the scooter one way and the offset is mostly sideways, so heading moves a lot. Point it 90° away and the same offset lines up with the field, making it look slightly stronger or weaker without turning it at all, and heading barely moves.

So the heading error goes up and down like a sine wave as you turn. With a well-centred calibration on this unit it works out to roughly:

heading error ≈ 2.2° per °C × sin(40° − heading)

Near zero pointing northeast or southwest, worst pointing southeast or northwest. Friday’s test was pointed a little east of north, about 016° magnetic, which is close to the northeast quiet spot. At that heading the effect is roughly a third of its worst. That number was never wrong. It was just a point on a curve, and I’d treated it as the curve.

Polar plot of heading error per °C against heading. The curve crosses zero at 040° and 220°, peaks at +2.2°/°C at 310° and −2.2°/°C at 130°. The north, east, south and two west runs sit on the curve at +1.3, −1.7, −1.4 and +1.7°/°C. Friday's 016° test is marked at +0.9°/°C.
The same sensor drift, seen from every direction. Friday's test sampled one of the quiet spots.

Here’s what that means if you calibrate at 21 °C and then dive colder:

Headingper °CDive 5 °C colderDive 10 °C colder
N+1.4°−8°−19°
NE−0.3°+2°+4°
E−1.7°+10°+22°
SE−2.2°+11°+20°
S−1.4°+6°+10°
SW+0.3°−1°−2°
W+1.7°−8°−14°
NW+2.2°−11°−22°

A 10 °C swing is an ordinary Lake Washington dive. Twenty degrees of heading error over a 500 m leg puts you about 170 m off to one side. That’s not “annoying.” That’s the wrong wreck.

There’s a second amplifier: the calibration

How much a microtesla of drift moves heading depends on how big the calibrated horizontal field is, and a good calibration makes that the same at every heading. On the day of the test, mine wasn’t. The field the unit saw was about 9 µT pointing north and 29 µT pointing south, so north was roughly three times as sensitive as south. With that calibration, 5 °C of warming moved heading +14° at north and only −3.6° at south.

The practical lesson is that “degrees of heading per degree of temperature” isn’t a property of the sensor at all. It’s a property of the sensor, plus the calibration, plus which way you’re pointing.

Fix the cause, not the symptom

My first instinct was to build a correction table: at this heading and this temperature, add this many degrees. It would have worked, sort of, until the next recalibration changed the geometry and quietly invalidated the whole table. Then I’d have to build a 4 hour thermal test into every calibration cycle, which is wholly untenable.

The better question, and it was a good one to be asked, was: if the temperature effect is on the raw magnetic reading, why correct the heading at all?

So the firmware now corrects the reading. Before any calibration math runs, every magnetometer sample has its temperature drift subtracted:

reading -= coefficient × (die temperature − 21 °C)

That’s three numbers for x, y and z, plus a reference temperature, in a small mag_temp.json file on the unit. 21 °C is where every calibration I’ve done so far was collected, so existing calibrations stay valid. It lives at the bottom of the stack, so it survives recalibration, and it fixes everything else that uses the magnetometer: the attitude filter, the dive logs, and the calibration samples themselves. Die temperature now goes in every log, even the smallest, so any future dive can be re-examined.

What the manufacturer says

I went looking for ST’s own temperature coefficient before trusting mine. The LIS3MDL datasheet lists a zero-field offset of ±1 gauss at 25 °C, which is about 100 µT, and gives no temperature coefficient for it at all. The application note (AN4602) says the chip compensates sensitivity for temperature internally. It says nothing about offset.

So “temperature compensated” on a sensor datasheet may mean the gain, not the zero point. The drift that matters here is unspecified, uncorrected by the part, and potentially different from unit to unit. This will be one that needs to be measured on every board until I know what the pattern is.

What I don’t know yet

What I’d tell past me