AC voltmeter on an Arduino Uno
A ZMPT101B AC voltage sensor on A0: the Uno takes the RMS of its sine and scales it to the mains voltage. Drag the mains and the trimmer on the sensor. The whole build, an Arduino Uno and 1 more part, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Beginner About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $30. On Mokxi they're free.
What you'll learn
- RMS of an AC signal
- Finding the middle of a wave that sits on an offset
- Measuring over whole mains cycles
- Calibrating a sensor with a scale factor
- Why mains wiring is for qualified people only
- Difficulty: Beginner
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 2
How it works
The ZMPT101B sensor shrinks the mains voltage with a transformer and puts the result on its OUT pin. That signal is a small sine wave riding on about half the 5 V supply. The Uno reads it on A0.
Each pass of loop does two timed reads of 100 ms, which is five 50 Hz cycles. The first finds the middle of the wave. The second measures how far readings stray from that middle and takes the RMS.
Counts become volts at OUT, then RATIO scales them up to mains volts. The result prints once a second.
Parts list
2 parts, plus the jumper wires. They retail for about $30; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × AC voltage sensor, ZMPT101B
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| AC voltage sensor, ZMPT101B pin VCC | Arduino Uno R3 pin 5V |
| AC voltage sensor, ZMPT101B pin GND | Arduino Uno R3 pin GND |
| AC voltage sensor, ZMPT101B pin OUT | Arduino Uno R3 pin A0 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// AC voltmeter.
//
// The ZMPT101B's OUT rests at half the supply, about 512 counts, with the
// mains sine on top of it, made small by the transformer and the gain the
// trimmer sets. So the sketch finds the middle first, the mean over whole
// mains cycles, then the RMS of the readings about it, then scales that up by
// the trimmer's ratio: how many mains volts make one volt at OUT.
//
// The ratio is found by calibrating: read the mains with a meter you trust,
// then RATIO = RATIO x (the meter's reading / this sketch's). Here the part's
// trimmer slider shows it.
//
// The mains is set on the part's slider. A ZMPT101B on real mains has line
// voltage on its screw terminals: only a qualified person should wire one.
const int SENSOR = A0;
const float RATIO = 600.0; // mains volts per volt at OUT, rms
const unsigned long WINDOW_US = 100000; // five 50 Hz cycles
void setup() {
Serial.begin(115200);
Serial.println("AC voltmeter");
}
void loop() {
// The middle of the wave: the mean over whole cycles.
unsigned long start = micros();
long sum = 0;
unsigned int n = 0;
while (micros() - start < WINDOW_US) {
sum += analogRead(SENSOR);
n++;
}
float middle = (float)sum / n;
// The RMS about it.
start = micros();
float squares = 0;
unsigned int m = 0;
while (micros() - start < WINDOW_US) {
float d = analogRead(SENSOR) - middle;
squares += d * d;
m++;
}
float rmsCounts = sqrt(squares / m);
float outVolts = rmsCounts * 5.0 / 1023.0;
float mains = outVolts * RATIO;
Serial.print("middle ");
Serial.print(middle, 1);
Serial.print(" OUT ");
Serial.print(outVolts, 3);
Serial.print(" V rms mains ");
Serial.print(mains, 1);
Serial.println(" V");
delay(1000);
}
Code walkthrough
The key functions of the sketch, and what each one does.
setup sketch.ino
Opens the serial port at 115200 baud and prints a title line. Nothing else needs preparing, because analogRead works on A0 without any pin setup.
void setup() {
Serial.begin(115200);
Serial.println("AC voltmeter");
}loop sketch.ino
Reads A0 for WINDOW_US microseconds and averages the readings to get the middle of the wave. It reads for another window, squares each distance from that middle, averages them and takes the square root. That is the RMS in counts. It converts counts to volts with 5.0 / 1023, multiplies by RATIO, prints, and waits a second.
void loop() {
// The middle of the wave: the mean over whole cycles.
unsigned long start = micros();
long sum = 0;
unsigned int n = 0;
while (micros() - start < WINDOW_US) {
sum += analogRead(SENSOR);
n++;
}
float middle = (float)sum / n;
// The RMS about it.
start = micros();
float squares = 0;
unsigned int m = 0;
while (micros() - start < WINDOW_US) {
float d = analogRead(SENSOR) - middle;
squares += d * d;
m++;
}
float rmsCounts = sqrt(squares / m);
float outVolts = rmsCounts * 5.0 / 1023.0;
float mains = outVolts * RATIO;
Serial.print("middle ");
Serial.print(middle, 1);
Serial.print(" OUT ");
Serial.print(outVolts, 3);
// ... 5 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- The mains reading is off by a steady factor This project
RATIOis set by the sensor's trimmer. Compare with a trusted meter and set RATIO to RATIO times the meter's reading divided by the sketch's reading. - The reading jumps around This project
The window must cover whole mains cycles.WINDOW_USis 100000 for 50 Hz mains, so check it matches your supply frequency. - The reading sits flat or at a constant number. AC voltage sensor
The module output is AC centered on about half the supply. Sample quickly over a full cycle, find peak to peak or the RMS, and subtract the center value. One analogRead alone tells nothing. - The mains voltage value is wrong. AC voltage sensor
Calibrate with the trimmer and a known reference, such as a meter, and set a scale factor in code. Use the supply the module is meant for and keep the ADC reference stable. - Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected. - Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.