Differential Amplifier Accuracy

EDIT: Better way to ask this question: For Photon 2, AdcReference::INTERNAL is always true. So this implies to me that the 4096=3.3v will vary with actual voltage. In other words if 3v3 is actually 3.5v then a measured voltage of 3.5v will produce 4096 on the ADC. So if I have a differential amplifier that is biased by hardware to have a zero value at actual 3v3 / 2 then I will always see 2048 at on the ADC even with 3v3 fluctuations whenever the outside system is at true zero? Sigh...

Can somebody direct me to an existing discussion - I'm sure it exists but I cannot determine the magic words.

To improve accuracy of a current shunt measurement I got a differential amp (TSC2010) I wired the device using 3v3 as both reference and supply. Now I'm trying to determine how to deal with the Particle device's 3v3 variation. There are a couple settings in firmware API that specify the ADC reference compensation. Before using the TSC2010 I was using an OPA33 with resistor networks and used a spare ADC to measure the 1.65 v reference. Do I still have to measure a reference, now 3v3? Seems like a terrible waste. Is there a proper ADC setting through the API? Is there an internal measurement of the 3v3? In the end, I'm looking for the least variation of shunt current measurement with variation of 3v3.


Hey @davegutz did ever figure out the answer to your question here? Interested in what you found if you did.

No I did not. I am using my own best guess: that if the current draw is less than 500 ma on the 3v3 it is accurate to no better than +/- 3 % (an industry typical value) and the actual value is unknown to the system. It would be easier to run a test than to mine the documents. If I really care, this test should be done anyway. In my case it hasn't become that important yet - other problems. To run the test I will draw varying amounts of current out 3v3 and record the drift on other signals. In a controlled setup this should result in a precise measurement.

Yes, the RTL872x ADC is a successive approximation ADC using 3V3 as the reference voltage.

As there is no way to use an external reference, if you have variations in 3V3 I would use an external I2C or SPI ADC that uses a precision reference. Some of these chips also support differential input, and some have built-in programmable amplifiers, as well.


I considered feeding a spare AD pin with 3v3. How silly is that? An external device connected to a bus appears to be the answer if fine accuracy needed.

It's not silly to have an external reference but it is more difficult than just connecting a pin to 3.3V. The A/D always divides the range between 0 V and whatever the 3.3V reference really is into 4096 steps (so the lowest value is 0 and the highest value is 4095). If you connect to 3.3V it will always read 4095; if you used a voltage divider set 3/4 of 3.3V, it will always read 3/4 of 4095 (around 3071) even when the 3.3V supply goes up or down because the reference voltage will track with the supply voltage.

If you were instead to connect an external voltage reference IC to a spare A/D input that would provide a scale that you could count on. Small simple 1.2V reference ICs are common. You would read the 1.2V reference and get an A/D value and then scale the readings on the measurement channel by a factor of 1.2/ref_AD to get volts in the measured channel. This will improve accuracy but not by a great deal in my experience since there are still other error sources beside the 3.3V reference voltage.

As Rick said, your best bet is to use an external A/D that meets your specifications, whatever they are.

1 Like

Apologies if this doesn't apply to the thread (I think it does). Can anyone provide additional information to how the Internal reference on the B404X, which has an ADC reference, works? The Particle documentation states the following:

On the B404X, BRN404X, and E404X, the ADC reference is the nRF52840 internal 0.6V reference, with a 1/6 gain from the input pin. This is different than other Gen 3 devices which use 3V3 as the ADC reference.

This will only affect B404X designs that have a 3V3 voltage that is not 3.3V. Previously, the ADC values would vary with the 3V3 voltage.

This reference and gain combination is a range of 0 to 3.6V for 0 - 4095. This is scaled in software so 3.3V will still be 4095, so there will be a slight loss of resolution.

The last paragraph confuses me with the "scaled in software" part. Are there any examples in code of how one would accurately measure a 1.8V signal on A0 using the internal reference?

For example:

#define AREF 3.6
ADCvalue = analogRead(A0);           // Read a value
float value = ADCvalue * (AREF/4095.0); 

On B404X, actually all nRF52 devices, analogRead() returns a value from 0-4095 for an input voltage of 0 - 3.3V (theoretically), in all modes.

For most devices, the 3.3V reference is 3V3, which may vary slightly from 3.3V due to the regulator and loading on 3V3.

When internal reference is enabled on nRF52, an internal precision 0.6V regulator in the MCU is used as the reference. To make this usable, input gain of 1/6 is also enabled. This results in an input range of 0 - 3.6V that does not depend on 3V3.

To make the behavior consistent across internal and 3V3 reference, when internal reference is used, basically this code is used on the ADC return value:

adcValue = (int16_t)(adcValue * 3600 / 3300);

Thus your code can just assume that 0 = 0V and 4095 = 3.3V (ish) regardless of whether you're using internal or 3V3 reference.

gotcha. thanks

Super helpful, @rickkas7 ! I found this out after doing some testing and can confirm the internal scaling is already occurring and I can just reference AREF = 3.3 in my code.

Is there any reason why the internal reference isn't the default reference on nRF52 devices? I would think that the internal 0.6V precision regulator reference would be a more stable reference, when compared to the 3.3V rail reference (which is default). I get that the resolution is worse, so I just assume it depends on whether you want higher resolution or higher precision.

I split 3v3 using two 1% resistors into a 1.65 volt signal and fed that into a spare ADC. My differential accuracy is at least 5x better. I put a cap on the final resistor to set up a 0.15 second first order lag filter and that kept noise down.