FlexPWM: using fractional bus clocks (Teensy4)

jmarsh

Well-known member
Recently I wanted to output a 4MHz clock from a Teensy 4.1 and found using analogWriteFrequency(X, 4000000) didn't quite get the job done, due to FlexPWM running from the IPBus clock which is 150MHz (600/4). An easy option would be dropping the CPU speed from 600MHz to 528MHz (IPBus = 132, an integer multiple of 4) but something else caught my attention: in addition to just counting bus clock cycles, FlexPWM also supports fractional cycles. Here's the relevant information from the reference manual (important parts in bold):
55.3.2.9 Fractional Delay Logic
For applications where more resolution than a single IPBus clock period is needed, the
fractional delay logic can be used to achieve
fine resolution on the rising and falling
edges of the PWM_A and PWM_B outputs and fine resolution for the PWM period.
Enable the use of the fractional delay logic by setting FRCTRL[FRACx_EN]. The
FRACVALx registers act as a fractional clock cycle addition to the turn on and turn off
count specified by the VAL2, VAL3, VAL4, or VAL5 registers. The FRACVAL1
register acts as a fractional increase in the PWM period as defined by VAL1
. If
FRACVAL1 is programmed to a non-zero value, then the largest value for the VAL1
register is 0xFFFE for unsigned usage or 0x7FFE for signed usage. This limit is needed
in order to avoid counter rollovers when accumulating the fractional additional period.
The results of the fractional delay logic depend on whether or not the PWM submodule
has an analog NanoEdge placer block available
.

(About the "NanoEdge" part: the manual also says the RT1060 has no NanoEdge placement block (in the chip-specific notes), yet fractional adjustments to the period still seem to work, read on!)

So I made the following modifications to flexpwmFrequency() in TeensyDuino's pwm.c for Teensy4, to accomodate frequencies that don't divide cleanly into the bus frequency:
Code:
void flexpwmFrequency(IMXRT_FLEXPWM_t *p, unsigned int submodule, uint8_t channel __attribute__((unused)), float frequency)
{
	uint16_t mask = 1 << submodule;
	uint32_t olddiv = p->SM[submodule].VAL1;
	[B]uint32_t newdiv = (uint32_t)((float)F_BUS_ACTUAL / frequency);[/B]
	uint32_t prescale = 0;
	//printf(" div=%lu\n", newdiv);
	while (newdiv > 65535 && prescale < 7) {
		newdiv = newdiv >> 1;
		prescale = prescale + 1;
	}
	if (newdiv > 65535) {
		newdiv = 65535;
	} else if (newdiv < 2) {
		newdiv = 2;
	}
	[B]uint32_t f_bus = F_BUS_ACTUAL >> prescale;
	uint32_t fracval = ((float)32*f_bus / frequency + 0.5f) - 32 * newdiv;
	printf(" div=%lu, scale=%lu, fracval=%lu\n", newdiv, prescale, fracval);[/B]
	p->MCTRL |= FLEXPWM_MCTRL_CLDOK(mask);
	p->SM[submodule].CTRL = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_PRSC(prescale);
	p->SM[submodule].VAL1 = newdiv - 1;
	p->SM[submodule].VAL0 = (p->SM[submodule].VAL0 * newdiv) / olddiv;
	p->SM[submodule].VAL3 = (p->SM[submodule].VAL3 * newdiv) / olddiv;
	p->SM[submodule].VAL5 = (p->SM[submodule].VAL5 * newdiv) / olddiv;
	[B]if (fracval) {
		p->SM[submodule].FRACVAL1 = fracval << 11;
		p->SM[submodule].FRCTRL |= FLEXPWM_SMFRCTRL_FRAC1_EN;
	} else {
		p->SM[submodule].FRCTRL &= ~FLEXPWM_SMFRCTRL_FRAC1_EN;
	}[/B]
	p->MCTRL |= FLEXPWM_MCTRL_LDOK(mask);
}

Testing various frequencies like 32MHz, 4MHz, 32768Hz, all output at their "proper" rates (measured with FreqCount) with the Teensy running at the default of 600MHz. Multiples that vary by more than 1/32 are inexact, but I think it's a significant improvement.

Slightly related, I'm seeing some odd behaviour where my Teensy starts to misbehave when generating output for any frequencies above ~37MHz when left for extended periods of time. If I leave it off for a few hours I can run the default example sketch for FreqCount (Serial_Output_T4.ino) and watch it consistently print 50000000 to show that the 50MHz output is correct, but if I leave it long enough it always starts to jitter and produce numbers less than expected - slowly reducing over time until it settles around ~37000000. The problem does not seem to be the counting, because if I enable 24MHz output on pin 0 ("*(portConfigRegister(0)) = 6;") I get consistent output of 24000000. There's also no issues if I switch to using one of the quadtimer pins for output; it seems like there's some issue with FlexPWM that only shows up when generating high frequencies for long periods of time.
This occurs regardless of the above changes to flexpwmFrequency() or running unmodified TeensyDuino code.
 
That seems an interesting development.

Bummer the freq slips over time though. And interesting the same shows with unmodified/current freq code at those higher rates.

