Display on my voltage divider fluctuates (Am I being too fussy with the result)

I used this schematic and code to create a voltmeter using an Arduino Nano (thank you T.K. Hareendran):
https://www.electroschematics.com/arduino-digital-voltmeter/. I used two resistors, 7100 and 33000 for the voltage divider. I also measured the voltage between 5 volts and ground. It measures 7.74 volts. I edited the code for both resistors, voltage reading of the Nano, and I used a SSD1306 oled display. Here is the code:

/*
DC Voltmeter 
An Arduino DVM based on voltage divider concept
T.K.Hareendran
*/
#include <Wire.h>
#include <Adafruit_GFX.h>  // Include core graphics library for the display
#include <Adafruit_SSD1306.h>  // Include Adafruit_SSD1306 library to drive the display

Adafruit_SSD1306 display(128, 64);  // Create display

#include <Fonts/FreeMonoBold12pt7b.h>  // Add a custom font
#include <Fonts/FreeMono9pt7b.h>  // Add a custom font
int analogInput = 0;
float vout = 0.0;
float vin = 0.0;
//This is the original code
float R1 = 33190;// resistance of R1  -see text!
float R2 = 7120; // resistance of R2 - see text!
//float R1 = 30080;// resistance of R1 -see text!
//float R2 = 7500; // resistance of R2  - see text!

int value = 0;
void setup() {
pinMode(analogInput, INPUT);
 delay(500 );  // This delay is needed to let the display to initialize
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // Initialize display with the I2C address of 0x3C
  display.clearDisplay();  // Clear the buffer
  display.setTextColor(WHITE);  // Set color of the text
  display.setRotation(0);  // Set orientation. Goes from 0, 1, 2 or 3
  display.setTextWrap(false);  // By default, long lines of text are set to automatically “wrap” back to the leftmost column.
                               // To override this behavior (so text will run off the right side of the display - useful for
                               // scrolling marquee effects), use setTextWrap(false). The normal wrapping behavior is restored
                               // with setTextWrap(true).

 display.dim(0);  //Set brightness (0 is maximun and 1 is a little dim)


}

void loop() {
  // read the value at analog input
   value = analogRead(analogInput);
   vout = (value * 4.74) / 1023.0; // see text
   vin = vout / (R2/(R1+R2)); 
   if (vin<0.09) {
   vin=0.0;//statement to quash undesired reading !
} 

  
display.clearDisplay();  // Clear the display so we can refresh
  display.setFont(&FreeMono9pt7b);  // Set a custom font
  display.setTextSize(1);  // Set text size. We are using a custom font so you should always use the text size of 0
  // Print text:
  display.setCursor(0,10);  // (x,y)
  display.println("Voltmeter: ");  // Text or value to print


  display.setCursor(0,27);  // (x,y)
  display.println("Volts: ");  // Text or value to print
  display.setCursor(70,27);  // (x,y)
  display.println(vin);  // Text or value to print


 
  // Print variable with left alignment:
 
display.display();  // Print everything we set previously
delay(1000 );
}

This is the first attempt. The voltage is very close to what my variable power supply reads and I have checked it with another voltmeter for accuracy which matches. The display on the SSD1306 bounces about 6 hundredths but sometimes goes as much as 1/10th of a volt low to 1/10th of a volt high compared to my voltmeter.

Then I tried a voltage sensor that uses a voltage divider with smd resistors. I measured the resistors and R1 which should have read 30K ohms measured 30,080 ohms and R2 was right on the money at 7.5K ohms. I wired the sensor and changed the values for R1 and R2 in the code. I uploaded the code. This time the variable meter read 8.11 volts and my display bounced between 8.01 volts to 8.24 volts.

Here is my dilemma. Looking at youtube videos using a Hitachi 16X2 display, the voltage values appear to be rock steady but on my SSD1306, the values bounce between 2/10ths of a volt. Am I being too picky? Should the SSD1306 be more stable? I could measure 10ths instead of hundredths. BTW, the voltmeter I use on my variable power supply also measures in hundredths and appears to be more stable.

Any suggestions?
Thanks.

Your code compares the input voltage to the supply of the Nano, which is potentially unstable.
As a result, your measured/displayed voltage is also unstable.

Calculate your voltage divider to ~1volt (not 5volt), and switch from default Aref to the more stable internal ~1.1volt Aref of the Nano, by adding this to setup().

analogReference(INTERNAL);

Example attached.
Leo..

/*
  0 - ~17volt voltmeter
  works with 3.3volt and 5volt Arduinos
  uses the stable internal ~1.1volt reference
  10k resistor from A0 to ground, and 150k resistor from A0 to +supply
  (1k8:27k or 2k2:33k are also valid 1:15 ratios)
  100n capacitor from A0 to ground for stable readings
*/
float voltage;

