Various aspects of home brew inverters


Author Message
poida

Guru

Joined: 02/02/2017
Location: Australia
Posts: 1432
Posted: 04:40pm 11 Sep 2017      

2017-09-12_024241_due_pll_mains_sync.zip Part 13: Phase locked loop control with Arduino Uno and Due

I've enjoyed quite a few pleasant hours devoted to exploring PLL control as implemented on
Arduino microcontrollers. As at the time of writing I have very well performing code that locks on
to the mains 50Hz and remains locked, tracking without any issues for days at a time.
This performance is well in excess of the design goal I set - get some code that will lock to mains 50Hz to allow low stress changeover
from street power to inverter power.

My solar system employs a Latronics ATS40 change over switch which is a good product, doing what it is supposed to do.
It takes two AC supplies, "primary" and secondary" and puts one or the other supply out the output terminals, depending on if
the primary is OK or not. IF the primary is OK, it sends that out to the output. IF the primary is not usable, it switches over to send the secondary out the output.
I have the inverter hooked up as "primary" and street power connected as "secondary". When the inverter restarts after gettting enough battery charge from the following day,
the changeover from street to inverter occurs.
It's all worked fine for 2 years.
And then the Victron 3000VA inverter died. I then used a home made inverter using an inverter board from aliexpress and this lasted 2 weeks.
I think there are circumstances during changeover that create large stresses on the inverter. Don't you?
Should the street AC phase be 180deg different to the inverter phase, there can be large effects generated by the home appliances
and the inverter toroid when the changeover event occurs.
I now want the changeover to happen with inverter phase being synchronous to street AC supply.
This means I need PLL control of the inverter output, to obtain sync with street.
So that's the why.

A bit of PLL theory

We have a sync signal, which being the signal we want to synchronise our system to.
We have a slave signal, which is taken from our system, to be used when comparing phase and frequency.
Luckily in my case both signals are fairly clean sine waves.
In testing I have seen it sync to square waves OK too. But to get decent results you must remove the high frequencies of
the square wave BEFORE you sample it. You will have poor results if you let frequencies greater than 1/2 sample rate into the PLL control loop.

Phase lock loops (PLL) are a filter. Nothing more or less.
The phase signal is obtained in my case by multiplying the sync signal with the slave signal and putting the result
through a low pass filter. The sync signal is near as can be a 50 Hz sine wave with not very much other frequencies.
The slave signal is a sine wave of the slave system's output and also is nearly all one frequency.
Think 95% fundamental frequency with 5% distortion for both.

Multiplying two sine waves of f1 Hz and f2 Hz frequencies gives you the result of (f1-f2)Hz and (f1+f2)Hz
In the case of 50Hz and, say, 51Hz we would get after multiplying (50-51) Hz and (50+51) Hz or
1 Hz and 101 Hz. Just forget about the negative result for a moment and call it positive.
Should you pass this result through a low pass filter, designed to permit the 1 Hz product to pass but prevent nearly all of
the 101 Hz product, you will have a clean signal that represents the difference in PHASE of the two signals.

This result is then used in a control loop to change the slave output frequency so as to minimise the phase difference.
It's honestly rather similar to a PD control loop.

This seems simple enough, how to do it with a microcontroller...
We will feel the full force of the consequences of digital signal sampling from now on.
How I did it was as follows:
1 - sample both the sync signal and the slave signal at a certain rate. In this case it's 200 Hz.
2 - scale and offset the samples to be bipolar, eg the mid point of the sine wave samples will now be zero.
3 - multiply the two samples, and scale result by the loop gain.
4 - put this number into a decent low pass filter, to remove the high frequency product of the multiplication but let the low frequency parts through.
5 - Now use the filtered result and use a portion of it to correct the slave's output frequency
6 - We also need to correct the slave's output freq with a bit of the frequency error.
7 - go to 1

This is easy to implement in code. I will attach working code for both Arduino Uno and Due.
Particular details of the code:

step 1,2
Samples of the sync signal from the ADC range from 0 to 1024 (Uno) and 0 - 4096 (Due)
These need to be level shifted so the signal varies from (-1/2 ADC range) to (+1/2 ADC range) for the phase detection arithmetic
to work. This is easy to do and I code max and min tracking variables to give me the best level shift possible.

step 3,4
Time to do some floating point and even the Uno is fast enough!
The LP filter has to be given input with frequencies less than 1/2 the sample rate for it to work properly.
The sample rate is 200Hz so if we limit our system to have sync and slave frequencies of about 50Hz +/- 1 Hz we should be OK.
The street supply 50Hz is quite well regulated and only varies about +/- 0.12 Hz over the long term of a day or so.
This means the phase detector's LP filtered output will be a signal with frequencies something around DC to +/- 0.12 Hz.
We need to configure the LP filter to have near 100% removal of the 100Hz tone of the phase detector and I use my trusty
2 pole Butterworth biquad filter to achive this. (See previous posts of mine in this thread. I used the filter in my arduino uno inverter
controller to clean up the AC output feedback)
In my case I choose a filter cutoff of 0.02 with 200 Hz sample rate, this means the cutoff (-3db) is at 0.02 x 200 Hz or 4Hz
This is great since it lets the 0.0 to 0.12 Hz signal through without any loss but the 100Hz signal will be cut to less than -80db. (about 1/10,000th)

step 5
maintain a running total of the correction, which starts out at zero but can be any value positive or negative.
call it "plllint" for phase lock loop loop integral...
We need a measure of the frequency difference. Funnily enough, we can get the frequency difference between the sync and slave by looking at the
change in phase error with respect to time. So if you keep a copy of the phase error from last time in the pll code run, you will have the
frequency_difference = (current_phase_error - past_phase_error)/some_time_constant. And after this, let past_phase_error = current_phase_error, ready for the next
time through the loop.

step 6
I adjust plllint by adding a small part of the phase error and a small part of the frequency difference to it.
Then convert this error integral to integer, ready for altering the slave frequency generator clock rate.

That's about it. The Uno runs fine, doing the pll calculations in un-interruped time with ease. The Due is about 20x faster and has more complicated
programming for the timer interrupts.

How to obtain a suitable sample of the mains AC?
I use a 9V AC plug pack, AC couple it with a 1000uF 25V cap, put a diode across the output. On the cathode will be the positive waveform, the anode will be zero or ground.
I divide the voltage using a potentiometer. This works fine for me. I feed this into the ADC input. Search for "DC restorer"

How to obtain a suitable sample of the slave?
Really easy, since the slave output is a sine wave, read from a lookup table via a circular counter, just look up the current sine table value
that was last sent to the DAC.
One point to note is a PLL syncronises the slave to the sync with a 90 deg offset. So to get your slave in sync with zero offset,
you instead offset the slave value that is fed into the PLL loop by -90 deg.

It's all clear in the code.

For the application of getting mains sync with the EGS002, I would need to use the output of the PLL to control a voltage controlled oscillator which would be used to drive the EG8010 instead of the onboard crystal. And we need to get a 270 deg phase shift of the mains too.
But I plan to use my own systems in the inverters from now on.



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

#include <SPI.h>

const uint16_t PROGMEM si[201] = {
2047,2111,2175,2239,2303,2367,2430,2493,2556,2618,2679,2740,2800,2859,2918,2976,
3033,3089,3143,3197,3250,3301,3351,3400,3448,3494,3539,3582,3624,3664,3703,3740,
3775,3808,3840,3870,3899,3925,3950,3972,3993,4012,4029,4044,4057,4068,4077,4084,
4089,4092,4094,4092,4089,4084,4077,4068,4057,4044,4029,4012,3993,3972,3950,3925,
3899,3870,3840,3808,3775,3740,3703,3664,3624,3582,3539,3494,3448,3400,3351,3301,
3250,3197,3143,3089,3033,2976,2918,2859,2800,2740,2679,2618,2556,2493,2430,2367,
2303,2239,2175,2111,2047,1982,1918,1854,1790,1726,1663,1600,1537,1475,1414,1353,
1293,1234,1175,1117,1060,1004,950,896,843,792,742,693,645,599,554,511,
469,429,390,353,318,285,253,223,194,168,143,121,100,81,64,49,
36,25,16,9,4,1,0,1,4,9,16,25,36,49,64,81,
100,121,143,168,194,223,253,285,318,353,390,429,469,511,554,599,
645,693,742,792,843,896,950,1004,1060,1117,1175,1234,1293,1353,1414,1475,
1537,1600,1663,1726,1790,1854,1918,1982}; // sine wave lookup table

int sc;
float loopgain,plllc,pllint,pllout,oldplllc;
int pint,jmax,jmin,kmax,kmin;
int np,cc;
float aa[10],d1[10],d2[10],w0[10],w1[10],w2[10];
long lm;

void t1() // this is used to generate the slave output sine wave
{
pinMode(9, OUTPUT);
TCCR1A = _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1) | _BV(WGM11);
TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);
OCR1A = 10;
ICR1 = 1600; // 10 Khz, 200 step 50Hz, using a 2 x pi wave table
TIMSK1 |= (1 << TOIE1);
}


ISR(TIMER1_OVF_vect)
{
int a;
sc++;
if (sc > 199)
sc = 0;
a = pgm_read_word(&si[sc]);
write_DAC(a);
ICR1 = 1600 + pint; // important!! update slave clock with PLL correction
}

void write_DAC(int i)
{
// write to MCP4912 2 channel DAC. This is the SLAVE signal output
// 0 <= i <= 4096. the DAC expects 12 bit data.
uint8_t b,c;
cbi(PORTD,2); // cs low, enables tx into DAC serial port
b = 0x30 + (i >> 8); // send high 8 bits of channel 0
SPI.transfer(b);
c = (i & 0xff); // low 8 bits
SPI.transfer(c);
cbi(PORTD,3); // latch low, move data to DAC r2r ladder. DAC outputs now reflect two input 16 bit words.
sbi(PORTD,3); // latch high
sbi(PORTD,2); //cs high
}


void setup()
{
sc = 0;
SPI.setClockDivider(SPI_CLOCK_DIV2);
SPI.begin();
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
t1();
sbi(ADCSRA,ADPS2) ; // faster ADC conversions please
cbi(ADCSRA,ADPS1) ;
cbi(ADCSRA,ADPS0) ;

Serial.begin(115200);
Serial.println("");
Serial.println("");

np = 2;
loopgain = 0.001;
get_biquad(0.02);
lm = millis();
}

void loop()
{
while ( (millis() - lm) < 5) // 200Hz
;
lm = millis();
do_pll();
}


void do_pll()
{
float a,b,fj,fk,s;
int i,j,k;
// get 90 deg early sample of output sine. sine table is 200 samples per 2 pi cycle, so 150 samples = 1/4 wave early
i = sc + 149; // i = circular buffer counter + 1/4 full wave
if (i > 199) i = i - 199; // it will overflow, so wrap around if needed
j = pgm_read_word(&si) - 2048; // both snyc and slave are now assumed to be full scale -2048 <= input < 2048
// get sync signal
k = 4 * analogRead(0) - 2048; // i.e. bipolar
// phase detection and loop gain
plllc = (float)j * (float)k * loopgain;
// Butterworth biquad LP filter the phase. This filters out the high freq rubbish we don't want to feedback into the loop
s = plllc;
for(i=0; i<np; ++i)
{
w0 = d1*w1 + d2*w2 + s;
s = aa*(w0 + 2.0*w1 + w2);
w2 = w1;
w1 = w0;
}
plllc = s;
// concoct a special feedback value, to be used to adjust the slave sine wave frequency.
// maintain a running total of adjustments, each time adding a small fraction of the phase error
// and a small part of the frequency error. Frequency = the change of phase over time.
pllint += plllc * 0.0001 + (plllc - oldplllc)*0.01;
// convert to int for fast wave table clock updates
pint = (int)pllint;
// get something to plot for the humans to see so they think they are controlling this process.
String withScale = "-21 ";
withScale += pint;
withScale += " 21"; // use "serial plotter" to watch it sync up
Serial.println(withScale); // and follow mains variations
// need this for frequency calcs. (new phase error - old phase error)/time increment
oldplllc = plllc;
}

void get_biquad(float fc)
{
float s,r;
int i;
float a = tan(M_PI*fc);
float a2 = a*a;
for(i=0; i<np; ++i)
{
r = sin(3.141592926 * (2.0*i+1.0)/(4.0*np));
s = a2 + 2.0*a*r + 1.0;
aa = a2/s;
d1 = 2.0*(1-a2)/s;
d2 = -(a2 - 2.0*a*r + 1.0)/s;
}
}



Edited by poida 2017-09-13
wronger than a phone book full of wrong phone numbers