PWM synchronization T4.1

Hello,

I want to control a brushless motor driver with 3 PWM signals from a Teensy 4.1. I use pins 7, 8 and 25 which are controlled by FlexPWM1 Module3. These outputs needs to be in sync with each other. The code below produces a 30kHz PWM output at 25%. Pin 7 and 8 are in sync and high in the beginning of the PWM interval, but pin 25 output is high at the end interval, so its falling edge coincides with the rising edge of pin 7 and 8. This is an unwanted behavior for my application. Does anyone know how to make the pin 25 align with pin 7 and 8?

Code:
void setup() {
	analogWriteResolution(11); // 
	pinMode(7, OUTPUT);
	analogWriteFrequency(7, 30000);
	pinMode(8, OUTPUT);
	analogWriteFrequency(8, 30000);
	pinMode(25, OUTPUT);
	analogWriteFrequency(25, 30000);

	analogWrite(7, 512);
	analogWrite(8, 512);
	analogWrite(25, 512);
}
void loop() {
}

7 and 8.jpg
Output from pin 7 (yellow) and 8 (purple) aligns.

7 and 25.jpg
Output from pin 7 (yellow) and 25 (purple) does not align. Output from pin 25 is shifted to the end of the PWM interval.
 
The thread https://forum.pjrc.com/threads/61605-How-to-reset-Teensy4-0-timers-for-phase-syncing-PWM-signals is the closest I have found so far, but not exactly.

I have read parts of the IMXRT manual related to the PWM timers. If there was a way to set the VAL0, VAL1 ... comparator registers, I think I will be able to solve my problem. I haven't made any low level programming yet, so if I can just get a starting point on how to change these registers, or find the names of them. Maybe the source code for analogWrite would be helpful, since it should load these registers at some point. Can you give me a hint where to find it?
 
The thread https://forum.pjrc.com/threads/61605-How-to-reset-Teensy4-0-timers-for-phase-syncing-PWM-signals is the closest I have found so far, but not exactly.

I have read parts of the IMXRT manual related to the PWM timers. If there was a way to set the VAL0, VAL1 ... comparator registers, I think I will be able to solve my problem. I haven't made any low level programming yet, so if I can just get a starting point on how to change these registers, or find the names of them. Maybe the source code for analogWrite would be helpful, since it should load these registers at some point. Can you give me a hint where to find it?

All sources are installed by TeensyDuino installer in the chosen Arduino install folder.

Going to that install folder this should be the source for the 1062 AnalogWrite: \hardware\teensy\avr\cores\teensy4\pwm.c:

In general, or even specifically - register and device names found in the processor manuals are presented in a Header file in those source directories to present the relevant address or masking type info.

An editor that can do a folder search can make discovery possible.
 
Does anyone know how to make the pin 25 align with pin 7 and 8?

I did some work a few months ago on producing 3 complementary pairs of PWM signals, and to do that, I had to get into lower level code. For 3 synchronized complementary pairs, you need 3 pairs of A/B outputs from the same FlexPWM module. Pins 7/8 are the A/B signals of FlexPWM module 1, submodule 3, and pin 25 is the X output of the same submodule. The capabilities of the X output are a little different from the A/B outputs, and they are not clocked by VAL0-5 in same way as the A/B outputs. You can read about this in section 55.4.2.4 of the iMXRT 1060 reference manual (rev 2).

If they are available, I suggest trying pins 4,5,6 instead, which are the A outputs of FlexPWM module 2, submodules 0,1,2. I'm not sure if you will be able to get synchronized output via the Arduino API. You may have to get into the low-level code, setting up the VAL registers for each submodule and then enabling them at the same time.

Just FYI, if you ever need 3 complementary pairs, I don't think you can do it with T4.1 because the only FlexPWM module for which 3 sets of A/B pins are available is FlexPWM1, submodules 0,1,2, and those are used for the SD card interface. Those pins are available on the T4.0 via pads on the bottom, and also on Teensy Micromod. I ended up using the Teensy Micromod and the "ATP" board. It may be possible to reconfigure pins on the T4.1, but that's not something I know how to do.
 
Thanks @defragster for pointing out where to start. I will dig into the low level part and see what I can accomplish. I will post a solution when I find one.

