Non-Linear Equation for DAC...Brain is stuck

Hello world. I'm working on a project that has a timer and a fuel gauge and have been banging my head against the wall trying to figure out a formula. I had a hard time researching and the best term that describes it is "Non-Linear Equation". I've been staring at X's and Y's for a couple days.

First, the fuel gauge is current driven. The needle reads Empty with 12V @ 10ohm on the signal line. Full is around 220ohm. I am using an MCP4728 DAC to output a nice smooth analog signal. The DAC has a 12-Bit resolution (0-4095 in DEC, 0-5V Vref). I have a timer that counts down starting from 240(4 Min).

:DAC Output(0-4095):
@720 Needle reads Full
@735 Needle reads 3/4ths
@760 Needle reads 1/2
@800 Needle reads 1/4th
@1100 Needle reads Empty

As you can see I cannot simply map the DAC output to a percentage of the total time left. What happens is the needle reads 1/4th and I still have 3 minutes left hence the Non-Linear equation. Here is a quick snippet of my state machine:

case RUNNING:

		status = "RUNNING";
		timer.Timer();

		//Calculate gauge values.
		unsigned long TotalSeconds;
		TotalSeconds = timer.ShowTotalSeconds();
		quadDACVoltVal = map(TotalSeconds, 0, ((timerMinutes * 60) + timerSeconds), quadDACVoltLow, quadDACVoltHigh);
		quadDACSpeedVal = map(TotalSeconds, 0, ((timerMinutes * 60) + timerSeconds), quadDACSpeedLow, quadDACSpeedHigh);
		quadDACFuelVal = map(TotalSeconds, 0, ((timerMinutes * 60) + timerSeconds), quadDACFuelLow, quadDACFuelHigh);

		quadDAC.voutWrite(quadDACFuelVal, quadDACVoltVal, quadDACSpeedVal, 0);

		if (timer.TimeHasChanged() )
		{
			//Dump timer value to Serial port.
			Serial.print("Time Left> ");
			Serial.print(timer.ShowMinutes());
			Serial.print(":");
			Serial.println(timer.ShowSeconds());
			Serial.print("Total Clock: ");
			Serial.println(timer.ShowTotalSeconds());
			
			Serial.println("");
			Serial.print("Volt: ");
			Serial.println(quadDACVoltVal);
			Serial.print("Speed: ");
			Serial.println(quadDACSpeedVal);
			Serial.print("Fuel: ");
			Serial.println(quadDACFuelVal);

			//Check if timer has ended.
			if (timer.TimeCheck() == true )
			{
				Serial.println("Timer has finished!");
				machine = STOPPED;
			}
		}

		break;

After talking to a buddy he was stating that I should have a ramping X that increases based on time taken. So lets say X = DAC Multiplier/Divider?. First step should occur after 4 seconds (X= X * 1.01), second step after 4 seconds (X= X * 1.02) and so on. I'm hoping someone has ran into this and I'm looking at this wrong. Any help would be much appreciated. Thank you.

it is quite significantly non-linear at the low end.
screenshot.86.png
The usual way to handle this sort of thing with with a lookup table. You create an array with pairs of data that say "this value" maps to "that value", and you can create any function shape that you want. If you want smooth control of the guage though, you will need many more points in the table.

Is there any information about the sensor you can share with use? A datasheet or some info about the input circuit?

To add to Jiggy ninja:

Look up interpolation. You don't need to map every point, you can put more points where it changes rapidly.

For a smooth curve, the data can be fit with an equation like a simple exponential, as shown in the plot below. For an exponential or logarithmic fit, you need to use a small number like 0.01 for "empty" rather than 0.

Here is a quick snippet from KiCad.

The fuel gauge is literally a fuel gauge from a car. I'm just using it to display the time left to the user. I'm working blind as I cannot locate the datasheet for this particular gauge. It has MORS printed on the bottom under the needle but I wasn't able to locate it using the only part number printed on the back. This gauge may have came from another country with limited access to resources.

Thanks for the suggestions. I will try some experiments and do some more research when I get home tonight. I still have to make the board, drill, and solder. Everything is bread boarded at the moment.

Can you post the schematic instead? The board is pretty useless unless we're helping debug a layout issue.

I found this website which says that gauges like this tend to work with bimettalic strips. Do you know how much current the gauge requires? If it's over about 15 mA it'll be overdriving the DAC's output without a buffer.

A better but more expensive option is an instrument stepper motor. They are small and not strong - they are built to drive an indicator needle and not much more - but accurate and fast. Being a stepper, the position can be specified exactly. You zero the needle by driving it against a stopper.

Hi,

Hello world. I'm working on a project that has a timer and a fuel gauge and have been banging my head against the wall trying to figure out a formula. I had a hard time researching and the best term that describes it is "Non-Linear Equation". I've been staring at X's and Y's for a couple days.

Why do you need a timer?
What do you want to display?

I'm just using it to display the time left to the user.

Of what?
What is the application?

Sorry but I thought all you want to do is calibrate a on-linear fuel level pickup to a linear gauge to show fuel level.

