Building a DIY AC voltage & frequency measurement circuit using Arduino development board is a nice project for students and hobbyists, as the one shown in this post with AC line voltage & frequency values printed on LCD and sent to Arduino IDE serial monitor tool.
The good thing with this project is that it is very accurate and stable when compared with a pre-calibrated Fluke 289 industrial digital multimeter. Also, mains network is totally isolated from our Arduino hardware circuit using a voltage transformer. This circuit can measure domestic Phase-to-Phase and Phase-to-Natural voltages up to 500V AC with RMS calculation.
Hints:
No warranty is provided with this project, so do it at your own risk!
A part of project circuit may be subjected to a high voltage level which is very harmful to human body, so be-careful!
Abbreviations:
AC: Alternating Current.
DC: Direct Current.
TRMS (True RMS): True Root Mean Square.
ADC: Analog-to-Digital Converter
PCB: Printed Circuit Board
DIY: Do It Yourself
Op amp: operational amplifier
DMM: Digital Multi-Meter
AC Voltage & Frequency Measurement with Arduino Circuit:
Project circuit diagram is shown below (click on the image to view the image in higher resolution).
Hardware required:
This is a summary of circuit required parts (circuit schematic diagram may contain some component parameters not mentioned below).
- Arduino MEGA or equivalent board (U1) —> ATmega2560 microcontroller details
- DL-PT202D 2mA/2mA voltage sensor transformer (T1)
- 2 x OPA2191 op amp (U4, U5) —> details
- ADR4520 2.048 voltage reference (U3) —> details
- TPS60403 charge pump voltage inverter (U2) —> details
- TLV3502 dual comparator IC —> details
- 1602 LCD screen
- BAT54S dual serial small signal Schottky diode (D1)
- SMBJ3.3CA TVS diode (D2)
- 3 x 2.2uF capacitor (C6, C7, C17)
- 3 x 1uF capacitor (C1, C2, C3)
- 8x 100nF capacitor (C4, C5, C9, C10, C12, C14, C18, C19)
- 3 x 10nF capacitor (C11, C13, C16)
- 2 x 1nF capacitor (C8, C15)
- 1M Ohm resistor (R20)
- 2 x 100k Ohm resistor (R17, R18)
- 4 x 75k Ohm resistor (R6, R7, R11, R12)
- 4 x 20k Ohm resistor (R3, R4, R13, R16)
- 8 x 10k Ohm resistor (R2, R5, R8, R14, R15, R19, R21, R22)
- 300 Ohm resistor (R10)
- 100 Ohm resistor (R9)
- 10k ohm variable resistor (R1)
- Breadboard & jumper wires
Circuit description:
The voltage to measure terminals is indicated in the circuit diagram as ‘AC Source’, we can apply an AC voltage up to 500V, this means we can measure home phase-to-natural (230V) and phase-to-phase (400V) voltages.
The two AC voltage source terminals are connected to DL-PT202D transformer (T1) through 4 current limiting resistors each of 75k ohm (R6, R7 & R11, R12), the tolerance of these resistors should be 1% or lower in order to get better results, voltage rating of each resistor should be greater than or equal to 200V with rated power of 0.25W or higher.
The voltage sensor transformer used in this project is just a current-type voltage transformer with ratio of 1000:1000, it converts an AC current on the primary to another equal one on the secondary. The maximum working current that may be applied to the primary is 2 mA RMS.
There are many alternatives to the voltage transformer that I’m using such as ZMPT112 and ZMPT101B, and any other replacement should have the same voltage rating (or higher) and ratio (2mA/2mA).
This type of transformers gives a significant safety factor to the circuit as it provide a galvanic isolation between the high voltage mains circuit and the low voltage control circuit which is in a direct contact with human body and other equipments. They also have a small size, low cost and require few components.
Since the voltage transformer used in this project is a current type then its secondary shouldn’t be kept floating, a resistor (R10) with resistance of 100 Ohm is connected in parallel with the secondary of the voltage transformer and hence the current is converted into voltage.
The ratio of the voltage transformer circuit including the resistors R6, R7 & R11, R12 & R10 is:
100/300k = 1/3000.
That means a voltage of 400V RMS will be scaled down to 0.13333 V = 133.33 mV RMS.
The voltage across the resistor R10 is amplified using OPA2191 differential amplifier from Texas Instruments, with a gain of 3. Therefore, the gain till this point becomes: 100 x 3 / 3000 = 1/1000
So, an input voltage of 400V RMS will be 400mV RMS at the output of OPA2191 U4A op amp.
The OPA2191 IC contains 2 independent op amps in one package which indicated in the circuit diagram by U4A and U4B. A bipolar supply of ±5 V is used to power this IC because we’re dealing with an AC signal.
To get a negative voltage of -5 V I used TPS60403 60mA charge pump voltage inverter from Texas Instruments. The TPS60403 device automatically generates -5 V from the +5 V supply.
The 5V positive of the OPA2191 and the TPS60403 comes from the Arduino MEGA board.
The second op amp of the OPA2191 (U4B) adds an offset voltage of 1.024 V to the input signal so the AC signal swings at 1.024V instead of 0V.
Before going to Arduino microcontroller (ATmega2560) ADC module, voltage signal enters to a voltage follower op amp circuit that provides a low impedance to the ADC and also gives more protection to the microcontroller against negative voltages.
Another OPA2191 IC is used to build 2 voltage follower circuits, one for the voltage signal earlier mentioned (U5A) and the other for DC bias voltage (U5B).
ADC Voltage reference:
I used the ADR4520 ultra low noise, high accuracy 2.048V voltage reference IC manufactured by Analog Devices to get a stable and accurate voltage of 2.048V, which is then used as a positive voltage reference for the Arduino microcontroller ADC module and a bias voltage for AC voltage signal.
The output of the ADR4520 is directly connected to Arduino mega AREF pin and to “U5B” op amp non-inverting terminal through a low pass filter. This “U5B” op-amp connection forms a voltage follower circuit with output voltage equals to input voltage 2.048V, and it is used as a DC bias voltage for the AC voltage signal and also for its zero-crossing events.
Zero-crossing detection:
For zero-crossing events detection I used TLV3502 dual, high-speed voltage comparator IC manufactured by Texas Instruments where AC signal (with DC bias) is connected to the inverting input and DC bias voltage to the non-inverting input of the comparator. Here, the DC bias is divided by 2 using a simple voltage divider circuit consists of resistors R17 & R18. Tolerance of R17 & R18 should be no more than 1%.
With this configuration the comparator compares the AC input voltage with 1.024V, if the input voltage is higher than 1.024V then the comparator output is low (0V) and if the input voltage is lower than 1.024V then the comparator output is high (5V). The output of the comparator is directly connected to Arduino digital pin 2.
Note that no pull-up resistor is needed for the comparator output as it is a push-pull type.
When a sufficient AC voltage is applied to the circuit the comparator outputs a series of pulses as described above. The Arduino can calculate time between pulse events and therefore signal period (P). With a known period we can calculate signal frequency using the formula: F = 1/P.
The 1602 LCD screen (2 rows and 16 columns) is used to display AC voltage & frequency values, it is connected to the Arduino board as follows:
RS —> Arduino digital pin 3
RW —> Arduino digital pin 4
E —> Arduino digital pin 5
D4 —> Arduino digital pin 6
D5 —> Arduino digital pin 7
D6 —> Arduino digital pin 8
D7 —> Arduino digital pin 9
VSS, D0, D1, D2, D3 and K are connected to GND,
VEE to the 10k Ohms variable resistor (or potentiometer) output,
Pins VDD & A to Arduino 5V.
VEE pin is used to control the contrast of the LCD. A (anode) and K (cathode) are back light LED pins.
AC Voltage & Frequency Measurement with Arduino Code:
Project Arduino code is the one below, it was tested with Arduino MEGA board.
This Arduino code calculates average and RMS values of AC signals, the average value is just used in the RMS calculations. The MCU only shows the RMS value of the applied voltage on the LCD and serial monitor.
Simply the average (mean or DC offset) value in discrete-time is the sum of all sample values divided by number of samples:
and the RMS value in discrete-time can be calculated using the following equation:
The Arduino uno and similar boards microcontroller (ATmega328P) contains a 10-bit ADC module, with a positive voltage reference of 2.048 V, a 0 V is digitally represented by 0 and a 2.048 V is represented by 1023.
Programming hints:
As we have a 10-bit ADC module working in the range [0, 2.048V], the MCU takes 64 samples every 1 cycle and saves the sampled data in an array named Samples.
The ADC module with auto-trigger option enabled, is configured to be triggered from Timer1 compare match B source, this allows automatic timed starting of analog-to-digital conversions.
For a 50 Hz signal the period is 20 ms, the MCU takes one sample every 20000/64 = 312.5 us. I used Timer1 compare match B to start an ADC conversion every 312.5 us and once the conversion is complete ADC interrupt saves the digital value on the the array variable Samples. The array element is incremented every new reading until the filling of all elements and the completion of the cycle.
If the line frequency is slightly changed the Arduino can measure the new frequency and adjust sample taking time according to the new frequency. Here, Timer 3 is used to measure pulse widths using external interrupt attached to Arduino digital pin 2.
The Arduino MEGA microcontroller ATmega2560 runs @16MHz (can achieve 16 MIPS), to start an analog-to-digital conversion every 312.5 us Timer1 is configured to run also @ 16MHz clock (prescaler = 1), this means Timer1 will make 5000 ticks in a duration of 312.5 us (16 x 312.5 = 5000) which is the pre-programmed value of OCR1A & OCR1B registers for a default line frequency of 50Hz. This value is not fixed and it continuously changing depending on input AC voltage frequency.
Rest of code is described through comments.
Full Arduino code:
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 | /********************************************************************************** * * Accurate & Stable AC voltage and frequency measurement using Arduino MEGA board * and current-type voltage transformer. * Calculated voltage values are printed on 1602 LCD screen and serial monitor. * This is a free software with NO WARRANTY - Use it at your own risk! * http://simple-circuit.com/ * **********************************************************************************/ #include <LiquidCrystal.h> // include Arduino LCD library // LCD module connections (RS, RW, E, D4, D5, D6, D7) LiquidCrystal lcd(3, 4, 5, 6, 7, 8, 9); #define CPU_CLK 16 // define CPU clock frequency in MHz #define Voltage_Correction 1.0116 // define voltage correction coefficient // variable declaration const uint8_t n = 64; // number of samples per cycle volatile int16_t Samples[2*n]; // voltage signal samples saving array volatile uint16_t SampleSum; volatile uint32_t period = 0; volatile bool ZC_Event = 0; uint8_t m1 = 0, // used to specify the writing array (sample collecting and saving array) m2 = 0; // used to specify the reading array volatile uint8_t m; // used for sample saving in the array Samples[] // Arduino setup function void setup() { Serial.begin(9600); Serial.println( "Starting..." ); lcd.begin(16, 2); // Set up the LCD's number of columns and rows // clear sample array for (uint8_t i = 0; i < 2*n; i++) Samples[i] = 0; SampleSum = 0; // Timer1 configuration TCCR1A = 0; TCCR1B = 0x09; // CTC mode, set timer prescaler to CPU_CLK/1 (Timer clock = 16 MHz) // output compare registers configuration with default line frequency of 50Hz, where: // 5000 = 20000 us (50Hz cycle period) * CPU_CLK (MHz) / (n * TIMER1_PRESCALER) = 20000*16/(64*1) // Optional: for 60Hz use the value 4167 OCR1A = 5000; OCR1B = 5000; // for 60Hz use the value: // ADC module configuration ADMUX = 0x00; // set ADC +ive reference to external AREF // use right adjusted ADC result presentation // select analog channel 0 ADCSRA = 0xFF; // enable ADC module // enable ADC Auto Trigger // enable ADC interrupt & clear flag bit (ADIF) // set ADC prescaler to 128 --> ADC CLK = 125kHz ADCSRB = 0x05; // set ADC trigger source to Timer/Counter1 Compare Match B // Timer3 configuration TCCR3A = 0; TCCR3B = 0x02; // set timer prescaler to CPU_CLK/8 (Timer clock = 16/8 = 2 MHz) TIFR3 = 0x01; // reset timer overflow interrupt flag TIMSK3 = 0x01; // enable timer overflow interrupt pinMode(2, INPUT_PULLUP); attachInterrupt( digitalPinToInterrupt(2), get_cycle_period, FALLING ); } // external interrupt ISR (attached on Arduino pin 2) void get_cycle_period() { period += TCNT3; TCNT3 = 0; // reset Timer3 ZC_Event = HIGH; } // TIMER3 overflow ISR ISR (TIMER3_OVF_vect) { period += 40000; // set cycle period to 20ms ZC_Event = HIGH; } // ADC ISR ISR (ADC_vect) { uint16_t an = ADCL | (uint16_t)ADCH << 8; Samples[m1 + m] = an; SampleSum += an; m++; TIFR1 = (1 << OCF1B); // clear OCB1B flag (Timer1 Output Compare B Match Flag) } // main loop function void loop() { static uint8_t cycle = 0; static uint32_t Voltage_RMS = 0; while (m < n) ; // wait for 1 cycle to complete TCCR1B &= ~(1 << CS10); // stop Timer1 m = 0; // toggle between reading & writing arrays if (m1 > 0) { m1 = 0; m2 = 64; } else { m1 = 64; m2 = 0; } uint16_t Average = SampleSum / n; // average calculation. n is number of samples per cycle SampleSum = 0; while ( ZC_Event == LOW ) ; // wait for zero crossing event TCNT1 = OCR1A - 1; TCCR1B |= (1 << CS10); // start Timer1 with No Prescaling (CLKin = CPU_CLK = 16MHz) ZC_Event = 0; // calculate last cycle RMS voltage uint32_t ch_voltage = 0; for ( uint8_t i = 0; i < n; i++ ) { Samples[m2 + i] -= Average ; Samples[m2 + i] = Samples[m2 + i] * Voltage_Correction ; uint16_t j = abs( Samples[m2 + i] ) ; ch_voltage += (uint32_t)j * j ; } ch_voltage = ch_voltage / n; ch_voltage = sqrt(ch_voltage) * 100; // 100 to get a more precise value // (2 significant digits after the decimal point) // calculate real RMS voltage applied to MCU analog channel (multiplied by 100) // 2 = 2048/1024 where 2048 is ADC +ive VREF = 2048 mV and 1024 is 10-bit ADC max digital value (aprroximately) // Note that this voltage is measured in milliVolts multiplied by 100 always, this is also the voltage // under measure (x100) ch_voltage *= 2; uint16_t RMS = ch_voltage; if (RMS > 5000) // if cycle RMS Voltage > 50.00V Voltage_RMS += RMS; cycle++; if (cycle >= 25) { uint32_t acc_pr, freq; acc_pr = period; period = 0; // calculate average cycle period in us. // 2 = CPU_CLK(MHz)/TIMER3_PRESCALER = 16/8 uint16_t cycle_pr = acc_pr / (cycle * 2); // update OCR registers according to new average cycle period. 16=CPU_CLK(MHz)/TIMER1_PRESCALER = 16/1 // n is number of samples per one cycle OCR1A = ( (uint32_t)cycle_pr * 16 ) / n; OCR1B = OCR1A; // calculate line frequency. // 1000000 to go from us to s, and 100 to get 2 significant digits after the decimal point freq = (1000000 * 100) / cycle_pr; // print voltage values on the LCD and Serial Monitor char display_buffer[12]; Voltage_RMS /= cycle; sprintf( display_buffer, "V = %03u.%01u V", (uint16_t)(Voltage_RMS/100), (uint8_t)( (Voltage_RMS % 100) / 10) ); lcd.setCursor(0, 0); // Move cursor to column 0, row 0 [position (0, 0)] lcd.print(display_buffer); Serial.println(display_buffer); // print line frequency values on the LCD and Serial Monitor sprintf( display_buffer, "F = %02u.%02u Hz", (uint8_t)(freq/100), (uint8_t)(freq % 100) ); lcd.setCursor(0, 1); // Move cursor to column 0, row 1 [position (0, 1)] lcd.print(display_buffer); Serial.println(display_buffer); Serial.println(); Voltage_RMS = 0; cycle = 0; } } // end of code. |
The following video shows my simple DIY circuit for this project:
Related Projects:
AC & DC Current Measurement with Arduino and LTSR 25-NP Sensor
AC Current Measurement using Arduino and Current Transformer
Measure AC & DC Currents with Arduino and ACS758 Sensor
Interfacing Arduino with Current Transformer – AC Current Measurement
AC Current Measurement using Arduino and Current Transformer
AC Voltage Measurement using PIC18F46K22 Microcontroller
Isolated AC Voltage Measurement with Arduino and AMC1301 Amplifier
Discover more from Simple Circuit
Subscribe to get the latest posts sent to your email.
Very stable reading.is it possible to achieve same stability using op-amp as differential amplifier for AC measurement
Hello, what is the maximum frequency you can measure?
I want to make this project but I have a question about the maximum voltages you used for the capacitors and the power ratings for the resistors. Does it affect the circuit if I use an different voltage or power ratings?
I read your blog post on “Arduino AC Voltage Frequency Measurement Project with LCD Circuit” with great interest, and I must say, you’ve provided a comprehensive guide for those looking to delve into AC voltage and frequency measurements using Arduino. Your clear and step-by-step explanation, along with the accompanying circuit diagrams and code snippets, make it an excellent resource for beginners and enthusiasts alike.