2
\$\begingroup\$

I am designing a SMPS (see previous question if curious) and need to provide the mcu with an output feedback voltage to dial in the duty cycle for closed loop control.

Using an Arduino (Leonardo in this case), I am always frustrated with the fluctuation of 'counts' returned from AnalogRead(). I have tried slowing the polling (I only want 50-100ms response), running average software filters, and other software band-aids. The purpose of this question is whether it can be solved in hardware instead so I can tinker and learn a bit.

Version 1 of my circuit looked like this: Analog Input Version 1

The purpose of Resistor R8 was to limit current for the D4 5.1V Zener in case the analog input saw more than 5V so as to not damage the mcu pin. But because it was in series with the analog input, it would create a voltage drop and therefore skew results. As the load resistance varied (potentiometer) so did the voltage across R8 and it was hard to calibrate the analog input in software.

I then began learning about using an op-amp as a buffer instead which has "infinite" input resistance, and therefore whatever series resistor value was chosen would be magnitudes smaller and have minimal effect on the outcome.

Version 2 of my circuit looks like this: Analog Input Version 2

The output is much more stable, but my question is whether I am 'over-doing' it with this circuit?. How do you calculate an RC time constant when the op-amp has infinite resistance? As stated above, I want my response time to be 50-100ms with 20-50mV peak to peak range. I feel like this is certainly doable... Any suggestions or tips and tricks would be suggested.

I am guessing the simplest solution would be a low pass filter with R = 100K and C = 150nF but figured this was a good learning opportunity to explore op-amps too.

PS: the 74LVC2G66 device is what I am using to enable and disable 10K pullup and 10K pulldown resistors for other experiments. When both switches are off, they ~should not effect the series resistance of the analog input.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ An Arduino, or rather, an AVR, should generally work well with impedances up to 10k. Your first attempt has only 1k to some 3.1V supply, so it should be fine. Your second attempt has capacitors directly on op-amp output, which is generally a problem. How much ripple you get on the first attempt and how much of it is really the signal you are supposed to be seeing and how much noise? \$\endgroup\$
    – Justme
    Commented Dec 30, 2023 at 19:20
  • \$\begingroup\$ What are the two transmission gates and associated resistors for? \$\endgroup\$
    – Dave Tweed
    Commented Dec 30, 2023 at 19:57
  • \$\begingroup\$ @DaveTweed To have a MCU select and deselect 10K Pullup or Pulldown resistors for a different project. Reused the circuit and schematic. \$\endgroup\$ Commented Dec 30, 2023 at 19:59
  • \$\begingroup\$ If they're not relevant to the question, they should be removed. \$\endgroup\$
    – Dave Tweed
    Commented Dec 30, 2023 at 20:01
  • \$\begingroup\$ I have done MCU-controlled SMPS in the past (dsPIC24-based). I always sampled the voltages synchronously with the switching, to eliminate the effects of the voltage ripple. If you're sampling asynchronously, most of the noise you're seeing is probably from that. Can you show us the entire power supply circuit, with irrelevant details removed? \$\endgroup\$
    – Dave Tweed
    Commented Dec 30, 2023 at 20:04

2 Answers 2

3
\$\begingroup\$

I am always frustrated with the fluctuation of 'counts' returned from AnalogRead()

well, welcome to the world of analog signals and digitization: Noise is ubiquitous, and unless you do proper analog filtering, you'll get aliasing.

So, deal with it, I guess?

I have tried slowing the polling (I only want 50-100ms response), running average software filters, and other software band-aids.

This is very clearly an application for regular time-interval sampling, not polling. Set up your MCU to trigger ADC conversions using a timer. Don't poll – that introduces significant jitter into your process.

The arduino framework is actively hostile towards doing this correct – you need to work around it.

So, my first suggestion is to drop Arduino and simply consider your Arduino Leonardo a slightly strange evaluation board for an ATMega 32u4, and write your controller in bare-metal C
Honestly, the ATMega32u4 is not the microcontroller you'd want for a high-speed controller that might have to do math, but there could be worse choices. I'd personally go for one of the cheap STM32L1 that are optimized for motor- and power control jobs. Cheaper, too.

Once you have sorted out how to do accurate regular sampling, design an analog front-end filter, the anti-aliasing filter that restricts your analog signal's bandwidth to less than half the sampling rate.

You do need something like your U9 opamp to drive your ADC inputs – their input currents during sampling would otherwise degrade the measurement. However, your C16 is highly counter-productive; you need the opamp to drive the capacitive load of your ADC, don't make it any harder!

So typical signal flow would be

analog signal
---> opamp voltage follower/buffer
---> filter (active or passive)
---> opamp ADC buffer
---> ADC input

The opamp buffer before the filter makes sure the filter is driven with sufficient drive strength, the opamp buffer after makes sure the filter isn't loaded by the load of the ADC.

Now you get properly filtered, properly sampled signals; suddenly you can design the digital filters for exactly the purposes you want, and without having to deal with the sample time jitter you get when polling.

