Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

[SOLVED] sPWM waveform distortion next to zero crossing

Status
Not open for further replies.

eduardoAvelar

Newbie level 6
Joined
Dec 11, 2017
Messages
11
Helped
0
Reputation
0
Reaction score
0
Trophy points
1
Location
MG, Brazil
Activity points
154
Hi all!

First of all, I want to tell that I've been facing this problem for a month and did a lot of things and search without any potential results.
DS1Z_QuickPrint1.jpg

I'm developing an sPWM voltage inverter using the AVR Atmega 328P with timer configuration.

There are two tables implemented. Table lookUp_1[] carries the values for the complementary side of the PWM and also implements the dead time for the lookUp_2[] that contain the active pulses to be applied at IR2110 Mosfet Drivers.

The sPWM was implemented using phase correct mode to maintain the pulses center aligned.

DS1Z_QuickPrint4.jpg
DS1Z_QuickPrint6.jpg

The other outputs (Yellow and fresh bold Blue) are just fundamental signals at 60Hz. The SPWM are applied on M1 and M3 (pink and blue waveforms)

DS1Z_QuickPrint13.jpg

I'm using the single topology for the inverter.

The control and power schematic, some photos, and algorithm are attached to this thread.

Esquema potencia.png
esquema controle.png
Code:

My main problem is with the shape of the waveform next to zero crossing.

Suggestions will be amazing.

Thanks.
Code:
#include <avr/io.h>
#include <avr/interrupt.h>

volatile static char theTCCR1A = 0b10110010; //varible for TCCR1A
int analogValue = 0;
int storageAnalogValue = 0;
volatile char cycle = 1;    // Variável que irá mudar de acordo com o ciclo para alterar o Dead-Time
volatile int value = 0;

// 200 values
int lookUp_1[] = {20, 24, 28, 31, 35, 39, 43, 46, 50, 54, 58, 61, 65, 69, 72, 76, 80, 83, 87, 91, 94, 98, 101, 105, 108, 112, 115, 119, 122, 126, 129, 132, 136, 139, 142, 145, 149, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 184, 187, 190, 192, 195, 198, 200, 202, 205, 207, 210, 212, 214, 216, 218, 221, 223, 225, 227, 228, 230, 232, 234, 236, 237, 239, 240, 242, 243, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 256, 257, 258, 258, 259, 259, 259, 260, 260, 260, 260, 260, 260, 260, 260, 260, 259, 259, 259, 258, 258, 257, 256, 256, 255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 243, 242, 240, 239, 237, 236, 234, 232, 230, 228, 227, 225, 223, 221, 218, 216, 214, 212, 210, 207, 205, 202, 200, 198, 195, 192, 190, 187, 184, 182, 179, 176, 173, 170, 167, 164, 161, 158, 155, 152, 149, 145, 142, 139, 136, 132, 129, 126, 122, 119, 115, 112, 108, 105, 101, 98, 94, 91, 87, 83, 80, 76, 72, 69, 65, 61, 58, 54, 50, 46, 43, 39, 35, 31, 28, 24};
int lookUp_2[] = {2, 4, 8, 11, 15, 19, 23, 26, 30, 34, 38, 41, 45, 49, 52, 56, 60, 63, 67, 71, 74, 78, 81, 85, 88, 92, 95, 99, 102, 106, 109, 112, 116, 119, 122, 125, 129, 132, 135, 138, 141, 144, 147, 150, 153, 156, 159, 162, 164, 167, 170, 172, 175, 178, 180, 182, 185, 187, 190, 192, 194, 196, 198, 201, 203, 205, 207, 208, 210, 212, 214, 216, 217, 219, 220, 222, 223, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 236, 237, 238, 238, 239, 239, 239, 240, 240, 240, 240, 240, 240, 240, 240, 240, 239, 239, 239, 238, 238, 237, 236, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 223, 222, 220, 219, 217, 216, 214, 212, 210, 208, 207, 205, 203, 201, 198, 196, 194, 192, 190, 187, 185, 182, 180, 178, 175, 172, 170, 167, 164, 162, 159, 156, 153, 150, 147, 144, 141, 138, 135, 132, 129, 125, 122, 119, 116, 112, 109, 106, 102, 99, 95, 92, 88, 85, 81, 78, 74, 71, 67, 63, 60, 56, 52, 49, 45, 41, 38, 34, 30, 26, 23, 19, 15, 11, 8, 4};

