Secret voltmeter in Arduino - measuring battery voltage using a microcontroller. Digital voltmeter on Arduino with connection to PC via serial port

Idea

Idea devices for measuring voltage, current, capacity, discharge, and maybe charge arose a long time ago and not only for me. You can find many toys called USB Tester (Doctor) for testing various devices from USB. I'm a little more interested universal device, independent of the interface, but simply designed for certain voltages and currents. For example, 0 - 20.00v, 0 - 5.00a, 0 - 99.99Ah. As for functions, I see it like this

  • Displays current voltage and current, that is, a volt-ampere meter. In principle, you can immediately reflect the power.
  • Counting and displaying accumulated capacity. In ampere hours and most likely in watt hours.
  • Process time display
  • And, most likely, adjustable lower and upper voltage cut-off thresholds (discharge and charge limits)

Development

To implement calculations and measurements we need a controller. I remembered this idea as part of my acquaintance with Arduino, so the controller will be a simple popular Atmega328 and it will be programmed in the environment Arduino. From an engineering point of view, the choice is probably not the best - the controller is a bit fat for the task, and its ADC cannot be called measuring, but... we'll try.

  • We won't solder much in this project. As a basis, we will take a ready-made Arduino module Pro Mini, fortunately the Chinese are ready to supply them at $1.5 retail.
  • The display device will be a 1602 display - another $1.5. I have an option with an I2C interface module, but in this project it is not really needed ($0.7).
  • For development we need bread board. In my case, this is a small BreadBoard for $1.
  • Of course, you will need wires and a number of resistors of different values. For a 1602 display without I2C, you also need to select the contrast - this is done variable resistor at 2 - 20 kOhm.
  • To implement an ammeter you will need a shunt. To a first approximation, it could be a 0.1 Ohm, 5 W resistor.
  • To implement automatic shutdown, you will need a relay with contacts designed for the maximum current of the device and a voltage equal to the supply voltage. To control the relay you need npn transistor and a protective diode.
  • The device will be powered by external source power supply, obviously, at least 5 V. If the power supply varies greatly, then an integrated stabilizer type 7805 will also be required - it will determine the relay voltage.
  • When Arduino Pro Mini will require a USB-TTL converter to upload the firmware.
  • For setup you will need a multimeter.

Voltmeter

I am implementing a simple voltmeter with one range of approximately 0 - 20V. This note is important because the ADC of our controller has a 10-bit capacity (1024 discrete values), so the error will be at least 0.02 V (20 / 1024). To implement the hardware, we need an analog input of the controller, a divider made of a pair of resistors and some kind of output (a display in the finished version, a serial port can be used for debugging).

Principle ADC measurements consists of comparing the voltage at the analog input with the reference VRef. The ADC output is always integer - 0 corresponds to 0V, 1023 corresponds to voltage VRef. The measurement is implemented by taking a series of sequential voltage readings and averaging over the period between updates of the value on the screen. The choice of reference voltage is important because it defaults to the supply voltage, which may not be stable. This does not suit us at all - we will take as a basis a stable internal reference source with a voltage of 1.1V, initializing it by calling analogReference(INTERNAL). We will then calibrate its value using the multimeter readings.

The diagram on the left shows a variant with direct control of the display (it is simply controlled - see the standard LiquidCrystal\HelloWorld sketch). On the right is the I2C option, which I will use further. I2C allows you to save on wires (of which the usual version- 10, not counting the backlight). But at the same time it is necessary additional module and more complex initialization. In any case, the display of characters on the module must first be checked and the contrast adjusted - to do this, you simply need to display any text after initialization. The contrast is adjusted by resistor R1, or a similar resistor of the I2C module.

The input is a 1:19 divider, which allows us to get maximum voltage about 20V (usually a capacitor + zener diode is placed parallel to the input for protection, but this is not important to us for now). Resistors have a spread, and so does the reference Vref of the controller, so after assembly we need to measure the voltage (at least the supply) in parallel with our device and a reference multimeter and select Vref in the code until the readings match. It’s also worth noting that any ADC has a zero offset voltage (which spoils the readings at the beginning of the range), but we won’t go into that for now.

It will also be important to separate the supply and measuring ground. Our ADC has a resolution slightly worse than 1mV, which can create problems if the wiring is incorrect, especially on a breadboard. Since the layout of the module board has already been done and we only have to select the pins. The module has several “ground” pins, so we must make sure that power enters the module through one “ground”, and measurements through the other. In fact, to make changes, I always use the ground pin closest to the analog inputs.

