Op amp and/or mux problems in RTD to ADC circuit

OBW0549

Joined Mar 2, 2015
3,566
Wow!!!! I think that was it. I tried dropping the A term portion of the formula entirely in my spreadsheet, making the formula just adc*B_TERM+C_TERM and got all the display readings to within a few tenths. I can't believe I found it. This is crazy. And all cause a few parentheses were in the wrong place. I still don't feel confident about which way the syntax should look to make it work, but I think I found the root of the problem.
Dropping it where? Which column?
 

Tesla23

Joined May 10, 2009
542
Code:
static const float A_TERM = 1.4841E-6;
static const float B_TERM = 3.3878E-2;
static const float C_TERM = 1.3228E2;

...

float _calcTemperature(uint16_t adc)
{
  return ((float)(adc*adc))*A_TERM + (float)(adc*B_TERM) + C_TERM;
}
Is part of this formula not handled correctly? Could we be truncating important numbers in the first and/or second portions of this formula? I'm embarrassed to have the formula right in front of me and still be so unsure, but it doesn't seem quite right to me.
I'm no expert, but from my (rudimentary) knowledge of the ANSI standard, C does it's integer arithmetic by default in the basic int type - which may be 16 bits in your case. If that is the case then it is possible that adc*adc does a 16x16 multiply and then truncates the result back to 16 bits, you need to talk to someone who is an expert. I'd suggest that long(adc)*long(adc) would give you a 32 bit result. So there is a real possibility that the first term is not even evaluating as you expect.

If the float is needed in the second term then it should be float(adc)*B_TERM (or (float)adc * B_TERM), but I thought that ANSI compilers would automatically promote the adc to type float and you could simply put adc*B_TERM.

