Introduction: Science Fair: How Accurate Is the AC Line Frequency?

In the developed world, electricity is delivered via AC - alternating current. One of the specifications of that electric current is the frequency at which it oscillates. Most of the world is tied into electric grids running at either 50 or 60 Hz.

In the early part of the 20th century, electric clocks driven by synchronous motors were developed and became popular. Because of that, an impetus existed to insure that the long term stability of the grid frequency was maintained. As technology moved into the electronics age, the idea of synchronizing a clock to the electric grid's frequency was often designed into clocks even if they no longer used synchronous motors. There are three distinct electric grids in the continental United States: the East, the West, and Texas (it really is like a whole 'nother country). The grid frequency is not synchronized between the three grids, but it is independently maintained and synchronized within each region.

But how good is it?

There are nominally 60 cycles per second, but there are variations due to the loads imposed on the grid. Since the grid is so large, instantaneous control of the frequency is not feasible. But it can be generally directed, and measured. For periods of time when the frequency drifts low, it can be countered with a period of higher frequency.

If it were perfectly accurate, there would be 5,184,000 cycles per day on average. We can certainly count them, but we need an unquestioned yardstick against which to compare.

GPS is an excellent option. Its stability and accuracy makes it a phenomenal value for stable and accurate time comparisons. The AdaFruit Ultimate GPS module, for example, quotes a 10 ns accuracy of the PPS output. Moreover, since that output is continuously disciplined by the GPS system, it should have (for our purposes), perfect long-term stability.

So if we count the number of cycles of the AC frequency that occur between two PPS leading edges, we should get 60 every time.

Of course, we won't. The AC frequency is not very well disciplined on a second-by-second basis. So we're sure to see 61 or 59 every once in a while. It's not outside the realm of possibility, in fact, to get 62 or 58.

But the hypothesis of the experiment is that over a long enough term, every period of 61 cycles should be balanced by a period of 59.

Step 1: The Experimental Apparatus

For our experiment, we will be using an Arduino UNO as the main data collector. You'll also need a breadboard, an AdaFruit Ultimate GPS module breakout board, an LM358 Op Amp, a 1N4001 diode, three 20k resistors and a 9 VAC "wall wart" power supply.


DO NOT attempt this experiment without using a commercially manufactured low-voltage AC power supply! AC line voltage is very dangerous, and it's only by using a transformer to reduce it to safe levels can we contemplate performing this experiment.

The circuit has two basic pieces. The first simply feeds the GPS PPS signal into digital pin 2 of the Uno, and applies +5 volt power and ground to the Vin and GND pins respectively of the GPS module.

The second is more complex. We need to turn the 9 volt AC signal from the AC power supply into a 60 Hz square wave compatible with the Uno.

To do that, we anchor one side of the transformer to ground. The other will swing between positive and negative 14 volts or so (remember, the 9 VAC is an RMS value. The actual peak-to-peak voltage is higher than that). That's ok - the operational amplifier we're using has a maximum input voltage of 32 volts. We'll stay well below that.

But we cannot feed the negative half of the AC cycle into our amp. We need to use a 1N4001 diode to 'throw away' the negative portion. Since our op amp has high impedance inputs, however, we must not simply allow the input to float when the diode is switched off. A 20k pull-down resistor to ground will take care of that. The output of the diode will be 14 volt "humps" separated by periods of zero volts. To turn that into a logic square wave, we'll use our LM358 wired as a comparator. We'll use two 20k resistors to make a voltage divider and feed 2.5 volts into the inverting pin of one side of the amp. The diode output from the transformer will be the non-inverting input. As a comparator, the op amp will output a high voltage whenever the non-inverting input is higher than the inverting input. So whenever the line voltage is higher than 2.5 volts, the op amp will output high. All other times, it will output low.

This is enough for our purposes. We will be counting rising edges. They will occur when the AC voltage climbs above 2.5 volts, which will happen once every cycle.