void setup() {
  Serial.begin(9600);
  analogReference(INTERNAL); // use (INTERNAL1V1) for a Mega
}

void loop() {
  voltage = analogRead(A0) * 0.01660 ; // calibrate by changing the last digit(s) of 0.0166
  // print
  Serial.print("The supply is ");
  Serial.print(voltage);
  Serial.println(" volt");
  delay(1000); // remove when combine with other code
}

Can't tell for sure but I believe you are seeing "noise" on you input. Contrary to what things appear to be, the real world is not so neat.

Try putting a 0.1 µF capacitor across the resistor going from A0 to ground.

The capacitor will act something like a shock absorber and reduce the amount of noise A0 "sees"

John

The screen you display it on has no effect on the numbers being displayed - the screen displays what your code tells it to display!

In your case, let's look at what you'd calculate the voltage for for a reading of 1 from the ADC, ie - this is the resolution that you can measure voltage with - all results will be a multiple of this.

(4.73/1023)/(7500/(30080+7500).... 0.023 - okay, so you're seeing a variation of 10 counts on the ADC! That's... more variation than I'd expect, for sure (you should expect noise on the order of 1, maybe 2, in readings from the ADC).

You're already knocking the voltage down by a factor of 5:1 (why? Are you planning to be measuring higher voltages? You want the difference between the reference voltage and the maximum that you expect to measure to be as small as possible, that way you can use more of the range, and get higher accuracy.

So there are two possible explanations - either there's noise in your input signal (is the power supply one of those fairly cheap chinese made bench supplies? Many of those have surprisingly noisy output to begin with (try measuring the voltage of, say, a 9v smoke detector battery - if it's stable, to within +/- 0.046v (2 LSB's), then that's why there's variation. (Or, if you have a 'scope, just look at the voltage on the scope)

There could also be noise on the power supply to the nano (the LCD could contribute to that if its power consumption is not constant, IIRC those generate the needed voltages by cap-switching, so it's not implausible). In this case, you would also see the size of the noise increase as the voltage being measured increased (ie, it would be a percentage of the measured voltage. 0.23/8.11 = 0.028 (2.8%), so if we accept the (presumably measured) 4.74v supply voltage (so - it's a nano, right?) it's varying +/- 1.4% around that, or +/- 0.066 V if we want it to explain all the variation. That would be bizarrely high if true - I hope for your sake it's not. The voltage will vary as the load changes, but an OLED display, even if it's being really obnoxious with it's cap-switch voltage generator, shouldn't make it change that much (barring a really crap cable - I have seen cables in the wild with internal resistance of OHMS (plural) on the supply and ground, which is just godawful...

Anyway - my advice to fix it:

  • If the above test with measuring a battery does produce a more-stable reading, the portion of the variation that went away when you switched to a battery was caused by the arduino accurately measuring a fast-changing voltage, wheras your multimeter was averaging it out. If you need to measure noisy voltages and just want the average, put a small capacitor between the analog pin and ground - this, combined with the resistors in the divider, will form an RC filter that trades a slower response for a more stable reading of the average. If you want to get picky about the capacitor value (say you care about response time), calculate the time constant and make sure it's shorter than any change you might want to measure. I'd probably try a 1uF cap for starters (~30ms time constant).

With the cap in place, repeat test. Variation of voltage measured on supply should hopefully now be on the same scale as when you measure the battery (if it's not, use a bigger cap, while swearing under your breath at the manufacturer of the power supply)

  • If there was more variation than you'd like measuring the battery (ie, more than 0.046v, translating to a reading of 2 on the ADC), then I'd suspect variation on the 5v rail. The solution here would be to use internal reference, see below. If I suspected significant noise on the 5v rail, even if I didn't care about ADC accuracy, I'd add maybe 10uF cap between 5v and ground on the arduino...

In any event, I have these general suggestions:

  • Use the internal analog reference. Assuming that's a nano (or other '328p-based board), you only have one choice for internal reference, a 1.1v internal reference: analogReference(INTERNAL); The mega has a 2.56v one too (on it, you can use INTERNAL1V1 or INTERNAL2V56 ). Other microcontrollers have different references available - usually the fancier ones have more, but even some classic-AVR ATtiny's have more than just the 1.1v one, and the new megaAVR parts (even the tinies) have five different ones to choose from. See the documentation for analogReference() for official boards, or the board package documentation if using a third party board package on non-standard hardware.

  • Choose resistors such that the highest voltage you want to measure is a little bit below the reference voltage (4.74v if using default Vcc as reference). This will make the "resolution", the smallest voltage change that you can measure, smaller, and make that 1-2 LSB's of noise that is inherent to the ADC smaller. If the measured voltage is higher than that, it will read near-full-scale - because of the large resistor between the input pin and the voltage being measured, you don't need to worry about damage from a voltage higher than Vcc reaching the arduino should you try to measure something with a voltage higher than you planned, when using Vcc as reference - the protection diodes are good up to at least 1mA, possibly more, so as long as putting (Vmeasure - Vcc) across the top resistor won't let more than 1mA flow, it's safe.

  • You can have your code remember what the last value it read from analogRead() is, and not change the value displayed if it's changed by only 1.

  • You can improve the resolution using oversampling and decimation (there's an Atmel application note describing that process - google for it)

  • Don't do vout/(R2/(R1+R2), FFS, do vout * (R1+R2)/R2 - same result, but less division. It doesn't really matter here, but division on arduino is slow (no hardware division). I don't think the compiler does this right. Actually,

  • You don't need to use floats - you probably don't care, but floats on arduino are slow, and getting rid of all floats also saves a surprising amount of flash. Store the result in an unsigned long, and do the math on that, using the reference voltage in millivolts: unsigned long vin = (4740UL (or 1100UL) * (R2+R1))/R2 / 1023; (that UL tells it to use an unsigned long for the value you're starting with - that way all the math will be done in unsigned longs - otherwise it would be doing the intermediate math with 16-bit signed integers, the default, and the math would overflow). That gets you Vin in millivolts... To print that as volts:

display.print(vin/1000); //gets the volts...
display.print("."); //the decimal
int mv=vin%1000; //gets the part we'll put after the decimal
if (mv<10){
display.print("00"); //add two leading 0's if the millivolts
} else if (mv < 100) {
display.print("0"); //add one leading 0
}
display.println(mv); //print the millivolts.

Code untested/off the top of my head.

Why did I write such a longass post to a simple question? it's not like I don't have other things to do!

Hi,
Have you got the Arduino gnd and the input gnd connected.
Also try adding a 0.1uF capacitor as shown.
voltage-divider-circuitedit.jpg
Tom.. :slight_smile:

voltage-divider-circuitedit.jpg

Hi,
Change this;

float R1 = 33190;// resistance of R1  -see text!
float R2 = 7120; // resistance of R2 - see text!

to

float R1 = 33190.0;// resistance of R1  -see text!
float R2 = 7120.0; // resistance of R2 - see text!

AND
Change this;

  vout = (value * 4.74) / 1023.0; // see text

to

  vout = ((float)value * 4.74) / 1023.0; // see text

Tom.. :slight_smile:

1023 is not a correct scale factor. It should be 1024, because that is how many steps the ADC has.

Thanks for answering.

1023 is not a correct scale factor. It should be 1024, because that is how many steps the ADC has.

I have read the number both ways;1023 and 1024. I chose 1023 because someone mentioned that the scale is 0 to 1023 which made sense to me.

Not picking on your reply, just saying why I am using 1023.

Thanks to all of you who replied! I appreciate you taking time to teach me. Unfortunately, I am out of town the next couple of days, so I cannot try your suggestions. I promise I will take it up over the weekend and report back. Onward and upward.

Everything I haven't learned about electronics is self taught.

jocarcub:
Unfortunately, I am out of town the next couple of days, so I cannot try your suggestions.

Really?

We are not allowed to be "out of town" now without an extremely good reason; the police are checking the highways. I wonder what your reason is? :grinning:

aarg:
1023 is not a correct scale factor. It should be 1024, because that is how many steps the ADC has.

No it doesn't, the count is from 0 to 1023, the first step is not zero but from 0 to 1, second step 1 to 2, as the ADC output only counts to 1023, there are only 1023 ‭11 1111 1111‬ binary steps.
Tom.. :slight_smile:

jocarcub:
I chose 1023 because someone mentioned that the scale is 0 to 1023 which made sense to me.

Can't count then :slight_smile: 0..1023 is 1024 distinct levels. Not that it matters.

HOWEVER, some 10-bit DACs need a 1023 factor due to the way R-2R ladders work, and if the
ADC is a successive-approximation type using an internal R-2R network then 1023 will be right
as one of the end-values isn't reachable if the input is strictly with GND--Vcc range.

However the ATmega328 ADC genuinely outputs 0 and 1023 at the range limits, so its 1024.
However I doubt anyone uses a < 0.1% precision 5V supply with an Arduino so the accuracy of
the supply voltage is the limiting factor.

Hi,
If 1023 represents 5V.

Volts = 5 * (adccount/1023)
not
Volts = 5 * (adcount/1024)

Did adding the bypass capacitor and adding floats help?

Tom.. :slight_smile:

Wow, thank you everyone for such complete and insightful answers. I do appreciate each and every reply. (Paul__B, I made it home safely without a temperature :slight_smile: )

I made changes to the code that had been suggested in this thread. Also I added the capacitor. It smoothed the numbers a little, but there were still some variance.

Then I looked on Youtube for other examples of Arduino voltage meters and noticed the same thing on every one of them ; All of the outputs bounced around. None of the outputs were rock steady. (One appeared to be solid, but I think that he edited the video to make it appear to be solid...my opinion.)

I realized
a) that the meter I was to compare the voltage uses has much more circuitry to smooth the readings (I think that someone alluded to that fact)

b) I really don't need the reading to the hundredths. I don't need that accuracy.

So the final change I made was changing the output to the tenth of a volt instead of a hundredth. It doesn't bounce very much at all and is close enough for me. Below is the code I wound up with:

/*
DC Voltmeter 
An Arduino DVM based on voltage divider concept
T.K.Hareendran
*/
#include <Wire.h>
#include <Adafruit_GFX.h>  // Include core graphics library for the display
#include <Adafruit_SSD1306.h>  // Include Adafruit_SSD1306 library to drive the display

Adafruit_SSD1306 display(128, 64);  // Create display

#include <Fonts/FreeMonoBold12pt7b.h>  // Add a custom font
#include <Fonts/FreeMono9pt7b.h>  // Add a custom font
int analogInput = 0;
float vout = 0.0;
float vin = 0.0;

//(Added this variable.)
float vin_1 = 0.0;

float R1 = 30080.0;// resistance of R1 (100K) -see text!
float R2 = 7500.0; // resistance of R2 (10K) - see text!
int value = 0;
void setup() {
pinMode(analogInput, INPUT);
 delay(500 );  // This delay is needed to let the display to initialize
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // Initialize display with the I2C address of 0x3C
  display.clearDisplay();  // Clear the buffer
  display.setTextColor(WHITE);  // Set color of the text
  display.setRotation(0);  // Set orientation. Goes from 0, 1, 2 or 3
  display.setTextWrap(false);  // By default, long lines of text are set to automatically “wrap” back to the leftmost column.
                               // To override this behavior (so text will run off the right side of the display - useful for
                               // scrolling marquee effects), use setTextWrap(false). The normal wrapping behavior is restored
                               // with setTextWrap(true).

 display.dim(0);  //Set brightness (0 is maximun and 1 is a little dim)


}

void loop() {
  // read the value at analog input
   value = analogRead(analogInput);
   //vout = (value * 4.74) / 1023.0; // see text

//Changed this
   vout = ((float)value * 4.74) / 1023.0; // see text

  // vin = vout / (R2/(R1+R2)); 
 
//(Changed this)
   vin_1 = vout * (R1+R2)/R2;

//(Added this)
    int newvolt = ((vin_1 + 0.05) * 10);
   vin = (newvolt / 10.0);


  if (vin<0.09) {
   vin=0.0;//statement to quash undesired reading !
} 

  
display.clearDisplay();  // Clear the display so we can refresh
  display.setFont(&FreeMono9pt7b);  // Set a custom font
  display.setTextSize(1);  // Set text size. We are using a custom font so you should always use the text size of 0
  // Print text:
  display.setCursor(0,10);  // (x,y)
  display.println("Voltmeter: ");  // Text or value to print


  display.setCursor(0,27);  // (x,y)
  display.println("Volts: ");  // Text or value to print
  display.setCursor(70,27);  // (x,y)

//(Changed this to make the output 1 decimal place.)
  display.println(vin,1);  // Text or value to print


 //display.setCursor(0,42);  // (x,y)
//  display.println("Amps: ");  // Text or value to print

 //display.setCursor(0,62);  // (x,y)
  //display.println("KV: ");  // Text or value to print


 
  // Print variable with left alignment:
 
display.display();  // Print everything we set previously
delay(1000 );
}

Thanks again!

vout = ((float)value * 4.74) / 1023.0; // so you didn't understand post#1

jocarcub:
Then I looked on Youtube for other examples of Arduino voltage meters and noticed the same thing on every one of them ; All of the outputs bounced around.

Did any one of them use the build-in stable reference of the Arduino, or did they just compare input voltage to a potentially unstable Arduino supply (like you do). That's like measuring distance with a rubber band.
Leo..

1 Like