3 phase PLL @47~63Hz

So I want to make a 3 phase PLL which will work @ 47Hz to 63Hz. The output and input voltage of the signals are not really important as I will first have to lower max 600Vac to 5Vac ( don't worry I got this :stuck_out_tongue_closed_eyes: [cheatcode:diff probe]) so I won't fry the Arduino :smiley: . The output will be 10Vac sine but I can use an opamp( a transformer would be a bit overkill) so that's not the issue.

My question is: is it possible to use an Arduino (I already have an Uno but other types would also be possible) to create three phases. I pick one phase as reference phase A and the other phase is 120 degrees out of phase when compared to phase A and the third one is 240 degrees out of phase when compared with phase A.

And use some PLL magic :wink: so I can make sure there are 3 different phases and they can be regulated so they won't collide with each other.

So in short a 3 phase PLL @47~63Hz on Arduino. If possible then where do I start with programming a 3 phase PLL and which Arduino would be the best suited for the job? I have seen DDS but I need to regulate my output at all times to be certain.

Not very easy. Particularly the high power side.. If you try and do this yourself you'll fry a lot of electronics.

And working with those voltages is not for a beginner.

You can buy excellent 3-phase variable speed motor drives.... I'd go that way

Regards

Allan

Thank you for your answer. However as I said I can manage and handle the high power side, that's not the issue for me. The input voltage to the Arduino would be max 2,2V. And the output from the Arduino can be max 5V. I was more concerned whether the Arduino can handle the necessary computation that is required for a 3 phase PLL.

So to make it easier, the input is 2V~3V and the output from the Arduino is going to be 5V or anything that an Arduino can output max. With a frequency of 47~63Hz.

PS. Im not just a beginner with electronics nor with high power. Im actually an EE student and this is a part of my final project.

Generating multiphase is trivial with direct digital synthesis, which uses a phase accumulator, via table-lookup of the top bits of the phase value - if your phase variable is unsigned int (16 bit), for instance:

  phase += frequency/Fsample ;
  phase_U = sine_table [phase >> 8] ;
  phase_V = sine_table [(phase + 0x5555) >> 8] ;
  phase_W = sine_table [(phase + 0xAAAB) >> 8] ;

And a PLL is like a PID loop for phase and/or frequency - you need a software phase detector to generate the reference phase value.

You don't need a PLL to create a 3 phase signal. A PLL is definitely not the way to go.

I have not run this code in a while. You can try it but no guarantees.

// Generates three equally spaced phases of a PWM sine signal
//    on an Arduino Uno
// makes use of the fact that sin(x+4pi/3) = - sin(x) - sin(x+2pi/3)
// (c) 2016     C. Masenas

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#define phase0  9    
#define phase1  10
#define phase2  11
#define delayPin  7    // use this optional signal for info on compute time

const float pi = 3.14159 ;
const float bump = .01 ;  // fractional change of frequency per cycle
// sample time frequency is 16/MHz / 256 / 8 
const float T = 128 ;    // sample time in microseconds
float freq = .1 ;  // frequency of tone in hertz
const float A = 125.0 ;  // amplitude
float omegaT = 2*pi*freq*T/1000000.0 ;
// next line initializes oscillation with amplitude A
volatile float a[]={0.0, A*sin(omegaT),0.0}; 
const float phase1_0 = A*sin(2.0*pi/3.0);  // this will be used again so calculate once
float bb0 = phase1_0 ;
float bb1 = A*sin(omegaT + 2.0*pi/3.0);
volatile float b[]={bb0, bb1 ,0.0}; 
// c1 is the difference equation coefficient
volatile float c1 = (8.0 - 2.0*pow(omegaT,2))/(4.0+pow(omegaT,2));
unsigned int sigma = 0;
unsigned int delta = 0x8000; 
const int freq_c = 100; // frequency separating decimation and coefficient recalculation 

void setup()                 
{

  //The base frequency for pins 3, 9, 10, and 11 is 31250 Hz (32 uS)
  //The base frequency for pins 5 and 6 is 62500 Hz.
//  TCCR1B = TCCR1B & 0b11111000 | 0x02 ;  // timer1, sets pins 9 and 10 to 32*8=256 uS PWM period
//  TCCR2B = TCCR2B & 0b11111000 | 0x02 ;  // timer2, sets pins 3 and 11 to 256 uS PWM period
//  TIMSK2 |= (1<<TOIE2) ;  // enable ovrflow detect
  Setup_timer1();
  Setup_timer2();

  pinMode(phase0, OUTPUT);
  pinMode(phase1, OUTPUT);
  pinMode(phase2, OUTPUT);
  pinMode(delayPin, OUTPUT);

}

void loop()                   
{ 
  
  bumpFreq(1000) ;
  delay(100);
  bumpFreq(60);
  delay(100); 
 
}

ISR(TIMER2_OVF_vect)    // called by Timer Interrupt
{ 
  sigma += delta ;
  if ( sigma > 0x7FFF ){ 
    sigma = sigma - 0x8000 ;
    sbi(PORTD, delayPin);
    
    a[2] = c1*a[1] - a[0] ;  // recursion equation
    a[0] = a[1] ;            // shift
    a[1] = a[2] ; 
  
    b[2] = c1*b[1] - b[0] ;  // calculate second phase
    b[0] = b[1] ;
    b[1] = b[2] ; 
  
    OCR1A = int(a[2])+127 ;  // pin 9
    OCR1B = int(b[2])+127 ; 
    OCR2A = int(-a[2]-b[2])+127 ;   
      
    cbi(PORTD, delayPin);
  }
}

void bumpFreq(float _freq){  // changes frequency
  noInterrupts();
  if( _freq <= freq_c && freq <= freq_c){
    delta = 0x8000*(_freq/freq_c); 
  } else {  // abrupt change in frequency and phase
    if( _freq <= freq_c && freq > freq_c){  
      delta = 0x8000*(_freq/freq_c); 
      freq = freq_c ;
    } else {
      delta = 0x8000 ; 
      freq = _freq ;
    }
    sigma = 0 ;
    omegaT = 2*pi*freq*T/1000000.0 ;
    a[0] = 0.0 ;
    a[1] = A*sin(omegaT);
    b[0] = phase1_0 ;
    b[1] = A*sin(omegaT + 2.0*pi/3.0); 
    c1 = (8.0 - 2.0*pow(omegaT,2))/(4.0+pow(omegaT,2));    
  } 
  interrupts();
}

void bumpAtZero( float _freq){
  while( !( a[1]> 0 && a[0] < 0 ));  // look for zero crossing of phase0
  bumpFreq( _freq);
  return ;
}

void changeFreq(float newFreq)
// this function gradually changes the frequency to the new frequency
// attempts to minimize abrupt jumps in frequency and phase
{
  if (newFreq > freq){
    while ( newFreq > freq ){
      if( (1.0 + bump)* freq < newFreq ) bumpAtZero( (1.0 +bump)*freq );
      else{
        bumpAtZero( newFreq );
        return ;
      }
    }
  }
  if (newFreq < freq){
    while ( newFreq < freq ){
      if( (1.0 - bump)* freq > newFreq ) bumpAtZero( (1.0 -bump)*freq );
      else{
        bumpAtZero( newFreq );
        return ;
      }
    }
  } 
}

// timer2 setup
// set prscaler to 1,  fast PWM
void Setup_timer2() {

// Timer2 Clock Prescaler to : 8
  cbi (TCCR2B, CS20);  // set
  sbi (TCCR2B, CS21);  // clear
  cbi (TCCR2B, CS22);

  // Timer2 PWM Mode 
  cbi (TCCR2A, COM2A0);  // clear OC2A on Compare Match, PWM pin 11
  sbi (TCCR2A, COM2A1);

  // set to fast PWM
  sbi (TCCR2A, WGM20);  // Mode 3, fast PWM
  sbi (TCCR2A, WGM21);
  cbi (TCCR2B, WGM22);

  sbi (TIMSK2,TOIE2);              // enable overflow detect
  
}
// timer1 setup  (sets pins 9 and 10)
// set prscaler to 8, PWM mode to phase correct PWM,  16000000/510 = 31372.55 Hz clock
void Setup_timer1() {

// Timer1 Clock Prescaler to : 8
  cbi (TCCR1B, CS10);
  sbi (TCCR1B, CS11);
  cbi (TCCR1B, CS12);

  // Timer1 PWM Mode set to Phase Correct PWM
  cbi (TCCR1A, COM1A0);  // clear OC1A on Compare Match, PWM pin 9
  sbi (TCCR1A, COM1A1);
  cbi (TCCR1A, COM1B0);  // clear OC1B on Compare Match, PWM pin 10
  sbi (TCCR1A, COM1B1);

  sbi (TCCR1A, WGM10);  // Mode 1  / phase correct
  cbi (TCCR1A, WGM11);
  sbi (TCCR1B, WGM12);
  cbi (TCCR1B, WGM13);
}

This is another way of creating 3 phases.

// 3 phase PWM sine
// (c) 2016 C. Masenas
// Modified from original DDS from: 
// KHM 2009 /  Martin Nawrath



// table of 256 sine values / one sine period / stored in flash memory
PROGMEM const unsigned char sine256[]  = {
  127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240,
  242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223,
  221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78,
  76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,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,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124

};
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
         
int testPin = 7;
int enablePin = 6 ;

volatile  float freq=1;
const float refclk=122.549  ;     //  16 MHz/510/256

// variables used inside interrupt service declared as voilatile
volatile unsigned long sigma;   // phase accumulator
volatile unsigned long delta;  // phase increment
byte phase0, phase1, phase2;

void setup()
{
  Serial.begin(9600);        // connect to the serial port
  Serial.println("DDS Test");

  pinMode(enablePin, OUTPUT);      // sets the digital pin as output
  pinMode(testPin, OUTPUT);      // sets the digital pin as output
  pinMode(9, OUTPUT);     // pin9= PWM  output / frequency output
  pinMode(10, OUTPUT);     // pin10= PWM  output / frequency output
  pinMode(11, OUTPUT);     // pin11= PWM  output / frequency output

  Setup_timer2();
  Setup_timer1();
  digitalWrite(enablePin, HIGH);

// the waveform index is the highest 8 bits of sigma
// choose refclk as freq to increment the lsb of the 8 highest bits
//    for every call to the ISR of timer2 overflow
// the lsb of the 8 highest bits is 1<<24 (1LL<<24 for long integer literal)
  delta = (1LL<<24)*freq/refclk ;  
}
void loop(){
  
  changeFreq(20);
  delay(10000);
  changeFreq(25);
  delay(10000);
              
 }

void changeFreq(float _freq){
  cbi (TIMSK2,TOIE2);              // disable timer2 overflow detect
  freq = _freq;
  delta=(1LL<<24)*freq/refclk;  // update phase increment
  sbi (TIMSK2,TOIE2);              // enable timer2 overflow detect
} 

//******************************************************************
// timer2 setup
// set prscaler to 1,  fast PWM
void Setup_timer2() {

// Timer2 Clock Prescaler to : 1
  sbi (TCCR2B, CS20);  // set
  cbi (TCCR2B, CS21);  // clear
  cbi (TCCR2B, CS22);

  // Timer2 PWM Mode 
  cbi (TCCR2A, COM2A0);  // clear OC2A on Compare Match, PWM pin 11
  sbi (TCCR2A, COM2A1);

  // set to fast PWM
  sbi (TCCR2A, WGM20);  // Mode 1, phase correct PWM
  cbi (TCCR2A, WGM21);
  cbi (TCCR2B, WGM22);

  sbi (TIMSK2,TOIE2);              // enable overflow detect
  
}
// timer1 setup  (sets pins 9 and 10)
// set prscaler to 1, PWM mode to phase correct PWM,  16000000/510 = 31372.55 Hz clock
void Setup_timer1() {

// Timer1 Clock Prescaler to : 1
  sbi (TCCR1B, CS10);
  cbi (TCCR1B, CS11);
  cbi (TCCR1B, CS12);

  // Timer1 PWM Mode set to Phase Correct PWM
  cbi (TCCR1A, COM1A0);  // clear OC1A on Compare Match, PWM pin 9
  sbi (TCCR1A, COM1A1);
  cbi (TCCR1A, COM1B0);  // clear OC1B on Compare Match, PWM pin 10
  sbi (TCCR1A, COM1B1);

  sbi (TCCR1A, WGM10);  // Mode 1  / phase correct PWM
  cbi (TCCR1A, WGM11);
  cbi (TCCR1B, WGM12);
  cbi (TCCR1B, WGM13);
}

//******************************************************************
// Timer2 Interrupt Service at 31372,550 KHz = 32uSec
// this is the timebase REFCLOCK for the DDS generator
// runtime : 8 microseconds ( inclusive push and pop)
// OC2A - pin 11
// OC1B - pin 10
// OC1A - pin 9
// https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM
ISR(TIMER2_OVF_vect) {

  sbi(PORTD,testPin);          

  sigma=sigma+delta; // soft DDS, phase accu with 32 bits
  phase0=sigma >> 24;     // use upper 8 bits for phase accu as frequency information
                         // read value fron ROM sine table and send to PWM DAC
  phase1 = phase0 +85 ;
  phase2 = phase0 +170 ;

  OCR2A=pgm_read_byte_near(sine256 + phase0);  // pwm pin 11
  OCR1B=pgm_read_byte_near(sine256 + phase1);  // pwm pin 10
  OCR1A=pgm_read_byte_near(sine256 + phase2);  // pwm pin 9

  cbi(PORTD,testPin);            
  
}

Thank you for your answer. Don't get me wrong but those are PWM signals right?I forgot to mention I only need sine waves. I tried with square and triangle waves, but it didn't work. Still this is a good start, thanks again. :slight_smile:

If you filter the PWM with an RC filter you will get sine waves. PWM is digital but it is also analog in a sense. If you filter out the high frequencies the remaining signal is analog. This is the only way to do it on an Uno unless you use an external DAC. The Due has built in DAC's if that is what you really want.

I only have one question about the last code.

phase1 = phase0 +85 ;
phase2 = phase0 +170 ;

Why is it that by adding 85 and 170 you get them 120 and 240 degrees apart? Can you show me a calculation or an explanation?

Same reason I use 0x5555 and 0xAAAB in my posting #3. 85 = 0x55, 170 = 0xAA.

0x10000 / 3 = 0x5555.555...
0x100 / 3 = 0x55.5555...

Of course I do it accurately by having a properly sized phase accumulator.

Oh. charliesixpack, a PLL is exactly what is needed, if you know your stuff. A software PLL
in this case. DDS for the oscillator, phase detector to be determined. The tricky bit is the
feedback poles and zeroes of course.

If you use storage of one byte as the look up table index it will act as an accumulator with a modulus of 256. A phase of 120 is one third into the index or 255/3 = 85. A phase of 240 is an index of 170.

No PLL is needed to generate the three phases. The 3 phases are generated by having the proper spacing between the indices of the 3 signals.

When I measure the output voltage of the 3 output pins(D9,D10,D11) I get 1,81Vac and 2.35Vdc. Does anyone have an explanation for that? Because I thought it would be near 5V.

I also programmed pin A0,A1,A2 high, because I needed other 3 5Vdc output voltages and the digital pins were programmed with the timer so I would get the PWM in those pins.

Ofcourse the A0,A1 and the A2 pins have lower voltages(4,7V instead of 5V), which is logical. But the fact that the digital pins output less than half of what I expected is a bit too much really.

Hi,
You have a PWM signal that is 0 to 5V.
So you have 5Vpeak to peak.
Which is 2.5Vpeak
Which is 2.5/ (sqrt of 2) = 2.5/1.414 = 1.76Vrms
Pretty close if you ask me.
You have a 2.5V offset.

Im actually an EE student and this is a part of my final project.

What is your level of AC theory?.
I am surprised you have not put an oscilloscope on it, or would you like us to do that for you too?

Charliesixpack nice bit of code. :slight_smile:

Tom.... :slight_smile:

charliesixpack:
This is another way of creating 3 phases.

// 3 phase PWM sine

// (c) 2016 C. Masenas
// Modified from original DDS from:
// KHM 2009 /  Martin Nawrath

// table of 256 sine values / one sine period / stored in flash memory
PROGMEM const unsigned char sine256[]  = {
 127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240,
 242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223,
 221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78,
 76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,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,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124

};
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
       
int testPin = 7;
int enablePin = 6 ;

volatile  float freq=1;
const float refclk=122.549  ;     //  16 MHz/510/256

// variables used inside interrupt service declared as voilatile
volatile unsigned long sigma;   // phase accumulator
volatile unsigned long delta;  // phase increment
byte phase0, phase1, phase2;

void setup()
{
 Serial.begin(9600);        // connect to the serial port
 Serial.println("DDS Test");

pinMode(enablePin, OUTPUT);      // sets the digital pin as output
 pinMode(testPin, OUTPUT);      // sets the digital pin as output
 pinMode(9, OUTPUT);     // pin9= PWM  output / frequency output
 pinMode(10, OUTPUT);     // pin10= PWM  output / frequency output
 pinMode(11, OUTPUT);     // pin11= PWM  output / frequency output

Setup_timer2();
 Setup_timer1();
 digitalWrite(enablePin, HIGH);

// the waveform index is the highest 8 bits of sigma
// choose refclk as freq to increment the lsb of the 8 highest bits
//    for every call to the ISR of timer2 overflow
// the lsb of the 8 highest bits is 1<<24 (1LL<<24 for long integer literal)
 delta = (1LL<<24)*freq/refclk ;  
}
void loop(){
 
 changeFreq(20);
 delay(10000);
 changeFreq(25);
 delay(10000);
             
}

void changeFreq(float _freq){
 cbi (TIMSK2,TOIE2);              // disable timer2 overflow detect
 freq = _freq;
 delta=(1LL<<24)*freq/refclk;  // update phase increment
 sbi (TIMSK2,TOIE2);              // enable timer2 overflow detect
}

//******************************************************************
// timer2 setup
// set prscaler to 1,  fast PWM
void Setup_timer2() {

// Timer2 Clock Prescaler to : 1
 sbi (TCCR2B, CS20);  // set
 cbi (TCCR2B, CS21);  // clear
 cbi (TCCR2B, CS22);

// Timer2 PWM Mode
 cbi (TCCR2A, COM2A0);  // clear OC2A on Compare Match, PWM pin 11
 sbi (TCCR2A, COM2A1);

// set to fast PWM
 sbi (TCCR2A, WGM20);  // Mode 1, phase correct PWM
 cbi (TCCR2A, WGM21);
 cbi (TCCR2B, WGM22);

sbi (TIMSK2,TOIE2);              // enable overflow detect
 
}
// timer1 setup  (sets pins 9 and 10)
// set prscaler to 1, PWM mode to phase correct PWM,  16000000/510 = 31372.55 Hz clock
void Setup_timer1() {

// Timer1 Clock Prescaler to : 1
 sbi (TCCR1B, CS10);
 cbi (TCCR1B, CS11);
 cbi (TCCR1B, CS12);

// Timer1 PWM Mode set to Phase Correct PWM
 cbi (TCCR1A, COM1A0);  // clear OC1A on Compare Match, PWM pin 9
 sbi (TCCR1A, COM1A1);
 cbi (TCCR1A, COM1B0);  // clear OC1B on Compare Match, PWM pin 10
 sbi (TCCR1A, COM1B1);

sbi (TCCR1A, WGM10);  // Mode 1  / phase correct PWM
 cbi (TCCR1A, WGM11);
 cbi (TCCR1B, WGM12);
 cbi (TCCR1B, WGM13);
}

//******************************************************************
// Timer2 Interrupt Service at 31372,550 KHz = 32uSec
// this is the timebase REFCLOCK for the DDS generator
// runtime : 8 microseconds ( inclusive push and pop)
// OC2A - pin 11
// OC1B - pin 10
// OC1A - pin 9
// https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM
ISR(TIMER2_OVF_vect) {

sbi(PORTD,testPin);

sigma=sigma+delta; // soft DDS, phase accu with 32 bits
 phase0=sigma >> 24;     // use upper 8 bits for phase accu as frequency information
                        // read value fron ROM sine table and send to PWM DAC
 phase1 = phase0 +85 ;
 phase2 = phase0 +170 ;

OCR2A=pgm_read_byte_near(sine256 + phase0);  // pwm pin 11
 OCR1B=pgm_read_byte_near(sine256 + phase1);  // pwm pin 10
 OCR1A=pgm_read_byte_near(sine256 + phase2);  // pwm pin 9

cbi(PORTD,testPin);            
 
}

how about to generate 3 phase pwm signal without sine wave? can anyone give the code?

Do you mean trapezoidal drive?

MarkT:
Do you mean trapezoidal drive?

yes.. 3 pwm signal with 120 degree phase shift.

For trapezoidal drive the usual procedude is float one phase, drive one low, PWM the other.

MarkT:
For trapezoidal drive the usual procedude is float one phase, drive one low, PWM the other.

can u show the code that i can use?

If you want me to write code that's one for gigs/collaborations and for someone with the available time...

MarkT:
If you want me to write code that's one for gigs/collaborations and for someone with the available time...

i had find the code for generate 3 pwm, but i need to use it with pi controller for my dcdc boost converter circuit. Can u help me?