Bluetooth voltmeter based on arduino. Digital voltmeter on Arduino with connection to PC via serial port

Presented useful diagram for those who like to experiment with Arduino. This is a simple digital voltmeter that can reliably measure DC voltage in the range 0 - 30V. The Arduino board, as usual, can be powered by a 9V battery.

As you probably know, Arduino's analog inputs can be used to measure DC voltage in the range 0 – 5V and this range can be increased,
using two resistors as a voltage divider. The divider will reduce the measured voltage to the level of the Arduino analog inputs. And then the program will calculate the real voltage value.

The analog sensor on the Arduino board detects the presence of voltage at the analog input and converts it into digital form for further processing by the microcontroller. In the figure, voltage is supplied to the analog input (A0) through a simple voltage divider consisting of resistors R1 (100 kOhm) and R2 (10 kOhm).

With these divider values, the Arduino board can be supplied with voltage from 0 to
55V. At input A0 we have the measured voltage divided by 11, i.e. 55V / 11=5V. In other words, when measuring 55V at the Arduino input we have the maximum permissible value 5V. In practice, it is better to write the range “0 - ​​30V” on this voltmeter so that it remains
Safety margin!

Notes

If the display readings do not coincide with the readings of an industrial (laboratory) voltmeter, then it is necessary precision instrument measure the resistance values ​​R1 and R2 and insert these values ​​instead of R1=100000.0 and R2=10000.0 in the program code. Then you should measure the real voltage between the 5V and “Ground” pins of the Arduino board with a laboratory voltmeter. The result will be a value less than 5V, for example, it will be 4.95V. This real value should be inserted in the line of code
vout = (value * 5.0) / 1024.0 instead of 5.0.
Also, try to use precision resistors with a 1% tolerance.

Resistors R1 and R2 provide some protection against increased input voltages. However, remember that any voltages above 55V can damage the Arduino board. In addition, this design does not provide other types of protection (against voltage surges, polarity reversal or overvoltage).

Digital voltmeter program

/*
DC Voltmeter
An Arduino DVM based on voltage divider concept
T.K.Hareendran
*/
#include
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
int analogInput = 0;
float vout = 0.0;
float vin = 0.0;
float R1 = 100000.0; // resistance of R1 (100K) -see text!
float R2 = 10000.0; // resistance of R2 (10K) – see text!
int value = 0;
void setup())(
pinMode(analogInput, INPUT);
lcd.begin(16, 2);
lcd.print(“DC VOLTMETER”);
}
void loop()
// read the value at analog input
value = analogRead(analogInput);
vout = (value * 5.0) / 1024.0; // see text
vin = vout / (R2/(R1+R2));
if (vin<0.09) {
vin=0.0;//statement to quash undesired reading !
}
lcd.setCursor(0, 1);
lcd.print(“INPUT V= “);
lcd.print(vin);
delay(500);
}

Schematic diagram of Arduino-voltmeter

List of components

Arduino Uno board
100 kOhm resistor
10 kOhm resistor
100 ohm resistor
10kOhm Trimmer resistor
LCD display 16?2 (Hitachi HD44780)

This article provides an interesting diagram for those who like to experiment and Arduino. It features a simple digital voltmeter that can safely measure DC voltage in the range of 0 to 30 V. The Arduino board itself can be powered from a standard 9 V supply.



As you know, using the Arduino analog input you can measure voltage from 0 to 5 V (with a standard reference voltage of 5 V). But this range can be expanded by using a voltage divider.


The divider reduces the measured voltage to a level acceptable for the analog input. Then specially written code calculates the actual voltage.



The analog sensor in Arduino detects the voltage at the analog input and converts it into a digital format that can be read by the microcontroller. We connect a voltage divider formed by resistances R1 (100K) and R2 (10K) to the analog input A0. With these resistance values, up to 55 V can be supplied to the Arduino, since the division coefficient in this case is 11, so 55V/11 = 5V. In order to be sure that measurements are safe for the board, it is better to measure voltage in the range from 0 to 30 V.