Step 2: The Sketch

The uno will be in charge of doing the counting. We will use the serial connection (over USB) from the Uno back to a host computer.

This sketch will perform counts of AC cycles on a (GPS) second-by-second basis. Any time that a second has other than 60 cycles, a line with the delta will be printed on the serial port. The host can watch the serial port and gather the deltas at its leisure.

We'll also indicate any loss of GPS PPS signaling, which can happen if the GPS loses its fix.

#include <Serial.h>

#define PPS_PIN 2
#define AC_PIN 3
#define PPS_IRQ 0
#define AC_IRQ 1


unsigned long last_pps_complaint;
unsigned int ac_cycle_count, last_ac_cycle_count;
boolean pps_occurred;

void pps_isr() {
  last_ac_cycle_count = ac_cycle_count;
  ac_cycle_count = 0;
  pps_occurred = true;

void ac_isr() {

void setup() {
  pinMode(PPS_PIN, INPUT);
  pps_occurred = false;
  ac_cycle_count = 0;
  last_pps_complaint = 0;
  attachInterrupt(PPS_IRQ, pps_isr, RISING);
  attachInterrupt(AC_IRQ, ac_isr, RISING);

void loop() {
  if (pps_occurred) {
    pps_occurred = false;
    last_pps_complaint = millis(); // no excuse to complain, actually.
    int delta = NOMINAL_FREQUENCY - last_ac_cycle_count;
    if (delta) {
  } else {
    if (millis() - last_pps_complaint > PPS_COMPLAINT_RATE) {
      last_pps_complaint = millis();
      Serial.print("Missed PPS\r\n");

Step 3: The Host Program

I plugged the USB cable into a Raspberry Pi, but in principle any *nix computer with the appropriate USB-to-serial driver will work.

The host program is very simple. It is merely a conduit from the serial port directly to syslog.

syslog is an excellent choice, since it will timestamp the lines and keep them in rotated log files. You can just look for the output lines in /var/log/syslog and aggregate the output as desired.

#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <termios.h>
#include <unistd.h>

int main(int argc, char **argv) {
	daemon(0, 0);
	openlog(argv[0], LOG_PERROR, LOG_USER);
	FILE *port = fopen("/dev/ttyUSB0", "r+");
	if (port == NULL) {
		perror("Error opening port");
		return 1;

	struct termios t;
	if (tcgetattr(fileno(port), &t)) {
		perror("Error getting termios");
		return 1;

	cfsetspeed(&t, B9600);
	if (tcsetattr(fileno(port), 0, &t)) {
		perror("Error setting speed");
		return 1;

	while(1) {
		char buf[1024];
		if (fgets(buf, sizeof(buf), port) == NULL) break;
		while (buf[strlen(buf) - 1] == '\015' || buf[strlen(buf) - 1] == '\012')
			buf[strlen(buf) - 1] = 0;
		syslog(LOG_INFO, "Line monitor reports %s", buf);

Step 4: Some Helpful Data Mining Script-lets

A typical syslog line might look like this:

Aug  4 17:16:48 rpi herzmon: Line monitor reports -1

The *nix 'awk' command is very helpful for deconstructing this in preparation for graphing. I like to make CSV files and import those into Excel for graphing. For each line, we want the time and the accumulated cycle debt, which we can get by adding the offsets to a running total.

This works pretty well for me:

grep herzmon /var/log/syslog | awk '{total+=$9; print $3,",",total}'

In the above, $9 refers to the 9th word. If you count it all, you'll find that that's "-1" in the above example. $3 is the third word, which is the "17:16:48" in the example above.

The result of this will be lines with the time and the cycle debt at that particular moment, assuming that it started at 0 at the beginning of the syslog file.

Excel has a nice CSV import wizard. You can tell it that your file is comma separated and it will fill column A with times and B with numbers. Select column A and B and ask for a scatter plot.