So.. I've made a primitive voltmeter using various bits of code
It works..
I have a dc voltage between a0 and gnd
this voltage is under 5v (under 2v actually
It sends it over serial 100 times a second
but because of the 10 bit ADC the resolution is only 5mv
I would like 1mv
I have read that I can do this by averaging the values
This is my first arduino project, I'm so pleased I got this bit to work on my own!
I think if i'm correct all voltages from 0-5v are divided into 4.8mv chuncks
a few things I think I could do to improve the resolution.. is there a way to lower the reference voltage so that all 1024 values are referenced to 2v dc maximum?
And can i somehow add say 10 values together and divide to give me more than 1024 possible values?
I think I would need 4096 or 8192 possible values (12 or 13 bits) to give me 1mv resolution at 0-5v possible less if we can reduce the reference voltage (0-2v I think would be 2048 values equivalent to 11 bits)
I'm currently reading the values with putty and logging them to a csv file that I can both manipulate later in excel and view live with live-graph
Thanks for any help you may be able to offer
FYI I am analysing a Ni-mh Battery being charged and discharged in various chargers
I'm trying to view the method the chargers use to terminate the charge (delta peak etc
Therefore need more than 5mv of resolution
//
int analogInput = 0;
float vout = 0.0000;
float vin = 0.0000;
float R1 = 1.0; // resistance of R1 (100K) -see text!
float R2 = 1.0; // resistance of R2 (10K) - see text!
int value = 0;
void setup() {
pinMode(analogInput, INPUT);
Serial.begin(115200); // open the serial port at 9600 bps:
}
void loop() {
// read the value at analog input
value = analogRead(analogInput);
vout = (value * 5.0000) / 1024.00000; // see text
vin = vout ;
if (vin < 0.09) {
vin = 0.0; //statement to quash undesired reading !
}
delay(10);
Serial.print(vin , 3);
Serial.print(",");
Serial.println(); // carriage return after the last label
}
Thankyou for any help you can offer me!
Sorry if I have made any mistakes or this is in the wrong place
You have to amplify with an Op-amp. Google non-inverting amplifier. But you cannot go above 2V! Also I would use a z-diode to make sure that you don’t go above 5V on the pin.
So you are trying to get 12 bits from a 10bit AD converter, right?
If you are only doing around 2 volts measurements then why not use an A:D internal reference?
do an internet search on words like "arduino uno internal reference." Gives links like: arduino uno - what is aref(analog reference) - Arduino Stack Exchange, words that can be read and contain info that is relevant to your posted desires. But getting 12 bits from a 10bit A:D, good luck.
Using 5 volts gives 1023/5 steps per volt. Using 3.3V gives 1023/3.3 steps per volt. But still does not give 12 bits of A:D info.
You can easily get 12 bits out of a 10 bit ADC, with some caveats, by using oversampling and averaging.
On the Arduino, add up 64 measurements (as unsigned int), add 8 to round up, then divide by 16 to convert the range 0-1023 to 0-4095. This gives you 1.2 mV resolution per bit. It works as described in the note below on the ATmega328 (Arduino Uno and similar), but of course this severely limits the frequency response.
Idahowalker:
So you are trying to get 12 bits from a 10bit AD converter, right?
If you are only doing around 2 volts measurements then why not use an A:D internal reference?
do an internet search on words like "arduino uno internal reference." Gives links like: arduino uno - what is aref(analog reference) - Arduino Stack Exchange, words that can be read and contain info that is relevant to your posted desires. But getting 12 bits from a 10bit A:D, good luck.
Using 5 volts gives 1023/5 steps per volt. Using 3.3V gives 1023/3.3 steps per volt. But still does not give 12 bits of A:D info.
Thanks so much
This has helped a lot! I have set my bench psu to 2v and used that as the reference on the aref pin
and set analogReference(EXTERNAL)
results much more stable and at least double the resolution
A very good start to improving things Thankyou
//
int analogInput = 0;
float vout = 0.0000;
float vin = 0.0000;
float R1 = 1.0; // resistance of R1 (100K) -see text!
float R2 = 1.0; // resistance of R2 (10K) - see text!
int value = 0;
void setup() {
pinMode(analogInput, INPUT);
Serial.begin(9600); // open the serial port at 9600 bps:
analogReference(EXTERNAL); // use AREF for reference voltage
}
void loop() {
// read the value at analog input
value = analogRead(analogInput);
vout = (value * 2.000) / 1023.00000; // see text
vin = vout ;
if (vin < 0.09) {
vin = 0.0; //statement to quash undesired reading !
}
delay(10);
Serial.print(vin , 3);
Serial.print(",");
Serial.println(); // carriage return after the last label
}
jremington:
You can easily get 12 bits out of a 10 bit ADC, with some caveats, by using oversampling and averaging.
On the Arduino, add up 64 measurements (as unsigned int), add 8 to round up, then divide by 16 to convert the range 0-1023 to 0-4095. This gives you 1.2 mV resolution per bit. It works as described in the note below on the ATmega328 (Arduino Uno and similar), but of course this severely limits the frequency response.
should be replaced by the following, where Vcc is the accurately measured value of the Arduino Vcc.
vout = (value * Vcc) / 1023.0; // see text
Thankyou have corrected these mistakes! Cannot pretend to understand the article but will look into it.. I understand how averaging results can give a higher resolution (at least 1 bit extra is relatively easy) but still don't understand how to implement
unsigned int average = 0;
float Vcc = ?.? ; //put your *measured* value of Vcc here
for (int i=0; i<64; i++) average += analogRead(input_pin); //add 64 readings
average = (average+8)/16; //round up and divide
Serial.println(average); //raw 12 bit value
Serial.println((average*Vcc)/4095.0); //convert to volts using measured value of Vcc
float Vcc = ?.? ; //put your measured value of Vcc here
for (int i=0; i<64; i++) average += analogRead(input_pin); //add 64 readings
average = (average+8)/16; //round up and divide
Serial.println(average); //raw 12 bit value
Serial.println((average*Vcc)/4095.0); //convert to volts using measured value of Vcc
float Vcc = ?.? ; //put your measured value of Vcc here
for (int i=0; i<64; i++) average += analogRead(input_pin); //add 64 readings
average = (average+8)/16; //round up and divide
Serial.println(average); //raw 12 bit value
Serial.println((average*Vcc)/4095.0); //convert to volts using measured value of Vcc
So I have used this code in a new file with some of my code
Its seems to work however the first reading is correct 1.46volts
Subsequent readings are high by about 0.1 volts
see my code..
I'm now using a regulated bench supply for the reference voltage at 2.5 volts (set on the psu and tested with a DMM)
I have set reference to external and added a delay
int analogInput = 0;
unsigned int average = 0;
float vref = 2.5 ; //put your *measured* value of Vcc here
void setup() {
// put your setup code here, to run once:
pinMode(analogInput, INPUT);
Serial.begin(9600); // open the serial port at 9600 bps:
analogReference(EXTERNAL); // use AREF for reference voltage
}
void loop() {
// put your main code here, to run repeatedly:
delay(10);
for (int i = 0; i < 64; i++) average += analogRead(analogInput); //add 64 readings
average = (average + 8) / 16; //round up and divide
//Serial.println(average); //raw 12 bit value
Serial.println((average * vref) / 4095.0 , 4); //convert to volts using measured value of Vcc
}
If you want to try something else you can look towards the ADS1115 A/D used along with your Arduino. Simple, inexpensive and affords easily programmable gain. Just make sure you look to a reputable source as there are plenty of counterfeit modules floating around. Again, just something different to consider depending on exactly what you want or need.
Note: in your posted code, this line
Code: [Select]
vout = (value * 5.0000) / 1024.00000; // see text
should be replaced by the following, where Vcc is the accurately measured value of the Arduino Vcc.
Code: [Select]
vout = (value * Vcc) / 1023.0; // see text
I thought we had agreed the correct conversion was
vout = ((value +0.5) * Vcc) / 1024.0;
I have a page here with an excel spreadsheet showing the process;
however lets simplify; if you take a 1 bit converter with a 4V range your reading n can be 0 or 1.
the measurement then is
v = (n +0.5) * 4/2 giving v= ( 0+0.5) *2 = 1V or v=(1+0.5) *2 = 3V.
those values lie in the centre of the quantisation intervals
and most closely represent the "true" input voltage of 0 -1.9999* and 2.0 -4.0
where 1.999* =2.0
whereas taking v = n*4/1 gives values of 0V and 4V
jremington:
Every pass through loop(), you must set average to zero, before you accumulate readings.
SO... I have changed to uint32_t otherwise i get a random negative number
This seems to work
Also added a time stamp with an rtc module
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 rtc;
//int ain = analogRead(A0);
int analogInput = 0;
//int average = 0;
//uint32_t extraBits = 0; // use an unsigned integer or the bit shifting goes wonky
//unsigned int average = 0;
//unsigned int average2 = 0;
float vref = 2.5 ; //put your *measured* value of Vcc here
//int value[100]; // variable to store the value coming from the sensor
void setup() {
// put your setup code here, to run once:
pinMode(analogInput, INPUT);
Serial.begin(9600); // open the serial port at 9600 bps:
analogReference(EXTERNAL); // use AREF for reference voltage
while (!Serial); // for Leonardo/Micro/Zero
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
while (1);
}
if (! rtc.isrunning()) {
Serial.println("RTC is NOT running!");
// following line sets the RTC to the date & time this sketch was compiled
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}
}
void loop () {
delay(500);
DateTime now = rtc.now();
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.print(",");
uint32_t average = 0;
for (uint32_t i = 0; i < 64; i++) average += analogRead(A0); //add 64 readings
average = (average + 8) / 16; //round up and divide
Serial.print((average * vref) / 4095 , 4); //convert to volts using measured value of Vcc
Serial.print(",");
Serial.println();
}
however you can only improve results by oversampling to a very limited extent, and results assume you have the right amount of truly random noise in the measurement chain.
Also dont get confused between precision and accuracy.
For measurements accurate to 1 part in 4096 you will need a reference voltage accurate to 0.025%;
you will also need an ADC that is linear, monotonic and has less than +_1 LSB offset error.
However I believe for your stated application the more important factor is precision, so oversampling will likely work as your signal should contain enough noise. (unless its decoupled).
I thought we had agreed the correct conversion was
No, not at all.
I'll stick with the basic understanding of how the ADC actually works, and how best to interpret the result, which is not changed by subsequent manipulations.
Ron_Blain:
If you want to try something else you can look towards the ADS1115 A/D used along with your Arduino. Simple, inexpensive and affords easily programmable gain. Just make sure you look to a reputable source as there are plenty of counterfeit modules floating around. Again, just something different to consider depending on exactly what you want or need.
Ron
I have ordered a 16 bit 4 channel adc board that does 2 channel differential as well that will be useful for other projects
And an sd card module so i can record direct to a card and not serial
however you can only improve results by oversampling to a very limited extent, and results assume you have the right amount of truly random noise in the measurement chain.
Also dont get confused between precision and accuracy.
For measurements accurate to 1 part in 4096 you will need a reference voltage accurate to 0.025%;
you will also need an ADC that is linear, monotonic and has less than +_1 LSB offset error.
However I believe for your stated application the more important factor is precision, so oversampling will likely work as your signal should contain enough noise. (unless its decoupled).
There is a lot of noise! none of the cables are shielded.. but the bench psu is very stable especially with no load!
The results are much better
I realise they may not be 'truly' 12 bits but its good enough
And I agree they do not need to be completely accurate - relative voltages
relatively trying to visualize the delta peak drop (or lack of) when battery is fully charged (a voltage drop of anywhere between 0mv and 20mv)
I have already discovered a charger that massively overcharges, and 2 that are ok
next stage will be to fit a very low resistance shunt to measure the current simultaneously
Not sure how that will affect the charging of the batteries but I'm guessing lower resistance shunt will mean less impact on the charge but more difficult to measure
I will do that when I have the 16 bit ADC board which should make it easier
and maybe use a lower reference voltage and split the battery voltage with a divider so the current and battery voltage are similar ? may create too much noise on the battery voltage though
May just stick to 10 bits for current as it isn't as critical and just use the 16 bit board for voltage
I will also add thermistor to measure battery temperature during charge
Thanks for all the help everyone - much appreciated please see current code that is working it's not perfect but it matches the scope and dmm
Also added RTC
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 rtc;
//int ain = analogRead(A0);
int analogInput = 0;
//int average = 0;
//uint32_t extraBits = 0; // use an unsigned integer or the bit shifting goes wonky
//unsigned int average = 0;
//unsigned int average2 = 0;
float vref = 2.5 ; //put your *measured* value of Vcc here
//int value[100]; // variable to store the value coming from the sensor
void setup() {
// put your setup code here, to run once:
pinMode(analogInput, INPUT);
Serial.begin(9600); // open the serial port at 9600 bps:
analogReference(EXTERNAL); // use AREF for reference voltage
while (!Serial); // for Leonardo/Micro/Zero
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
while (1);
}
if (! rtc.isrunning()) {
Serial.println("RTC is NOT running!");
// following line sets the RTC to the date & time this sketch was compiled
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}
}
void loop () {
delay(20);
DateTime now = rtc.now();
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.print(",");
uint32_t average = 0;
for (uint32_t i = 0; i < 64; i++) average += analogRead(A0); //add 64 readings
average = (average + 8) / 16; //round up and divide
Serial.print((average * vref) / 4095 , 4); //convert to volts using measured value of Vcc
Serial.print(",");
Serial.println();
}
It seems I cannot upload images yet.. I wanted to share the first graph I'm happy with to describe that charge discharge charge voltages of a single ni-mh cell