If the display readings do not match the verified voltmeter readings, use a precision digital multimeter to find the exact values ​​of R1 and R2. In this case, in the code you will need to replace R1=100000.0 and R2=10000.0 with your own values. Then you should check the power supply by measuring the voltage on the board between 5V and GND. The voltage can be 4.95 V. Then in the code vout = (value * 5.0) / 1024.0 you need to replace 5.0 with 4.95. It is advisable to use precision resistors with an error of no more than 1%. Remember that voltage above 55V can damage the Arduino board!



#include LiquidCrystal lcd(7, 8, 9, 10, 11, 12); int analogInput = 0; float vout = 0.0; float vin = 0.0; float R1 = 100000.0; // resistance R1 (100K) float R2 = 10000.0; // resistance R2 (10K) int value = 0; void setup())( pinMode(analogInput, INPUT); lcd.begin(16, 2); lcd.print("DC VOLTMETER"); ) void loop())( // read the analog value value = analogRead(analogInput); vout = (value * 5.0) / 1024.0; vin = vout / (R2/(R1+R2));<0.09) { vin=0.0;// обнуляем нежелательное значение } lcd.setCursor(0, 1); lcd.print("INPUT V= "); lcd.print(vin); delay(500); }


Elements used:


Arduino Uno board
Resistor 100 KOhm
Resistor 10 KOhm
100 ohm resistor
Potentiometer 10KOhm
LCD display 16×2

Analog inputs of the Arduino board.

The Arduino UNO board contains 6 analog inputs designed to measure voltage signals. It would be more correct to say that the 6 pins of the board can operate in both discrete output and analog input modes.

These pins are numbered 14 to 19. They are initially configured as analog inputs and can be accessed using the names A0-A5. They can be configured to operate as discrete outputs at any time.

pinMode(A3, OUTPUT); // setting the discrete output mode for A3
digitalWrite(A3, LOW); // setting output A3 low

To return to analog input mode:

pinMode(A3, INPUT); // setting the analog input mode for A3

Analog inputs and pull-up resistors.

Pull-up resistors are connected to the analog input pins, as well as to the discrete pins. These resistors are turned on using the command

digitalWrite(A3, HIGH); // turn on the pull-up resistor to input A3

The command must be applied to a pin configured in input mode.

It must be remembered that the resistor can affect the level of the analog input signal. The current from the 5V power supply, through the pull-up resistor, will cause a voltage drop across the internal resistance of the signal source. So it's better to disconnect the resistor.

Analog-to-digital converter of Arduino board.

The actual voltage measurement at the inputs is carried out by an analog-to-digital converter (ADC) with a switch for 6 channels. The ADC has a resolution of 10 bits, which corresponds to the code at the output of the converter 0...1023. The measurement error is no more than 2 units of the least significant digit.

To maintain maximum accuracy (10 bits), it is necessary that the internal resistance of the signal source does not exceed 10 kOhm. This requirement is especially important when using resistor dividers connected to the analog inputs of the board. The resistance of the divider resistors cannot be too high.

Analog input software functions.

int analogRead(port)

Reads the voltage value at the specified analog input. The input voltage ranges from 0 to the level of the reference voltage (often 5 V) converts to a code from 0 to 1023.

With a reference voltage of 5 V, the resolution is 5 V / 1024 = 4.88 mV.

The conversion takes approximately 100 μs.

int inputCod; // input voltage code
float inputVoltage; // input voltage in V

inputCod= analogRead(A3); // reading voltage at input A3
inputVoltage= ((float)inputCod * 5. / 1024.); // conversion of code to voltage (V)

void analogReference(type)

Sets the reference voltage for the ADC. It defines the maximum analog input voltage that the ADC can correctly convert. The value of the reference voltage also determines the code-to-voltage conversion factor:

Input voltage = ADC code * reference voltage / 1024.

The type argument can take the following values:

  • DEFAULT – the reference voltage is equal to the controller supply voltage (5 V or 3.3 V). For Arduino UNO R3 – 5 V.
  • INTERNAL – internal reference voltage 1.1 V for boards with ATmega168 and ATmega328 controllers, for ATmega8 – 2.56 V.
  • INTERNAL1V1 – internal 1.1 V reference voltage for Arduino Mega controllers.
  • INTERNAL2V56 – internal 2.56 V reference voltage for Arduino Mega controllers.
  • EXTERNAL – external reference voltage source, connected to the AREF input.

analogReference(INTERNAL); // reference voltage is 1.1 V

