5

I have two square wave signals with the same frequency. I want to measure the phase shift between these two signals. There will be a small phase difference between two signals. (0-90 degree) and the frequency will vary in the range 100mHz - 50kHz. I am using an Arduino DUE (ATMEL ATSAM3X8E AU ,ARM)

I tried to use "micros()" for timer functionality to get 2 different times and then calculate the phase shift using the following code:

int led=47,count1=0,count2=0,temp=0;
int i=0,j=0;
float time1[3]={0,0};
float time2[3]={0,0};
float freq1=0,freq2=0;
float period1=0,period2=0;
float ps=0;


void setup()
{
      Serial.begin(9600);
      Serial.println();
       Serial.println();
      pinMode(31,INPUT);
      pinMode(33,INPUT);
      pinMode(led,OUTPUT);
      digitalWrite(led,HIGH);
      noInterrupts();  
      attachInterrupt(31,in1,RISING);
      attachInterrupt(33,in2,RISING);
      interrupts();
}

void in1()
{      noInterrupts();
       switch(i){
           case 0 : time1[i]=micros(); i++;  break;
           case 1 : if(j==1){time1[i]=micros(); i++; break;}
       }
       interrupts();
}
void in2()
{      noInterrupts();
       switch(j){
            case 0 : if(i==1){time2[j]=micros(); j++; break;}
            case 1 : if(i==2){time2[j]=micros(); j++; break;}
       }
           if(j==2){
             period1 = (time1[1]-time1[0]);
             period2 = (time2[1]-time2[0]);          
             freq1 = 1000.0/period1;         
             freq2 = 1000.0/period2;         
             ps = (((time2[1]-time1[1])*360.0)s/period1);
          if(freq1==freq2){
          Serial.println("");
          Serial.print("TIME1[0] = ");
          Serial.print(time1[0],6);
          Serial.print("\t TIME1[1] = ");
          Serial.print(time1[1],6);
          Serial.print("\t DEL1 = ");
          Serial.println(time1[1]-time1[0],6);
          Serial.print("TIME2[0] = ");
          Serial.print(time2[0],6);
          Serial.print("\t TIME2[1] = ");
          Serial.print(time2[1],6);
          Serial.print("\t DEL2 = ");
          Serial.println(time2[1]-time2[0],6);
          Serial.print("\t x = ");
          Serial.print(time2[0]-time1[0],6);
          Serial.print("period1(ms) = ");
          Serial.print(period1,6);
          Serial.print("\t period2(ms) = ");
          Serial.println(period2,6);
          Serial.print("freq1 = ");
          Serial.print(freq1,6);
          Serial.print("\t freq2 = ");
          Serial.print(freq2,6);
          Serial.print("\t Phase Shift = ");
          Serial.println(ps,6);
          } 
          else { 
          i=0;j=0;
          interrupts();
          }        
       }
       interrupts();
}
void loop()
{
       ;
}

When I run this code I am not getting any errors, but in high frequency (above 400 Hz) Ihave a problem. When I tested at 0 degrees, this code produced this output:

400 Hz 1.152 degree
500 Hz 1.440 degree
600 Hz 1.727 degree
700 Hz 2.015 degree
800 Hz 2.304 degree
900 Hz 2.592 degree
1 kHz  2.880 degree
2 kHz  5.760 degree
3 kHz  8.649 degree
.
.
.
10 kHz 28.8 degree

Each frequency has lowest degree that this code can calculate.

How can I fix this code? Is it possible to calculate the phase shift between two square wave signals this way?

6
  • First make sure you limit the data sent to the serial port inside your ISR when the interrupts are disabled. At 9600 baud it takes 1ms to send one byte over the serial link.
    – Alex
    Mar 24, 2015 at 15:52
  • @Alex the Due might have hardware serial, which is dealt with asynchronously by the hardware UART/USB. The Due is a powerful Cortex M3 I doubt there is any processing issue there..
    – KyranF
    Mar 24, 2015 at 15:55
  • chaya have you looked at an oscilloscope and seen if the numbers are correct, or even close at all?
    – KyranF
    Mar 24, 2015 at 15:56
  • chaya your output doesn't match the strings shown in the code, where are the lines showing "period" and "phase shift"?
    – KyranF
    Mar 24, 2015 at 15:58
  • 2
    The best solution would be to just capture time in ISR1 and ISR2. And then subtract the values and display the difference in the main loop at a slower speed. Obviously you may miss a few samples every now and then.
    – Alex
    Mar 24, 2015 at 16:05

3 Answers 3

4

With 0° you have two inputs triggering interrupts at the same time. That can't happen. You can't run two interrupts at the same time, so the second one gets queued up until the first has finished.

With all that serial printing in there there's going to be quite a delay between one interrupt triggering and the next one being allowed to trigger - giving you skewed results.

I would suggest passing your signal through an XOR gate so that you end up with a single small pulse that represents the phase shift between the two signals.

4
  • XOR won't tell you which one happens first
    – Scott Seidman
    Mar 24, 2015 at 16:07
  • You could look at A XOR B to get the length of the pulse and A to figure out which one came first.
    – Greg d'Eon
    Mar 24, 2015 at 16:13
  • I agree. XOR would be nice plus using a timer in capture mode. It's still fishy when you get close to 0°.
    – Alex
    Mar 24, 2015 at 16:21
  • You could implement a phase detector (e.g. Type 2) and filter (charge pump) to produce a voltage proportional to phase. A type-2 PD would be sensitive to neg or pos phase shift.
    – akellyirl
    Mar 24, 2015 at 16:55
