Go Down

### Topic: Measuring AC line frequency (Read 3950 times)previous topic - next topic

#### kriscpm

##### Oct 07, 2013, 05:56 pm
Hi all,

I would like to ask for suggestions on how to measure the frequency from my main supply which is 240V using Arduino Mega 2560? As i know the frequency varies very little, hence i would like to measure it as accurate as possible.The reason for this is because I'm doing a smart meter project with Arduino Mega 2560.

The problem that I am facing is that I don't know what is the best way to measure the frequency from the AC mains. I was thinking of converting the sine wave into square, then capture the rising edge, from there calculate the frequency. Is this way accurate? The simulation i made is attached.

Other ways that I've researched is using zero crossings, Schmitt Trigger, and also TC9400. However, I am not so familiar with these methods. Can anyone help me to further explain how to measure the frequency using these methods?

Suggestion are much appreciated.
Thanks!

#### robtillaart

#1
##### Oct 07, 2013, 07:36 pm
You might adapt this a bit - http://ruggedcircuits.com/html/circuit_-26.html -
Think R2 should be doubled to get the same current when the voltage doubles

if you connect it to an interrupt pin (see attachInterrupt) it should be fairly easy to count the pulse/second.
or the timing delta - micros() - between to pulses.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

#### jremington

#2
##### Oct 07, 2013, 08:09 pm
To accurately measure the AC line frequency, you need to have a very accurate time base and count cycles (e.g. rising edge of a zero-crossing detector) for a long time. Variations in the frequency are typically a few parts in 10,000, so counting pulses for something like 1000 seconds is required. See this web page for some examples http://leapsecond.com/pages/mains/

#### jack wp

#3
##### Oct 07, 2013, 08:10 pm
Rather than measuring the time between cycles, I suggest count the cycles per second ( 50 or 60), or even over 2 seconds.
The waveform over one or two cycles could be in error, if loads are applied, and especially if inductive loads are applied. Counting the cycles over one or two seconds should pretty much eliminate these errors.

#### Jack Christensen

#4
##### Oct 07, 2013, 08:44 pm
This seems to work well, as Rob said, the code just counts interrupts.

#### kriscpm

#5
##### Oct 08, 2013, 10:35 am

This seems to work well, as Rob said, the code just counts interrupts.

Does it mean that by using this circuit, the AC will convert into a square wave in the Arduino itself? Then interrupt is used to count the pulses. Hence, as said by  'jack wp', to get a more accurate reading, i will count how many pulses occur in 1 or 2 second and then get the frequency?

Thanks.

#### tmd3

#6
##### Oct 08, 2013, 12:10 pm
Here are three application notes from Atmel that might help:

#### robtillaart

#7
##### Oct 08, 2013, 12:15 pm

This seems to work well, as Rob said, the code just counts interrupts.

Does it mean that by using this circuit, the AC will convert into a square wave in the Arduino itself? Then interrupt is used to count the pulses. Hence, as said by  'jack wp', to get a more accurate reading, i will count how many pulses occur in 1 or 2 second and then get the frequency?

Thanks.

Note you will get only the positive part of the SInus  _/\_/\_/\_  and those pulses will trigger the IRQ pin.
50/60 pulses per second on average.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

#### dc42

#8
##### Oct 08, 2013, 12:30 pmLast Edit: Oct 08, 2013, 12:34 pm by dc42 Reason: 1
Two points:

1. Over the long term, the average frequency of the mains is very stable; but over the short term it can vary quite a bit. See http://www.nationalgrid.com/uk/Electricity/Data/Realtime/Frequency/Freq60.htm for an example. I remember many years ago when were debugging a problem with read errors on 8 inch floppy disk drives, most of the problems we saw were around 5pm and were caused by the mains frequency varying most at that time (the spindle motors of those drives were mains-driven induction motors).

2. Whatever mechanism you use to count cycles, you must ensure that you count only genuine cycles and not transients. One possibility is to use a low pass filter followed by a Schmitt trigger. Alternatively, use the attached circuit (which incorporates a simple low pass filter) and in the software, don't count any pulses you receive that are too close to the last pulse.

