How to build an ADC generating 0-100mV 0.5mV accuracy or better?

I need to provide 0-100mV for another electronic device, regulating its state of charge (SOC). The mV represents a SOC of 0-100%. The controlled device does its own SOC calculation (which is incorrect at the top and bottom by up to 7%.
I have an UNO which runs runs related code and I want to give it the task to produce the 0-100mV very accurately and constantly.

The processing pseudo code:

setup() {
  set mV_Output to OFF
}

loop() {
  read 60 bytes on serial
  determine battery SOC
  send correct value in mV to controlled device (continuously)
}

Now the circuit for the mV source... I understand that I can use common op amps, but to not know how to configure their peripherals to produce a a stable mV value based on a float calculation.

void controlSOC(float controlValue) {
  /*
   * SoC value to control charger
   * analog out is 0-5V with 255 steps or 5V/255 steps = 0.0196mV/step
   * 100mv ~
   * input: AHR = amp hour value for charge (+) and discharge (-) value
   *        -400 ~ 0% SoC to 0 = 100% SoC
   * Battery capacity 400 AH
   * calc: ((-AHR / AH) + 1) * 100 | (-172/400 = -0.43 - 1 = .57 * 100 = 57%
   */

  // calc A/D output value
  controlValueOut = ((controlValue / BATT_CAPACITY_AH) + 1) * 100;
  analogWrite(PIN_SOC_OUT, controlValueOut * 2.56);

  return;
}

Anyone keen to suggest a circuit I can hang of the analog out?
I have seen a PWM out can be used to DAC?
I am open to suggestions WRT the circuit,but it has to be low noise, stable though variable Voltage.

Arduinos don't have a DAC, the AnalogWrite command sets PWM duty cycle.

You'll need an external DAC for this. Or check out the ESP32, it has a DAC built in. I don't know its resolution.

Thanks, I have to use the UNO...
... so an MCP4725 could be something of use?!

According to the spec sheet it doesn't have the 0.5 mV resolution you're looking for. You'll need something with separate reference voltage that you can set to something closer to 100 mV than the minimum 2.7V of this device, or find one with a 16-bit resolution. Some more options here.

... so an MCP4725 could be something of use?!

That should work if you use a voltage-divider to knock-down the output voltage. 12-bits with a 0-100mV range would give you more than enough resolution.

Then depending on the load* (impedance/resistance/current) you may want to buffer the voltage divider with an op-amp. You'll probably need plus & minus power supplies for the op-amp to be accurate near zero-volts output.

Or, you could make an attenuator with an op-amp (an amplifier with a gain of less than one). But, only inverting op-amp circuits can have a gain less than one, so you'd need a 2nd inverting buffer to re-invert it.

  • I haven't studied the datasheet and I don't know anything about your load, but with a 1/50 voltage divider and a low-value "bottom" resistor you may not need a buffer.

DVDdoug:
Or, you could make an attenuator with an op-amp (an amplifier with a gain of less than one). But, only inverting op-amp circuits can have a gain less than one, so you'd need a 2nd inverting buffer to re-invert it.

The first inversion you can do in code, by setting the DAC to the lowest when you need the highest and the other way around. Then no need for the second OpAmp.

Thanks :slight_smile:
Well, I am not too worried, whether I use one or two OpAmps :slight_smile:
The requirement is reliability and precision... So, no matter how the Arduino Vcc may change (within limits), the output from 0-100mV should be stable.

The load is a shunt input; e.g. a milliOhm resistor, which creates a V when I flows through it.

What should a circuit look like?

If I understand what you're trying to do (but I'm not sure), you're into an x-y problem situation. http://xyproblem.info/

If the load is in fact a shunt resistor with an output of 0-100mv, you need to force current through shunt to get that voltage, you cannot generate 0-100mv and get the same results - unless you can remove the shunt and substitute your voltage source, which may not actually work in all situations due to the very low resistance of the shunt.

As a secondary comment, your comment of:

analog out is 0-5V with 255 steps or 5V/255 steps = 0.0196mV/step

Is incorrect, 5V / 255 is 0.0196 but that's 0.019.6 volts per step which is 19.6mv/step.

Could you please drawn and post a block diagram of this system and show the devices and signals?

100mV into a mohm load requires 100mA - a dac or ordinary opamp won't provide this .

You''l need a highish current precision buffer as well.

Is the load grounded or floating?

Allan.

I need to be more precise...

There is no load as such...
The input of the device where I send the 100mV is an input for a shunt; this input can handle 0-100mV.
Meaning, I do not need juice (higher amps) to drive it... I am just saying it takes 100mV, being a high impedance input.

|--------------|                       |\                   |---------|
|              |---- (analog / PWM)--->| >-------(0-100mV)--| charger |
|      UNO     |                       |/                   |---------|
|              |                     some OpAmp
|--------------|