void setup() {
  // Register initilisation, see datasheet for more detail.
  // Timer Counter Control Register 1 A
  TCCR1A = theTCCR1A; // 0b10110010;
  /*10 Desliga OC1A (D9) no valor de comparação, Liga OC1A em 0V (Modo Não Invertido) Tabela 15-3 Datasheet
    11 Liga OC1B (D10) no valor de comparação, Desliga OC1B em 0V (Modo Invertido)  Tabela 15-3 Datasheet
    00 Bits 3 e 2 Não possuem função
    10 WGM11=1 e WGM10=0 for waveform 10 - Phase Correct mode - Garante o tempo morto
  */
  // Timer Counter Control Register 1 A
  TCCR1B = 0b00010001;
  /*000
    10 WGM13=1 e WGM12=0 Para a forma de onda 10 Phase Corretc PWM mode
    001 Sem preescale para o contador.
  */
  // Timer Interrupt Mask Register 1
  TIMSK1 = 0b00000001;
  /*0000000
    1 TOV1 Flag interrupt enable.
  */
  ICR1 = 333;
  sei();             // Enable global interrupts.

  // Set outputs pins.
  // Data Direction Register PORTB
  // 1 -> Saída
  // 0 -> Entrada
  // Configura os pinos PB1(D9), PB2 (D10), PB3 (D11), PB4 (D12) como saidas
  DDRB   = 0b00011110;
  // Inicialização das saídas
  PORTB |= 0b00001000; // Escreve HIGH no PB3 (D11) sem afetar os outros pinos
  PORTB &= 0b11101111; // Escreve LOW no bit  PB4 (D12) sem afetar os outros pinos
}

void loop() {

  value = (analogRead(A0) / 10);
}

// Interrupt Service Routine
ISR(TIMER1_OVF_vect) {
  static int num;
  static int i;
  static int delay1;
  static char trig;

  if (num >= 200) {
    TCCR1A ^= 0b01010000;

    if (cycle) {
      OCR1A = lookUp_2[192] + value; // PIN D9 (2HO) -> Canal 4 (AZUL ESCURO) do Ciclo 1, onde Amarelo está em ON e Azul Escuro Modula Junto ao Amarelo. Rosa é o complemento
      OCR1B = lookUp_1[num] + value ; // PIN D10 (2LO) -> Canal 3 (ROSA) do cilco 1, onde Azul claro está em OFF e Rosa é o complemento do Azul escuro
      //num ++;
    } else {
      OCR1A = lookUp_1[num] + value ; // PIN D9 (2HO) -> Canal 4 (AZUL ESCURO) do ciclo 2 onde Amarelo está em OFF e é o complemento de ROSA
      OCR1B = lookUp_2[192] + value; // PIN D10 (2LO) -> Canal 3 (ROSA) do cliclo 2, onde Azul Claro está em ON e ROSA modula junto com Azul Claro.
      //num ++;
    }

    // Delay  for 2000 entries on table
    // Just to sincronize
    for ( int i = 0; i <= 43; i++ ) {
      asm volatile ("nop");
    }

    if (cycle) {
      PORTB ^= 0b00001000;  //Faz um togle no pino PB3 (D11) e PB4 (D12) Sem afetar os outros Pinos
      for ( int i = 0; i <= 6; i++ ) {
        asm volatile ("nop");
      }
      PORTB ^= 0b00010000;  //Faz um togle no pino PB3 (D11) e PB4 (D12) Sem afetar os outros Pinos
    } else {
      PORTB ^= 0b00010000;  // Faz um togle no pino PB3 (D11) e PB4 (D12) Sem afetar os outros Pinos
      for ( int i = 0; i <= 6; i++ ) {
        asm volatile ("nop");
      }
      PORTB ^= 0b00001000;  // Faz um togle no pino PB3 (D11) e PB4 (D12) Sem afetar os outros Pinos
    }
    cycle  ^= 0b00000001;
    num = 0;
  }

  if (cycle) {
    OCR1A = lookUp_2[num] + value; // PIN D9 (2HO) -> Canal 4 (AZUL ESCURO) do Ciclo 1, onde Amarelo está em ON e Azul Escuro Modula Junto ao Amarelo. Rosa é o complemento
    OCR1B = lookUp_1[num] + value ; // PIN D10 (2LO) -> Canal 3 (ROSA) do cilco 1, onde Azul claro está em OFF e Rosa é o complemento do Azul escuro
    num ++;
  } else {
    OCR1A = lookUp_1[num] + value ; // PIN D9 (2HO) -> Canal 4 (AZUL ESCURO) do ciclo 2 onde Amarelo está em OFF e é o complemento de ROSA
    OCR1B = lookUp_2[num] + value; // PIN D10 (2LO) -> Canal 3 (ROSA) do cliclo 2, onde Azul Claro está em ON e ROSA modula junto com Azul Claro.
    num ++;
  }
}
 

Hi,

The values of lookUp_1 are DC shifted by a value (about 18) with respect to the values of lookUp_2.
You output the values to OCR1A and OCR1B
But depending on "cycle" you interchange OCR1A with OCR1B .... thus... the values for OCR1x will jump..
Isn't it expectable that this causes some jump in the resulting waveform?

Btw: OCRx registers seem to be single byte registers ... with a valid range of 0...255 ...but lookUp_1 values go up to 260, which is beyond the valid range. How can this work?

Klaus

Added:
I recommend to use Excel to "simulate" what your OCR1x values look like.
Then let Excel draw a chart.
It should be done within a couple of minutes.

Klaus
 
The zero crossing discontinuity is mainly caused by the huge dead time (> 1 us) in your pwm pattern and the respective magnitude offset in the tables. Useful MOSFET deadtime is in a 10 ns to 100 ns range. You have asymmetrical gate driver which possibly allows down to zero deadtime.

Using an unipolar pwm pattern which applies pwm to all four switches will probably improve the zero crossing because it has 50% duty cycle for zero output.
 
Hi KlausST!

Thanks for your kindly answer!

The values of lookUp_1 are DC shifted by a value (about 18) with respect to the values of lookUp_2.
You output the values to OCR1A and OCR1B
But depending on "cycle" you interchange OCR1A with OCR1B .... thus... the values for OCR1x will jump..
Isn't it expectable that this causes some jump in the resulting waveform?

These tables are used to store the values for the duty cycle applied to MOSFET drivers. They are changed dynamically as the counter of the time overflows inside the ISR(TIMER1_OVF_vect) routine.

I'm interchanged the OCR1A and OCR1B when the counter reaches the table limit value, in this case, 200 iterations for the ICR1 = 333. So, I'm changing each semi-cycle of the sinusoidal waveform.

Btw: OCRx registers seem to be single byte registers ... with a valid range of 0...255 ...but lookUp_1 values go up to 260, which is beyond the valid range. How can this work?

In this case, the size of timer 1 of the ATMEGA 328P is 16 bit so the OCRx register can store a 16 bit value.

About the tables, only the lookUp_2[] is effectively applying the duty cycle to the MOSFETs at each semi cycle. As you said, when the semi cycle changes, we change the output and the int lookUp_2[] is the same for the other output also.

The int lookUp_1[] is just the complementary side of int lookUp_2[] for this kind of modulation type is named unipolar modulation. The picture below shows the result of lookUp_2[], in blue, and lookUp_1[] in pink. The dead time is also shown.

1DS1Z_QuickPrint8.jpg

The picture below shows an overview modulation waveform.

DS1Z_QuickPrint20.jpg

So, I'm guessing doing this in the right way as the pulses are applied based on the lookUp_2[] that starts with a little value.

If you guys have more suggestions, I'll be glad.

Thanks.

- - - Updated - - -

Hi FvM,

I have also tested this design with almost no dead time and the results were basically the same at zero crossing.

I thought more dead time represents more safety for MOSFETs operation as the effective work is done by values inside lookUp_2 [].

I'll also test switch four MOSFETs as you said and post here the results.

By the way, I've seen a strange waveform at the output of the microcontroller when the prototype inverts starts using a battery or power supply. The picture below shows this. Is that interference applied to the oscilloscope or is this effectively contributing to the waveform shape problem?

DS1Z_QuickPrint7.jpg

When only microcontroller is on, the output is like this:

DS1Z_QuickPrint13.jpg

Just to refresh, yellow waveform works together with the dark blue and the light blue works with the pink waveform depending basically on which semi cycle is on course.

Later I'll post the results that you sugested.

Thanks.
 

Hi everyone,

Thanks for the reply!

Now I have this clear waveform shape.

DS1Z_QuickPrint21.jpg

I've just adjusted the dead time for 200ns as you guys said.

DS1Z_QuickPrint15.jpg

DS1Z_QuickPrint16.jpg

And in the zero crossing, the modulation looks like this:

DS1Z_QuickPrint17.jpg

DS1Z_QuickPrint18.jpg

Now I have another doubt: When I'm trying to adjust the amplitude of the waveform using a potentiometer attached to analog channel 0, I'm having the waveform shape indicated below.

DS1Z_QuickPrint22.jpg

I know that I'm just adjusting the duty cycle by adding some values from the Analog Channel.

What could be the cause of this issue?

Do you guys have some suggestion for me about the correction and adjustment of the amplitude of the waveform?

Thanks in advance.
Eduardo.
 

At a glance I could not find any potentiometer at the above schematic, anyway is there any capacitor (e.g ceramic) in parallel with the pot cutting of high frequencies due to contact glitches?
 
Hi André!

Thanks for the reply.

I've not updated the schematic for the potentiometer but is just a pot attached to Analog 0 without any special component.

Inside the void loop() we can see the value been getting and storage in the value variable. It is just a test for a kind of amplitude correction.

Is there necessary to put a ceramic cap in parallel with the Analog input?

I've designed other kinds of stuff using the analog channel and just a moving average to filter.

Thanks
 

Hi,

Is there necessary to put a ceramic cap in parallel with the Analog input?

I've designed other kinds of stuff using the analog channel and just a moving average to filter.
Nyquist applies to every ADConversion.
This means, if there is higher frequency noise than fs/2, then alias frequencies will be generated.
The alias frequency range is from DC to fs/2. Thus you have no chance to filter out all alias frequencies on the digital side.
--> use an anti aliasing filter to get more stable signal at the digital side.
Often this is just a C, or an RC .... is this too much effort?

******
You know your voltage scheme is "dirty" because it just adds DC to each half wave and generates exctly the waveform you are worried in post#1.
Depending on filter this "step" may cause ringing.
--> do the clean amplitude regulation by multiplying the PWM values.

Klaus
 
Hi KlausST,

I've just added an RC Low pass filter with a ceramic Cap about 200nF and a resistor with 10K Ohms as Atmega 328 datasheet recommends input impedance lower than this for analog channels.

The waveform glitches are the same as without filter.

--> do the clean amplitude regulation by multiplying the PWM values.

What do you mean with "multiplying the PWM values"?

At the moment, I'm just getting the values of the conversion and adding that at all values of the table.

Thanks.
 

Hi,

What do you mean with "multiplying the PWM values"?

At the moment, I'm just getting the values of the conversion and adding that at all values of the table.
You didn't do the Excel calculations yet.
If you did, you know (see) what I mean.

You currrently do sonething like this:
OCRx = tablevalue + ADCvalue

But you should do:
OCRx = tablevalue x ADCvalue

More precise:
OCRx = tablevalue x ( 1+ 0.2 × ADCvalue / 1024)
(This is the formula for a 0%...+20% amplitude adjust for a 10 bit ADC)

But on a microcontroller you try to avoid division and you need to limit the values thus one may optimize:
* Your table values currently are 0..260, this is 9 bits wide.But you only have an 8 bit hardware multiplier. Thus I recommend to shift them 1 bit right. Now you get only half of the value: 0..130 (one byte)
* Your ADC values are 10 bit. I recommend to use "left aligned mode" and just take the upper byte for further calculations. Range: 0..255.
Now you can multiply the (8 bit) ADCvalue with the (8bit) tablevalue...you get a 16 bit result. (Range: 0...33150)
Take the upper byte: (range 0..129) and shift it one bit right. (The range now is 0...64)
Now add this value to the initial table value.
..and move this value to the OCR register.
Finished

This results in a 100% to 125% amplitude adjust according ADC input.

With inline assembler this may take just about 10 clock cycles. On an 8MHz AVR less than 1.5us

Klaus
 
Hi everyone!

* Your table values currently are 0..260, this is 9 bits wide.But you only have an 8 bit hardware multiplier. Thus I recommend to shift them 1 bit right. Now you get only half of the value: 0..130 (one byte)
* Your ADC values are 10 bit. I recommend to use "left aligned mode" and just take the upper byte for further calculations. Range: 0..255.
Now you can multiply the (8 bit) ADCvalue with the (8bit) tablevalue...you get a 16 bit result. (Range: 0...33150)
Take the upper byte: (range 0..129) and shift it one bit right. (The range now is 0...64)
Now add this value to the initial table value.
..and move this value to the OCR register.

KlausST, I've done every part as described from you and works just Fine!

Thanks a lot for your tips.

Below is the part of the code suggested.

Code:
void setupADC() {
  DDRC   = 0x00;         // Make ADC port as input

  ADMUX = (1 << REFS0) | // Select reference voltage as AVcc
          (1 << ADLAR) | // Select Left Adjust Mode (8 bits)
          (1 << MUX0)  | // Select Bit 0 for ADC5
          (1 << MUX2);   // Select bit 2 for ADC5

  ADCSRA = (1 << ADEN)  | // Enables de ADC
           (1 << ADIE)  | // Enables the ADC interrup routine
           (1 << ADPS0) | // Set the Preescaler bit S0 for 128 division factor
           (1 << ADPS1) | // Set the Preescaler bit S1 for 128 division factor
           (1 << ADPS2);  // Set the Preescaler bit S3 for 128 division factor

  DIDR0 = (1 << ADC5D);   // Disable the digital input buffer for this specific AD5
  startConversion();
}

void startConversion() {
  ADCSRA |= (1 << ADSC);  // Start single conversion mode of the ADC
}

// ADC Interrupt Service Routine
ISR(ADC_vect) {
  valueADC = ADCH;
  startConversion();
}