PS - the capacitor in the schematic should be a metallized film one, not ceramic.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

#### dc42

#9
##### Oct 08, 2013, 12:35 pm
One further point: the Mega 2560 uses a ceramic resonator, not a crystal, so it is not a very accurate frequency reference.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

#### gromain

#10
##### Oct 08, 2013, 12:52 pmLast Edit: Oct 08, 2013, 12:56 pm by gromain Reason: 1
Hey all,

For your need, the simple setup described here work as well: http://blog.blinkenlight.net/experiments/measurements/power-grid-monitor-2/
Basically it does count the interrupts triggered to infer the mains frequency.

I did it myself, and it works pretty flawlessly, I just did a little twist to the code (not pretty, but works well) so it prints the frequency to the serial console.
I can post my code if needed.

However, I run into a little glitch when the arduino is not connected to the computer. (i.e powered by a wall adapter)
I added a Xbee radio so I can retrieve the frequency value wirelessly, however, when I don't share a GND with the computer (either via the USB connection, or via a simple USB-RS232 cable where I only use the GND), the frequency measurement is far off. I don't understand why, and the understanding of this problem might be a bit out of my league.

I will try some of the suggestions posted above to see if it's better. I reckon the optocoupler circuit might be a good idea as it ensures the arduino is a bit more protected. And it should work with the code of the above link.

Cheers,

Gromain

EDIT: Oh well, I might as well add my code, it's not secret after all!

