Trying to measure time between zero crossing of two sinewaves.

I’ve been staring at this code for to long now. I’m running this on an esp32. I’m simulating two 50hz sinewaves with the sine function. Having trouble measuring the delay between the zero crossing of the two waveforms. I imagined measuring the time between two events happening would be straigth forward but for some reason it just won’t happen on a reliable basis.

what am i doing wrong here? two to three of five times the reading is an order of magnitude off.

I have two functions to simplify finding the zero crossing. what they do is return a -1 if it crossed from positive to negative and a 1 if the wave went from negative to positive. I did not include the second one but it’s exactly the same as the first.

void setup(){
  Serial.begin(9600);
}

int lastVoltReading;
int voltCrossing;
byte vCrossingTaken;

void getVoltCrossing(int voltReading) {
  if (voltReading > vZeroPoint && lastVoltReading < vZeroPoint) {
    voltCrossing = 1;
    vCrossingTaken = 1;
  }
  if (voltReading < vZeroPoint && lastVoltReading > vZeroPoint) {
    voltCrossing = -1;
    vCrossingTaken = 1;
  }
  if(vCrossingTaken == 0){
    voltCrossing = 0;
  }
  lastVoltReading = voltReading;
  vCrossingTaken = 0;
}

     1 -|         ,-'''-.
        |      ,-'       `-.
        |    ,'             `.
        |  ,'                 `.
        | /                     \
        |/                       \
    ----+-------------------------\--------------------------
        |          __           __ \          __           /  __
        |          ||/2         ||  \        3||/2        /  2||
        |                            `.                 ,'
        |                              `.             ,'
        |                                `-.       ,-'
    -1 -|                                   `-,,,-'
output:01000000000000000000000000-100000000000000000000000001

unsigned long t1 = 0;
unsigned long t2 = 0;
unsigned long zCrossDelay[5];
int zCross;
float zCrossRadians = 0;
float pf;
bool counting = 0;
byte n = 0;

void getPowerFactor() {
  if (voltCrossing == 1 && counting == 0 ){
      t1 = micros();
      counting = 1;
    } 
    
    if (ampCrossing == 1 && counting == 1 && micros() - t1 > 5) {
      t2 = micros();
      zCrossDelay[n] = t2 - t1; // this is where the problem is. off by an
                                // order of magnitude some of the time
      zCross = zCrossDelay[0] + zCrossDelay[1] + zCrossDelay[2] + zCrossDelay[3] + zCrossDelay[4];
      zCross = zCross / 5;
      zCrossRadians = (float)zCross / 20000 * 6.283185;
      n++;
      if (n == 5) {
        n = 0;
      }
      pf = cos(zCrossRadians); 
      counting = 0;
    }
  }

double dY, dY2;
float seconds;
float frequency = 50;

  void loop(){
    seconds = (float)millis() / 1000;
  dY = sin(6.2831853 * frequency * seconds);
  dY = dY * 325;
  dY2 = sin(6.2831853 * frequency * seconds + PI / 16);
  dY2 = dY2 * 325; 
  //everything above generates the two sinewaves
  getVoltCrossing(dY);
  getAmpCrossing(dY2);
  getPowerFactor();
  }

Where is the value for vZeroPoint setup?

G

Where is the value for vZeroPoint setup?

In the language specification!

Mark

Any time you comparing floats (doubles) you’re playing with fire. What you think may be zero could be +/- 0.00001. It’s often better to compare against a small error threshold, (a - b) < 0.001, rather than a == b as an example.

You need to have a closer look a the value of zCross. As it is declared an integer, you may find overflow issues when adding your zCrossDelay values as micros() can get big as a number,

Zardof: Where is the value for vZeroPoint setup?

G

Forgot to include that but it's declared above as zero.

int vZeroPoint = 0;

DKWatson:
Any time you comparing floats (doubles) you’re playing with fire. What you think may be zero could be +/- 0.00001. It’s often better to compare against a small error threshold, (a - b) < 0.001, rather than a == b as an example.

You need to have a closer look a the value of zCross. As it is declared an integer, you may find overflow issues when adding your zCrossDelay values as micros() can get big as a number,

I changed the value of zCross to unsigned long. My goal is to do as much as i can with integer math and convert the result to float if needed. The reason the zero crossing function is using floating point math is because i can’t generate the sinewave with integers (i think?).

RippoZero: The reason the zero crossing function is using floating point math is because i can't generate the sinewave with integers (i think?).

https://en.wikipedia.org/wiki/Bhaskara_I%27s_sine_approximation_formula

I'd suggest you run a fast fourier transform on both signals (surely someone has worked out how to do this with continuous input). Look at the 50Hz component. A fourier transform gives you the phase of each component frequency, which is what you want.

The DAC on an arduino takes a little time to run. Apparently, it's best to read twice and discard the first reading?

so something like :

const uint32_t fiftyHzUs = 1000000 / 50;
const int number_of_samples = 32; // needs to be a power of 2
const int sampleDelayUs = fiftyHzUs / number_of_samples;
const int halfSampleDelayUs = sampleDelayUs/2;


FourierTransformThingy signal1(number_of_samples);
FourierTransformThingy signal2(number_of_samples);

boolean signal1_turn = true;
uint32_t prevSampleUs;

void setup() {
  prevSampleUs = micros();
}

void loop() {
  if(the button has just become pressed) {
    print_out_the_reading();
    prevSampleUs = micros(); // reset this
  }

  if(micros() - prevSampleUs >= halfSampleDelayUs) {
    if(signal1_turn) {
      signal1.takeASample(analogRead(A0));
    }
    else {
      signal2.takeASample(analogRead(A1));
    }

    signal1_turn = !signal1_turn;
    prevSampleUs += halfSampleDelayUs; //more accurate than using micros()
  }
}


void print_out_the_reading() {
  float phase1 = signal1.get_the_phase_of_component(0);
  float phase2 = signal2.get_the_phase_of_component(0);

  // the phase will be out by halfSampleDelayUs

  if(signal1_turn) { // not quite sure which way around this needs to be
    phase1 += PI * 2 / (number_of_samples * 2);
  }
  else {
    phase2 += PI * 2 / (number_of_samples * 2);
  }

  Serial.print("phase diff ");
  Serial.print(phase1-phase2);
  Serial.print(" radians.");
  Serial.println();
}

I did not know about the fourier transform. Seems like it requires some reading and work to realize. I might try it if i don't find a way to realize my current approach.

I've managed to make the zero crossing readings more reliable by adding some more lenient thresholds. The zero crossing measurements works at 0,5 and 5 hz. It's only when i increase the frequency to 50 hz that it's unable to measure the readings reliably. What is the limiting factor here?

My main suspect is the time variable i use in the sine function. At higher frequencies it needs to rely on numbers further behind the decimal point to calculate the new sine. I printed the result and it does not show a very granular change past the first three digits of the the decimal point. How do i go about solving this?

PaulMurrayCbr:
I’d suggest you run a fast fourier transform on both signals (surely someone has worked out how to do this with continuous input). Look at the 50Hz component. A fourier transform gives you the phase of each component frequency, which is what you want.

Since the OP is using a Sin wave of exactly 50 Hz all the Fourier coefficients other than the 50 Hz one will be zero! What therefore is the advantage of doing a complete Fourier Transform ? One only needs to compute the Fourier coefficient for 50 Hz. Even then, I’m not convinced the 50Hz Fourier coefficients does anything for the OP; I would need to get more information about what post processing the OP is thinking of.

My plan is to convert the phase delay into radians so that i can calculate the power factor. I'll then use that to calculate real power.

RippoZero: My plan is to convert the phase delay into radians so that i can calculate the power factor. I'll then use that to calculate real power.

dumb idea... if you are only interested in the zero crossing then it's very similar to a rotary encoder problem, right?

the most sensible way to do that to me would be to use comparators to compare the sine wave wrt to you DC value (I'm assuming you offset the signal so that the sine varies between 0-5V) - that would give you a square wave

then use interrupt on change to detect the edges generated by each waves and record/measure the time the interrupts occur... similar to how u would do for an encoder to count steps!

the Uno I believe has one anolog comparator... example of how to use here

that would be my approach... its not the only way of course! :)

I don’t think the esp32 has this feature. Atleast i can’t find it :confused: . Does not matter that much since I CRACKED IT!

I used the second core of the esp32 to generate the sinewave. Switched to using a lookup table instead of the sin() function. It would still count to many microseconds between the zero crossings. This got fixed when i sepperated the floating point math i have to use into a different function. The main function keeps a running average of the time between the zero crossings and i only run the calculatePowerFactor() once a second or so.

TaskHandle_t Task1;

byte vZeroPoint = 127;
byte aZeroPoint = 127;

byte sineLookup[256] = {
  128, 131, 134, 137, 140, 143, 146, 149,
  152, 156, 159, 162, 165, 168, 171, 174,
  176, 179, 182, 185, 188, 191, 193, 196,
  199, 201, 204, 206, 209, 211, 213, 216,
  218, 220, 222, 224, 226, 228, 230, 232,
  234, 235, 237, 239, 240, 242, 243, 244,
  246, 247, 248, 249, 250, 251, 251, 252,
  253, 253, 254, 254, 254, 255, 255, 255,
  255, 255, 255, 255, 254, 254, 253, 253,
  252, 252, 251, 250, 249, 248, 247, 246,
  245, 244, 242, 241, 239, 238, 236, 235,
  233, 231, 229, 227, 225, 223, 221, 219,
  217, 215, 212, 210, 207, 205, 202, 200,
  197, 195, 192, 189, 186, 184, 181, 178,
  175, 172, 169, 166, 163, 160, 157, 154,
  151, 148, 145, 142, 138, 135, 132, 129,
  126, 123, 120, 117, 113, 110, 107, 104,
  101, 98, 95, 92, 89, 86, 83, 80,
  77, 74, 71, 69, 66, 63, 60, 58,
  55, 53, 50, 48, 45, 43, 40, 38,
  36, 34, 32, 30, 28, 26, 24, 22,
  20, 19, 17, 16, 14, 13, 11, 10,
  9, 8, 7, 6, 5, 4, 3, 3,
  2, 2, 1, 1, 0, 0, 0, 0,
  0, 0, 1, 1, 1, 2, 2,
  3, 4, 4, 5, 6, 7, 8, 9,
  11, 12, 13, 15, 16, 18, 20, 21,
  23, 25, 27, 29, 31, 33, 35, 37,
  39, 42, 44, 46, 49, 51, 54, 56,
  59, 62, 64, 67, 70, 73, 76, 79,
  81, 84, 87, 90, 93, 96, 99, 103,
  106, 109, 112, 115, 118, 121, 124, 128
};
int i;
int i2;
byte frequency = 50;
int oneCycleInUs = 1000000 / frequency;
int intervalWait = oneCycleInUs / 259;
unsigned long timer;
int dY, dY2;

void core0( void * parameter ) {
  for (;;) {
    dY = sineLookup[i];
    dY2 = sineLookup[i2];
    if (micros() - timer > intervalWait) {
      i++;
      timer = micros();
    }
    i2 = i - 20;
    if(i2 < 0){
    i2 = i2 + 255;
    }
    if (i == 255) {
      i = 0;
    }
  }
}

void setup() {
  Serial.begin(115200);
  xTaskCreatePinnedToCore(
    core0,
    "Task_1",
    1000,
    NULL,
    1,
    &Task1,
    0);
}

int voltCrossing;
int lastVoltReading;

void getVoltCrossing(int voltReading) {
  if (voltReading > vZeroPoint && lastVoltReading < vZeroPoint + 0.1) {
    lastVoltReading = voltReading;
    voltCrossing = 1;
    return;
  }
  if (voltReading < vZeroPoint && lastVoltReading > vZeroPoint - 0.1) {
    lastVoltReading = voltReading;
    voltCrossing = -1;
    return;
  }
  lastVoltReading = voltReading;
  voltCrossing = 0;
  return;
}

int ampCrossing;
int lastAmpReading;

void getAmpCrossing(int ampReading) {
  if (ampReading > aZeroPoint && lastAmpReading < aZeroPoint + 0.1) {
    lastAmpReading = ampReading;
    ampCrossing = 1;
    return;
  }
  if (ampReading < aZeroPoint && lastAmpReading > aZeroPoint - 0.1) {
    lastAmpReading = ampReading;
    ampCrossing = -1;
    return;
  }
  lastAmpReading = ampReading;
  ampCrossing = 0;
  return;
}

unsigned long t1 = 0;
unsigned long t2 = 0;
unsigned long zCrossDelay[5];
unsigned long zCross;
float zCrossRadians = 0;
float pf;
bool counting = 0;
byte n = 0;

void getPhaseDelay() {
  if (voltCrossing == 1 && counting == 0 ) {
    t1 = micros();
    counting = 1;
  }
  if (ampCrossing == 1 && counting == 1 && micros() - t1 > 10) {
    t2 = micros();
    zCrossDelay[n] = t2 - t1;
    zCross = zCrossDelay[0] + zCrossDelay[1] + zCrossDelay[2] + zCrossDelay[3] + zCrossDelay[4];
    zCross = zCross / 5;
    counting = 0;
  }
}

void calculatePowerFactor(){ //contains strenuous floating point math so should only be run once a second or so
zCrossRadians = (float)zCross / oneCycleInUs * 6.283185;
    n++;
    if (n == 5) {
      n = 0;
    }
    pf = cos(zCrossRadians);
}

unsigned long timer2 = micros();

void loop() {
  getVoltCrossing(dY);
  getAmpCrossing(dY2);
  getPhaseDelay();
  if (micros() - timer2 > 1000000) {
  calculatePowerFactor();
  Serial.println(pf);
  timer2 = micros();
  }
}