3

Even if your code worked perfectly, with zero processing latency, you still are working with a timer resolution of only 1 microsecond. If I did my math correctly, that means at 50 kHz, the smallest phase difference you can detect is around 18 degrees. At 100 MHz, 1 microsecond is 100 complete cycles. You probably want to look into hardware specifically designed for phase detection.

4
  • 3
    That's "mHz" (small m) -- millihertz, not megahertz.
    – Dave Tweed
    Mar 24, 2015 at 16:49
  • Oops. Well, the 50 kHz limit still applies. He didn't specify his needed precision, but one degree precision would limit you to 2.7 kHz. Mar 24, 2015 at 18:12
  • I think you can at least get a lot closer with the Due as in my answer, although I'm not sure if 50kHz is reachable. Using a 42MHz timebase gives just barely enough resolution, and 20us per cycle is just enough to get some calculation done. The most obvious problem is that the serial can't possibly send 50,000 reports per second, so there would have to be some aggregation/decimation there!
    – hobbs
    Mar 24, 2015 at 20:13
  • Good point, @hobbs. I should be more explicit that my limit is referring to the OP's code that uses the micros() function to do the timing. Using the timer directly can get you better resolution. Mar 24, 2015 at 20:56
3

Connect the two signals to the TIOA pins of two different timer/counter channels on the same timer/counter block (either TIOA0 and TIOA1, which are Arduino pins 2 and A7, or any two of TIOA6, TIOA7, and TIOA8, which are Arduino pins 5, 3, and 11, respectively).

Set the two timer channels to capture mode, "up" counting, and internal clocking at the fastest rate that seems appropriate (I don't see any reason not to use TIMER_CLOCK1 = 42MHz on the Due). Set them both to load RA on rising edge of TIOA, and RB on falling edge. Assuming TIOA0 and 1:

pmc_enable_periph_clk(ID_TC0);
TC_Configure(TC0, 0,
    TC_CMR_TCCLKS_TIMER1 |
    TC_CMR_WAVSEL_UP |
    TC_CMR_LDRA_RISING |
    TC_CMR_LDRB_FALLING
);

pmc_enable_periph_clk(ID_TC1);
TC_Configure(TC0, 1,
    TC_CMR_TCCLKS_TIMER1 |
    TC_CMR_WAVSEL_UP |
    TC_CMR_LDRA_RISING |
    TC_CMR_LDRB_FALLING
);

Set them both to generate interrupts on RA load:

TC0->TC_CHANNEL[0].TC_IER = TC_IER_LDRAS;
NVIC_EnableIRQ(TC0_IRQn);
TC0->TC_CHANNEL[1].TC_IER = TC_IER_LDRAS;
NVIC_EnableIRQ(TC1_IRQn);

Enable both clocks, and synchronize them so that they're both 0 at the same moment:

TC0->TC_CHANNEL[0].TC_CCR = TC_CCR_CLKEN;
TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKEN;
TC0->TC_BCR = TC_BCR_SYNC;

And handle the interrupts:

void TC0_Handler() {
    uint32_t status = TC0->TC_CHANNEL[0].TC_SR;
    if (status & TC_SR_LDRAS) {
        uint32_t tm = TC0->TC_CHANNEL[0].TC_RA;
        // rising edge of TIOA0 happened at "tm",
        // do something here to deal with it...
    }
}

void TC1_Handler() {
    uint32_t status = TC0->TC_CHANNEL[1].TC_SR;
    if (status & TC_SR_LDRAS) {
        uint32_t tm = TC0->TC_CHANNEL[1].TC_RA;
        // rising edge of TIOA1 happened at "tm",
        // do something here to deal with it...
    }
}

(Just defining functions with those names registers them as interrupt handlers, using weak-symbol magic).

If you choose to use TIOA6/7/8, the clock will be ID_TC6/ID_TC7/ID_TC8, and the EnableIRQ will be TC6_IRQn/TC7_IRQn/TC8_IRQn, but the TC_Configure calls will be TC2, 0 / TC2, 1 / TC2, 2, the SYNC will be issued to TC2, and the register accesses will be to TC2->TC_CHANNEL[0] / TC2->TC_CHANNEL[1] / TC2->TC_CHANNEL[2]. This is due to the way that the channels are grouped into blocks of three in the hardware. This is also why you have to use 0 and 1, or two of 6, 7, and 8; in order to sync the timers they have to be on the same block (and TIOA2 through TIOA5 aren't routed on the Due).

Because the clocks run at MClk/2 = 42MHz, the times you get will be in units of 1/42 microsecond, 42 times better than you get from micros(). Because the timer values are latched into the timer hardware before the interrupt is set, there isn't a problem if the two events come close to each other in time; you'll get the second interrupt after you finish handling the first, and the value in the register will still be valid (as long as you don't take so long that you miss more than one edge).

If you're interested in the falling edges, you can get those as well by setting the TC_IER_LDRBS bits and checking for status & TC_SR_LDRBS in your interrupt handlers.

Final advice: set your serial baud rate higher than 9600 if you can, you'll greatly decrease the amount of time you spend in Serial.print!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.