Built-in project · Arduino Uno R3

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.

The circuit runs here once the page has loaded.

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.

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 pinConnects to
AC voltage sensor, ZMPT101B pin VCCArduino Uno R3 pin 5V
AC voltage sensor, ZMPT101B pin GNDArduino Uno R3 pin GND
AC voltage sensor, ZMPT101B pin OUTArduino 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.

sketch.ino · Arduino Uno R3
// 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 editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The mains reading is off by a steady factor This project
    RATIO is 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_US is 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.