Thanks @joepasquariello for you advice on using the pin 4,5,6. In fact, using these pins seems to be a better choice. I'm using the Texas Instruments DRV8332 as brushless motor driver. It handles the control and deadtime for the upper and lower FETs of each half bridges, so I don't have to worry about generating complementary outputs.

My problem is related to controlling a motor in either fast-decay mode (coasting the motor for the non-driving part of the PWM period) or slow-decay (braking the motor for the non-driving part of the PWM period). For the fast-decay mode, I need to set each half-bridge output to high-impedance after each respective sourcing or sinking period to avoid current recirculation through the lower FETs. For this I need 3 synchronous rising edge PWM signals.
 
I did some work a few months ago on producing 3 complementary pairs of PWM signals, and to do that, I had to get into lower level code. For 3 synchronized complementary pairs, you need 3 pairs of A/B outputs from the same FlexPWM module. Pins 7/8 are the A/B signals of FlexPWM module 1, submodule 3, and pin 25 is the X output of the same submodule. The capabilities of the X output are a little different from the A/B outputs, and they are not clocked by VAL0-5 in same way as the A/B outputs. You can read about this in section 55.4.2.4 of the iMXRT 1060 reference manual (rev 2).
Actually that's not the case, while it is intended that a single FlexPWM unit is used for synchronizing multiple A/B pairs its
not the only way - the OUT_TRIG signals can be routed as EXT_SYNC on another FlexPWM unit via the XBAR switch, and I've
managed to synchronize multiple FlexPWM units to about 20ns accuracy this way.

See reply #10 on https://forum.pjrc.com/threads/59815-teensy-4-0-XBAR-questions

I settled on using FlexPWM4/2 (pins 2&3) to send trigger signal to FlexPWM2/2 (pins 6&9) and FlexPWM1/3 (pins 8&7)

If they are available, I suggest trying pins 4,5,6 instead, which are the A outputs of FlexPWM module 2, submodules 0,1,2. I'm not sure if you will be able to get synchronized output via the Arduino API. You may have to get into the low-level code, setting up the VAL registers for each submodule and then enabling them at the same time.
I think you just need to set INIT_SEL in CTRL2 reg to submodule0 master sync (value 0b10) to synchronize within a single FlexPWM.

Just FYI, if you ever need 3 complementary pairs, I don't think you can do it with T4.1 because the only FlexPWM module for which 3 sets of A/B pins are available is FlexPWM1, submodules 0,1,2, and those are used for the SD card interface. Those pins are available on the T4.0 via pads on the bottom, and also on Teensy Micromod. I ended up using the Teensy Micromod and the "ATP" board. It may be possible to reconfigure pins on the T4.1, but that's not something I know how to do.

The alternate pins are fairly limited for FlexPWM, many are one pin or two, and only a few have 4 or 5 options. Trying
to figure out this against the GPIO mappings for the Teensy itself is laborious work too.
 
For completeness my test code:
Code:
#ifndef IMXRT_FLEXPWM4
#error Needs Teensy 4.x, or compatible i.MXRT1062 board.
#endif

#define DEADTIME    3  // in half cycles (PWM clocked at 150MHz normally), so 3 = 24 cycles = 170ns
#define MIN_PWM    -444 // with the 115% PWM trick this permits upto 512 amplitude
#define MAX_PWM    +444
#define MID_PWM    (MAX_PWM + 2 * DEADTIME)
#define HALFCYCLE  (2 * MAX_PWM + 4 * DEADTIME)
#define TWOTO32    (65536.0 * 65536.0)  // 2^32
#define MAX_AMPLITUDE 512

// The i.MXRT1062 uses one config register per two XBAR outputs, so a helper
// function to make code more readable. 
bool xbar_connect (uint8_t input, uint8_t output)
{
  if (input >= 88 || output >= 132) 
    return false;
  volatile uint16_t * xbar_select_reg = &XBARA1_SEL0 + (output >> 1); // 1 reg per 2 outputs
  if (output & 1)  // high byte or low byte choice.
    *xbar_select_reg = (*xbar_select_reg & 0x00FF) | (input << 8);
  else
    *xbar_select_reg = (*xbar_select_reg & 0xFF00) | input;
  return true;
}