How do you calculate an RC time constant when the op-amp has infinite resistance?

This question makes no sense; the time constant of an RC low-pass filter only exists for an R through which a current flows through the C to ground. You're using R21, 1 kΩ, for that.

D6 is cute and all, but the moment D6 starts to conduct significant current, U10A would probably already have broken down!

\$\endgroup\$
4
  • \$\begingroup\$ 1. Removed C16 2. In favor of D6 which was for overvoltage protection, I found a 40V Rail to Rail Op-Amp (TI TLV930x). Powering it with 5V & Gnd should limit voltage moving forward. 3. Polling was the wrong word choice - I plan on using an STM32F4 eventually, figured it was easier to prototype on an Arduino and the hardware would carry over. I call the AnalogRead() every 50-100ms using an Interrupt Timer. 4. New Circuit: <img src="imgur.com/a/CphIrCS"> Op-Amp Buffer -> RC Circuit Low Pass Filter -> Op-Amp Buffer -> MCU Good? \$\endgroup\$ Commented Dec 30, 2023 at 20:11
  • \$\begingroup\$ 40 V rail to rail doesn't help with anything! If your input is significantly above supply voltage, the protection diodes between input and Vcc begin to conduct. \$\endgroup\$ Commented Dec 30, 2023 at 20:23
  • 1
    \$\begingroup\$ and the software you write with arduino is very different from what you would have to write on an stm32f4; and the hardware is so fundamentally different between an 8 bit AVR and a 32 bit Cortex-M4 that really nothing carries over. Start with the STM32F4, ditch the Arduino; it makes your life easier. Honestly, the option to actually debug the code running on your microcontroller while it's running alone would make this a no-brainer for me. \$\endgroup\$ Commented Dec 30, 2023 at 20:24
  • \$\begingroup\$ Okay, I'll use the Nucleo dev board I have with an STM32F4 on it. But the Keep it Simple Stupid Circuit I linked is the way to go for smoother readings? I've used a 1K resistor and a 5.1V Zener in some other designs similar to this application and have not had issues. I just don't want the 1K resistor making trouble.. I size those components large enough for the application and they do limit the voltage to 5V when the input is 20V or larger. \$\endgroup\$ Commented Dec 30, 2023 at 20:29
2
\$\begingroup\$

The purpose of Resistor R8 was to limit current for the D4 5.1V Zener in case the analog input saw more than 5V so as to not damage the mcu pin. But because it was in series with the analog input, it would create a voltage drop and therefore skew results.

Analog inputs draw negligible leakage current when the ADC is not sampling. During sampling, they draw a small current spike to charge the current capacitor. In your case this current comes from the 100nF filter cap, and voltage drop on the 1k resistor will be negligible.

If you measure 0.1V across the 1k resistor, that's 100µA which is orders of magnitude too high.

Maybe you forgot to disable the micro's internal pullup/pulldown on the analog input, or the Zener diode has too much leakage.

I am guessing the simplest solution would be a low pass filter with R = 100K and C = 150nF but figured this was a good learning opportunity to explore op-amps too.

Every time the ADC samples, it draws a spike of current to charge its internal sampling capacitor. The minimum source impedance specified in the datasheet is calculated so the sampling capacitor charges fast enough to settle to <1LSB of the target voltage within the sampling time window. However if you put a filter cap on the input, then the source resistance is near zero, as the sampling capacitor charges directly from the cap. This also discharges your filter cap every time the ADC samples. For accurate measurement, if your source impedance is higher than the maximum specified in the datasheet, then the cap should be 2^N times higher than the ADC sampling cap (with N ADC bits) so when the ADC pulls charge from it, voltage on the sampling cap does not decrease more than 1LSB.

Also if your source impedance is higher than the maximum specified in the datasheet, sampling too frequently will discharge the filter cap before it has time to recharge via the source resistance, so you have to be careful about that.

Some micros have very accurate ADCs, others not so much. It's important to look at the ENOB (Effective Number of Bits) or SNR (Signal Noise Ratio) in the datasheet. If a 12-bit ADC has 9 ENOB (like many STM32) then you get 9 bits of ADC and 3 bits of noise on top for free.

Also the accuracy of your ADC is the accuracy of the voltage reference used by the ADC, because the digital output value is the ratio of input voltage and reference voltage. If the reference is VCC and it has some ripple on it due to varying supply current draw, then your ADC will lose accuracy. Solutions are a separate reference chip which can be a cheap LDO, or at least some decent filtering, or using the internal reference.

Arduino boards are made cheap, they use VCC as reference and use 2 layer board with no ground plane, and there are few ground pins on the connector. This means any current going in and out of the pins will have its return current in the single ground pin, which creates voltage drop. So the actual potential of "ground" as seen by the ADC inside the micro may be different from what you think "0V" is. A proper PCB where you know where "0V" actually is really helps.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.