Two-channel voltmeter on Arduino.

As an example of using analog input functions, let's create a project for a simple digital voltmeter on Arduino. The device must measure voltages at two analog inputs of the board, and transmit the measured values ​​to the computer via a serial port. Using this project as an example, I will show the principles of creating simple measurement and information collection systems.

Let's decide that the voltmeter should measure voltage within the range of at least 0...20 V and develop a circuit for connecting the voltmeter inputs to the Arduino UNO board.

If we set the reference voltage to 5 V, then the analog inputs of the board will measure voltage within 0...5 V. And we need at least 0...20 V. This means we need to use a voltage divider.

The voltage at the input and output of the divider are related by the relation:

Uoutput = (Uinput / (R1 + R2)) * R2

Transmission ratio:

K = Uoutput / Uinput = R2 / (R1 + R2)

We need a transfer ratio of 1/4 (20 V * 1/4 = 5 V).

To maintain maximum accuracy (10 bits), it is necessary that the internal resistance of the signal source does not exceed 10 kOhm. Therefore, we choose resistor R2 equal to 4.22 kOhm. We calculate the resistance of resistor R1.

0.25 = 4.22 / (R1 + 4.22)
R1 = 4.22 / 0.25 – 4.22 = 12.66 kOhm

I found resistors with a resistance of 15 kOhm with the closest value. With resistors R1 = 15 kOhm and R2 = 4.22:

5 / (4.22 / (15 + 4.22)) = 22.77 V.

The Arduino-based voltmeter circuit will look like this.

Two voltage dividers are connected to analog inputs A0 and A1. Capacitors C1 and C2, together with divider resistors, form low-pass filters that remove high-frequency noise from the signals.

I assembled this circuit on a breadboard.

I connected the first input of the voltmeter to a regulated power source, and the second to the 3.3 V power supply of the Arduino board. To monitor the voltage, I connected a voltmeter to the first input. All that remains is to write the program.

A program for measuring voltage using an Arduino board.

The algorithm is simple. Necessary:

  • read the ADC code twice per second;
  • convert it into voltage;
  • send measured values ​​via serial port to a computer;
  • port monitor program Arduino IDE display the obtained voltage values ​​on the computer screen.

I’ll give you a complete sketch of the program right away.

// voltage measurement program
// on analog inputs A0 and A1

#include

measurement period time
#define R1 15. // resistance of resistor R1
#define R2 4.22 // resistance of resistor R2


float u1, u2; // measured voltages

void setup() (
Serial.begin(9600); //

MsTimer2::start(); // interrupt enable
}

void loop() (

// period 500 ms
if (timeCount >= MEASURE_PERIOD) (
timeCount= 0;

//

// reading channel 2 code and converting to voltage
u2= ((float)analogRead(A1)) * 5. / 1024. / R2 * (R1 + R2);

// data transfer via serial port
Serial.print("U1 = "); Serial.print(u1, 2);
Serial.print(" U2 = "); Serial.println(u2, 2);
}
}

// interrupt processing 1 ms
void timerInterrupt() (
timeCount++;
}

Let me explain the line in which the ADC code is converted into voltage:

// reading channel 1 code and converting to voltage
u1= ((float)analogRead(A0)) * 5. / 1024. / R2 * (R1 + R2);

  • The ADC code is read: analogRead(A0) .
  • Explicitly converted to floating point format: (float) .
  • Converted to voltage at the analog input: * 5. / 1024. The dot at the end of the numbers indicates that this is a floating point number.
  • The divider transmission coefficient is taken into account: / R2 * (R1 + R2).

Let's load the program into the board and launch the serial port monitor.

Two running bars show the values ​​of the measured voltages. Everything is working.

Measuring the average signal value.

Let's connect the first channel of our voltmeter to a voltage source with a high ripple level. We will see this picture on the monitor.

The voltage values ​​of the first channel on the monitor screen are constantly twitching and jumping. And the readings of the control voltmeter are quite stable. This is because the reference voltmeter measures the average value of the signal, while the Arduino board reads individual samples every 500 ms. Naturally, the moment the ADC reads falls at different points in the signal. And at a high level of pulsations, the amplitude at these points is different.