/* 3-phase PWM with deadtime
 * 
 *
 * Three A/B FlexPWM pairs, Teensy 4.x pins 2/3 (U-channel) and 6/9 (V-channel) and 8/7 (W-channel)
 *  
 */

IMXRT_FLEXPWM_t * flexpwm23 = &IMXRT_FLEXPWM4 ;
#define submodule23  2                         
IMXRT_FLEXPWM_t * flexpwm87 = &IMXRT_FLEXPWM1 ;
#define submodule87  3
IMXRT_FLEXPWM_t * flexpwm69 = &IMXRT_FLEXPWM2 ;
#define submodule69  2                         


// Helper to set up A/B pair on a FlexPWM submodule.
// can configure sync, prescale and B inversion.
void setup_pwm_pair (IMXRT_FLEXPWM_t * flexpwm, int submodule, bool ext_sync, int prescaler, bool invert_B)
{
  int submodule_mask = 1 << submodule ;
  flexpwm->MCTRL &= ~ FLEXPWM_MCTRL_RUN (submodule_mask) ;  // stop it if its already running
  flexpwm->MCTRL |= FLEXPWM_MCTRL_CLDOK (submodule_mask) ;  // clear load OK

  int sel = ext_sync ? 3 : 0;
  flexpwm->SM[submodule].CTRL2 = FLEXPWM_SMCTRL2_INDEP | FLEXPWM_SMCTRL2_WAITEN | FLEXPWM_SMCTRL2_DBGEN |
                                 FLEXPWM_SMCTRL2_FRCEN | FLEXPWM_SMCTRL2_INIT_SEL(sel) | FLEXPWM_SMCTRL2_FORCE_SEL(6) ;
  flexpwm->SM[submodule].CTRL  = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_HALF | FLEXPWM_SMCTRL_PRSC(prescaler) ;
  flexpwm->SM[submodule].OCTRL = invert_B ? FLEXPWM_SMOCTRL_POLB : 0 ;
  flexpwm->SM[submodule].DTCNT0 = 0 ;  // should try this out (deadtime control)
  flexpwm->SM[submodule].TCTRL = FLEXPWM_SMTCTRL_OUT_TRIG_EN (0b000010)  ; // sync trig out on VAL1 match.
  flexpwm->SM[submodule].INIT = -HALFCYCLE ;      // count from -HALFCYCLE to +HALFCYCLE
  flexpwm->SM[submodule].VAL0 = 0 ;
  flexpwm->SM[submodule].VAL1 = HALFCYCLE ;
  flexpwm->SM[submodule].VAL2 = MID_PWM + DEADTIME ;
  flexpwm->SM[submodule].VAL3 = MID_PWM - DEADTIME ;
  flexpwm->SM[submodule].VAL4 = MID_PWM - DEADTIME ;
  flexpwm->SM[submodule].VAL5 = MID_PWM + DEADTIME ;
  
  flexpwm->MCTRL |= FLEXPWM_MCTRL_LDOK (submodule_mask) ;  // loading reenabled
  flexpwm->MCTRL |= FLEXPWM_MCTRL_RUN (submodule_mask) ;   // start it running
}

void startup_pwm_pair (IMXRT_FLEXPWM_t * flexpwm, int submodule)
{
  int submodule_mask = 1 << submodule ;

  flexpwm->OUTEN |= FLEXPWM_OUTEN_PWMA_EN (submodule_mask); // enable A output
  flexpwm->OUTEN |= FLEXPWM_OUTEN_PWMB_EN (submodule_mask); // enable B output
}


volatile static uint32_t time0 = 0 ;   // store cycle count ARM_DWT_CYCCNT here when record phase
volatile static float freq = 0.0 ;     // used to advance the phase whenever its accessed by update_phase
volatile static uint32_t phase = 0.0 ;  // signed integer phase, MININT--MAXINT represents -pi to +pi.

void set_freq (float f)
{
  uint32_t now = ARM_DWT_CYCCNT ;  // reference timestamp for freq change
  uint64_t ph = phase ;
  int64_t inc = (int64_t) (freq * long(now - time0)) ;  // allow signed phase exceeding +/-pi range
  inc += ph ;
  ph = (uint32_t) (inc & 0xffffffffL) ;
  
  noInterrupts() ;   // critical section to update time0, phase and freq atomically
  time0 = now ;
  phase = ph ;
  freq = f ;
  interrupts() ;
}

// read the current phase, allowing for current value of freq, re-store it updated with new timestamp
inline uint32_t update_phase (void)
{
  uint32_t now = ARM_DWT_CYCCNT ;  // reference timestamp for freq change
  uint64_t ph = phase ;
  int64_t inc = (int64_t) (freq * long(now - time0)) ;  // allow signed phase exceeding +/-pi range
  inc += ph ;
  ph = (uint32_t) (inc & 0xffffffffL) ;
  
  //noInterrupts() ;   // critical section to update time0 and phase atomically
  time0 = now ;
  phase = ph ;
  //interrupts() ;
  
  return ph ;
}

void init_phase (float frequency)
{
  time0 = ARM_DWT_CYCCNT ;
  set_freq (TWOTO32 * frequency / 6e8) ;
}

// helpers
inline int max3 (int a, int b, int c)
{
  if (b > a) a = b ;
  if (c > a) a = c ;
  return a ;
}
inline int min3 (int a, int b, int c)
{
  if (b < a) a = b ;
  if (c < a) a = c ;
  return a ;
}


volatile static float amplitude = 0.0 ;

void pwm_handler (void)
{  FLEXPWM4_SM2STS |= FLEXPWM_SMSTS_RF; // Clear Reload

  // Use pin 1 for 'scope trigger.
  static volatile byte pin1_val = 0 ;
  digitalWrite (1, pin1_val) ;  
  pin1_val = 1-pin1_val ;
  
  int32_t intph = update_phase() ;
  // could improve this to use lookup rather than transcendental function calls
  float ph = 2*M_PI * intph / TWOTO32 ;
  int Uactual = int(amplitude * sin (ph)) ;
  int Vactual = int(amplitude * sin (ph+M_PI*2/3)) ;
  int Wactual = -(Uactual+Vactual) ;

  // Hack to allow 15% higher amplitude by shifting the phase triangle to the midpoint of its max and min values
  int midpoint = MID_PWM - (max3 (Uactual, Vactual, Wactual) +
                            min3 (Uactual, Vactual, Wactual)) / 2 ;
  // PWM requires swing between 0 and HALF_CYCLE, midpoint shifts the signed values up
  Uactual += midpoint ;
  Vactual += midpoint ;
  Wactual += midpoint ;

  // using phase-correct PWM with updates for half point as well as full for lowest latency
  // future work - figure out using the inbuilt deadtime options
  // U channel
  flexpwm23->SM[submodule23].VAL2 = -Uactual + DEADTIME ; // A on
  flexpwm23->SM[submodule23].VAL3 = +Uactual - DEADTIME ; // A off
  flexpwm23->SM[submodule23].VAL4 = -Uactual - DEADTIME ; // B off  (assuming B inverted)
  flexpwm23->SM[submodule23].VAL5 = +Uactual + DEADTIME ; // B on
  flexpwm23->MCTRL |= FLEXPWM_MCTRL_LDOK (1<<submodule23) ;  // signal new values
  // V channel
  flexpwm69->SM[submodule69].VAL2 = -Vactual + DEADTIME ;
  flexpwm69->SM[submodule69].VAL3 = +Vactual - DEADTIME ;
  flexpwm69->SM[submodule69].VAL4 = -Vactual - DEADTIME ;
  flexpwm69->SM[submodule69].VAL5 = +Vactual + DEADTIME ;
  flexpwm69->MCTRL |= FLEXPWM_MCTRL_LDOK (1<<submodule69) ;
  // W channel
  flexpwm87->SM[submodule87].VAL2 = -Wactual + DEADTIME ;
  flexpwm87->SM[submodule87].VAL3 = +Wactual - DEADTIME ;
  flexpwm87->SM[submodule87].VAL4 = -Wactual - DEADTIME ;
  flexpwm87->SM[submodule87].VAL5 = +Wactual + DEADTIME ;
  flexpwm87->MCTRL |= FLEXPWM_MCTRL_LDOK (1<<submodule87) ;
  
  digitalWrite (1, LOW) ;
 }


float frequency = 0.0;

