Three-Phase SPWM With Teensy 4.1

Hi everyone, so I need help with my code. I'm using a library called 'Teensy_PWM.h'. I want to create 3 SPWM Signals with a phase shift of 120º. I'm generating the signals in Pins 7, 8, and 25 which belong to FlexPWM1.3. As Teensy 4.1 allows a 'Floating Point Unit' I'm creating the Sine Waves mathematically.

I was expecting to see a phase shift of 120º on my oscilloscope, but in reality, it looks more like 180 degrees. What I am doing wrong?

Ps: I changed the 'Teensy_PWM.h' to work with 12 bits, I have already tested it and it's working (just made a 50% Duty Cycle wave with 15 kHz). I also know that when I define the PWM Frequency in Pin 7 I also set the PWM Frequency to Pins 8 and 25, however I just came across this library and I'm not master yet.

Thank you for your help!

My code:
/*
* Test 1: 12 bits resolution
* 24 Pins (Flex and Quad Timer)
* Duty Cycle = 50%
*
* Timer Pins Total Pins
* FlexPWM1 0, 1, 7, 8, 24, 25 (6)
* FlexPWM2 4, 5, 6, 9, 33, 36, 37 (7)
* FlexPWM3 28, 29 (2)
* FlexPWM4 2, 3, 22, 23 (4)
* (19)
* QuadTimer1 10, 11, 12 (3)
* QuadTimer2 13 (1)
* QuadTimer3 14, 15, 18, 19 (4)
* (8)
*
* Total of PWM Pins is 19 + 8 = 27
*/

#define BOARD_NAME F("Teensy 4.1")

#include "Teensy_PWM.h"
#include "imxrt.h"
#include "core_pins.h"
#include <math.h>

#define USING_FLEX_TIMERS true

uint32_t PWM_Pins[] = {7, 8, 25};

#define NUM_OF_PINS (sizeof(PWM_Pins) / sizeof(uint32_t))

#define MAX_COUNT_12BIT 4095UL
#define MAX_12BIT 4095UL

float sineWave[NUM_OF_PINS] = {0};
float sineFreq = 50.0f; // Sine Wave Frequency of 50 Hz
float pwmFreq = 15000.0f; // PWM Frequency in Hz
float dutyCycle = 0.0f;

uint8_t omega = 0;


// Create PWM Instances
Teensy_PWM* PWM_Instance[NUM_OF_PINS];

void setup() {
// Create instances for each PWM pin
for (int i = 0; i < NUM_OF_PINS; i++) {
PWM_Instance = new Teensy_PWM(PWM_Pins, pwmFreq, dutyCycle);
}
}

void updateDC(){
unsigned long currentTime = millis();

for (uint8_t i = 0; i < NUM_OF_PINS; i++) {
// Calculate sine wave with a phase shift of 120 degrees for each pin
sineWave = sin(((2 * PI * sineFreq * currentTime) / 1000.0) + radians(omega));
omega = omega + 120;

if(omega == 240){
omega = 0;
}
}

for (uint8_t i = 0; i < NUM_OF_PINS; i++) {
// Calculate duty cycle for each pin
uint16_t dutyCycle = map(sineWave, -1.0, 1.0, 0, MAX_COUNT_12BIT);

// Set the PWM duty cycle for each pin
PWM_Instance->setPWM_manual(PWM_Pins, dutyCycle);
}
}


void check_status(){
#define UPDATE_INTERVAL 0.2L

static unsigned long update_timeout = UPDATE_INTERVAL;

// Update DC every UPDATE_INTERVAL (0.2) milliseconds [5000 Hz]
if (millis() > update_timeout)
{
updateDC();
update_timeout = millis() + UPDATE_INTERVAL;
}
}

void loop() {
check_status();
}
 
Code:
/*
* Test 1: 12 bits resolution
* 24 Pins (Flex and Quad Timer)
* Duty Cycle = 50%
*
* Timer Pins Total Pins
* FlexPWM1 0, 1, 7, 8, 24, 25 (6)
* FlexPWM2 4, 5, 6, 9, 33, 36, 37 (7)
* FlexPWM3 28, 29 (2)
* FlexPWM4 2, 3, 22, 23 (4)
* (19)
* QuadTimer1 10, 11, 12 (3)
* QuadTimer2 13 (1)
* QuadTimer3 14, 15, 18, 19 (4)
* (8)
*
* Total of PWM Pins is 19 + 8 = 27
*/

#define BOARD_NAME F("Teensy 4.1")

#include "Teensy_PWM.h"
#include "imxrt.h"
#include "core_pins.h"
#include <math.h>

#define USING_FLEX_TIMERS true

uint32_t PWM_Pins[] = { 7, 8, 25 };

#define NUM_OF_PINS (sizeof(PWM_Pins) / sizeof(uint32_t))

#define MAX_COUNT_12BIT 4095UL
#define MAX_12BIT 4095UL

float sineWave[NUM_OF_PINS] = { 0 };
float sineFreq = 50.0f; // Sine Wave Frequency of 50 Hz
float pwmFreq = 15000.0f; // PWM Frequency in Hz
float dutyCycle = 0.0f;

uint8_t omega = 0;


// Create PWM Instances
Teensy_PWM* PWM_Instance[NUM_OF_PINS];

void setup() {
	// Create instances for each PWM pin
	for (int i = 0; i < NUM_OF_PINS; i++) {
		PWM_Instance[i] = new Teensy_PWM(PWM_Pins[i], pwmFreq, dutyCycle);
	}
}

void updateDC() {
	unsigned long currentTime = millis();

	for (uint8_t i = 0; i < NUM_OF_PINS; i++) {
		// Calculate sine wave with a phase shift of 120 degrees for each pin
		sineWave[i] = sin(((2 * PI * sineFreq * currentTime) / 1000.0) + radians(omega));
		omega = omega + 120;

		if (omega == 240) {
			omega = 0;
		}
	}

	for (uint8_t i = 0; i < NUM_OF_PINS; i++) {
		// Calculate duty cycle for each pin
		uint16_t dutyCycle = map(sineWave[i], -1.0, 1.0, 0, MAX_COUNT_12BIT);

		// Set the PWM duty cycle for each pin
		PWM_Instance[i]->setPWM_manual(PWM_Pins[i], dutyCycle);
	}
}


void check_status() {
#define UPDATE_INTERVAL 0.2L

	static unsigned long update_timeout = UPDATE_INTERVAL;

	// Update DC every UPDATE_INTERVAL (0.2) milliseconds [5000 Hz]
	if (millis() > update_timeout)
	{
		updateDC();
		update_timeout = millis() + UPDATE_INTERVAL;
	}
}

void loop() {
	check_status();
}}

When posting Code please enclose it between CODE tags using the # button on the data entry form.
It makes your code much easier to understand.
 
Define omega as unsigned (its completely overflowing an uint8_t - on a 32 bit processor int and unsigned are faster that smaller types), and best make it local to the function updateDC() as that's the only place its used.

When I run it on a T4.0 I see 16 bit PWM, not 12, and I think the map() function isn't working as expected, I just replaced it with suitable scale+shift and things seemed to become plausible.

I also had to fix the phase accumulator to be a phase accumulator. The argument to trig functions should be kept small, within a few factors of 2pi from zero, otherwise you start to lose precision until eventually its nonsense.

The standard way to code direct digital synthesis (DDS) like this is use an integer value for the phase, with the convention that it wraps at pi (if signed) or 2pi (if unsigned). The wrapping prevents any loss of precision over time. To be properly C++ compliant use unsigned as integer overflow on signed integers is undefined behaviour according to the standard.

Thus something like
Code:
float x = sin(2*PI*(phase_accumulator += frequency_word) + offset_in_radians);
where phase_accumulator is unsigned, offset_in_radians is float, and frequency_word is prescaled to change the phase as expected.

You time loop cannot go faster than 1kHz as millis() returns an integer value, not a float. Use micros, and use the correct idiom to avoid wrap-around issues:
Code:
  static unsigned long update_time = 0; // us
  if (micros() - update_time >= 200)  // must subtract, then compare, or wraparound will fail
  {
    update_time += 200; //us   // don't call micros() again here or the timing will be imprecise and drift
    updateDC();
  }
 
So, I tried to include your input on my code, and I still have the same problem. I also want the SPWM and the code you provided set a fixed Duty Cycle, that´s why I used the currentTime in the sine wave generation.
 
Sorry for the late response, I realized I was doing something wrong with the DDS (I had never heard of it, so had to study a little bit), so I tried again yesterday afternoon and this morning. Actually, I changed some stuff, and my code right now is this.
When I see it with an oscilloscope I still think it's not with 12-bit resolution, and the reference frequency doesn't seem 50 Hz. However, your input already helped a lot, because I actually understood that this is the way to do this. I think one of my problems was trying to use time to do this.
I think the problem is with the sineWave generation. And I will se if I can solve this problem.

Code:
Code:
/*
 * DDS (Direct Digital Synthesis) 3-Phase SPWM
 * 120º Phase-Shift
 */

#include "Teensy_PWM.h"
#include "imxrt.h"
#include "core_pins.h"
#include <math.h>

#define BOARD_NAME F("Teensy 4.1")

// I/O pins
uint32_t PWM_Pins[] = {2, 3, 6};
#define NUM_OF_PINS (sizeof(PWM_Pins) / sizeof(uint32_t))


// Discrete signal variables
float sineFreq = 50.0f;    // Sine Wave Frequency 0f 50 Hz
float pwmFreq = 15000.0f;  // PWM Frequency in Hz
float sineWave[NUM_OF_PINS] = {0.0f};
float dutyCycle[NUM_OF_PINS] = {0.0f};

// Phase Shift (3 Phase)
float phaseShift[NUM_OF_PINS] = {2.0 * PI, 2.0 * PI / 3.0, 4.0 * PI / 3.0};

// Discrete time variables
int phase_accumulator = 0;          // time step counter (current), n = [0, N-1] domain
int n = 0;
int nSamples = 528;        // desired number of samples in a period (max, user defined)
float period = (1.0 / sineFreq); // period length (50Hz = 0.020 sec)
float frequency_word = n * period;

int _resolution = 12;

// Create PWM Instances
Teensy_PWM* PWM_Instance[6];

void setup() {
  analogWriteResolution(_resolution);
  // Create instances for each PWM pin
  for (uint8_t i = 0; i < NUM_OF_PINS; i++){
    PWM_Instance[i] = new Teensy_PWM(PWM_Pins[i], pwmFreq, dutyCycle[i]);
  }
}


void updateDC(){
  for(uint8_t i = 0; i < NUM_OF_PINS; i++){
    sineWave[i] = sin(2 * PI * (n * period) + phaseShift[i]);
    dutyCycle[i] = (2047 * sineWave[i] + 2048) * 100;     // In Percentage
    n += 1;
  }

  if(n == nSamples){
    n = 0;
  }
  
  for(uint8_t i = 0; i < NUM_OF_PINS; i++){
    PWM_Instance[i]->setPWM_manual(PWM_Pins[i], dutyCycle[i]);
  }
}


void check_status(){
  static unsigned long update_time = 0; // us
  if (micros() - update_time >= 200){
    update_time += 200; 
    updateDC();
  }
}


void loop() {
  check_status();
}
 
Back
Top