Phase locked oscillator

Status
Not open for further replies.

1.618

Member
Hello all and thanks in advance for any advice rendered. https://forum.pjrc.com/threads/26608-Frequency-Locked-Sine-Wave-Generator-Teensy-3-1?highlight=lock+sine is the original source and inspiration, though our application involves generators more directly. Apologies for the wordy preamble.

A bit of background first. I'm a retired power engineer whose last programming experiences were in the 1990's writing a bit of assembly for a Z-80 based product. My co-conspirator in this project is my son who is primarily a mechanical designer. We're doing this initial phase as a proof of concept before hiring an experienced embedded programmer to finish off the remaining 130%. This forum has been a Godsend in getting us bootstrapped to this point. Neither of us has any C or CPP experience other than what we've picked up here over the last few months.

The application will involve a square wave sync signal created from the Phase A output of a three phase generator. From this, three, reference, low distortion sine waves will be generated. We chose to produce two signals directly from the Teensy 3.6 internal D/As at 0 and 90 degrees. From these the three waveforms will be developed by simple summing amplifiers. Concurrent will be the acquisition of the three output voltages and currents from the generator itself, also at the rate of 128 per cycle, for both RMS calculations and the eventual FFTs.

We've made a stab at the basics and have a "working" breadboard, but the problem is a "notch" at the first step of the waveform. Though only noticable on the 0 degree waveform I'm sure it occurs on the 90 degree as well. This notch does not appear to be caused by the accuracy of the timing period but a failure to write the data to the DACs. Various attempts to modestly "fudge" the default 130.208usec period were tried without resolving the issue.

We've set "probes" in place at various points in the code to monitor actions. These points being observed at the D2 output. From this we feel we're not overtaxing the processor, but have an "obvious" code error, or an interrupt stepping on another, or??

The code is presented below along with a photograph showing a section of the sinewave as well as our "D2 probe" during the writing to the DACs and the final math stage of the RMS metering calculations. This wider pulse occurs at the 127th and final step of the sine wave.

We've changed the rate between 64-256 steps/cycle with similar results. The notch becomes less noticable at 256 which leads me to suspect the issue occured in Wozzy's code (800 steps) but just wasn't observed or pertinent to his application.

Also well received will be suggestions for more refined methods of generating the sine waves and data acquisition. We have been reading posts concerning the use of the DMA, but the implementation of advanced techniques are well above our present pay grade.

Code:
/*

   This program runs on Teensy 3.6 .
   Compile for 180 MHz.  <Tools/CPU Speed: "180MHZ ">
   Good for "0" to around 135 Hz with 128 steps.

   Wiring:
   Sync source is a squarewave input on Teensy 3.6 Pin3  (0-3.3V) from the generator's Phase A to neutral voltage.
   Analog sinewave outputs on Teensy pins DAC0 (0 degree) and DAC1 (90 degree), 0 to 3.3V sine waves.  Summing amps 
   and resistors used to form the three phase Y signals.
   Sampling of three phase voltages and currents synchronous with the DAC outputs.
*/

#include <ADC.h>
#include <Wire.h>
#include <FreqMeasure.h>
#include <LiquidCrystal_I2C.h>
#include <LiquidMenu.h>
IntervalTimer myTimer;
const int Pin_D2 = 2;     // digital pin 2
const int steps = 128;    // number of phase angle steps for sinewave generation
const float clock = 180.00000f;    // Clock Calibration Factor (eventually)
const float twopi = 6.2831853f;
const float clock_steps = (clock * (float)steps) * 0.5;
float amplitude;
float freq, freq_min = 55.0, freq_max = 65.0; // this will eventually come from the nominal parameters setup table
float phase;
float phasestep = twopi / (float)steps;
volatile float period;    //microseconds
volatile int count;  //time base in cpu clock cycles
int i;
int amplitudeValue[160];  //Initialize array 1.25 * steps.
int metering[128][6];   //Initialize array steps.
int amplitude0;
int amplitude1;
float Va_now, Va_sum, Va_rms;
float Vb_now, Vb_sum, Vb_rms;
float Vc_now, Vc_sum, Vc_rms;
float Ia_now, Ia_sum, Ia_rms;
float Ib_now, Ib_sum, Ib_rms;
float Ic_now, Ic_sum, Ic_rms;
float Pa_sum, Pb_sum, Pc_sum;
float kVAa, kVAb, kVAc;
float kWa_total, kWb_total, kWc_total;


void setup() {

  Serial.begin(115200);
  FreqMeasureClass FreqMeasure;
  LiquidCrystal_I2C lcd(0x27, 20, 4);
  pinMode(Pin_D2, OUTPUT); // Used to evaluate CPU loading during sine wave.  Dual trace scope to look at this pin and the DAC0 output.
  analogReadResolution(12);
  analogWriteResolution(12);
  FreqMeasure.begin();
  fillArray();
  sinewave();
  myTimer.priority(0);
  delay(100); // delay is needed so it doesn't fall on its face

}

