about measuring AC frequency with digital input

Hello guys, let me ask about something
Im so sorry, im not expert on Arduino :slight_smile: :slight_smile:
So i have project making prototype that will measure the ac line frequency, i already have ac voltage with range about 0-4v. Then I put the output to the digital PIN 2 of Arduino UNO. Here is my code :

int cnt=0;
int sample=75;    //Sample size, should be at least 1.5 times expected frequency
float t1,t2,freq;

void setup() // code for counting the increasing values 
{
  Serial.begin(9600);
  pinMode(2,INPUT);
  attachInterrupt(0,count,RISING);
  t1=millis();
  t2=0;
  Serial.print("Starting\n");
}

void loop()
{
  if (t2!=0) {
    noInterrupts();  //Disable interrupts to ensure accuracy of readings
    freq=sample/((t2-t1)/1000);
    Serial.print(cnt);
    Serial.print(" Freq: "); 
    Serial.println(freq);  //Treat output to your liking
    t1=millis();    //Reset variables 
    t2=0;
    cnt=0;
    interrupts();  //Enable interrupts again
    }
}

void count()
{
  cnt++;

  if (cnt==sample) {  //signal when set number of samples have been taken
    t2=millis();
    }
}

I get the code from someone in one random forum (im forget where i get it, so i cant put the link) and it's working. It measure the ac line and get about 50,... Hz.
So it's my questions :

  1. Is it safe I put the ac signal (sine wave) 0-4v in the digital input of arduino?
  2. How can I get the delay 125ms every frequency readings to the serial monitor? because it take about 0.5-1.5s in this code because of the interrupt

thank you sir :slight_smile: :slight_smile:

If it is really an AC voltage, with negative excursions, you may destroy the digital input. Put a 10K Ohm resistor in series with the input pin to prevent that.

jremington:
If it is really an AC voltage, with negative excursions, you may destroy the digital input. Put a 10K Ohm resistor in series with the input pin to prevent that.

I already add dc offset on my ac voltage so the output is 4-5volt with no negative there. And now, I want to make the delay 125ms with high accuracy, can it?
Thank you for your reply :slight_smile:

Delay from what?
Define "high accuracy".

jremington:
Delay from what?
Define "high accuracy".

  • It is the delay frequency measurements to be displayed on the serial, so I can have 8 data in 1 second.
  • High accuracy means the frequency data I get from this project is enough accurate comparing the frequency that already get from the other tool (in this project, the dispatcher display that has 1s delay measurement)
    thank you for the reply again sir :slight_smile:

If you wait until you sample 75 cycles of 50 hz then it will take 1.5 seconds regardless of any delays in the code.

If you wish to calculate and print an update very 125ms then you need to calculate how many samples are expected in that time.

rw950431:
If you wait until you sample 75 cycles of 50 hz then it will take 1.5 seconds regardless of any delays in the code.

If you wish to calculate and print an update very 125ms then you need to calculate how many samples are expected in that time.

yeah, its 75/50 = 1.5s per update. So if I want to get 125ms I must use 6.25 samples, and it will not accurate. Do you have solution sir about the code? Can I use 2 interrupt? as we know arduino UNO has 2 pin interrupt..
thank you

So if I want to get 125ms I must use 6.25 samples, and it will not accurate

What accuracy do you require?

What is the difference in the measured frequency value obtained when dividing 6 counts by a measured time period compared to dividing 75 counts by a longer time period? You should be able to present a mean value and standard deviation for multiple measurements made with the two different sample sizes.

//Sample size, should be at least 1.5 times expected frequency

Where does that assertion come from? Many frequency measurements rely on the accurate determination of a single period.

float t1,t2,freq;

t1 and t2 are millis() values and should be declared as unsigned long.

High accuracy means the frequency data I get from this project is enough accurate comparing the frequency that already get from the other tool

Good to know!

A longer counting period will give better accuracy than a shorter one- no amount of interupt trickery will change that.

If you want to have the display update every 125ms but still retain accuracy I suggest you use an array to save the last few samples and re-calculate a rolling average each time.

Even so its unlikely that a general-purpose arduino that costs a few dollars is going to achive the same accuracy as a purpose-built lab instrument costing hundreds or thousands.

Try this ...

const byte acInputPin = 2;
const byte acCycles = 10;
volatile unsigned long startTime, stopTime;
volatile byte acCount, testState;
unsigned long acPeriod;
float acFrequency;

void setup()
{
  Serial.begin(115200);
  pinMode(acInputPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(acInputPin), acMeasure, RISING);
}

