3 Phase SPWM(sinusoidal PWM waveforms using the Arduino) to fed 3 Phase Inverter

Greetings to all,
i am not able to generate 3Phase SPWM. Also I need to have control over frequency of output waveform from 40Hz to 800Hz.
Code Im using is given below

// Look Up table of a single sine period divied up into 256 values.
// Max PWM value is 99
const uint8_t sine256[256] PROGMEM =
{
  50,  51, 52, 53, 54, 56, 57, 58,
  59, 60, 62, 63, 64, 65, 66, 67,
  69, 70, 71, 72, 73, 74, 75, 76,
  77, 78, 79, 80, 81, 82, 83, 84,
  85, 85, 86, 87, 88, 89, 89, 90,
  91, 91, 92, 93, 93, 94, 94, 95,
  95, 96, 96, 97, 97, 97, 98, 98,
  98, 98, 99, 99, 99, 99, 99, 99,
  99, 99, 99, 99, 99, 99, 98, 98,
  98, 98, 97, 97, 97, 96, 96, 96,
  95, 95, 94, 94, 93, 92, 92, 91,
  90, 90, 89, 88, 87, 87, 86, 85,
  84, 83, 82, 81, 81, 80, 79, 78,
  77, 76, 75, 73, 72, 71, 70, 69,
  68, 67, 66, 65, 63, 62, 61, 60,
  59, 57, 56, 55, 54, 53, 51, 50,
  49, 48, 46, 45, 44, 43, 42, 40,
  39, 38, 37, 36, 34, 33, 32, 31,
  30, 29, 28, 27, 26, 24, 23, 22,
  21, 20, 19, 18, 18, 17, 16, 15,
  14, 13, 12, 12, 11, 10, 9,  9,
  8,  7,  7,  6,  5,  5,  4,  4,
  3,  3,  3,  2,  2,  2,  1,  1,
  1,  1,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  1,  1,
  1,  1,  2,  2,  2,  3,  3,  4,
  4,  5,  5,  6,  6,  7,  8,  8,
  9,  10, 10, 11, 12, 13, 14, 14,
  15, 16, 17, 18, 19, 20, 21, 22,
  23, 24, 25, 26, 27, 28, 29, 30,
  32, 33, 34, 35, 36, 37, 39, 40,
  41, 42, 43, 45, 46, 47, 48, 50
};

// Output pins on UNO or Nano
int PWM1 = 3;  // Phase 1 = OC2B
int PWM2 = 9;  // Phase 2 = OC1A
int PWM3 = 10; // Phase 3 = OC1B

const int Phase2Offset = 256 / 3; // 120° phase shift
const int Phase3Offset = (256 * 2) / 3; // 240° phase shift

double OutputFrequency = 50.0;
const double CarrierFrequency = 10000.0;    // Carrier frequency in Hz

volatile unsigned long TuningValue;  // dds tuning word m, refer to DDS_calculator (from Martin Nawrath) for explination.


// Timer 1 setup
// WGM=8 Phase/Frequency Correct PWM with TOP in ICR1
// To get 10 kHz we need TOP = (16 MHz / 10 kHz / prescale / 2) - 1 = (1600/8/2) - 1 = 99
// Note: We have to use the same TOP on the 8-bit Timer2 so we can't use prescale=1 (799 won't fit in 8 bits)
void Setup_timer1(void)
{
  TCCR1B = 0;  // Stop the counter (clock select = 0)
  TCCR1A = 0;  // Clear the register
  TIMSK1 = 0;  // Disable all Timer1 interrupts

  ICR1 = 99; // 10 kHz with prescale = 8;

  TCCR1A |= _BV(COM1A1) | _BV(COM1B1); // Enable PWM on A and B

  TCCR1B |= _BV(WGM13);  // WGM = 8
  TCCR1B |= _BV(CS11);   // Prescale = 8
}

// Timer 2 setup
// WGM=5 Phase Correct PWM with TOP in OCR2A
// To get 10 kHz we need TOP = (16 MHz / 10 kHz / prescale / 2) - 1 = (1600/8/2) - 1 = 99
void Setup_timer2()
{
  TCCR2B = 0;  // Stop the counter (clock select = 0)
  TCCR2A = 0;  // Clear the register
  TIMSK2 = 0;  // Disable all Timer2 interrupts

  OCR2A = 99; // 10 kHz with prescale = 8;

  TCCR2A |= _BV(COM1B1); // Enable PWM on B only. (OCR2A holds TOP)

  TCCR2A |= _BV(WGM20);  // WGM = 5
  TCCR2B |= _BV(WGM22) | _BV(CS21);   //  WGM = 5, Prescale = 8

  TIMSK2 |= _BV(TOIE2);  // Enable Timer2 Overflow Interrupt
}

void setup()
{
  pinMode(PWM1, OUTPUT);      //sets the digital pin as output
  pinMode(PWM2, OUTPUT);      //sets the digital pin as output
  pinMode(PWM3, OUTPUT);      //sets the digital pin as output

  // The tuning value
  TuningValue = pow(2, 32) * OutputFrequency / CarrierFrequency; //calulate DDS new tuning word

  Setup_timer1();
  Setup_timer2();
}


// Timer2 Overflow Interrupt (10 KHz)
ISR(TIMER2_OVF_vect)
{
  static uint32_t phase_accumulator = 0;
  phase_accumulator += TuningValue;

  uint8_t current_count = phase_accumulator >> 24;   // use upper 8 bits of phase_accumulator as frequency information

  // read value fron ROM sine table and send to PWM1
  OCR2B = pgm_read_byte_near(sine256 + current_count);

  // read value fron ROM sine table (120° out of phase) and send to PWM2
  OCR1A = pgm_read_byte_near(sine256 + (uint8_t)(current_count + Phase2Offset));

  // read value from ROM sine table (240° out of phase) and send to PWM3
  OCR1B = pgm_read_byte_near(sine256 + (uint8_t)(current_count + Phase3Offset));
}

void loop() {}

output im getting is attached below .
please Help me
Thank you


have you tried a web search for 3Phase SPWM and arduino 3Phase SPWM - gives some links which may help, e.g. 3-phase-inverter-using-esp32-as-spwm-generator

1 Like

Looks like it is working to me.

Channel 4 is connected to a low-pass filter on pin3.

1 Like

thank you for your response
Which software are you using for simulation. Can you help me to get same output using proteus. What might be the reason that Im not able to get the output as you getting and im trying to generate same since last few weeks but...
Thank you for your time and contribution.

thank you for your response
Yes I have been trying every web page and resources available. And I found link you shared very helpful but I am trying to generate 3phase SPWM using arduino and in the link you have shared they are using ESP32.
Thank you!!

I'm not doing a simulation.

I programmed an Arduino Uno R3 with your code.

The results were obtained by connecting an oscilloscope to pins 3, 9 and 10 of the Uno.

I have a low pass filter comprising a 10kΩ resistor and 1µF capacitor, cut off frequency 16Hz connected to pin 3.
The cut off frequency of 16Hz isn't optimum for viewing a 50Hz sine wave, but I used it because I already had it available.

I was referring on web and I got one Youtube video related to my project.
Code I got from that video.

#include "avr/pgmspace.h"
#include "avr/io.h"

const byte sine256[] PROGMEM={128, 131, 134, 137, 140, 143, 146, 149, 152, 156, 159, 162, 165, 168, 171, 174, 176, 179, 182, 185,
188, 191, 193, 196, 199, 201, 204, 206, 209, 211, 213, 216, 218, 220, 222, 224, 226, 228, 230, 232,
234, 235, 237, 239, 240, 242, 243, 244, 246, 247, 248, 249, 250, 251, 251, 252, 253, 253, 254, 254,
254, 255, 255, 255, 255, 255, 255, 255, 254, 254, 253, 253, 252, 252, 251, 250, 249, 248, 247, 246,
245, 244, 242, 241, 239, 238, 236, 235, 233, 231, 229, 227, 225, 223, 221, 219, 217, 215, 212, 210,
207, 205, 202, 200, 197, 195, 192, 189, 186, 184, 181, 178, 175, 172, 169, 166, 163, 160, 157, 154,
151, 148, 145, 142, 138, 135, 132, 129, 126, 123, 120, 117, 113, 110, 107, 104, 101, 98, 95, 92,
89, 86, 83, 80, 77, 74, 71, 69, 66, 63, 60, 58, 55, 53, 50, 48, 45, 43, 40, 38,
36, 34, 32, 30, 28, 26, 24, 22, 20, 19, 17, 16, 14, 13, 11, 10, 9, 8, 7, 6,
5, 4, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2,
3, 4, 4, 5, 6, 7, 8, 9, 11, 12, 13, 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, 79,
81, 84, 87, 90, 93, 96, 99, 103, 106, 109, 112, 115, 118, 121, 124

};

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

int PWM_OUT_1=11; // PWM output on pin 11
int PWM_OUT_2=10; // PWM output on pin 10
int PWM_OUT_3=9; // PWM output on pin 9

int LED_PIN=13; //LED STATUS ON PIN 13
int TEST_PIN=7; // Scope trigger on pin 7
int POTEN_IN = A0; //Potentiometer on pin 0
int OFFSET_1=85; // Offset for second phase
int OFFSET_2=170; //Offset for third phase
double dfreq;
const double refclk = 31376.6; // measured
const uint64_t twoTo32 = pow(2,32); // compute value at startup and use as constant

// Variables used inside interrupt services are declared as volatile
volatile uint8_t icnt; //var inside interrupt
volatile uint8_t icnt1; //var inside interrupt
volatile uint8_t c4ms; //counter increamented every 4ms
volatile uint32_t phase_accum; //phase accumulator
volatile uint32_t tword_m; // dds tunning word m

//***********************************************************************************

void setup() {
  // put your setup code here, to run once:
  pinMode (LED_PIN, OUTPUT); // sets the digital pin as output 
  Serial.begin(115200); // connect to the serial port 
  Serial.println("DDS Test");

pinMode (TEST_PIN, OUTPUT); // sets the digital pin as output 
pinMode (PWM_OUT_1, OUTPUT); // PWM output frequency output 
pinMode (PWM_OUT_2, OUTPUT); // PWM output frequency output 
pinMode (PWM_OUT_3, OUTPUT); // PWM output frequency output 

// Setup the timers
setup_timerl();
setup_timer2();

// disable interrupts to avoid timing distortion 
 cbi (TIMSK0, TOIE0); // disable Timező !!! delay() is now not available 
 sbi (TIMSK2, TOIE2); // enable Timer2 Interrupt 
 dfreq = 1000.0; // initial output frequency 1000.0 Hz 
 tword_m = twoTo32 * dfreq/refclk; //calculate DDSnew tuning word
}

void loop() 
{
  // put your main code here, to run repeatedly:
  if (c4ms > 250) // timer / wait for a full second
  {

   c4ms > 0;
   dfreq = analogRead (POTEN_IN); // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz
   cbi (TIMSK2, TOIE2); // disble Timer2 Interrupt
   tword_m = twoTo32 * dfreq/refclk; // calulate DDS new tuning word
   sbi (TIMSK2, TOIE2); // enable Timer2 Interrupt
   Serial.print (dfreq);
   Serial.print(" ");
   Serial.println(tword_m);
   }
}

//*************
// timerl setup
// set prscaler to 1, PWM mode to phase correct PWM, 16000000/512 = 31.25kHz clock

void setup_timerl (void){
  // Timerl Clock Prescaler to: 1
sbi (TCCR1B, CS10);
cbi (TCCR1B, CS11);
cbi (TCCR1B, CS12);
// Timer 0 PWM mode set to Phase Correct PWM
cbi (TCCR1A, COM1A0); // clear Compare Match
sbi (TCCR1A, COM1A1);
cbi (TCCR1A, COM1A0); // clear Compare Match
sbi (TCCR1A, COM1B1);
sbi (TCCR1A, WGM10); // Mode 1 Phase Correct PWM
cbi (TCCR1A, WGM11);
cbi (TCCR1B, WGM12);
cbi (TCCR1B, WGM13);
}

//timer2 setup
//set prscaler to 1, PWM mode to phase correct PWM, 16000000/512=31.25kHz clock
void setup_timer2(){
// Timer2 Clock Prescaler to: 1
sbi (TCCR2B, CS20); 
cbi (TCCR2B, CS21);
cbi (TCCR2B, CS22);

// Timer2 PWM Mode set to Phase Correct PM
cbi (TCCR2A, COM2A0); // clear Compare Match
sbi (TCCR2A, COM2A1);
sbi (TCCR2A, WGM20); // Mode 1 Phase Correct PWM
cbi (TCCR2A, WGM21);
cbi (TCCR2B, WGM22);
}
// Timer2 Interrupt Service at 31.25kHz=32us
// this is timebase REFCLOCK for the DDS generator
// FOUT (M (REFCLK))/ (2 exp 32)
// runtime: 8 microseconds (inclusive push and pop)

ISR (TIMER2_OVF_vect)
{
sbi (PORTD, TEST_PIN); // Test / set PORTD, TEST_PIN high to observe timing with a oscope
phase_accum += tword_m; // soft DDS, phase accu with 32 bits 
icnt = phase_accum >> 24; // use upper 8 bits for phase accu an frequency information OCR2A pgm read byte near (sine256+ icnt); // read value fron ROM sine table and send to PWM DAC OCRIA pgm read_byte_near(sine256+ (uintet) (icnt OFFSET_1)): OCRIB pgm read byte near (sine256+ (uintet) (icnt + OFFSET_2)); if (icnti+125) // increment variable cama every 4 milliseconda
OCR2A=pgm_read_byte_near(sine256+icnt);//read value from ROM sine table and send to PWM DAC
OCR1A=pgm_read_byte_near(sine256+(uint8_t)(icnt+OFFSET_1));
OCR1B=pgm_read_byte_near(sine256+(uint8_t)(icnt+OFFSET_2));

if(icnt1++ ==125)// increament variable c4ms every 4 milliseconds
{
 c4ms++; 
 icnt1=0;
}

cbi (PORTD, TEST_PIN); // reset PORTD, TEST_PIN
}


Link to Youtube video is
https://youtu.be/zlNvO4pP0pk?si=CBqgYr3o9vbef4l7

I tried the code shown in video but didn't get shown output...
Note that Sine lookup table was not visible in video completely, so I put some(256) sine values generated from net...
Also I need to modify this code such that it has 25kHz switching frequency and controllable output(SPWM pulses) frequency from 10Hz to 800Hz...
I will be grateful for your help..

Hi Hare_rushi
your code work good but it contains just 3 pin PWM, i need 6
how to generate the complementary of the three PWM on pins 7 5 2 to control 3 phase inverter with 6 switchs ???

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.