void loop() {
  if (FreqMeasure.available()) {
    count = FreqMeasure.read();
    period = (count / clock_steps) * 1.5;
    freq = (1000000 / period) * steps;
    myTimer.end();
    i = 0;
    myTimer.begin(sinewave, period);  // create sinewave
  }
}

/************************ sinewave function *********************************/

void sinewave() {
  digitalWrite(Pin_D2, HIGH); // To see where and how long the sine wave write takes
  amplitude0 = amplitudeValue[i];     // first index value is 2048
  amplitude1 = amplitudeValue[i + 32];
  analogWrite(A22, amplitude0);
  analogWrite(A21, amplitude1);
  digitalWrite(Pin_D2, LOW);

  /****************** kept metering inside the sinewave function for now ***************/

  Va_now = analogRead(A0) - 2048;
  metering[i][0] = Va_now;
  Va_sum = Va_sum + (Va_now * Va_now);

  Vb_now = analogRead(A1) - 2048;
  metering[i][1] = Vb_now;
  Vb_sum = Vb_sum + (Vb_now * Vb_now);

  Vc_now = analogRead(A2) - 2048;
  metering[i][2] = Vc_now;
  Vc_sum = Vc_sum + (Vc_now * Vc_now);

  Ia_now = analogRead(A3) - 2048;
  metering[i][3] = Ia_now;
  Ia_sum = Ia_sum + (Ia_now * Ia_now);

  Ib_now = analogRead(A4) - 2048;
  metering[i][4] = Ib_now;
  Ib_sum = Ib_sum + (Ib_now * Ib_now);

  Ic_now = analogRead(A5) - 2048;
  metering[i][5] = Ic_now;
  Ic_sum = Ic_sum + (Ic_now * Ic_now);

  Pa_sum = Pa_sum + (Va_now * Ia_now);
  Pb_sum = Pb_sum + (Vb_now * Ib_now);
  Pc_sum = Pc_sum + (Vc_now * Ic_now);

  i++;

  if (i >= 127) {

    digitalWrite(Pin_D2, HIGH);

    Va_rms = sqrt(Va_sum / steps); Va_sum = 0; Va_now = 0;
    Vb_rms = sqrt(Vb_sum / steps); Vb_sum = 0; Vb_now = 0;
    Vc_rms = sqrt(Vc_sum / steps); Vc_sum = 0; Vc_now = 0;

    Ia_rms = sqrt(Ia_sum / steps); Ia_sum = 0; Ia_now = 0;
    Ib_rms = sqrt(Ib_sum / steps); Ib_sum = 0; Ib_now = 0;
    Ic_rms = sqrt(Ic_sum / steps); Ic_sum = 0; Ic_now = 0;

    kVAa = (Va_now * Ia_rms) * .001f;
    kVAb = (Vb_now * Ib_rms) * .001f;
    kVAc = (Vc_now * Ic_rms) * .001f;

    kWa_total = (Pa_sum / steps) * .001f;
    kWb_total = (Pb_sum / steps) * .001f;
    kWc_total = (Pc_sum / steps) * .001f;

    digitalWrite(Pin_D2, LOW);

    i = 0;
  }
}

/************************fillArray function*********************************/

void fillArray() {

  for (int i = 0; i <= 159; i++) {
    phase = (float)i * phasestep;
    amplitudeValue[i] = int(sin(phase) * 2048) + 2048;
  }
}

Missing_0_step[387].jpg
 
I can spot what looks like two issues with then code:

First there is an extra 'period' delay when restarting the timer before the restarted timer expires and calls 'sinewave' function, so one extra call to sinewave should be inserted just before the timer is restarted.

Code:
void loop() {
  if (FreqMeasure.available()) {
    count = FreqMeasure.read();
    period = (count / clock_steps) * 1.5;
    freq = (1000000 / period) * steps;
    myTimer.end();
    i = 0;
 [B]   sinewave();[/B]
    myTimer.begin(sinewave, period);  // create sinewave
  }
}

Second there are only 127 distinct values written as the loop counter i is reset one step to early, after writing value number 127 with i set to 126:

Code:
  i++;

  if (i >= 127) {    [B]Should be if (i>127)[/B]

    digitalWrite(Pin_D2, HIGH);

I dont have the setup to test this at them moment, but it seems the long period in the oscilloscope trace is not the first dac value at 0 but actually the last vale written before the timer restart. The changes should make the loop start one period earlier and have one more value written.
 
Thank you MLU,

We stumbled upon the second problem on Saturday morning, and will implement the first suggestion this morning. Thank you again for helping us out!

Best regards.
 
The first suggestion (the added "sinewave ()" ) performed better than our workaround did. Thanks again!

Best regards.
 
Status
Not open for further replies.
Back
Top