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):
(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:
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.
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.