Pages: [1]   Go Down
Author Topic: Measuring AC line frequency  (Read 1132 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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!


* schematic.png (28.22 KB, 1270x692 - viewed 94 times.)

* schematic results.png (33.37 KB, 1230x692 - viewed 43 times.)
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 227
Posts: 14024
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Rob Tillaart

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

Oregon, USA
Offline Offline
Edison Member
*
Karma: 72
Posts: 2476
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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/
Logged

"It seems to run on some form of electricity"

Texas
Offline Offline
Edison Member
*
Karma: 25
Posts: 1298
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Good luck, Jack

Grand Blanc, MI, USA
Offline Offline
Faraday Member
**
Karma: 95
Posts: 4094
CODE is a mass noun and should not be used in the plural or with an indefinite article.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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


* opto.png (11.46 KB, 780x450 - viewed 288 times.)
Logged

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

USA
Offline Offline
Sr. Member
****
Karma: 17
Posts: 392
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Here are three application notes from Atmel that might help:


Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 227
Posts: 14024
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Rob Tillaart

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

United Kingdom
Offline Offline
Tesla Member
***
Karma: 227
Posts: 6639
Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.


* Scan 211.JPG (59.84 KB, 1653x1165 - viewed 88 times.)
« Last Edit: October 08, 2013, 05:34:29 am by dc42 » Logged

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.

United Kingdom
Offline Offline
Tesla Member
***
Karma: 227
Posts: 6639
Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

One further point: the Mega 2560 uses a ceramic resonator, not a crystal, so it is not a very accurate frequency reference.
Logged

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.

0
Offline Offline
Newbie
*
Karma: 0
Posts: 8
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
//
//  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 IO

const uint8_t input_capture_pin = 8;


// helper type for efficient access of the counter structure
typedef union {
    uint32_t clock_ticks;
    struct {
        uint8_t  byte_0;
        uint8_t  byte_1;
        uint16_t word_1;
    };
} converter;



// Timing variables for sending
unsigned long now;
unsigned long next_send;
const unsigned long delay_send = 5000;

const String STATE = "STATE";
const char eom = ';'; //end of message
const char eol = '\n'; //end of line
const char sep = '~'; //separator for measures
const char initOrd = '>'; //first order character
const int waitingDelay = 50;

const String sensorName = "FREQ";    //Simple name to recognise the arduino on the Xbee Network

// Serial receive variables
char 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;
    }
}
« Last Edit: October 08, 2013, 05:56:18 am by gromain » Logged

Grand Blanc, MI, USA
Offline Offline
Faraday Member
**
Karma: 95
Posts: 4094
CODE is a mass noun and should not be used in the plural or with an indefinite article.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Montreal
Offline Offline
Faraday Member
**
Karma: 30
Posts: 2608
Per aspera ad astra.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You may find this project useful:
http://coolarduino.wordpress.com/2013/01/04/power-quality-meter/
Logged

Pages: [1]   Go Up
Jump to: