Arduino Based Autoranging AC-DC Voltmeter with TRMS
Arduino Based Autoranging AC-DC Voltmeter with TRMS
This Arduino project shows how to build a digital voltmeter which can measure up to 600V. This voltmeter can measure AC and DC voltages with automatic switching between the 2 signals, it also can automatically switch between measuring ranges (autoranging).
This Arduino based voltmeter measures the TRMS (true RMS) value of AC voltage signals and calculates its frequency.
A 1602 LCD screen is used to display voltage value, signal type (AC or DC) and frequency (if signal is AC).
Hints:
No warranty is provided with this project, so do it at your own risk!
No isolation transformer is used between the Arduino board and mains source, please don’t connect the Arduino to your laptop or PC while you’re measuring voltages.
While measuring mains voltage (the Arduino is connected to the mains), there will be a very small current (less than 0.4 mA) that may pass through the Arduino. Even this current is so small which may not harm human body and for safety purpose, please don’t touch the Arduino while you’re measuring mains voltage!
For more safety and better results, the Arduino board should be powered from battery (for example battery of 9 Volts).
AC: Alternating Current.
DC: Direct Current.
TRMS: True Root Mean Square.
Hardware Required:
- Arduino UNO board —-> Atmega328P datasheet
- 16×2 LCD screen
- 330 ohms resistor
- 10k ohms variable resistor or potentiometer
- 4 x MOC3020 optocoupler (MOC3021, MOC3022, MOC3023, or equivalent) —-> datasheet
- 4 x 220 ohm resistor
- LM4040 – 4.1V (4.096V shunt voltage reference) —-> datasheet
- 100 nF capacitor
- 2 x 0.1 nF capacitor
- 2 x 1M (Mega) ohms resistor with 1% tolerance or better
- 220k ohms resistor with 1% tolerance or better
- 15k ohms resistor with 1% tolerance or better
- 2 x 4.7k ohms resistor with 1% tolerance or better
- 4 x 10k ohms resistor with 1% tolerance or better
- 1k ohms resistor
- Breadboard
- Jumper wires
Arduino Autoranging AC/DC Voltmeter Circuit:
Project circuit diagram is shown below.
Grounded terminals should be externally connected together!
Basically, a voltage of more than 5 Volts can not be directly connected to the Arduino UNO board. To do that we need a step down circuit which downs any input voltage to another one lower than 5V. For an AC voltage we can use a step down transformer, but measurements will not be accurate!
In this project I used voltage divider circuit which consists of 6 resistors as shown in the circuit schematic above (for better results resistors tolerance should be of 1% or lower).
The total resistance of the voltage divider is: 2M + 220k + 15k + 4.7k + 4.7k = 2244.4k Ohms.
For the autoranging purpose I used 4 optoisolators (MOC3020 or equivalent) with triac in one side, this allows current to flow in either directions when the optoisolator LED is triggered.
As shown in the circuit diagram, pin number 4 of each optoisolator (optocoupler) is connected to Arduino analog channel 3 (A3), pin number 6 of each opto-isolator is connected to 1 voltage divider output.
For example, the upper opto-isolator shown in the circuit diagram is connected to largest second resistance of the voltage divider which is equal to: 220k + 15k + 4.7k + 4.7k = 244.4k Ohms. So, voltage divider ration equals to: 2244.4/244.4 = 9.183.
The LM4040-4.1 shunt voltage reference has 2 roles: the fist one is to provide a precise voltage of 4.096V which is then connected to pin AREF of the Arduino board. This voltage is used as positive reference of the ADC module, the negative reference is GND (0V).
The 2nd role is to provide a precise DC bias of 2.048V to the input signal (voltage under measure) which prevents the voltage on Arduino analog channel 3 from going under 0V (negative voltage).
The ATmega328P microcontroller has 2 internal clamping diodes on the I/O pins, these diodes are connected from the I/O pin to VCC and GND and they keep all input signals within the operating voltage (-0.5V to VCC + 0.5V). Any voltage higher than VCC + 0.5V will be forced down to VCC + 0.5V and any voltage below GND – 0.5V will be forced up to GND – 0.5V.
As a result, connecting 2-phase voltage of 400V RMS to the above circuit will not do any damage to the ATmega328P microcontroller, even when the voltage divider with lowest ration is selected.
Arduino digital pin 6 is also connected to the common pin of the optoisolators through 10k resistor. Arduino digital pin 7 is connected to +2.048V point through 10k resistor.
Arduino digital pins 6 and 7 are ATmega328P analog comparator inputs (respectively AIN0 and AIN1). The analog comparator is used to detect whether the voltage signal is AC or DC and also for counting AC voltage frequency.
The 16×2 LCD screen (2 rows and 16 columns) is used to display the value of the input voltage, signal type (AC or DC) and AC voltage frequency value, it’s connected to the Arduino board as follows:
RS —> Arduino digital pin 8
E —> Arduino digital pin 9
D4 —> Arduino digital pin 10
D5 —> Arduino digital pin 11
D6 —> Arduino digital pin 12
D7 —> Arduino digital pin 13
VSS, RW, D0, D1, D2, D3 and K are connected to Arduino GND,
VEE to the 10k Ohms variable resistor (or potentiometer) output,
VDD to Arduino 5V and A to Arduino 5V through 330 ohm resistor.
VEE pin is used to control the contrast of the LCD. A (anode) and K (cathode) are the back light LED pins.
Arduino Autoranging AC/DC Voltmeter Code:
The following Arduino code can measure TRMS value of AC voltages with its corresponding frequency.
Voltage type (AC or DC) is automatically detected, and range is automatically selected (4 ranges).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
|
/**************************************************************************
*
* Arduino autoranging AC/DC voltmeter.
* Voltage and frequency are printed on 1602 LCD screen.
* This is a free software with NO WARRANTY – Use it at your own risk!
* https://simple-circuit.com/
*
*************************************************************************/
#include <LiquidCrystal.h> // include Arduino LCD library
// LCD module connections (RS, E, D4, D5, D6, D7)
LiquidCrystal lcd(8, 9, 10, 11, 12, 13);
// define autoranging channel pins
#define CH0 2
#define CH1 3
#define CH2 4
#define CH3 5
const uint16_t Time_Out = 50000, // time out in microseconds
Periods = 10; // number of periods of measurement (for AC voltage only)
// variables
byte ch_number;
const uint16_t res_table[4] = {2444, 244, 94, 47}, // voltage divider resistances in tenths kOhms
total_res = 22444; // total resistance in tenths kOhms
uint16_t current_res;
volatile byte per;
void setup(void)
{
pinMode(CH0, OUTPUT);
pinMode(CH1, OUTPUT);
pinMode(CH2, OUTPUT);
pinMode(CH3, OUTPUT);
lcd.begin(16, 2); // set up the LCD’s number of columns and rows
lcd.setCursor(1, 0);
lcd.print(“Voltage:”);
ch_number = 0;
ch_select(ch_number);
// ADC and analog comparator configuration
ADMUX = 0x03;
ADCSRA = 0x87;
ADCSRB = (0 << ACME); // select AIN1 as comparator negative input
ACSR = 0x13; // turn on analog comparator
}
// analog comparator ISR
ISR (ANALOG_COMP_vect)
{
byte count = 0;
for(byte i = 0; i < 50; i++) {
if ( ACSR & 0x20 )
count++;
}
if(count > 48)
per++;
}
// main loop
void loop()
{
bool dc_flag = 0; // DC voltage flag bit
int32_t sum = 0; // sum of all readings
uint16_t n = 0; // number of readings (samples)
ACSR = (1 << ACI); // clear analog comparator interrupt flag
ACSR = (1 << ACIE); // enable analog comparator interrupt
uint32_t current_m = micros(); // save current millis
byte current_per = per; // save current period number
while ( (current_per == per) && (micros() – current_m < Time_Out) ) ;
if( micros() – current_m >= Time_Out ) { // if there’s time out event ==> voltage signal is DC
dc_flag = 1;
for (byte i = 0; i < 200; i++) {
ADCSRA |= 1 << ADSC; // start conversion
while(ADCSRA & 0x40); // wait for conversion complete
int16_t an = (int16_t)(ADCL | (uint16_t)ADCH << 8) – 511;
sum += an;
n++; // increment number of readings
delay(1);
}
}
else { // here, voltage signal is AC
current_m = micros(); // save current millis()
per = 0;
while ( (per < Periods) && (micros() – current_m < (uint32_t)Time_Out * Periods) ) {
ADCSRA |= 1 << ADSC; // start conversion
while(ADCSRA & 0x40); // wait for conversion complete
int32_t an = (int16_t)(ADCL | (uint16_t)ADCH << 8) – 511;
sum += sq(an); // sq: square
n++; // increment number of readings
}
}
ACSR = (0 << ACIE); // disable analog comparator interrupt
uint32_t total_time = micros() – current_m; // used to claculate frequency
// voltage calculation
float v;
if(dc_flag) // if voltage signal is DC
v = (4 * sum)/n; // calculate Arduino analog channel DC voltage in milli-Volts
else // here voltage signal is AC
v = 4 * sqrt(sum/n); // calculate Arduino analog channel RMS voltage in milli-Volts
// claculate actual (input) voltage in milli-Volts (apply voltage divider equation)
v = v * (float)total_res/current_res;
v /= 1000; // get voltage in Volts
uint16_t v_abs = abs(int16_t(v));
if( (v_abs >= 10 && ch_number == 0) || (v_abs >= 100 && ch_number == 1) || (v_abs >= 250 && ch_number == 2) ) {
ch_number++;
ch_select(ch_number);
delay(10);
return;
}
if( (v_abs < 220 && ch_number == 3) || (v_abs < 80 && ch_number == 2) || (v_abs < 8 && ch_number == 1) ) {
ch_number—;
ch_select(ch_number);
delay(10);
return;
}
char _buffer[8];
lcd.setCursor(0, 1);
if( v < 0)
lcd.print(‘-‘);
else
lcd.print(‘ ‘);
if(v_abs < 10)
sprintf( _buffer, “%01u.%02u”, v_abs, abs((int16_t)(v * 100)) % 100 );
else if( v_abs < 100)
sprintf( _buffer, “%02u.%01u”, v_abs, abs((int16_t)(v * 10)) % 10 );
else
sprintf( _buffer, “%03u “, v_abs );
lcd.print(_buffer);
if(dc_flag)
lcd.print(“VDC “);
else {
lcd.print(“VAC “);
// calculate signal frequency in Hz
uint32_t period_time = total_time/Periods;
float freq = 1000000.0/period_time;
sprintf( _buffer, “%02u.%02uHz”, (uint16_t)freq % 100, (uint16_t)(freq * 100) % 100 );
lcd.print(_buffer);
}
delay(500); // wait half a second
}
void ch_select(byte n) {
switch(n) {
case 0:
digitalWrite(CH0, HIGH);
digitalWrite(CH1, LOW);
digitalWrite(CH2, LOW);
digitalWrite(CH3, LOW);
break;
case 1:
digitalWrite(CH0, LOW);
digitalWrite(CH1, HIGH);
digitalWrite(CH2, LOW);
digitalWrite(CH3, LOW);
break;
case 2:
digitalWrite(CH0, LOW);
digitalWrite(CH1, LOW);
digitalWrite(CH2, HIGH);
digitalWrite(CH3, LOW);
break;
case 3:
digitalWrite(CH0, LOW);
digitalWrite(CH1, LOW);
digitalWrite(CH2, LOW);
digitalWrite(CH3, HIGH);
}
current_res = res_table[n];
}
// end of code.
|
Arduino Autoranging AC/DC Voltmeter video:
The following video shows a simple DIY hardware circuit tests: