How to change frequency of 3 phase PWM signal output

hi buddies…
i have try this coding on arduino uno to generate 3 phase pwm with 120 degree phase shift using oscilloscope (picture in attachment). i try to increase the frequency but it only stay at 25khz . Can anybody show how to increase the frequency to 50khz?

byte phaseCount;
unsigned long currentMicros;
unsigned long previousMicros;
unsigned long elapsedMicros;
void setup(){
pinMode (8, OUTPUT);
pinMode (9, OUTPUT);
pinMode (10, OUTPUT);
currentMicros = micros();
previousMicros = currentMicros;
}

void loop() {
currentMicros = micros();
elapsedMicros = currentMicros - previousMicros;
if (elapsedMicros >= 1664)
phaseCount = phaseCount +1;
if (phaseCount == 6){ phaseCount = 0; }
switch (phaseCount) {
case 0:
PORTB = 0b00000001; // pins x-x-13-12-11-10-9-8
break;
case 1:
PORTB = 0b00000001;
break;
case 2:
PORTB = 0b00000010;
break;
case 3:
PORTB = 0b00000010;
break;
case 4:
PORTB = 0b00000100;
break;
case 5:
PORTB = 0b00000100;
break;
}
}

3phasepwm.png

I bet that with the line below, the frequency should be twice :

if (elapsedMicros >= 832) { // 1664/2

phaseCount++;
previousMicros = micros();
}

And if frequency is not 50 KHz, adjust elaspsedMicros threshold accordingly.

You should post the really used code. What you presented does not match the diagram.

DrDiettrich:
You should post the really used code. What you presented does not match the diagram.

Sorry, little typo. this is the coding that i use for 3 phase pwm with 120 degree phase shift. i also have adjust elapsedMicros value but the frequency only stay at 25khz just like the picture (in attachment).

byte phaseCount;
unsigned long currentMicros;
unsigned long previousMicros;
unsigned long elapsedMicros;
void setup(){
pinMode (8, OUTPUT);
pinMode (9, OUTPUT);
pinMode (10, OUTPUT);
currentMicros = micros();
previousMicros = currentMicros;
}

void loop() {
currentMicros = micros();
elapsedMicros = currentMicros - previousMicros;
if (elapsedMicros >= 1664)
phaseCount = phaseCount +1;
if (phaseCount == 6){ phaseCount = 0; }
switch (phaseCount) {
case 0:
PORTB = 0b00000101; 
break;
case 1:
PORTB = 0b00000001;
break;
case 2:
PORTB = 0b00000011;
break;
case 3:
PORTB = 0b00000010;
break;
case 4:
PORTB = 0b00000110;
break;
case 5:
PORTB = 0b00000100;
break;
}
}

3phasepwm.png

azeyzoul:
i also have adjust elapsedMicros value

No, you don't as I told you in reply #1

ard_newbie:
No, you don't as I told you in reply #1

i have try it. but the frequency remain 25khz.

byte phaseCount;
unsigned long currentMicros;
unsigned long previousMicros;
unsigned long elapsedMicros;
void setup(){
pinMode (8, OUTPUT);
pinMode (9, OUTPUT);
pinMode (10, OUTPUT);
currentMicros = micros();
previousMicros = currentMicros;
}

void loop() {
currentMicros = micros();
elapsedMicros = currentMicros - previousMicros;
if (elapsedMicros >= 832)
phaseCount = phaseCount +1;
if (phaseCount == 6){ phaseCount = 0; }
switch (phaseCount) {
case 0:
PORTB = 0b00000101; 
break;
case 1:
PORTB = 0b00000001;
break;
case 2:
PORTB = 0b00000011;
break;
case 3:
PORTB = 0b00000010;
break;
case 4:
PORTB = 0b00000110;
break;
case 5:
PORTB = 0b00000100;
break;
}
}

Try the snippet below, should be close to 3 PWM, 50KHz, 33% Duty cycle, 120° phase shift :

/***     3 PWM  50 KHz , 33% Duty cycle , 120° Phase shift     ******/

/*  macro NOPX  */

__asm__ __volatile__(
  ".macro NOPX  P1, P2          \n\t"
  ".rept &P1                    \n\t"   // adjust P1
  ".rept &P2                    \n\t"   // adjust P2
  " NOP                         \n\t"   // (P1 * P2) NOP
  ".endr                        \n\t"   // End of Repeat P2
  ".endr                        \n\t"   // End of Repeat P1
  ".endm                        \n\t"   // End of macro
);


void setup() {
  //         Pins 11,    12   and     13 in Output
  DDRB |= (1 << DDB3) | (1 << DDB4) | (1 << DDB5);

}

void loop() {

  /*  To set precisely a delay, several tools:
       _NOP(); for 1 NOP = 62.5 ns , 16 NOP = 1 us
       __asm__ __volatile__("NOPX 150 , 5");  for 150*5 = 750  NOP
       Delay to adjust with delay1, delay2 inside the function
  */

  while (true) {

    PINB |= (1 << PINB3);                         // PINB3 up
    Delay();

    PINB |= (1 << PINB3) | (1 << PINB4);          // PINB3 down PINB4 up
    Delay();

    PINB |= (1 << PINB4) | (1 << PINB5);          // PINB4 down PINB5 up
    Delay();

    PINB |= (1 << PINB5);                         // PINB5 down
  }


}

void Delay(void) {

  // Equals "more or less" to (65535 - delay1) * delay2 NOP
  __asm__ __volatile__ (

    ".set delay1, 60210              \n\t"  // ***** minimum 0  - maximum 65535
    ".set delay2, 2                  \n\t"  // ***** minimum 1  - maximum 255

    "LDI    r16, delay2              \n\t"
    "outer_loop:                     \n\t"
    "LDI    r24, lo8(delay1)         \n\t"
    "LDI    r25, hi8(delay1)         \n\t"
    "delay_loop:                     \n\t"
    "ADIW   r24, 1                   \n\t"
    "BRNE   delay_loop               \n\t"
    "DEC    r16                      \n\t"
    "BRNE   outer_loop               \n\t"
    "RET                             \n\t"
  );

}

Such signals can be created entirely in hardware, using an Arduino Mega. That controller has 3 output channels per timer, which can be programmed for an individual phase shift, in timer CTC mode and toggle output pin on compare match.

ard_newbie:
Try the snippet below, should be close to 3 PWM, 50KHz, 33% Duty cycle, 120° phase shift :

/***     3 PWM  50 KHz , 33% Duty cycle , 120° Phase shift     ******/

/*  macro NOPX  */

asm volatile(
  “.macro NOPX  P1, P2          \n\t”
  “.rept &P1                    \n\t”  // adjust P1
  “.rept &P2                    \n\t”  // adjust P2
  " NOP                        \n\t"  // (P1 * P2) NOP
  “.endr                        \n\t”  // End of Repeat P2
  “.endr                        \n\t”  // End of Repeat P1
  “.endm                        \n\t”  // End of macro
);

void setup() {
  //        Pins 11,    12  and    13 in Output
  DDRB |= (1 << DDB3) | (1 << DDB4) | (1 << DDB5);

}

void loop() {

/*  To set precisely a delay, several tools:
      _NOP(); for 1 NOP = 62.5 ns , 16 NOP = 1 us
      asm volatile(“NOPX 150 , 5”);  for 150*5 = 750  NOP
      Delay to adjust with delay1, delay2 inside the function
  */

while (true) {

PINB |= (1 << PINB3);                        // PINB3 up
    Delay();

PINB |= (1 << PINB3) | (1 << PINB4);          // PINB3 down PINB4 up
    Delay();

PINB |= (1 << PINB4) | (1 << PINB5);          // PINB4 down PINB5 up
    Delay();

PINB |= (1 << PINB5);                        // PINB5 down
  }

}

void Delay(void) {

// Equals “more or less” to (65535 - delay1) * delay2 NOP
  asm volatile (

“.set delay1, 60210              \n\t”  // ***** minimum 0  - maximum 65535
    “.set delay2, 2                  \n\t”  // ***** minimum 1  - maximum 255

“LDI    r16, delay2              \n\t”
    “outer_loop:                    \n\t”
    “LDI    r24, lo8(delay1)        \n\t”
    “LDI    r25, hi8(delay1)        \n\t”
    “delay_loop:                    \n\t”
    “ADIW  r24, 1                  \n\t”
    “BRNE  delay_loop              \n\t”
    “DEC    r16                      \n\t”
    “BRNE  outer_loop              \n\t”
    “RET                            \n\t”
  );

}

the duty cycle can be change to 50%?

Yes, it can be changed to any duty cycle.

Draw your 3 PWM waveforms (50 KHz frequency, 50% Duty cycle, 120° phase shift), time each event (PINB3 up/down, PINB4 up/down, PINB5 up/down) in term of fraction of the PWM period since Time = 0 until you see a loop. You have got 11 events before you can loop your code.

Count the number of clock cycles in an entire PWM period, then write your code accordingly.

Because you will need different parameters to fill in the Delay() function, it will be handy to use a macro for Delay, like I did for NOPX :

/*  macro Delay  */
// Equals "more or less" to (65535 - P1) * P2 NOP
__asm__ __volatile__ (
  ".macro Delay  P1, P2       \n\t"  // 1<= P1 <= 255  , 0<= P2 <= 65535
  "LDI    r16, &P1            \n\t"
  "1:                         \n\t"
  "LDI    r24, lo8(&P2)       \n\t"
  "LDI    r25, hi8(&P2)       \n\t"
  "2:                         \n\t"
  "ADIW   r24, 1              \n\t"
  "BRNE   2b                  \n\t"
  "DEC    r16                 \n\t"
  "BRNE   1b                  \n\t"
  ".endm                      \n\t"   // End of macro
);

its okay, i will use this coding to generate 3 phase, 120 degree phase shift.

byte phaseCount;
unsigned long currentMicros;
unsigned long previousMicros;
unsigned long elapsedMicros;
void setup(){
pinMode (8, OUTPUT);
pinMode (9, OUTPUT);
pinMode (10, OUTPUT);
currentMicros = micros();
previousMicros = currentMicros;
}

void loop() {
currentMicros = micros();
elapsedMicros = currentMicros - previousMicros;
if (elapsedMicros >= 832)
phaseCount = phaseCount +1;
if (phaseCount == 6){ phaseCount = 0; }
switch (phaseCount) {
case 0:
PORTB = 0b00000101; 
break;
case 1:
PORTB = 0b00000001;
break;
case 2:
PORTB = 0b00000011;
break;
case 3:
PORTB = 0b00000010;
break;
case 4:
PORTB = 0b00000110;
break;
case 5:
PORTB = 0b00000100;
break;
}
}

anyone can show how to use variable resistor to control duty cycle of the signal?

A resistor cannot do that.

DrDiettrich:
A resistor cannot do that.

it can by adding analogRead() of a pin at the top of loop(), and then change the amount of high time and low time based on the analog value.But, i dont know how to add the coding.

Please draw and present a diagram of the output signals of a different duty cycle, how you think that they should look like.

Here’s one way.
Right now there is one switch case to create a 3-phase 50% duty cycles.

switch (phaseCount) {
case 0:
PORTB = 0b00000101; 
break;
case 1:
PORTB = 0b00000001;
break;
case 2:
PORTB = 0b00000011;
break;
case 3:
PORTB = 0b00000010;
break;
case 4:
PORTB = 0b00000110;
break;
case 5:
PORTB = 0b00000100;
break;
}

If you change those, you can change the duty cycle, see the picture for some examples.
3 phase patterns.jpg
At the top of loop, read the voltage on a pot wiper and decide which set of phases to run.

void loop(){
analogLevel = analogRead(A0);
if (analogLevel <=100){
phases = 0;
}
if (analogLevel >100){
phases = 1;
}
if (analogLevel >200){
phases = 2;
}
if (analogLevel >300){
phases = 3;
}
if (analogLevel >400){
phases = 4;
}
if (analogLevel >500){
phases = 5;
}
// then the time capture stuff from before
currentMicros = micros();
elapsedMicros = currentMicros - previousMicros;
if (elapsedMicros >= 832)
phaseCount = phaseCount +1;
if (phaseCount == 6){ phaseCount = 0; }
// now decide which set of phases to use:
switch (phases){
case 0:
  switch (phaseCount) {
  case 0:
  PORTB = 0b00000101; 
  break;
  case 1:
  PORTB = 0b00000001;
  break;
  case 2:
  PORTB = 0b00000011;
  break;
  case 3:
  PORTB = 0b00000010;
  break;
  case 4:
  PORTB = 0b00000110;
  break;
  case 5:
  PORTB = 0b00000100;
  break;
  }
break;
case 1:
  // data for 33 % waveforms
  :
  :
  break;
}
case 2:
  // data for 16% waveforms
  :
  :
  break;
    } // end case 5
  } // end phases select
} // end loop

( }s at the end may need some tweaking to keep the syntax lined up)
Disadvantage - you’re stuck with the 6 time periods for the waveforms that can be created. Some of the earlier suggestions provide more flexibility, if you can follow what they are doing. Not exactly beginner-ish level coding.
Advantage - easy to follow, you just need to draw the pattern you want and then fill in the 1s & 0s in the various switch cases.
You can see the setup starts getting repetitive, this might lend itself to pulling the data from arrays.

This will also limit the speed as analogRead() takes ~100uS. With 1/6 of a waveform taking 832uS, you wouldn't notice it. As that time shrank, the 100uS becomes the limit, so 1/300uS = 3.3KHz is about the fastest you could achieve.
If you put the analogRead in setup() instead, you could gain the frequency range back, but a reset would be needed after a pot adjustment so the analogRead would occur again.

CrossRoads:
This will also limit the speed as analogRead() takes ~100uS. With 1/6 of a waveform taking 832uS, you wouldn't notice it. As that time shrank, the 100uS becomes the limit, so 1/300uS = 3.3KHz is about the fastest you could achieve.
If you put the analogRead in setup() instead, you could gain the frequency range back, but a reset would be needed after a pot adjustment so the analogRead would occur again.

Thanks very much Sir CrossRoads, i will try it..

@CrossRoads thanks for the drawing, but I doubt that such signals are what’s really required. E.g. a BLDC motor will stop as soon as the phases don’t overlap any more, i.e. below 30% or above 70%.

For the ADC timing, it’s possible to start a conversion asynchronously, and wait for its completion. This will not block the wave generation, is not slower than analogRead(), and certainly fast enough for tracking the changes of a manually adjusted pot.