void loop()
{
  if (testState == 2) {    // testing completed, results are ready
    EIMSK &= ~bit(INT0);   // disable INT0 interrupt
    // calculate and print
    noInterrupts();
    acPeriod = (stopTime - startTime) / acCycles;
    interrupts();
    acFrequency = 1000000.0 / acPeriod;
    Serial.print(acPeriod);
    Serial.print(" us  ");
    Serial.print(acFrequency, 3);
    Serial.println(" Hz");
    acCount = 0;
    testState = 0;        // clear testState
    EIFR |= bit(INTF0);   // clear INT0 interrupt flag
    EIMSK |= bit(INT0);   // enable INT0 interrupt
  }
}

void acMeasure() {
  switch (testState) {
    case 0:
      startTime = micros();
      testState = 1;
      break;
    case 1:
      acCount++;
      if (acCount == acCycles) {
        stopTime = micros();
        testState = 2;
      }
      break;
  }
}

You can change the measurement interval "acCycles" to suit.
Might need a 100nF capacitor from pin2 to GND.

dlloyd:
Try this ...

const byte acInputPin = 2;

const byte acCycles = 10;
volatile unsigned long startTime, stopTime;
volatile byte acCount, testState;
unsigned long acPeriod;
float acFrequency;

void setup()
{
  Serial.begin(115200);
  pinMode(acInputPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(acInputPin), acMeasure, RISING);
}

void loop()
{
  if (testState == 2) {    // testing completed, results are ready
    EIMSK &= ~bit(INT0);  // disable INT0 interrupt
    // calculate and print
    noInterrupts();
    acPeriod = (stopTime - startTime) / acCycles;
    interrupts();
    acFrequency = 1000000.0 / acPeriod;
    Serial.print(acPeriod);
    Serial.print(" us  ");
    Serial.print(acFrequency, 3);
    Serial.println(" Hz");
    acCount = 0;
    testState = 0;        // clear testState
    EIFR |= bit(INTF0);  // clear INT0 interrupt flag
    EIMSK |= bit(INT0);  // enable INT0 interrupt
  }
}

void acMeasure() {
  switch (testState) {
    case 0:
      startTime = micros();
      testState = 1;
      break;
    case 1:
      acCount++;
      if (acCount == acCycles) {
        stopTime = micros();
        testState = 2;
      }
      break;
  }
}



You can change the measurement interval "acCycles" to suit.
Might need a 100nF capacitor from pin2 to GND.

It's really work thank you sir! :slight_smile: I can set the delay by change the acCycle. So now i will analyze the code to get the calculation for the delay I want (125ms). I just want to know what this code "EIFR and EIMSK" means?
Oh yeah, what the capacitor doing here? is it for throwing the ac voltage to ground and getting the dc voltage? I already give offset DC to the AC input from transformer, so its really 0-5V input for arduino with low current, so it is safe i think for the digital input.
thanks sir you are really amazing.. :slight_smile:

cattledog:
What accuracy do you require?

What is the difference in the measured frequency value obtained when dividing 6 counts by a measured time period compared to dividing 75 counts by a longer time period? You should be able to present a mean value and standard deviation for multiple measurements made with the two different sample sizes.
Where does that assertion come from? Many frequency measurements rely on the accurate determination of a single period.

float t1,t2,freq;

t1 and t2 are millis() values and should be declared as unsigned long.

it can approach the frequency of dispatcher display

of course you are right, in the theory whatever I choose the counts it will generate the same frequency as other counts. But in this code when I'm trying to change the counts the frequency it changes the frequency too, I think it's because internal clock and the interrupt so affect on the time sample. The higher counts it will get the average so it will more accurate.
Yes thank you :))

jremington:
Good to know!

Thank youu :slight_smile:

rw950431:
A longer counting period will give better accuracy than a shorter one- no amount of interupt trickery will change that.

If you want to have the display update every 125ms but still retain accuracy I suggest you use an array to save the last few samples and re-calculate a rolling average each time.

Even so its unlikely that a general-purpose arduino that costs a few dollars is going to achive the same accuracy as a purpose-built lab instrument costing hundreds or thousands.

yes i get that ideas too from my senior, maybe if I cant use the dlloyd code I will try this idea, thank you sir :))

I just want to know what this code "EIFR and EIMSK" means?

Interrupt control registers. See the processor data sheet.

Oh yeah, what the capacitor doing here?

Low pass filter, may be needed to reduce false counts due to high frequency noise in the AC line.

How can I get the delay 125ms every frequency readings to the serial monitor?

So now i will analyze the code to get the calculation for the delay I want (125ms).

To ensure a new measurement is available each time you need to print, I would suggest changing "acCycles" to 5. Now, for a 50Hz AC signal, the measurement interval would be 100ms which is less than the required print rate (good).

In the code, a new measurement is requested after printing by using:

testState = 0;  // clear testState