Tom.... :slight_smile:

Jiggy-Ninja: I have a tendency to layout the board similar to the schematic :slight_smile: Excuse the mess. The DAC has a resistor ladder with 4095 taps. No resistor is needed from Vout_A-D.

Tom: I left out some info as I thought it was irrelevant. I have taken an RTF kit for an RC Heli/Boat/Car/Quadro and put the transmitter inside a makeshift cabinet. When the user drops $0.25 it will release controls and turn on for 4 minutes. The time can be changed in the future. I simply would like to map the total time left to a position on the fuel gauge and make it as smooth as possible. Nothing more. If you notice an issue in the schematic please feel free to drop a hint. I'm open minded.

Thanks for the suggestions everyone. This put me in the right direction. I wasn't able to test last night as I had focused on developing and etching. I'm going to try "multiMap" and "SmoothStep". I'm also going to make a lookup table and see which option works best. I'm not too worried about processing time. If it takes a few extra micros or even 100, it wont effect the overall functionality.

Jiggy-Ninja: I have a tendency to layout the board similar to the schematic :slight_smile: Excuse the mess. The DAC has a resistor ladder with 4095 taps. No resistor is needed from Vout_A-D.

I can read a datasheet just fine. I also have a couple of Microchip's DACs myself so I know how they work. That's not the problem. The problem is the fuel gauge with the unknown internal mechanism and unknown driving requirements.

If it's a bimetallic strip like the HowStuffWorks page suggests, it may require a non-trivial amount of current in order to get enough heating to move the needle. It says the sender is hooked to a variable resistor, but it doesn't say how large that resistor is. it could be a 100 ohm potentiometer, 500, 1k, 10k, anything. The DAC can only output a limited amount of current[1], and if the gauge requires more than that you may damage it in the long term.

Link to the schematic image is broken. Can you highlight or circle around the connection to the fuel gauge when you repost it, there was a lot of irrelevant stuff on there and it was hard to read the text.

Is this fuel gauge a 2-terminal device, or does it have more wires than that? If it's just a 2-terminal device, you can convert a linear voltage regulator into a constant current generator to test the current required for different fuel levels. This might provide a more useful figure than the DAC output if it's just a straight connection like I thought I saw.


The current through the DUT is given by the equation in the image. You can adjust the resistor to give different amounts of current to the gauge, and plot the displayed value against those. Then we can work out a driving circuit.

[1] 25 mA is the absolute maximum quoted in the datasheet, but you usually want to stay quite a bit under that. I'd recommend no more than 15 mA.

Here ya go:

/**
:DAC Output(0-4095):
@720 Needle reads Full
@735 Needle reads 3/4ths
@760 Needle reads 1/2
@800 Needle reads 1/4th
@1100 Needle reads Empty
 */

int getDac(float x) {
if(x <= 0)
  return 1100;
else if(x<.25)
  return map(x, 0, .25, 1100, 800);
else if(x<.5)
  return map(x, .25, .5, 800, 760);
else if(x<.75)
  return map(x, .25, .75, 760, 735);
else if(x<1)
  return map(x, .5, 1, 760, 720);
else 
  return 720;
}

float map(float x, float a1, float a2, float b1, float b2) {
  return (x-a1)/(a2-a1)*(b2-b1)+b1;
}

Thanks for the function PaulMurrayCbr. I was staring at the graph from jremington above and it finally hit me... I made some tweaks to my circuit, re-routed traces, and consolidated DACs. The value for the gauge changed slightly. I used a starting exponent of 0.986 instead of 0.98559. Works perfectly.

Just to re-iterate, Timer counts down starting from 3 minutes(Could be 4 or 10, still works well!). Fuel gauge represents how much time is left. Every second I run a routine. Math is done and gauge is adjusted during this routine.

#include <mcp4728.h>
#include <CountUpDownTimer.h>

//Instantiate DAC/Timer classes
mcp4728 quadDAC = mcp4728(1);
CountUpDownTimer timer(DOWN);


//DACs
////Quad {Vout_A, Vout_B, Vout_C, Vout_D} == {Fuel Gauge, Throttle, Rudder, NOT USED}
int quadDACFuelLow = 1000;
int quadDACFuelHigh = 699;
int quadDACFuelVal = 0;

//Timer
const int timerMinutes = 3;
const int timerSeconds = 0;
const int xFactor = 4 * timerMinutes;
int z = 0;
float x = xFactor;
float y = 0.98600;


typedef enum State
{
	BOOT,
	IDLE,
	RUNNING,
	STOPPED
};

State machine = BOOT;


void setup() {
	Serial.begin(9600);
	Serial.setTimeout(100);

	quadDAC.begin();
	quadDAC.vdd(5000);
	quadDAC.setVref(1,1,1,1);
	quadDAC.setGain(1,1,1,1);

	machine = BOOT;

}


