Doing the math

A project log for Light Aircraft Fuel Timer

Device to assist with monitoring fuel in a light aircraft's multiple tanks.

Stephen HoldawayStephen Holdaway 03/05/2019 at 09:200 Comments

From the original list of features I sketched out a year or two ago, I primarily want address the following:

Common to all of these are two calculations:

These calculations have to be reliable, but not necessarily fast. As long as the system stays responsive on a human scale (eg. a few hundred milliseconds when a button is pushed). This makes the main constraints program size and memory.

Half of the ATMega328p's 2KB of SRAM is currently taken up by a screen buffer, but that could be reduced at the cost of draw time if needed.

Diffing date/times on an AVR

Time is fairly straight forward to store and read back. The real-time clock source for this project is a Maxim DS3231, which provides a date and time in binary coded decimal (BCD) format. This can be simply be stored as a series of 8-bit integers to avoid dealing with BCD values outside of the RTC read/write routines:

typedef struct RtcTime {
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
    uint8_t day;
    uint8_t month;
    uint8_t year;
} RtcTime;

It's easy to display this information, but as soon as you want to do any calculations the firmware needs to know all about how calendars work: days in months, months in year, and how leap years work. Reducing the problem to diffing times within the same day isn't suitable here as the clock needs to be set to UTC to compare against a configured SAR time. Where I live is 12-13 hours ahead of UTC, so it's very common for a flight to cross the boundary of a UTC day.

AVR libc provides a version of time.h with some modifications to make it more suitable for AVR microcontrollers, like using 32-bit integers instead of double-precision floats, but it still uses a fairly large chunk of program space just to diff two times. Calling only mktime and difftime increases the program size by around 1,500 bytes: 4.5% of the total program space on the 328p! Each tm struct also wastes 6 bytes of SRAM with fields that won't be used here.

This project doesn't need completely general date/time functions (currently), so a more specialised time diff implementation can save a bit of space:

An implementation with these assumptions uses only 334 bytes of program space. With another small function, the seconds columns of two RtcTime structs could be diffed to supplement the delta of minutes with a remainder of seconds.

How much fuel is left?

Fuel is a different beast to keep track of. The burn rate in flight (litres per hour) and capacity of each tank (litres) both fit comfortably into the 0-255 range for light aircraft, but to find the quantity remaining after an elapsed time requires much greater resolution than this. Enter fixed point math.

The resolution a fixed point number can represent is simply 1 over the scaling factor, so finding an appropriate scaling factor is fairly easy. For reference, a burn rate of 38L/h is exactly 10mL per second, or 600mL per minute.

Scaling factorRequired typeResolution (approx)
28Unsigned 16-bit3.9mL
216Unsigned 24-bit0.15mL
224Unsigned 32-bit0.00006mL

Scaling by 216 would be the minimum suitable resolution in this case to avoid accumulating too much error, but as 24-bit values aren't typically used (though they're technically supported by GCC), it's easier to use a 32-bit value and go with a 224 scaling factor. The result is a "UQ8.24" fixed-point value - enough resolution to only be millilitres out after days of operation.

Once we have a fixed point representation for the fuel quantity in each tank, we'll need a strategy to keep track of how much fuel is onboard. Let's explore a few:

Reduce the selected tank's quantity by the per-second burn rate for every second that passes.

I was considering using this for a while, and it would technically work, but it has some problems:

Store the start and stop time of each state of flight. Calculate remaining fuel as total fuel minus the burn in each recorded period.

This solves the issues from the previous idea:

However, it introduces one major issue:

How about a small modification to keep storage bounded:

Store the start time of the current state of flight. When the state of flight changes, calculate the fuel used during the last state and subtract it from the current tank

This is the approach I'm planning to implement.