T4.0 FlexPWM Documentation?

nickdaria

Member
Hey everyone, I am trying to get 4 PWM channels (2 sets of complimentary signals, with one pair phase shifted dynamically.

Below is my current code, it is built from code I found here, as I cannot find any documentation on how to use the FlexPWM registers. This code works for Channels A & B (FlexPWM 4.2 - pins 2/3), but does not work for Channels C & D (FlexPWM 2.2 - pins 6/9).

I have two questions:
  1. How do I get channels C & D working? I assumed I could simply copy over the A & B code and change from timer FlexPWM 4 to FlexPWM 2 because they are both X.2, however this does not appear to work.
  2. What is the best way to add a shift angle to this? I have found shift angle examples here, but they use analogWrite()

Thanks for any help!

Code:
#include "imxrt.h"
#include "core_pins.h"
#include "debug/printf.h"

#define Mask 4
#define ResolutionPWM 12 //12-Bit PWM

#define ChannelA 2
#define ChannelB 3
#define ChannelC 6
#define ChannelD 9

#define SET_AB	0
#define SET_CD	1

void hbridge_setFreq(uint8_t setIdx, float freqHz) {
	if(setIdx == SET_AB) {
		//	Set AB
		uint32_t CurrentCycles = (uint32_t)((float)F_BUS_ACTUAL / freqHz + 0.5);
		uint32_t Prescaler = 0;

		//Falls Frequenz zu gering Prescaler switchen!
		while (CurrentCycles > 65535 && Prescaler < 7) {
			CurrentCycles = CurrentCycles >> 1;
			Prescaler = Prescaler + 1;
		}
		if (CurrentCycles > 65535) {
			CurrentCycles = 65535;
			} else if (CurrentCycles < 2) {
			CurrentCycles = 2; //minimale Cycles --> 10nS oder so
		}

		FLEXPWM4_MCTRL |= FLEXPWM_MCTRL_CLDOK(Mask);
		FLEXPWM4_SM2CTRL = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_PRSC(Prescaler); //Erst nach vollem Cycle updaten und Prescaler setzen!
		FLEXPWM4_SM2VAL1 = CurrentCycles - 1; //Cycles für Periodendauer setzen!
		FLEXPWM4_MCTRL |= FLEXPWM_MCTRL_LDOK(Mask);
	}
	else if(setIdx == SET_CD) {
		//	Set CD
		uint32_t CurrentCycles = (uint32_t)((float)F_BUS_ACTUAL / freqHz + 0.5);
		uint32_t Prescaler = 0;

		//Falls Frequenz zu gering Prescaler switchen!
		while (CurrentCycles > 65535 && Prescaler < 7) {
			CurrentCycles = CurrentCycles >> 1;
			Prescaler = Prescaler + 1;
		}
		if (CurrentCycles > 65535) {
			CurrentCycles = 65535;
			} else if (CurrentCycles < 2) {
			CurrentCycles = 2; //minimale Cycles --> 10nS oder so
		}

		FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_CLDOK(Mask);
		FLEXPWM2_SM2CTRL = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_PRSC(Prescaler); //Erst nach vollem Cycle updaten und Prescaler setzen!
		FLEXPWM2_SM2VAL1 = CurrentCycles - 1; //Cycles für Periodendauer setzen!
		FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_LDOK(Mask);
	}
}

void hbridge_setDuty(uint8_t setIdx, uint16_t val) {
	if(setIdx == SET_AB) {
		//	Set AB
		uint32_t PeriodendauerCycles = FLEXPWM4_SM2VAL1; //Periodendauer
		uint32_t Cycles = ((uint32_t)val * (PeriodendauerCycles + 1)) >> ResolutionPWM;
		if (Cycles > PeriodendauerCycles) Cycles = PeriodendauerCycles;
		FLEXPWM4_MCTRL |= FLEXPWM_MCTRL_CLDOK(Mask);
		FLEXPWM4_SM2VAL3 = Cycles;
		FLEXPWM4_SM2VAL4 = FLEXPWM4_SM2VAL3 + (((FLEXPWM4_SM2VAL1 - 2 * Cycles) / 2)); //PauseOffset anpassen!
		FLEXPWM4_SM2VAL5 = FLEXPWM4_SM2VAL4 + (Cycles); //tOn für Channel B anpassen
		FLEXPWM4_OUTEN |= FLEXPWM_OUTEN_PWMA_EN(Mask); //ChannelA aktivieren
		FLEXPWM4_OUTEN |= FLEXPWM_OUTEN_PWMB_EN(Mask); //ChannelB aktivieren
		FLEXPWM4_MCTRL |= FLEXPWM_MCTRL_LDOK(Mask);
		*(portConfigRegister(ChannelA)) = 1;//Maske;
		*(portConfigRegister(ChannelB)) = 1;//Maske;
	}
	else if(setIdx == SET_CD) {
		//	Set CD
		uint32_t PeriodendauerCycles = FLEXPWM2_SM2VAL1; //Periodendauer
		uint32_t Cycles = ((uint32_t)val * (PeriodendauerCycles + 1)) >> ResolutionPWM;
		if (Cycles > PeriodendauerCycles) Cycles = PeriodendauerCycles;
		FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_CLDOK(Mask);
		FLEXPWM2_SM2VAL3 = Cycles;
		FLEXPWM2_SM2VAL4 = FLEXPWM2_SM2VAL3 + (((FLEXPWM2_SM2VAL1 - 2 * Cycles) / 2)); //PauseOffset anpassen!
		FLEXPWM2_SM2VAL5 = FLEXPWM2_SM2VAL4 + (Cycles); //tOn für Channel B anpassen
		FLEXPWM2_OUTEN |= FLEXPWM_OUTEN_PWMA_EN(Mask); //ChannelA aktivieren
		FLEXPWM2_OUTEN |= FLEXPWM_OUTEN_PWMB_EN(Mask); //ChannelB aktivieren
		FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_LDOK(Mask);
		*(portConfigRegister(ChannelC)) = 1;//Maske;
		*(portConfigRegister(ChannelD)) = 1;//Maske;
	}
}

#define HBRIDGE_FREQ_HZ	100000
#define HBRIDGE_DUTY	0	//	todo - make definition for allocating dead time

//	Start H-Bridge Timing H-Bridge
void hbridge_on() {
	hbridge_setFreq(SET_AB, HBRIDGE_FREQ_HZ);
	hbridge_setDuty(SET_AB, 2000);
	hbridge_setFreq(SET_CD, HBRIDGE_FREQ_HZ);
	hbridge_setDuty(SET_CD, 2000);
	//hbridge_setDuty(SET_AB, 820);	//	10% PWM
}

//	Turn off H-Bridge
void hbridge_off() {
	*(portConfigRegister(ChannelA)) = 0;
	*(portConfigRegister(ChannelB)) = 0;
	*(portConfigRegister(ChannelC)) = 0;
	*(portConfigRegister(ChannelD)) = 0;
}

void setup() {
	hbridge_on();
}

void loop() {
	
}
 
Thanks a ton! The mask fixed the issue for channels C & D! I knew it was some dumb mistake on my part.

As for the phase shifting, I did see those resources, but it was my understanding that the Teensy 4.X boards dropped FTM for FlexPWM and QuadTimer. I'm looking for documentation on those - specifically the definitions of the appropriate registers defined in the Teensy imrtx.h file.
 
I have been looking at the technical manual, but I still can't tell the best way to dynamically apply a phase shift angle (with constant frequency)
 
I would imagine you'd write the channels mask to the MCTRL CLDOK bits to zero the LDOK bits for all the timers you wish to alter. Then write your new settings to some or all of the 6 timing registers. Repeat for each timer. Then write the channels to MCTRL LDOK bits to cause all of your new settings to any combination of the 4 timers to all take effect at the same moment.

The trick with FlexPWM is it's so flexible that you really have to think carefully about what to do with those 6 registers.
 
Back
Top