In addition, if the signal is read in separate sparse samples, then any impulse noise can introduce a significant error into the measurement.

The solution is to take several frequent samples and average the measured value. For this:

  • in the interrupt handler we read the ADC code and sum it with the previous samples;
  • count the averaging time (number of averaging samples);
  • when the specified number of samples is reached, we save the total value of the ADC codes;
  • To obtain the average value, divide the sum of ADC codes by the number of averaging samples.

Problem from an 8th grade mathematics textbook. Here is a sketch of the program, a two-channel average-value voltmeter.

// medium voltage measurement program
// on analog inputs A0 and A1

#include

#define MEASURE_PERIOD 500 // measurement period time
#define R1 15. // resistance of resistor R1
#define R2 4.22 // resistance of resistor R2

int timeCount; // time counter
long sumU1, sumU2; // variables for summing ADC codes
long avarageU1, avarageU2; // sum of ADC codes (average value * 500)
boolean flagReady; // indicator of readiness of measurement data

void setup() (
Serial.begin(9600); // initialize the port, speed 9600
MsTimer2::set(1, timerInterupt); // timer interrupts, period 1 ms
MsTimer2::start(); // interrupt enable
}

void loop() (

if (flagReady == true) (
flagReady= false;
// conversion to voltage and transfer to computer
Serial.print("U1 = ");
Serial.print((float)avarageU1 / 500. * 5. / 1024. / R2 * (R1 + R2), 2);
Serial.print(" U2 = ");
Serial.println((float)avarageU2 / 500. * 5. / 1024. / R2 * (R1 + R2), 2);
}
}

// interrupt processing 1 ms
void timerInterrupt() (

timeCount++; // +1 averaging sample counter
sumU1+= analogRead(A0); // summing ADC codes
sumU2+= analogRead(A1); // summing ADC codes

// checking the number of averaging samples
if (timeCount >= MEASURE_PERIOD) (
timeCount= 0;
avarageU1= sumU1; // average value overload
avarageU2= sumU2; // average value overload
sumU1= 0;
sumU2= 0;
flagReady= true; // sign measurement result is ready
}
}

In the formula for converting the ADC code into voltage, /500 was added - the number of samples. Load, launch the port monitor (Cntr+Shift+M).

Now, even with a significant level of pulsation, the readings change by hundredths. This is only because the voltage is not stabilized.

The number of samples must be chosen taking into account:

  • the number of samples determines the measurement time;
  • how larger number samples, the less the influence of noise will be.

The main source of interference in analog signals is the 50 Hz network. Therefore, it is advisable to choose an averaging time that is a multiple of 10 ms – the half-cycle time of a 50 Hz network.

Optimization of calculations.

Floating point calculations simply consume the resources of an 8-bit microcontroller. Any floating point operation requires mantissa denormalization, fixed point operation, mantissa normalization, order correction... And all operations with 32 digit numbers. Therefore, it is necessary to minimize the use of floating point calculations. I'll tell you how to do this in the following lessons, but let's at least optimize our calculations. The effect will be significant.

In our program, the conversion of the ADC code into voltage is written as follows:

(float)avarageU1 / 500. * 5. / 1024. / R2 * (R1 + R2)

There are so many calculations here, all with floating point. But most of the calculations are operations with constants. Part of the line:

/ 500. * 5. / 1024. / R2 * (R1 + R2)

(float)avarageU1 * 0.00004447756

Smart compilers themselves recognize calculations with constants and calculate them at the compilation stage. I have a question about how smart Andruino's compiler is. I decided to check it out.

I wrote a short program. It performs a cycle of 10,000 passes and then transmits the execution time of those 10,000 cycles to the computer. Those. it allows you to see the execution time of operations placed in the body of the loop.

// calculation optimization check

int x= 876;
float y;
unsigned int count;
unsigned long timeCurrent, timePrev;

void setup() (
Serial.begin(9600);
}

void loop() (
count++;
// y= (float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22);
// y= (float)x * 0.00004447756 ;

if (count >= 10000) (
count= 0;
timeCurrent= millis();
Serial.println(timeCurrent - timePrev);
timePrev= timeCurrent;
}
}

In the first option, when floating point operations in the loop are commented out and not executed, the program produced a result of 34 ms.

Those. 10,000 empty loops are completed in 34 ms.