Talk to an expert in coding the MSP430 about that expression, about where you need to convert to float and about the possible overflow in adc*adc.
If you must try something, (although I recall you saying you didn't even have a compiler), try

Code:
float _calcTemperature(uint16_t adc)
{
 // to do an integer multiplication
  return float(long(adc)*long(adc)) * A_TERM + float(adc) * B_TERM + C_TERM;
 //  or do the multiplication in floating point - the compiler should optimise and convert adc to a float only once
  return float(adc)*float(adc) * A_TERM + float(adc) * B_TERM + C_TERM;
}
Note: I prefer float(adc), but (float)adc is the same.

Chasing bugs in code is nowhere as satisfying as chasing the last few mV in the analogue stages. It's clear you have bigger problems that a 14mV offset.
 

OBW0549

Joined Mar 2, 2015
3,566
I would like very much to see the actual ADC input voltage vs. RTD resistance, as I said in #36 above; I think that's going to be key to localizing the problem. If the measured ADC input voltages agree with their calculated values, the problem is definitely something going on in the microcontroller: either Vref configuration, or improper math, or something else. If the voltage levels aren't what they should be, then the errors are occurring ahead of the ADC.
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
Dropping it where? Which column?
The fourth green column, "calc temp display," was taking the 0-4095 ADC value from the adjacent column and plugging it into the quadratic equation used to approximate the temperature reading (the values in the quadratic equation were taken directly from the code.) If I replace the Ax^2 + Bx + C style formula with just the Bx + C portion, the resulting temperatures match the displayed numbers from my real world tests. So clearly the calculation in the code is somehow ignoring the Ax^2 portion of the formula, presumably because of the float implementation.
 

OBW0549

Joined Mar 2, 2015
3,566
The fourth green column, "calc temp display," was taking the 0-4095 ADC value from the adjacent column and plugging it into the quadratic equation used to approximate the temperature reading (the values in the quadratic equation were taken directly from the code.) If I replace the Ax^2 + Bx + C style formula with just the Bx + C portion, the resulting temperatures match the displayed numbers from my real world tests. So clearly the calculation in the code is somehow ignoring the Ax^2 portion of the formula, presumably because of the float implementation.
Ah. OK, that's what I thought but I wanted to make sure.

Yes, I have to agree that it appears the code is not evaluating that polynomial properly. As to why, I'd have to leave it up to the software engineer to figure out, as I don't have the knowledge needed.
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
If you must try something, (although I recall you saying you didn't even have a compiler), try

Code:
float _calcTemperature(uint16_t adc)
{
// to do an integer multiplication
  return float(long(adc)*long(adc)) * A_TERM + float(adc) * B_TERM + C_TERM;
//  or do the multiplication in floating point - the compiler should optimise and convert adc to a float only once
  return float(adc)*float(adc) * A_TERM + float(adc) * B_TERM + C_TERM;
}
Note: I prefer float(adc), but (float)adc is the same.

Chasing bugs in code is nowhere as satisfying as chasing the last few mV in the analogue stages. It's clear you have bigger problems that a 14mV offset.
My firmware editing/compiling situation is a bit odd. My company has contracted with a programmer to make all sorts of changes to our code for a new version of the machine. I've known about this RTD measurement problem, without understanding its exact nature, for several years now, but been powerless to fix it, as we have neither EEs nor programmers on staff.

Unfortunately, I'm the only person who really cares about this issue (since it can be made functional for all practical purposes through programmed offsets) and this fix was not part of scope of the project and budget. As such, I can't spend a lot of time making the programmer try stuff while I slowly perform experiments and figure things out.

However, with a smoking gun problem like this one, it should be perfectly reasonable for me to say "figure out where the parentheses go to make this function work" and have a working machine in a matter of minutes!

While I'm not sure how exactly to fix this line of code, I'm confident that our programmer will be able to fix it easily now that we know exactly where to look.
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
I'll update again when new code is in place (hopefully in a day or two.) Until then, thank you all so much!

Even though I'm almost positive the circuit isn't the problem at this point, I learned a lot in going through the process of analyzing it, testing what I could, and digging deeper into subtleties like grounding differentials. I really appreciate all the help.

Also, it was only because I felt confident that we had a pretty good handle on the circuit that I really buckled down and worked harder on figuring out the code, which is dense, confusing, and a bit tedious for me. I never would've worked this out without all your help.
 

Tesla23

Joined May 10, 2009
542
While I'm not sure how exactly to fix this line of code, I'm confident that our programmer will be able to fix it easily now that we know exactly where to look.
I notice that the MSP430 doesn't have hardware floating point, so if floating point operations are expensive time-wise, you can save a multiplication using the polynomial evaluation trick:

Code:
float _calcTemperature(uint16_t adc)
{
return C_TERM + float(adc) * (B_TERM + float(adc) * A_TERM);
}
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
I notice that the MSP430 doesn't have hardware floating point, so if floating point operations are expensive time-wise, you can save a multiplication using the polynomial evaluation trick:

Code:
float _calcTemperature(uint16_t adc)
{
return C_TERM + float(adc) * (B_TERM + float(adc) * A_TERM);
}
Cool trick and great idea. Thanks!
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
The programmer emailed code today with a mod based on Tesla23's last suggestion:
Code:
adc_initTemperature();
    adc_startConversion();
    float adc = (float)adc_getResult();
    adc_disable();
    return adc*(adc*A_TERM + B_TERM) + C_TERM;
The operation saving rearrangement was used, and the variable type conversion was avoided by simply making adc a floating point variable to begin with. The code structure looks a little different than before because two functions were combined into one.

The results were great! Instead of up to 15F of error, the errors with my reference resistors were down to:

0.6 @ 173.5 to
0.4 @ 252.1

If that's as good as it gets, we're already in pretty good shape. The board components and RTDs both have larger tolerances that force us to calibrate each input anyway. But, the programmer is excited about fixing this now too, so tomorrow we're going to try changing the ground reference and manipulating delay times between mux switching and adc sampling. I had misread the code, and there is no 1ms delay. That line is commented out, but somehow I didn't notice!

My gut feeling is that a capacitor charging issue would yield errors that were proportional (in a fixed amount of time, the cap will charge to x% of final voltage) so I'd expect larger errors at higher temps. Whereas the ground reference issue would lead to errors that get smaller approaching full scale. Hard to say with such a tight spread, but this feels to me more like the grounding one.

Anyway, tomorrow we'll play with both timing and ground references and see if we can squeeze out that last half-degree. If not, I'd still call this a win.

Thanks everyone!!! I'll update again soon with results of tomorrow's experiments.
 
Last edited:

Tesla23

Joined May 10, 2009
542
My gut feeling is that a capacitor charging issue would yield errors that were a proportional (in a fixed amount of time, the cap will charge to x% of final voltage) so I'd expect larger errors at higher temps. Whereas the ground reference issue would lead to errors that get smaller approaching full scale. Hard to say with such a tight spread, but this feels to me more like the grounding one.
You should also consider the effect of charge injection by the mux when it switches - this is another reason for the delay, and it will give you an offset. If you look at the ADG708 datasheet, you will see

dQ.PNG

What this says is that if you are operating at 3V, when switching a 1V signal you will get a charge injection on switching of about 6pC. If this was dumped directly into a 1nF capacitor (i.e. no C30) you would see a voltage change of
dV = dQ/C = 6mV.

using C30 reduces this by a factor of 21 if my calculations are correct, but it doesn't seem to be the right thing to do - if you have the time - wait for the capacitors to charge to the desired voltage and as a by-product, the transient effect of the glitch will die out.

You are correct in that one effect of the transient is that the capacitors will not have fully charged to the new voltage, and that could be considered a relatively fixed scaling factor, but the other effect you are ignoring is that the old value on the capacitor will not have discharged. So by not allowing sufficient delay, you are seeing too much of the 'old' value and not enough of the 'new' value, plus some of a fixed offset due to the glitch. This effect is worse when trying to read successive channels that have a significant voltage difference.
 
Last edited:

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
You should also consider the effect of charge injection by the mux when it switches - this is another reason for the delay, and it will give you an offset. If you look at the ADG708 datasheet, you will see

View attachment 87525

What this says is that if you are operating at 3V, when switching a 1V signal you will get a charge injection on switching of about 6pC. If this was dumped directly into a 1nF capacitor (i.e. no C30) you would see a voltage change of
dV = dQ/C = 6mV.

using C30 reduces this by a factor of 21 if my calculations are correct, but it doesn't seem to be the right thing to do - if you have the time - wait for the capacitors to charge to the desired voltage and as a by-product, the transient effect of the glitch will die out.
Thanks! Didn't even know charge injection from a mux existed. This may turn into an interesting problem. Adding the delay will not be as easy as simply adding the delay. Our machine is already dangerously close to missing inputs from certain short duration signals because a number of important inputs are monitored with polling instead of interrupts. If we add frequent delays during which no polling occurs, we'll almost certainly have problems.

So adding the delay would either require some other means of coming back to the adc after a fixed period of time (there's already a scheduler handling many of the functions in the code, so that seems a likely candidate) or we'd need to stop polling the inputs and use interrupts instead. I don't know code or this processor well enough to know the pros and cons of either approach, but I know that either one involves a fair bit of restructuring.

Still, we can start by just adding the delay the simple way to see what effect it has on RTD readings, then decide how much effort it's worth to properly implement a timing scheme.
 

Tesla23

Joined May 10, 2009
542
Thanks! Didn't even know charge injection from a mux existed. This may turn into an interesting problem. Adding the delay will not be as easy as simply adding the delay. Our machine is already dangerously close to missing inputs from certain short duration signals because a number of important inputs are monitored with polling instead of interrupts. If we add frequent delays during which no polling occurs, we'll almost certainly have problems.

So adding the delay would either require some other means of coming back to the adc after a fixed period of time (there's already a scheduler handling many of the functions in the code, so that seems a likely candidate) or we'd need to stop polling the inputs and use interrupts instead. I don't know code or this processor well enough to know the pros and cons of either approach, but I know that either one involves a fair bit of restructuring.

Still, we can start by just adding the delay the simple way to see what effect it has on RTD readings, then decide how much effort it's worth to properly implement a timing scheme.
Sounds like your current flow is:

... stuff
set mux to current RTD
read ADC
... stuff

If you are reading the RTDs in a particular order, then it may be easy to change it to:

... stuff
read ADC
set mux to next RTD
... stuff
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
Good call. I'll mention that possibility to our programmer tomorrow. I much prefer simple solutions like that to the multi-layered complexity I suggested earlier! Who knows, our new programmer is pretty clever and may already be brainstorming ideas like this as well. Tomorrow should be fun and interesting.
 

OBW0549

Joined Mar 2, 2015
3,566
The results were great! Instead of up to 15F of error, the errors with my reference resistors were down to:

0.6 @ 173.5 to
0.4 @ 252.1

If that's as good as it gets, we're already in pretty good shape.
I wouldn't say you're "in pretty good shape", I'd say you're done. You've nailed it. Congrats!

If you're only seeing half a degree of error at those temperatures, then I'd say your gains, your offsets, and your calculations are all spot-on, and the only further improvement you'll be able to make would be by using 0.01% resistors throughout the signal chain and by using a better grade of instrumentation amp and a premium-quality off-chip voltage reference. And given our discussion so far, I can't imagine any further improvement would be worth the expense.

Good job!!
 

Tesla23

Joined May 10, 2009
542
I wouldn't say you're "in pretty good shape", I'd say you're done. You've nailed it. Congrats!

If you're only seeing half a degree of error at those temperatures, then I'd say your gains, your offsets, and your calculations are all spot-on, and the only further improvement you'll be able to make would be by using 0.01% resistors throughout the signal chain and by using a better grade of instrumentation amp and a premium-quality off-chip voltage reference. And given our discussion so far, I can't imagine any further improvement would be worth the expense.

Good job!!
I agree, but..

You've eliminated the major static errors, but given the absence of significant delay between switching the mux and reading the ADC, I'd like to see this accuracy repeated as the reading on the previous mux setting is varied.

For example, say you read RTD1 then RTD2, and let's say the reading on RTD2 gives a voltage at the bottom of the likely range. I'd like to know how the reading on RTD2 changes as the voltage on RTD1 varies from the bottom of the range to the top of the range. This is because before the mux changes C30 is charged with the voltage differential corresponding to the reading from RTD1, then you switch the mux and the voltage on C30 slowly changes to that corresponding to RTD2, and sometime during this transient you do an ADC conversion. If the delay is not long enough your reading will be a combination of the voltages corresponding to RTD1 and RTD2. As a worst case test you should try to arrange the previous mux voltage to have maximum difference to the current mux voltage.

Now that you tell me that there is no significant delay between changing the mux and the ADC conversion, I really don't like C30!
 

OBW0549

Joined Mar 2, 2015
3,566
You're right, the effects of C30 should not be overlooked. I also agree with your advice in post #54 about changing the order of execution of the ADC-related tasks to maximize acquisition time.

With C30 in there, there ought to be at least a 1 millisecond delay between switching the mux and starting your conversion. Half a millisecond might suffice (that would be about 23 RC time constants), but a full millisecond would be better.
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
We did a few experiments today. Neither changing the Vref- for the ADC nor adding a 1ms delay between MUX switching and ADC conversion made any difference in temperature readout. Given that we're already within 0.4 to 0.6°F of perfect, I'm very content to call this done. I'm so happy to finally have realistic readings from this circuit after nearly 5 years of confusion. Thank you very, very much to everyone who helped me with this. Couldn't have done it without you all!
 
Top