Noted that it has to be left off 'some extended time' to recover? So, it seems even restarting where the flexpwmFrequency() math and setup is redone it doesn't work as it did initially - or will after some hours?

So then periodically or 'on request by added push of an input button' making another call to flexpwmFrequency() likely wouldn't restore the proper function?
 
The slippage is something to do with the pulse width. This is the default (Teensy4) FreqCount example sketch:

Code:
/* FreqCount - Example with serial output
 * http://www.pjrc.com/teensy/td_libs_FreqCount.html
 *
 * This example code is in the public domain.
 *
 * For the Teensy 4.0 pin 9 is used to measure the freq
 * FreqCount.begin time is in microseconds vs milliseconds
 * for other boards.
 * As a test the sketch is setup to output 50Mhz on pin 11 sopen
 * add a jumper from pin 8 to pin 9.
 */
#include <FreqCount.h>

void setup() {
  Serial.begin(115200);

  delay(2000);
  analogWriteFrequency(8, 50000000);  // test jumper 8 to 9
  analogWrite(8, 128);
  
  FreqCount.begin(1000000);  //Time in microseconds
}

void loop() {
  if (FreqCount.available()) {
    unsigned long count = FreqCount.read();
    Serial.println(count);
  }
}
I'd be interested if other people can run this sketch as-is with clean TeensyDuino code and get the expected 50000000 output for extended periods of time. It's included in the Arduino's example menu, under FreqCount->Serial_Output_T4. I just did a "cold" run and this is what happened:
17:54:17.654 -> 50000000
17:54:18.635 -> 50000000
17:54:19.652 -> 50000000
17:54:20.661 -> 49999997
17:54:21.651 -> 50000000
17:54:22.660 -> 49999997
17:54:27.577 -> 49994975
17:54:28.565 -> 49999996
17:54:29.575 -> 49999997
17:54:30.567 -> 49999996
17:54:31.580 -> 49999996
17:54:32.575 -> 49999994
17:54:33.558 -> 49999989
17:54:34.573 -> 49999987
And the longer I leave it running, the smaller the counts get.

If I change analogWrite to set 171 instead of 128, it works consistently. For a frequency of 50MHz that basically flips the PWM on and off periods (3 ticks total, 1 on+2 off -> 2 on+1 off). The ~37MHz value that I mentioned it slowly slips towards would be 37.5MHz, which is 150/4 where the on/off counts would be equal (2 on+2 off). I don't have a scope so that's probably about as far as I can troubleshoot it.

With lower frequencies (when the on/off periods are much larger than 1 or 2 ticks) there's no problem at all, and the fractional code seems to work fine despite the manual saying it shouldn't.
 
I'd be interested if other people can run this sketch as-is with clean TeensyDuino code and get the expected 50000000 output for extended periods of time. /QUOTE]

I'm running it with IDE 1.8.19 and TD 1.58 and it looks good so far. I've done some testing with other FlexPWM besides the left-aligned mode used by pwm.c, and I remember finding that some things break down or did not work at all for high or low times less than 2 cycles.
 
Sometime under two hours, I start seeing counts < 50'000'000. Sometimes 49'999'999, sometimes a bit less. I don't know why this would happen, but I wonder if there is anything in the manual about high/low times less than 2 clock cycles. It's okay at 75'000'000, at least at first. Will report back what happens after time.
 
When I have precise timing requirements, I avoid USB serial because it turns off interrupts, so I tried modifying the example program to call Serial.print() only when the measured count was not 50,000,000, and it ran for 3+ hours with no measurements that were not exactly 50,000,000. So, I think it's not the FlexPWM that is the issue, but rather something to do with USB. Why it gets worse as time goes on, I don't know. After the 3 hours, when I moved my (USB) mouse, that seemed to somehow cause the Teensy to measure 49,999,999, then revert back to 50,000,000.

Each time it starts, the first measurement is off, as expected, and then after 7-10 seconds, there is one measurement of 49,999,999, and then it locks in at 50,000,000 and stays there.

Code:
       1 49989468
       7 49999999

Here is the modified example.

Code:
/* FreqCount - Example with serial output
 * http://www.pjrc.com/teensy/td_libs_FreqCount.html
 *
 * This example code is in the public domain.
 *
 * For the Teensy 4.0 pin 9 is used to measure the freq
 * FreqCount.begin time is in microseconds vs milliseconds
 * for other boards.
 * As a test the sketch is setup to output 50Mhz on pin 11 sopen
 * add a jumper from pin 8 to pin 9.
 */
#include <FreqCount.h>

void setup() {
  Serial.begin(57600);
  
  delay(2000);
  analogWriteFrequency(8, 50'000'000);  // test jumper 11 to 25
  analogWrite(8, 128);
  
  FreqCount.begin(1000000);  //Time in microseconds
}

unsigned long seconds=0;
void loop() {
  if (FreqCount.available()) {
    unsigned long count = FreqCount.read();
    seconds++;
    if (count != 50000000) {
      Serial.printf( "%8lu %8lu\n", seconds, count );
    }
  }
}
 
Back
Top