To measure the voltage, you need an ADC. Which is called Analog to Digital converter. In arduino, for instance uno has 10 bit ADC, that means you can measure whatever you want as a 10 bit resolution that also means your scale is between 0 to pow(2,10). If you have a better resolution than you get better result as you expect. But here is the one thing that you can just measure the 0-5 volt with only arduino, because if you give much voltage to the analog input then you burn up the board basicly, so we have two problems. One of them is the ability to meausre voltage which is higher then 5 volt, another one is resolution.
The key word is, to measure the voltage higher then 5 Volt, Voltage Divider rule. Using the rule, it is possible to measure the voltage whatever you want theoretically. You can google it the word then you get exactly what you want. But let me explain. You have two resistor. One of them has much resistance then the other. You connect them in serial. So give the voltage difference to this circuit. Then you need to take the voltage information on the resistance which is lower then the other. For example you have 68k resistor and 1 k resitor. Connect them serially and take the voltage difference on the 1 k resistor. So basically, you can measure voltage scale up to 400 volts !! I think that is great !!. That means we solve the scale problem.
So the other problem, that we have to solve, is the resolution. You can ask why is it so important ? Than I can simply answer, If you want to measure the voltage like 56,332124 Volt than resolution will be needed.
To get over this problem high resolution adc is needed. I use for example LTC 2400 which has 24 bit resolution. It is from Linear Technology. The situation is, I used the LTC 2400 not ADC of arduino.I just use arduino to take the value and sent it to somewhere.
In the project LTC 2400 module is the heart of the system. You can find the code on the internet which
work with arduino. But first time when I used the code I realize that there is no returnable value. I mean you can see the voltage information on the screen but you can not use it in the any mathematical equation. That makes me crazy !! So I modified the code than I wrote it to give me returnable voltage value. I give the code you can check it or use it.
Also I need to say LTC 2400 communicate with arduino using SPI protocol. The code includes spi protocol
that I do not explain here it. Here you can find the information about spi and arduino.
//LTC2400 24bit ADC Module
//
//Application Demo: 7 digit voltmeter
//24bit ADC IC: LTC2400
//4.096 precision reference: TI REF3040
//
//Modified by Ulas Dikme
//www.ulasdikme.com
#include <Stdio.h>
#include<stdlib.h>
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
#define LTC_CS 2 // LTC2400 Chip Select Pin on Portb 2
#define LTC_MISO 4 // LTC2400 SDO Select Pin on Portb 4
#define LTC_SCK 5 // LTC2400 SCK Select Pin on Portb 5
void setup() {
cbi(PORTB,LTC_SCK); // LTC2400 SCK low
sbi (DDRB,LTC_CS); // LTC2400 CS HIGH
cbi (DDRB,LTC_MISO);
sbi (DDRB,LTC_SCK);
Serial.begin(9600);
// init SPI Hardware
sbi(SPCR,MSTR) ; // SPI master mode
sbi(SPCR,SPR0) ; // SPI speed
sbi(SPCR,SPR1); // SPI speed
sbi(SPCR,SPE); //SPI enable
//Serial.println("LTC2400 ADC Test");
}
double volt;
float v_ref=4.094; // Reference Voltage, 5.0 Volt for LT1021 or 3.0 for LP2950-3
//float v_ref=6;
long int ltw = 0; // ADC Data ling int
int cnt; // counter
byte b0; //
byte sig; // sign bit flag
char st1[20]; // float voltage text
int counter = 0;
char inData[20]; // Allocate some space for the string
char inChar; // Where to store the character read
char IDN[]="IDN";
byte index = 0; // Index into array; where to store the character
int incomingByte = 0; // for incoming serial data
char byteRead;
/********************************************************************/
void loop() {
cbi(PORTB,LTC_CS); // LTC2400 CS Low
delayMicroseconds(1);
if (!(PINB & (1 << 4))) { // ADC Converter ready ?
// cli();
ltw=0;
sig=0;
b0 = SPI_read(); // read 4 bytes adc raw data with SPI
if ((b0 & 0x20) ==0) sig=1; // is input negative ?
b0 &=0x1F; // discard bit 25..31
ltw |= b0;
ltw <<= 8;
b0 = SPI_read();
ltw |= b0;
ltw <<= 8;
b0 = SPI_read();
ltw |= b0;
ltw <<= 8;
b0 = SPI_read();
ltw |= b0;
delayMicroseconds(1);
sbi(PORTB,LTC_CS); // LTC2400 CS Low
if (sig) ltw |= 0xf0000000; // if input negative insert sign bit
ltw=ltw/16; // scale result down , last 4 bits have no information
volt = ltw * v_ref / 16777216; // max scale
printFloat(volt,6); // print voltage as floating number
delay(1);
sbi(PORTB,LTC_CS); // LTC2400 CS hi
}
}
/********************************************************************/
byte SPI_read()
{
SPDR = 0;
while (!(SPSR & (1 << SPIF))) ; /* Wait for SPI shift out done */
return SPDR;
}
/********************************************************************/
// printFloat from tim / Arduino: Playground
// printFloat prints out the float "value" rounded to "places" places
//after the decimal point
void printFloat(float value, int places) {
delay(1);
// this is used to cast digits
int digit;
float tens = 0.1;
int tenscount = 0;
int i;
float tempfloat = value;
int s=tenscount;
/* variables for the right side of the float */
int array1[3];
int a=0;
int degerR=0; //this value is the before the comma
/*----------------------------------------------------------------------------------------*/
/* variables for the left side of the float */
int array2[6]; // for the left side of the float. Becareful about the size of the array
int b=0; //counter
double degerL=0; // it is the variable left side is setted
/*-----------------------------------------------------------------------------------------*/
/*variables for the real whole voltage value and ten of them*/
double realValue;
/*---------------------------------------------------------------------------------*/
// if value is negative, set tempfloat to the abs value
// make sure we round properly. this could use pow from
//<math.h>, but doesn`t seem worth the import
// if this rounding step isn`t here, the value 54.321 prints as
// calculate rounding term d: 0.5/pow(10,places)
float d = 0.5;
if (value < 0)
d *= -1.0;
// divide by ten for each decimal place
for (i = 0; i < places; i )
d/= 10.0;
// this small addition, combined with truncation will round our
tempfloat = d;
if (value < 0)
tempfloat *= -1.0;
while ((tens * 10.0) <= tempfloat) {
tens *= 10.0;
tenscount = 1;
}
// write out the negative if needed
//Serial.println(tenscount);
a=0;
for (i=0; i<tenscount; i ) {
digit = (int) (tempfloat/tens);
//Serial.print(digit,DEC);
array1[a]=digit;
a ;
tempfloat = tempfloat - ((float)digit * tens);
tens /= 10.0;
} // end of tenscount for loop
//Serial.println(a);
if(a==0){ // it is when left side of the comma is equal to 0 ( like 0.123123 )
degerR=0;
}
else if(a==1){ // it is when left side of the comma has 1 digit number ( like 1.123123 )
degerR=array1[0];
}
else if(a==2){ // it is when left side of the comma has 2 digit number ( like 12.123123 )
degerR=array1[0]*10 array1[1];
}
else if(a==3){ // it is when left side of the comma has 3 digit number ( like 123.123123 )
delay(1);
degerR=array1[0]*100 array1[1]*10 array1[3];
}
//Serial.println(degerR); // it is the right side of the voltage information
a=0; //counter a is setted to 0 after loop
// if no places after decimal, stop now and return
if (places <= 0)
return;
// otherwise, write the point and continue on
//Serial.print(",");
for (i = 0; i < places; i ) {
tempfloat *= 10.0;
digit = (int) tempfloat;
//Serial.print(digit,DEC);
array2[b]=digit;
b ;
// once written, subtract off that digit
tempfloat = tempfloat - (float) digit;
}
delay(1);
degerL=array2[0]*0.1 array2[1]*0.01 array2[2]*0.001 array2[3]*0.0001 array2[4]*0.00001 array2[5]*0.000001 array2[6]*0.0000001;
realValue=degerR degerL;
//Serial.println(degerL,6);
if(value<0){
realValue=realValue*(-1);
}
realVal(realValue); // it is used for much precision
//Serial.println(realValue,6);
}
void realVal(double realValue){
double array3[10];
float dividedVoltage;
array3[counter]=realValue;
counter ;
delay(1);
dividedVoltage=(array3[0] array3[1] array3[2] array3[3] array3[4] array3[5] array3[6] array3[7] array3[8] array3[9])/10;
if(counter==10){
/***** To check the all double number in array3[] *************
*
* for(int i=0; i<10; i ){
* Serial.println(array3[i],6);
* }
*
**************************************************************/
Serial.print(dividedVoltage,6);
Serial.print(" Volt");
//Serial.println();
//delay(3000);
counter=0;
}
}
I give you a hint. When you use the voltage divider rule, some points you can not
measure perfectly. I mean some points you can good measure but some point you do not. To solve that you can divide the voltage
scale you want to meausure. For example below the 1 volt you can use different resistor values, above the 1 volt you can use different
resistor values this increase the resolution of the data you get. For instance in my project When red light is on below the 4 volt I
can measure when the other two leds are on above the 4 volt that I can measure.
As I said before when you use voltage divider you can measure some points perfectly but some points are not desirable.To get over this problem, you should calibrate the circuit. There are bloody thousands way to do that. For example you can change the reference voltage of the LTC 2400 but I do not recomend it.Therefore I used a way which is all about software that makes me no handling much with electronical components. Using MATLAB !!! Actually using interpolation MATLAB helps to do it. Let me explain how it is possible. First you need to power supply which is really sensitive and you need MATLAB. You start a point for example 1 volt give to the circuit and note that what you read on the lower value resistance. Then also note real value you have to read which is 1 volt. Then take some data. Using matlab find all values which are needed to multiply our value on the lower resistor to get the real voltage value. Basicly divide real voltage value by the value on the lower resistor lets call it Vout. The result lets call it multiplier. Now graph the Vout vs Multiplier. Here you can find the link about basic fitting.
Now on left side of the figure edit>figure properties then tools>basic fitting. From the opened screen click Center and scale x data and choose quadratic one. When click it you see some exponential line on the your graph also it gives the equaition. copy equaiton and paste it in you code. When code is working read Vout but do not show it, depends on Vout Mutliplier will be change so using equaiton find Multiplier and multiply it with Vout.Than the information is really equal to the incoming real voltage. You do not have to worry about equation MATLAB will do what you need absolutely.
Multiplier = p1*dividedVoltage^2 p2*dividedVoltage p3;
Serial.print(dividedVoltage*Multiplier);
// my equation I get it from basic fitting tool using matlab.
// Then first I found the relationship between multiplier and dividedVoltage
//you can find the dividedVoltage in my code above
//after that basicly I multiply it with Multiplier.
//when incoming voltage changes multiplier also changes
//so you calibrate the system.
Explanation is finished. If you have any question click the green arrow (probably you click before) and also click contact which is blue image. Now ask me whatever you want about the project. This project is developed to measure the voltage on the ion pomps for Turkish Accelerator Center at Ankara. Thanks to Burak Koç to help me about electronics , thanks to Mustafa Yıldız to help me about design to box. Also I want to say thanks to Emre Kazancı who trigs me to learn EPICS. I do not say anything about EPICS here so do not worry about it. I use it during the developing voltmeter.