Code: [Select]
`////  www.blinkenlight.net////  Copyright 2012 Udo Klein////  This program is free software: you can redistribute it and/or modify//  it under the terms of the GNU General Public License as published by//  the Free Software Foundation, either version 3 of the License, or//  (at your option) any later version.////  This program is distributed in the hope that it will be useful,//  but WITHOUT ANY WARRANTY; without even the implied warranty of//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the//  GNU General Public License for more details.////  You should have received a copy of the GNU General Public License//  along with this program. If not, see http://www.gnu.org/licenses/// //  input capture = pin8 --> now how to exploit the display as good as possible//// Pins// 01 00 08 --> used of serial connection and input capture IOconst uint8_t input_capture_pin = 8;// helper type for efficient access of the counter structuretypedef union {    uint32_t clock_ticks;    struct {        uint8_t  byte_0;        uint8_t  byte_1;        uint16_t word_1;    }; } converter;// Timing variables for sendingunsigned long now;unsigned long next_send;const unsigned long delay_send = 5000;const String STATE = "STATE";const char eom = ';'; //end of messageconst char eol = '\n'; //end of lineconst char sep = '~'; //separator for measuresconst char initOrd = '>'; //first order characterconst int waitingDelay = 50;const String sensorName = "FREQ";    //Simple name to recognise the arduino on the Xbee Network// Serial receive variableschar inputC;String inputS;// The timer values will be initialized nowhere.// This works because we are intested in the differences only.// The first differences will be meaningless due to lack// of initialization. However they would be meaningless anyway// because the very first capture has (by definition) no predecessor.// So the lack of initialization semantically reflects this.volatile converter input_capture_time;volatile uint16_t timer1_overflow_count;// 0 indicates "invalid"volatile uint32_t period_length = 0;volatile bool next_sample_ready = false;ISR(TIMER1_OVF_vect) {    ++timer1_overflow_count;}ISR(TIMER1_CAPT_vect) {    static uint32_t previous_capture_time = 0;        // according to the datasheet the low byte must be read first    input_capture_time.byte_0 = ICR1L;    input_capture_time.byte_1 = ICR1H;                                   if ((TIFR1 & (1<<TOV1) && input_capture_time.byte_1 < 128)) {        // we have a timer1 overflow AND        // input capture time is so low that we must assume that it        // was wrapped around               ++timer1_overflow_count;                     // we clear the overflow bit in order to not trigger the        // overflow ISR, otherwise this overflow would be        // counted twice        TIFR1 = (1<<TOV1);    }      input_capture_time.word_1 = timer1_overflow_count;         period_length = input_capture_time.clock_ticks - previous_capture_time;    previous_capture_time = input_capture_time.clock_ticks;         next_sample_ready = true;}void initialize_timer1() {    // Timer1: "normal mode", no automatic toggle of output pins    //                        wave form generation mode  with Top = 0xffff    TCCR1A = 0;    // Timer 1: input capture noise canceler active    //          input capture trigger on rising edge    //          clock select: no prescaler, use system clock    TCCR1B = (1<<ICNC1) | (1<< ICES1) | (1<<CS10);        // Timer1: enable input capture and overflow interrupts    TIMSK1 = (1<<ICIE1) | (1<<TOIE1);        // Timer1: clear input capture and overflow flags by writing a 1 into them       TIFR1 = (1<<ICF1) | (1<<TOV1) ;}void visualize_frequency_deviation(const uint8_t target_frequency, const uint32_t period_length) {        const int64_t deviation_1000 = 1000*(int64_t)F_CPU / period_length - 1000*(int64_t)target_frequency;         Serial.print(F("period length  "));    Serial.println(period_length);         static int8_t sign = 1;    if (deviation_1000 != 0) {        // only compute new sign for frequency deviation != 0        sign = deviation_1000 >= 0? 1: -1;    }        Serial.print(F("deviation: "));    Serial.println((int32_t)deviation_1000);        const uint64_t value = abs(deviation_1000);            Serial.print(F("value: "));    Serial.println((uint32_t)value);}void setup() {    Serial.begin(57600);    // just to indicate which pins will be the output pins    // this "output test" makes it easier to wire the circuit    delay(1000);    pinMode(input_capture_pin, INPUT);    digitalWrite(input_capture_pin, HIGH);    now = millis();    next_send = now + delay_send;    initialize_timer1();}const uint8_t target_frequency = 50;const uint8_t sample_buffer_size = 50;int64_t value_1000 = 0;uint32_t sample_buffer[sample_buffer_size];void loop() {    static uint8_t sample_index = 0;    now = millis();    if (next_sample_ready) {        next_sample_ready = false;                cli(); // Disable global interrupts        sample_buffer[sample_index] = period_length;           sei(); // Enable global interrupts        sample_index = sample_index > 0? sample_index - 1: sample_buffer_size - 1;                uint32_t average_period_length = 0;        for (uint8_t index = 0; index < sample_buffer_size; ++index) {            average_period_length += sample_buffer[index];        }        average_period_length /= sample_buffer_size;                       value_1000 = 1000*(int64_t)F_CPU / average_period_length;    }    // boucle de comptage et tous les X tours (qui correspondent à 5s) envoie de la valeur en série    if (now >= next_send) {        Serial.print(sensorName);        Serial.print(sep);        Serial.print((int32_t)value_1000, DEC);        Serial.println(eom);        next_send = now + delay_send;    }}`

#### Jack Christensen

#11
##### Oct 08, 2013, 01:34 pm

This seems to work well, as Rob said, the code just counts interrupts.

Does it mean that by using this circuit, the AC will convert into a square wave in the Arduino itself? Then interrupt is used to count the pulses. Hence, as said by  'jack wp', to get a more accurate reading, i will count how many pulses occur in 1 or 2 second and then get the frequency?

It's really the opto-isolator that does the conversion. Its LED blinks at the line frequency. As @jremington said in reply #2, more than 1 or 2 seconds may be required. The link he gave is pretty interesting so be sure to check that out for some techniques.

#12

#### seaspac

#13
##### Dec 04, 2016, 08:27 pm
Hi, I found this discussion really helpful for the same purpose, but in my case when I am going to use a Precision RTC chip DS3231, I have a different idea about how to make AC frequency measurement more accurate. I am going to use two interrupt pins one to increment a counter on each zero crossing of AC line voltage and other to count 1Hz Square wave generated pulses. after 5 sec. the counted pulses from zero crossing detector would be divided by 5 and give average AC line frequency since 1Hz signal would be precise the calculated AC frequency would also be relatively accurate when RTC is not used.

Go Up

Please enter a valid email to subscribe