// Timer Interrupt Service Routine
ISR(TIMER1_OVF_vect) {
  valueAdjusted_1 = (valueADC * lookUp_3[num]);         // Take the value
  valueAdjusted_1 = (valueAdjusted_1 & 0xFF00) >> 8;    // Take the high side of the int
  valueAdjusted_1 =  valueAdjusted_1 >> 1;              // shift them 1 bit right
  valueAdjusted_1 =  valueAdjusted_1 + lookUp_2[num];   // Add the new value to the original table

  valueAdjusted_2 = (valueADC * lookUp_4[num]);         // Take the value
  valueAdjusted_2 = (valueAdjusted_2 & 0xFF00) >> 8;    // Take the high side of the int
  valueAdjusted_2 =  valueAdjusted_2 >> 1;              // shift them 1 bit right
  valueAdjusted_2 =  valueAdjusted_2 + lookUp_1[num];   // Add the new value to the original table

  if (num >= 200) {
    TCCR1A ^= 0b01010000; // Toggle the way timer works doing an XOR operation


    if (cycle) {
      OCR1A = valueAdjusted_1; // PIN D9 (2HO) -> Channel 4 (DARK BLUE)
      OCR1B = valueAdjusted_2; // PIN D10 (2LO) -> Channel 3 (PINK)
    } else {
      OCR1A = valueAdjusted_2; // PIN D9 (2HO) -> Channel 4 (DARK BLUE)
      OCR1B = valueAdjusted_1;  // PIN D10 (2LO) -> Channel 3 (PINK)
    }

    // Delay  for 200 entries on table
    // Just to sincronize
    for ( int i = 0; i <= 27; i++ ) {
      asm volatile ("nop");
    }
    asm volatile ("nop");
    asm volatile ("nop");
    asm volatile ("nop");

    // This way we can garantee a "dead time" between pin toggle
    if (cycle) {
      PORTB ^= 0b00001000;  //Toggle PB3 pin (D11) without afect others pins
      PORTB ^= 0b00010000;  //Toggle PB4 pin (D12) without afect others pins
    } else {
      PORTB ^= 0b00010000;  // Toggle PB4 pin (D12) without afect others pins
      PORTB ^= 0b00001000;  // Toggle PB3 pin (D11) without afect others pins
    }
    cycle  ^= 0b00000001;   // Toggle cycle variable to define with semi cycle is on course
    num = 0;                // Resets num variable
  }

  if (cycle) {
    OCR1A = valueAdjusted_1;  // PIN D9 (2HO) -> Channel 4 (DARK BLUE)
    OCR1B = valueAdjusted_2; // PIN D10 (2LO) -> Channel 3 (PINK)
  } else {
    OCR1A = valueAdjusted_2;  // PIN D9 (2HO) -> Channel 4 (DARK BLUE)
    OCR1B = valueAdjusted_1;  // PIN D10 (2LO) -> Channel 3 (PINK)
  }
  num ++;
}

Nyquist applies to every ADConversion.

Also, I've just added a low pass filter at the input of this analog channel and the signal became more stable.

Now, I have another doubt: When the time reaches 200 counts, I change the state of pins PB3 and PB4 by XOR operation. Before this, I also change the way of OCRx works (Non inverting and inverting mode) by another XOR as we can see here: TCCR1A ^= 0b01010000.

if (cycle) {
OCR1A = valueAdjusted_1; // PIN D9 (2HO) -> Channel 4 (DARK BLUE)
OCR1B = valueAdjusted_2; // PIN D10 (2LO) -> Channel 3 (PINK)
} else {
OCR1A = valueAdjusted_2; // PIN D9 (2HO) -> Channel 4 (DARK BLUE)
OCR1B = valueAdjusted_1; // PIN D10 (2LO) -> Channel 3 (PINK)
}

// Delay for 200 entries on table
// Just to sincronize
for ( int i = 0; i <= 27; i++ ) {
asm volatile ("nop");
}
asm volatile ("nop");
asm volatile ("nop");
asm volatile ("nop");

he lines above were used to synchronize all changes at the zero cross.

If I don't do that, the output waveform of the microcontroller is like in the picture below, as pink and dark blue are the OCRx.

DS1Z_QuickPrint25.jpg

With the modification, the output is synchronized like this:

DS1Z_QuickPrint28.jpg

So, It looks like a buffer of the OCRx.

I need to "delay" the OCR using "asm volatile ("nop");" witch is a lost of macine cycles.

Is that another way to sincronize the change of the pins PB3 and PB4 at the same time with OCRA and OCRB?

Thanks in advance.
 

Status
Not open for further replies.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top