void setup(void)
{
  // ensure pins inactive
  pinMode (2, INPUT) ;
  pinMode (3, INPUT) ;
  pinMode (6, INPUT) ;
  pinMode (7, INPUT) ;
  pinMode (8, INPUT) ;
  pinMode (9, INPUT) ;
  // pins used for 'scope observation.
  pinMode (1, OUTPUT) ;
  pinMode (4, OUTPUT) ;

  delay(20) ;



  // Configure FlexPWM units, each driving A/B pair, B inverted.
  // full speed about 80kHz, prescale 2 (div by 4) gives 20kHz
  setup_pwm_pair (flexpwm23, submodule23, false, 2, true) ;  // this is the master, internally synced
  //delayMicroseconds (5) ;
  setup_pwm_pair (flexpwm69, submodule69, true, 2, true) ;   // others externally synced
  //delayMicroseconds (5) ;
  setup_pwm_pair (flexpwm87, submodule87, true, 2, true) ;
  delayMicroseconds (100) ;

  // turn on XBAR1 clock for all but stop mode
  CCM_CCGR2 |= CCM_CCGR2_XBAR1(3);  

  // Connect trigger to synchronize all three units
  xbar_connect (XBARA1_IN_FLEXPWM4_PWM3_OUT_TRIG0, XBARA1_OUT_FLEXPWM1_PWM3_EXT_SYNC) ;
  xbar_connect (XBARA1_IN_FLEXPWM4_PWM3_OUT_TRIG0, XBARA1_OUT_FLEXPWM2_PWM2_EXT_SYNC) ;

  
  startup_pwm_pair (flexpwm23, submodule23) ;
  startup_pwm_pair (flexpwm69, submodule69) ;
  startup_pwm_pair (flexpwm87, submodule87) ;

  init_phase (frequency) ;
  
  // Configure FlexPWM4 SM2 interrupt on reload
  attachInterruptVector (IRQ_FLEXPWM4_2, pwm_handler);
  NVIC_ENABLE_IRQ (IRQ_FLEXPWM4_2);
  FLEXPWM4_SM2INTEN |= FLEXPWM_SMINTEN_RIE ; // Reload Interrupt Enable

   delayMicroseconds(50) ;
   // config the pins 2/3/6/9/8/7 as their FLEXPWM alternates.
  CORE_PIN2_CONFIG = 1 ; // pwm_pin_info[2].muxval ;
  CORE_PIN3_CONFIG = 1 ; // pwm_pin_info[3].muxval ;

  CORE_PIN6_CONFIG = 2 ; // pwm_pin_info[6].muxval ;
  CORE_PIN9_CONFIG = 2 ; // pwm_pin_info[9].muxval ;

  CORE_PIN8_CONFIG = 6 ; // pwm_pin_info[8].muxval ;
  CORE_PIN7_CONFIG = 6 ; // pwm_pin_info[7].muxval ;
 
   // pin 4 observes out trigger line for 'scope
  xbar_connect (XBARA1_IN_FLEXPWM4_PWM3_OUT_TRIG0, XBARA1_OUT_IOMUX_XBAR_INOUT08) ;
  IOMUXC_GPR_GPR6 |= IOMUXC_GPR_GPR6_IOMUXC_XBAR_DIR_SEL_8 ;  // select output mode for INOUT8
  // Select alt 3 for  EMC_06 (XBAR), rather than original 5 (GPIO)
  CORE_PIN4_CONFIG = 3 ; // shorthand for  IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_06 =  3 ;
  // turn up drive & speed as very short pulse
  IOMUXC_SW_PAD_CTL_PAD_GPIO_EMC_06 = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_SRE ;
}

float frequency_inc = 0.002 ;

void loop ()
{
  set_freq (TWOTO32 * frequency / 6e8) ;  
  // If I got things right frequency is in units of Hz if running at 600MHz

  // play with frequency and amplitude to show off waveforms on 'scope.
  frequency = constrain (frequency + frequency_inc, -10.0, 10.0) ;
  if (frequency == 10.0)
    frequency = -10.0 ;
  amplitude = constrain (amplitude + 0.04, 0.0, MAX_AMPLITUDE) ;
  //if (amplitude == MAX_AMPLITUDE)
   // amplitude = 0.0 ;
  delay (1) ;
}
 
Back
Top