I'm not convinced that this is a solution to your problem but generating 0-100mv is trivial if the input impedance is high (>1meg) since you don't need an opamp - to at least test it for proof of concept.

The MCP4725 powered from the Uno's 5 volt supply will do the job. Pass the DAC output into a 9800/200 ohm divider (50:1) and you have 0 to 100mv with 12 bits of resolution. You can make ~9800 ohms with a 10k and 510k in parallel. They should be 1% metal film resistors for stability.

Before leaping into this though, I'd first ensure that the charger input is isolated from its output and the battery it's charging. Resistance checks with power off and then voltage checks with power on would be prudent if you don't have schematics.

KISS.
An Arduino PWM output connected to a 1:50 voltage divider with smoothing cap on the tap will do (slightly) better than 5mV.
DACs and opamps might introduce other problems.
Leo..

KISS may not do it... :slight_smile:

The requirement is for a stable and precise mV output in 1mV steps; hence, the 0.5mV accuracy I believe I needs to achieve the desired outcome... precise 1% = 1mV steps.

The charger has its own SOC and V measurements.
The shunt input can be used / programmed to accept either 25, 50, or 100mV as a SOC representation of 100%.

So I am happy to use a reference V, like 3.3V (or was there a 1.1V reference in the UNO (have to check)... use this for a V divider of sort, and drive (correct) the SOC of the charger.

MaxG:
The requirement is for a stable and precise mV output in 1mV steps; hence, the 0.5mV accuracy I believe I needs to achieve the desired outcome... precise 1% = 1mV steps.

100mV / 256 steps (8-bit) = ~0.39mV per step.
That can be changed (downgraded) in code to e.g exact 0.5mV steps.
Stability depends on Arduino's 5volt, and the load.
Leo..

I am not hung up on 0.5mV... I simply thought it needs to be more accurate than 1mV... you know tolerance; e.g. +/- 0.5mV would just work... I am happy with the 8-bit... but assume I need to be more precise than the value I want to achieve?!

Maybe clearer what I want to achieve: Say we have 200 steps; If the calculation of some value leads to 100 (digital), then say the mV out should then be 50mV, 102 = 51, 104 = 52mV, etc. Makes sense? I want the accuracy in the relationship, that 100 is always resulting in 50mV.

If you want precise 0.5mV steps from 0-100 (that's 201 steps), then restrict PWM range (from 0) to 200.
Make one of the resistors of the voltage divider adjustable, so you can trim for 100mV with a PWM value of 200.
PWM should be fairly linear, so a PWM value of 100 should result in 50mV.
Assuming the load does not upset things.
Leo..

Since you simulate SoC it will change slowly - you can use 8 bit (or even more from Timer1) PWM and HEAVY filtering to get nearly no ripple.
But reference voltage will be problem. You cannot get stable voltage for the Arduino?
Maybe instead of DAC driven from reference voltage you may buffer the PWM by a gate powered from the reference.

Thanks for all the feedback and ideas...
Given you're following the conversation, what is you judgement on the proposed solution - now that all parameters are understood; parts don't matter, will use what is required to achieve the outcome...
... which solution would you chose?

Leo's solution is simple and should work well.

Given that PWM is timer-controlled it's linearity should be good.

Choose a higher frequency ( look on this site) to get lower ripple.

Allan

Thank you for all your input... :slight_smile:

OK, I have built the guts of it; pot on analog in, divide by 4 and PWM out feeding a LED.
Put the scope on... and it is clear now how this works.

However, and I apologise for this mishap, further analysis of the problem revealed that the charger can discern 0.1mV steps.
BUT, the BMS (battery Management System) only provides me with a negative integer for the Amps 'missing' from the battery.
This means, that my calculation of the SOC, like so: ((-AHR / AH) + 1) * 100, where -AHR are the ampere-hours drawn from the battery, AH is the capacity of the battery (400Ah).
So, say -172Ah / 400Ah = -0.43 + 1 = 0.57 * 100 = 57% SOC (State of charge) of the battery; hence, the resolution is only 0.25%.

This means, the 8-bit PWM out does not cut it... also meaning, I need a 10-bit DAC.
So I am back to the MPC4725... then add Leo's V divider, and should arrive at values like 70.0, 70.2, 70.5, 70.7 and 80. this is good enough for what I need to achieve... which I never stated, and is:
The charger display values up to 7% different from my BMS, which leads to all sort of problems, like battery disconnect, and battery grid support; both of which occur earlier than it needs to be.

I can see that I might add a precision shunt voltage reference to feed the voltage divider on DAC out... to ensure, my DAC out is not negatively impacted by Vcc changes in the Arduino supply.

And, I forgot, add a low pass filter to DAC out with 10k / 10uF.

Have I got it now, or is there still flawed thinking on my part?