void loop() {

switch (machine)
	{
	case BOOT:

		quadDAC.voutWrite(0, quadDACThrottleVal, quadDACRudderVal, 0);
		machine = IDLE;

		break;


	case IDLE:

		//Start Timer
		timer.SetTimer(0,timerMinutes,timerSeconds);
		timer.StartTimer();
			
		//Reset DAC vars
		z = 0;                                 //Counter: Seconds passed.
		x = xFactor;                           //Exponent: How many seconds before we move needle 1 step.
		quadDACFuelVal = quadDACFuelHigh;

		//Change state machine to RUNNING
		machine = RUNNING;

		break;


	case RUNNING:

		timer.Timer();

		if (timer.TimeHasChanged())
		{
			z += 1;
			
			if (z >= x)
			{
				x *= y;
				y *= 0.986000;
				z = 0;
				if (x < 0.01){
				  //If we are changing every second, bump up DAC step value to 4 instead of 1.
				  quadDACFuelVal += 4;
				}else{
				  quadDACFuelVal += 1;
				}

			quadDAC.analogWrite(0, quadDACFuelVal);
			Serial.print("X = ");
			Serial.println(x);
			Serial.print("Y = ");
			Serial.println(y);
			}
			
      
			//Dump timer value to //Serial port.
			Serial.print("Time Left> ");
			Serial.print(timer.ShowMinutes());
			Serial.print(":");
			Serial.println(timer.ShowSeconds());
			Serial.print("Total Clock: ");
			Serial.println(timer.ShowTotalSeconds());
			
			Serial.println("");
			Serial.print("Fuel: ");
			Serial.println(quadDACFuelVal);
			Serial.println("");
			
			//Check if timer has ended.
			if (timer.TimeCheck() == true )
			{
				Serial.println("Timer has finished!");
				machine = STOPPED;
			}
		}

		break;

	default:
		//Force STOPPED state which will toggle to IDLE afterwards. Safety feature.
		machine = STOPPED;
		break;

	}
}

Works nice and smoothly. As an added bonus, I learned about exponential growth and decay :slight_smile: Thanks for the help everyone.

Jiggy-Ninja: I will be trying your suggestion of the constant current generator as I do not know the values for this gauge still. I was simply lucky. The fuel gauge is a 5 wire gauge. 2 +12v wires for backlights (White or Orange colors), Red +12v, Blue GND, Black SIG. SIG goes to C of BC547, E to GND, B to DAC output (Vout_A). I'm not driving the gauge directly from the DAC. Simply switching a BC547. I was under time constraints for this project so I had very little time last week for testing theory's. So far everything is working great! I was able to hack a 4 channel RC transmitter into a cabinet console and lock controls using the DAC. Even figured out how to self-calibrate to adjust for trim on the transmitter. I will update schematics, and pics below when I get home.

akuma6099:
Jiggy-Ninja: I will be trying your suggestion of the constant current generator as I do not know the values for this gauge still. I was simply lucky. The fuel gauge is a 5 wire gauge. 2 +12v wires for backlights (White or Orange colors), Red +12v, Blue GND, Black SIG. SIG goes to C of BC547, E to GND, B to DAC output (Vout_A). I'm not driving the gauge directly from the DAC. Simply switching a BC547. I was under time constraints for this project so I had very little time last week for testing theory's. So far everything is working great! I was able to hack a 4 channel RC transmitter into a cabinet console and lock controls using the DAC. Even figured out how to self-calibrate to adjust for trim on the transmitter. I will update schematics, and pics below when I get home.

I can see what's going on now.

That is an utterly terrible way to interface the DAC to fuel guage. Fortunately that DAC claims in the datasheet to be about to survive an output short for an infinite amount of time, otherwise you'd have ruined it by now. A BJT is fundamentally a current-operated device, not voltage. The exact output current you get from the transistor will depend heavily on the output resistance of the DAC driver and the current gain of the transistor, both of which can be highly variable quantities. Slap a different DAC chip and a different transistor in there, and even if they are the same part number you might need to recalibrate your lookup table.

How did you figure out the pinout? By experimentation or do you have a reference? If the two backlights are independently controllable, I would suspect 3 of the wires to be power + common return for the two lights, and the remaining pair to be isolated to the meter movement.

You're best way to control the meter movement might be to ditch the DAC and use PWM. Connect the base to an Arduino pin through a 1k resistor, and put another resistor between the collector and gauge. What value that resistor needs to be will depend on the resistance and current requirements of the metallic strip, which you will need to find by experiment.

Thermal devices have very slow response times compared to electronic, and the thermal mass of the strip will make a natural low-pass filter to average out the pulses of current into a (hopefully) steady temperature and steady needle setting. If you notice a detectable wobble you can always increase the PWM frequency above the default 500 Hz.

Its much worse than that, the temperature of a BJT affects the base-emitter voltage, so your
circuit will behave quite differently on a cold day to a hot day, expect about a doubling or
halving of current for each 10 degree temperature change. And the self-heating in the
transistor itself could lead to thermal run-away.

You could add emitter resistors to linearize the response and make it a crude voltage-controlled
current sink - you'd get a much wider range of control voltage then too.