Now all that needs to be done is to review the "BlinkWithoutDelay" example that's installed with the Arduino IDE. See how the code sets an interval to blink an LED and uses millis() for the timer. Instead of blinking an LED, it could be modified to print at a set 125ms interval.

Just apply this technique to your code.

Im sorry for my late responses..

jremington:
Interrupt control registers. See the processor data sheet.

aye sir thank you :slight_smile:

Low pass filter, may be needed to reduce false counts due to high frequency noise in the AC line.

yes i use 100uF here :slight_smile:

dlloyd:
To ensure a new measurement is available each time you need to print, I would suggest changing "acCycles" to 5. Now, for a 50Hz AC signal, the measurement interval would be 100ms which is less than the required print rate (good).

yes thank you sir I got 100-120ms delay and its more than enough hehe, but now I have other problem. The serial monitor is not working after 5 minutes, but the Tx lamp in arduino still beep beep like the data still be measured. I dont know why.. :frowning:
And after 15 minutes, I got the arduino not responding, the Tx lamp on continuously (not beep). I try to reset it with the reset button, it still not responding.
It is my attachments, trial 1 and 2 is when the arduino still beep beep but the serial monitor stopped. And trial 3 is when the arduino not responding, I must unplug the serial cable to get it responding again..
Thank you for the response :slight_smile:

A few lines marked "<--- added" should be included here:

  if (testState == 2) {    // testing completed, results are ready
    EIMSK &= ~bit(INT0);   // disable INT0 interrupt
    // calculate and print
    noInterrupts();
    acPeriod = (stopTime - startTime) / acCycles;
    interrupts();
    acFrequency = 1000000.0 / acPeriod;
    Serial.print(acPeriod);
    Serial.print(" us  ");
    Serial.print(acFrequency, 3);
    Serial.println(" Hz");
    noInterrupts();       // <--- added
    acCount = 0;
    testState = 0;        // clear testState
    EIFR |= bit(INTF0);   // clear INT0 interrupt flag
    EIMSK |= bit(INT0);   // enable INT0 interrupt
    interrupts();         // <--- added
  }

You'll need to post your code ... thanks.

The current Uno is not suitable for this unless you replace the 16MHz ceramic resonator
with a quartz crystal and caps, because ceramic resonators have much more variation than the
mains frequency.

Did you say what accuracy you were looking for (that's a question requiring a numerical
answer)?

dlloyd:
You'll need to post your code ... thanks.

yes thank you sir, the two parameters before the frequency in the images that I attached is the stop time and starttime hehe, and the problem that make my arduino not responding is my serial cable, when I change the cable it is run normally for some hours :slight_smile:
oh yea sir can I ask about this code? its for my study

const byte acCycles = 5;

when I change the acCycles it will make the delay faster or slower, what the concequences of the changes? I need to change it to 5 so it gives me 100-120ms.

MarkT:
The current Uno is not suitable for this unless you replace the 16MHz ceramic resonator
with a quartz crystal and caps, because ceramic resonators have much more variation than the
mains frequency.

Did you say what accuracy you were looking for (that's a question requiring a numerical
answer)?

yes, firstly I think that the UNO cant take the delay (125ms) because of the clock in the board, so I think I must change the arduino to Arduino Due (maybe) because it has 84Mhz clock, but I need to setting up the micros() function because of the different calculation with the UNO right?

If you say it must be numerical answer, I think I need the stable frequency with +-0.03 to 0.05 difference to the real frequency. The problem is I don't have the reference that can give 125ms delay for the measurements to compare the device I make, I'm using the 1s delay device for the comparison. Maybe you can give some suggestion sir? because I already get the delay 100-120ms (good) with the code above but with the fluctuations fairly high (you can see my attachments). Thank you :slight_smile:

I think the UNO has more than enough speed and stability. Accuracy should also be good ... calibration is possible if needed.

As for stability, note that the line frequency is not perfectly stable as it would be periodically adjusted up to±0.02% for time error correction (check with your local hydro utility).

If using a low voltage AC circuit to derive your zero crossing signal, this would exhibit instability and large error from the true zero-crossing points. An opto isolated circuit that derives the zero cross from line voltage would be much more accurate and stable as it would be less influenced by any distortion.

Try the FreqMeasure library as it uses the input capture unit and should give the most accurate results.

No method will be accurate to better than your reference.

Some arduino's use a ceramic resonator which will be +/- 3% or so. No good for you!

Some use crystals - but which one? cheap computer crystals could be as bad as +/- 100ppm, which is

+/- 0.01% - on the edge of your accuracy requirement.

You can buy 30ppm crystals very cheaply . That's +/- 0.003% . Much better.

I suggest you find out what you've got - or just buy a 30ppm crystal.

ppm = parts per million.

regards

Allan.