To control I2C, a version of the LiquidCrystal_I2C library is used - in my case, the specific pinout of the I2C module is indicated (the Chinese produce modules with different controls). I also note that I2C in Arduino involves the use of pins A4, A5 - on Pro board Mini, they are not located on the edge, which is inconvenient for prototyping on the BreadBoard.

Source

#include #include // Simple voltmeter with i2c display 1602. V 16.11 // Settings for i2c display 1602 with non-standard pinout #define LCD_I2C_ADDR 0x27 #define BACKLIGHT 3 #define LCD_EN 2 #define LCD_RW 1 #define LCD_RS 0 #define LCD_D4 4 #define LCD_D5 5 #define LCD_D6 6 #define LCD_D7 7 LiquidCrystal_I2C lcd(LCD_I2C_ADDR,LCD_EN,LCD_RW,LCD_RS,LCD_D4,LCD_D5,LCD_D6,LCD_D7); // Reading update time, ms (200-2000) #define REFRESH_TIME 330 // Analog input #define PIN_VOLT A0 // Internal reference voltage (select) const float VRef = 1.10; // Input resistive divider coefficient (Rh + Rl) / Rl. IN<-[ Rh ]--(analogInPin)--[ Rl ]--|GND const float VoltMult = (180.0 + 10.0) / 10.0; float InVolt, Volt; void setup() { analogReference(INTERNAL); // Инициализация дисплея lcd.begin (16, 2); lcd.setBacklightPin(BACKLIGHT, POSITIVE); lcd.setBacklight(HIGH); // включить подсветку lcd.clear(); // очистить дисплей lcd.print("Voltage"); } void loop() { unsigned long CalcStart = millis(); int ReadCnt = 0; InVolt = 0; // Чтение из порта с усреднением while ((millis() - CalcStart) < REFRESH_TIME) { InVolt += analogRead(PIN_VOLT); ReadCnt++; } InVolt = InVolt / ReadCnt; // Смещение 0 для конкретного ADC (подобрать или отключить) if (InVolt >0.2) InVolt += 3;

// Convert to volts (Value: 0..1023 -> (0..VRef) scaled by Mult) Volt = InVolt * VoltMult * VRef / 1023; // Output data lcd.setCursor (0, 1); lcd.print(Volt); lcd.print("V "); ) This article provides an interesting diagram for those who like to experiment and Arduino. It presents a simple



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 to digital format, perceived by the microcontroller. We connect a voltage divider formed by resistances R1 (100K) and R2 (10K) to analog input A0. With these resistance values, up to 55 V can be supplied to the Arduino, since the division coefficient is in this case it turns out 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 correspond to the readings of a verified voltmeter, you should use a precision digital multimeter to find exact values 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%. Please note that voltages above 55V may cause Arduino board out of service!



#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

A useful diagram is presented 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 of 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 a maximum allowable value of 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 to measure the value of resistances R1 and R2 with an accurate instrument 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)

With some additions.

A little-known feature of Arduino and many other AVR chips is the ability to measure the internal 1.1 V reference voltage. This feature can be used for increasing accuracy Arduino functions - analogRead using standard reference voltage of 5 V (on platforms with a supply voltage of 5 V) or 3.3 V (on platforms with a supply voltage of 3.3 V).She may also be used to measure Vcc applied to the chip, providing means of control battery voltage without use precious analog pins.

Motivation

There is at least at least two reasons for measuring supply voltage our Arduino (Vcc). One of them is our battery powered project if we want to keep an eye on the battery voltage level. Also, when the battery power (Vcc) cannot be 5.0 volts (eg a 3 cell 1.5 V power supply) and we want to make analog measurements more accurate - we must use either an internal 1.1 V reference or an external reference voltage source. Why?

It is common to assume when using analogRead() that the controller's analog supply voltage is 5.0 volts, when in reality this may not be the case at all (for example, a power supply from 3 elements is 1.5 V). The official Arduino documentation may even lead us to this incorrect assumption. The fact is that the power is not necessarily 5.0 volts; regardless of the current level, this power is supplied to the Vcc of the chip. If our power supply is not stabilized or if we are running on battery power, this voltage may vary quite a bit. Here is a code example that illustrates this problem:

Double Vcc = 5.0; // not necessarily true int value = analogRead(0); / read the readings from A0 double volt = (value / 1023.0) * Vcc; // only true if Vcc = 5.0 volts In order to measure voltage accurately, an accurate reference voltage is needed. Most AVR chips provide three voltage references:

  • 1.1 V from the internal source, in the documentation it passes as a bandgap reference (some of them are 2.56 V, for example ATMega 2560). The selection is made by the analogReference() function with the INTERNAL parameter: analogReference(INTERNAL) ;
  • external source of reference voltage, labeled AREF on the arduino. Select: analogReference(EXTERNAL);
  • Vcc is the power supply of the controller itself. Select: analogReference(DEFAULT).

In Arduino you can't just connect Vcc to the analog pin directly - by default AREF is connected to Vcc and you will always get a maximum value of 1023, no matter what voltage you are using. Connecting to AREF a voltage source with a previously known, stable voltage helps, but this is an extra element in the circuit.

You can also connect Vcc to AREF via diode: The voltage drop across the diode is known in advance, so calculating Vcc is not difficult. However, with such a circuit through a diode current flows constantly, shortening the battery life, which is also not very good.

An external voltage reference is the most accurate, but requires additional hardware. Internal ION is stable but not accurate +/- 10% deviation. Vcc is completely unreliable in most cases. Selecting an internal voltage reference is inexpensive and stable, but most of the time, we would like to measure more voltage than 1.1V, so using Vcc is the most practical, but potentially the least accurate. In some cases it can be very unreliable!

How to do it

Many AVR chips including the ATmega and ATtiny series provide a means of measuring internal reference voltage. Why is this necessary? The reason is simple - by measuring the internal voltage, we can determine the value of Vcc. Here's how:

  1. Set default voltage reference: analogReference(DEFAULT); . We use Vcc as a source.
  2. Take ADC readings for the internal 1.1 V source.
  3. Calculate the Vcc value based on the 1.1 V measurement using the formula:

Vcc * (ADC reading) / 1023 = 1.1 V

What follows:

Vcc = 1.1 V * 1023 / (ADC reading)

Putting everything together and we get the code:

long readVcc() ( // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV (REFS0) | _BV(MUX3) | _BV(MUX1); #elif defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) (MUX0); #elif defined (__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX2); #else ADMUX = _BV(MUX3) | MUX2) | _BV(MUX1); #endif delay(75); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA,ADSC)); ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high<<8) | low; result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 return result; // Vcc in millivolts }

Usage

Checking Vcc or Battery Voltage

You can call this function readVcc() if you want to monitor Vcc. An example would be checking the battery charge level. You can also use it to determine whether you are connected to a power source or running on battery power.

Vcc measurement for reference voltage

You can also use it to get the correct Vcc value for use with analogRead() when you are using the reference voltage (Vcc). Unless you are using a regulated power supply, you cannot be sure that Vcc = 5.0 volts. This function allows you to get the correct value. There is one caveat though...

In one of the articles I made a statement that this function could be used to improve the accuracy of analog measurements in cases where Vcc was not quite 5.0 volts. Unfortunately, this procedure will not give an accurate result. Why? This depends on the accuracy of the internal voltage reference. The specification gives a nominal voltage of 1.1 volts, but says it can vary by up to 10%. Such measurements may be less accurate than our Arduino power supply!

Increasing accuracy

While the large tolerances of the internal 1.1V power supply significantly limit measurement accuracy when used in mass production, we can achieve greater accuracy for custom projects. This is easy to do by simply measuring Vcc using a voltmeter and our readVcc() function. Next, replace the constant 1125300L with a new variable:

scale_constant = internal1.1Ref * 1023 * 1000

internal1.1Ref = 1.1 * Vcc1 (voltmeter_readings) / Vcc2 (readVcc_function_readings)

This calibrated value will be a good indicator for AVR chip measurements, but may be affected by temperature changes. Feel free to experiment with your own measurements.

Conclusion

There's a lot you can do with this little feature. You can use a stable reference voltage close to 5.0V without actually having 5.0V on Vcc. You can measure your battery voltage or even see whether you are running on battery power or stationary power.

Finally, the code will support all Arduinos, including the new Leonardo, as well as the ATtinyX4 and ATtinyX5 series chips.

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 discrete output mode 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;
  • The Arduino IDE port monitor program displays 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;
  • The larger the number of samples, the smaller the interference 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-bit 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, a specialized program on the computer must receive the data. 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 (