Then I opened the line:

y= (float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22);

repeats our calculations. Result of 10,000 passes in 922 ms or

(922 – 34) / 10,000 = 88.8 µs.

Those. this line of floating point calculations takes 89 µs to complete. I thought there would be more.

Now I closed this line with a comment and opened the next one, multiplying by a pre-calculated constant:

y= (float)x * 0.00004447756 ;

Result of 10,000 passes in 166 ms or

(166 – 34) / 10,000 = 13.2 µs.

Amazing result. We saved 75.6 μs per line. We completed it almost 7 times faster. We have 2 such lines. But there can be much more of them in the program.

Conclusion - calculations with constants must be done yourself on a calculator and used in programs as ready-made coefficients. The Arduino compiler will not calculate them at the compilation stage. In our case we should do this:

#define ADC_U_COEFF 0.00004447756 // conversion factor of ADC code to voltage

Serial.print((float)avarageU1 * ADC_U_COEFF, 2);

The optimal option for performance is to transfer the ADC code to the computer, and along with it all floating point calculations. In this case, the computer must receive data specialized program. The port monitor from the Arduino IDE will not work.

I will talk about other ways to optimize Arduino programs in future lessons as needed. But without solving this issue it is impossible to develop complex programs on an 8-bit microcontroller.

Another lesson has appeared on the site (

There are times when you want to check voltage or some point in a circuit, but you don't have a voltmeter or multimeter at hand? Run to buy? It's long and expensive. Before you do that, how about building a voltmeter yourself? In fact, with the help simple components you can make it yourself.

  • In the lesson we used a board compatible with Arduino - SunFounder Uno / Mars (http://bit.ly/2tkaMba)
  • USB data cable
  • 2 potentiometers (50k)
  • LCD1602 - http://bit.ly/2ubNEfi
  • Development board - http://bit.ly/2slvfrB
  • Multiple jumpers

Before connecting, let's first understand how it works.

Use SunFounder Uno board for the main part of the voltmeter data processing, LCD1602 as the screen, a potentiometer to adjust the LCD contrast, and another to divide the voltage.

When you rotate the potentiometer connected to the Uno board, the potentiometer resistor changes, thereby changing the voltage across it. The voltage signal will be sent to the Uno board through pin A0, and the Uno will convert the received analog signal into digital form and records on the LCD display. This way you can see the voltage value at the current capacitance resistance.

LCD1602 has two operating modes: 4-bit and 8-bit. When MCU IO is insufficient, you can choose 4-bit mode, which only uses pins D4~D7.

Follow the table to connect them.

Step 4: Connect the potentiometer to LCD1602

Connect the middle pin of the potentiometer to the Vo pin on the LCD1602 and any of the other pins to GND.

Connect the middle pin of the potentiometer to pin A0 from the SunFounder Uno, and one of the others to 5V while the other to GND.

Step 6: Upload the code

This code:

#include /****************************************************** *****/ const int analogIn = A0;//potentiometer attach to A0 LiquidCrystal lcd(4, 6, 10, 11, 12, 13);//lcd(RS,E,D4,D5,D6.D7) float val = 0;// define the variable as value=0 /*********************************** *****************/ void setup() ( Serial.begin(9600);//Initialize the serial lcd.begin(16, 2);//set the position of the characters on the LCD as Line 2, Column 16 lcd.print("Voltage Value:");//print "Voltage Value:" ) /****************** **********************************/ void loop() ( val = analogRead(A0);//Read the value of the potentiometer to val val = val/1024*5.0;// Convert the data to the corresponding voltage value in a math way Serial.print(val);//Print the number of val on the serial monitor Serial.print ("V"); // print the unit as V, short for voltage on the serial monitor lcd.setCursor(6,1);//Place the cursor at Line 1, Column 6. From here the characters are to be displayed lcd.print(val);//Print the number of val on the LCD lcd.print("V");//Then print the unit as V, short for voltage on the LCD delay(200); // Wait for 200ms)

Rotate the potentiometer to check the voltage on the LCD1602 in real time.

Here's a tricky thing. After I ran the code, the LCD showed characters. I then adjusted the screen contrast (gradual change from black to white) by turning the potentiometer clockwise or counterclockwise until the screen displayed the characters clearly.

Take two batteries to measure their voltage: 1.5 V and 3.7 V. Unhook the connection of the second potentiometer to the A0 and GND pin, which means removing the potentiometer from the circuit. Clamp the end of wire A0 to the battery anode and the GND circuit to the cathode. DO NOT plug them back in or you will get short circuit on the battery. A value of 0V is a reverse connection.

So, the battery voltage is displayed on the LCD display. There may be some error between the value and the nominal value because the battery is not fully charged. And that's why I need to measure the voltage to understand whether I can use the battery or not.

PS: If you have problems with display on the display - see this FAQ for LCD displays - http://wiki.sunfounder.cc/index.php?title=LCD1602/I2C_LCD1602_FAQ.

Initial data and revision

So at this point we have a constant voltage voltmeter with a limit of 0..20V (see the previous part). Now we add a 0..5a ammeter to it. To do this, we slightly modify the circuit - it will become a pass-through circuit, that is, it has both an input and an output.

I removed the part regarding the display on the LCD - it will not change. Basically basic new element- Rx shunt at 0.1 Ohm. The R1-C1-VD1 chain serves to protect the analog input. It makes sense to install the same at input A0. Since we assume enough high currents, there are installation requirements - power lines must be made with a sufficiently thick wire and connected directly to the shunt terminals (in other words, soldered), otherwise the readings will be far from reality. There is also a note on current - in principle, a reference voltage of 1.1V allows you to register it on the shunt 0.1 Ohm current up to 11 amperes with an accuracy slightly worse than 0.01a, but when such a voltage drops across Rx, the released power will exceed 10 W, which is not fun at all. To solve the problem, you could use an amplifier with a gain of 11 using a high-quality op-amp and a 10 mOhm (0.01 Ohm) shunt. But for now we won’t complicate our lives and simply limit the current to 5A (in this case, the Rx power can be selected on the order of 3-5 W).

At this stage, a surprise awaited me - it turned out that the controller’s ADC had a fairly large zero offset - about -3mV. That is, the ADC simply does not see signals less than 3 mV, and signals of a slightly higher level are visible with a characteristic inaccuracy of -3 mV, which spoils the linearity at the beginning of the range. A quick search did not give any obvious references to such a problem (a zero offset is normal, but it should be significantly smaller), so it is quite possible that this is a problem with a specific Atmega 328 instance. The solution I chose was twofold - in voltage - a software step at the beginning of the range (the display starts at 0.06 volts), for current - a pull-up resistor to the 5V bus. The resistor is indicated by a dotted line.

Source

The full version of this volt-ampere meter (in the I2C version) can be downloaded from the link at the end of the article. Next I will show the changes in source code. Added reading of analog input A1 with the same averaging as for the voltmeter. In essence, this is the same voltmeter, only without a divider, and we get amperes using Ohm’s formula: I = U/Rx (for example, if the voltage drop across Rx = 0.01 V, then the current is 0.1A). I also introduced the current gain constant AmpMult - for the future. The AmpRx constant with the shunt resistance will probably have to be matched to take into account the inaccuracy of the shunt resistor. Well, since this is already a volt-ampere meter and there is still space left on the 1602 display, it remains to display the current power consumption in watts, obtaining simple additional functionality.

.... // Analog input #define PIN_VOLT A0 #define PIN_AMP A1 // Internal reference voltage (select) const float VRef = 1.10; // Input resistive divider coefficient (Rh + Rl) / Rl. IN 0.2) InVolt += 3; // Convert to volts (In: 0..1023 -> (0..VRef) scaled by Mult) float Volt = InVolt * VoltMult * VRef / 1023; float Amp = InAmp * VRef / AmpMult / AmpRx / 1023 ; // To take into account the drop on the shunt, uncomment 2 lines //float RxVolt = InAmp * VRef / 1023 / AmpMult; // Volt -= RxVolt; float Watt = Volt * Amp; // Output data lcd.setCursor (8, 0); lcd.print(Watt); lcd.print("W "); lcd.setCursor(0, 1); lcd.print(Volt); lcd.print("V "); lcd.setCursor(8, 1); lcd.print(Amp); lcd.print("A "); )

Links

  • LiquidCrystal_I2C library, which allows you to set the pinout