Changing the PWM frequency on Teensy 3.6

Status
Not open for further replies.

Raptor

Active member
I think I saw someplace that you can change the frequency the Teensy 3.6 uses but I can't remember where I saw it. At present the frequency is about 480Hz but I'd like to lower it to perhaps 10Hz or there abouts. So, how does one do this?


Brian
 
Thanks, tested this out and it works just fine. Interestingly, with a given PWM setting the duty cycle as measured by my scope can change when the frequency changes. For example, with a PWM setting of, say, 25 the duty cycle might be 9.78% or 10.00% with the only change being the frequency. So, I wonder if there some kind of binning going on. I tired a freq of 10Hz but my bench power supply was bouncing all over the place so I upped the frequency to 75Hz and the readings from the power supply are now more stable and the duty cycle values are in line with calculated values.


Brian
 
Thanks, tested this out and it works just fine. Interestingly, with a given PWM setting the duty cycle as measured by my scope can change when the frequency changes. For example, with a PWM setting of, say, 25 the duty cycle might be 9.78% or 10.00% with the only change being the frequency. So, I wonder if there some kind of binning going on. I tired a freq of 10Hz but my bench power supply was bouncing all over the place so I upped the frequency to 75Hz and the readings from the power supply are now more stable and the duty cycle values are in line with calculated values.


Brian

With a logic analyzer and doing
analogWriteFrequency(23,25);
analogWrite(23,127);


I see 50% duty pretty much for any value of hz i use in analogWriteFrequency(). So I don't know what kind of electrical interference or evil spirits are affecting your measurements. ???
 
Studying the FTM chapter of the processor's reference manual would have explained everything to you. The FTM timers have a mod register which is set to a number x of bus clock pulses which determines the PWM frequency by F_BUS/x and which indicates the number of clock cycles for the whole thing, high and low period (t_hi + t_lo) in sum.
The channel CV register for the pin you use is set to a value y which determines the the active duty cycle (t_hi only). Thus, when you use analogWrite(z), the y value is calculated from z depending on resolution and the mod value (x). If you change the PWM frequency and thus x afterwards, it is normal that an unchanged y (t_hi) will lead to a different duty cycle, that's elementary logic, because for an unchanged y, the high pulse width remains unchanged which makes that the duty cycle changes.
Thus, changing the frequency with analogWriteFrequency() should always be preceded by analogWrite(0) and followed by a new analogWrite(whatever_again) to make sure that the duty cycle is recalculated depending on the new PWM frequency.
 
Last edited:
I didn't know this, but I'd expect the Teensy to maintain the duty cycle through PWM frequency changes. So is this a bug or documented behaviour (apart from "documented in the code")? If it's neither I suggest we make it either one?
 
Studying the FTM chapter of the processor's reference manual would have explained everything to you. The FTM timers have a mod register which is set to a number x of bus clock pulses which determines the PWM frequency by F_BUS/x and which indicates the number of clock cycles for the whole thing, high and low period (t_hi + t_lo) in sum.
The channel CV register for the pin you use is set to a value y which determines the the active duty cycle (t_hi only). Thus, when you use analogWrite(z), the y value is calculated from z depending on resolution and the mod value (x). If you change the PWM frequency and thus x afterwards, it is normal that an unchanged y (t_hi) will lead to a different duty cycle, that's elementary logic, because for an unchanged y, the high pulse width remains unchanged which makes that the duty cycle changes.
Thus, changing the frequency with analogWriteFrequency() should always be preceded by analogWrite(0) and followed by a new analogWrite(whatever_again) to make sure that the duty cycle is recalculated depending on the new PWM frequency.

Thanks, I just tested this with a preceding analogWrite(4,0) and then tested some PWM values -- came out the same as my previous test. What I did was code the frequency in the setup section and then loop through several PWM values while watching the scope and when I changed the freq I resent the program and it began anew. That is, I did not change the freq multiple times within a single program.

Anyway, I appreciate the feedback and the slowed freq has improved the slow turn-off issue I have with the MOSFET I'm using to drive the heater. Reducing the freq from 480 to 75 reduced the error by about 5X and is now quite livable.


Brian
 
There is naturally some binning going on, too, since the timer registers can only be preset with integer numbers which will act as dividers for the bus clock. Since the max divider in a 16bit register is 65535, an internal pre-scaler is activated for lower pwm frequencies than F_BUS(typically 48MHz) divided by 65536 which makes that binning effect still more important. That's why the PJRC website lists optimal PWM frequencies for given resolutions and given bus clock rates (which depend on the CPU clock): https://www.pjrc.com/teensy/td_pulse.html (scroll down to the PWM frequency section).
 
I didn't know this, but I'd expect the Teensy to maintain the duty cycle through PWM frequency changes. So is this a bug or documented behaviour (apart from "documented in the code")? If it's neither I suggest we make it either one?

yes, the teensy library maintains duty cycle even as you change frequency with analogWriteFrequency()

in analogWriteFrequency() duty (mod) is recalculated in hardware/teensy/avr/cores/teensy3/pins_teensy.c

mod = (float)(ftmClock >> prescale) / frequency - 0.5f; //Use ftmClock instead of F_TIMER

EDIT: corrected below in post #12
 
Last edited:
in analogWriteFrequency() duty (mod) is recalculated in hardware/teensy/avr/cores/teensy3/pins_teensy.c

mod = (float)(ftmClock >> prescale) / frequency - 0.5f; //Use ftmClock instead of F_TIMER

Duty is not recalculated by analogWriteFrequency since, as @Theremingenieur explained it very well, MOD is about total period, not about duty.
 
Indeed - Based on the code indicated by Manitou ... Duty is calculated at time of analogWrite() based on the mod value calculated in analogWriteFrequency() as manitou noted.

Until the Freq is changed - all Duty values are based on current value.
 
yes, the teensy library maintains duty cycle even as you change frequency with analogWriteFrequency()

in analogWriteFrequency() duty (mod) is recalculated in hardware/teensy/avr/cores/teensy3/pins_teensy.c

mod = (float)(ftmClock >> prescale) / frequency - 0.5f; //Use ftmClock instead of F_TIMER

Ooops, my comment is wrong, the mod value (FTMx_MOD) is the period/frequency, and as defragster notes, the analogWrite() in post #4 updates the duty (cval, FTMx_CnV) after the frequency has been updated. The core library does not update the duty when you update frequency. analogWriteFrequency() stops the PWM (FTMx_SC =0) before updating FTMx_MOD.
 
Last edited:
I didn't know this, but I'd expect the Teensy to maintain the duty cycle through PWM frequency changes. So is this a bug or documented behaviour (apart from "documented in the code")? If it's neither I suggest we make it either one?

Based on what I saw (documented) in the code as observed and manitou's follow up - a given duty cycle for an analogWrite() will indeed persist across frequency change - as analogWriteFrequency() has no reference for prior duty/value and makes no update to the output - the new frequency will be used on following analogWrite()'s until a similar change process.
 
Let's see how things are connected together: The user does want to change the frequency of the signal while maintaining the relative duty cycle (50% in tis case).
But the FTM handles things differently. In the MOD register, it stores a number of ticks which are proportional to the overall signal period and which are thus calculated as round(timer_clock/frequency), where timer_clock is defined by F_BUS and the pre-scaler settings.
When it comes to the relative duty cycle, some math is needed, too, because the FTM has no direct setting fo it. Similar to the MOD register, the FTMx_CnV register stores the number of ticks for which the output goes high, independent of the MOD register content. Thus, changing the frequency while maintaining the relative duty cycle requires writing to the MOD and the FTMx_CnV register.
Let's take an example: Using a T3.6 at 180MHz will give us a F_BUS=60MHz as the FTM clock when no pre-scaler is used. We want an output frequency of 1kHz, so we set the MOD register to 60000 ticks. That means that the timer will restart every 1ms. But nothing will happen at the output as long as we do not tell the timer that we want it to go high for half of the time, which corresponds to writing 30000 to the FTMx_CnV register. Now, the output will go high for 0.5ms every 1ms which corresponds to a 50% duty cycle.
Now let's raise the frequency to 1.5kHz. That means, that the signal period goes from 1ms to 0.667ms or from 60000 ticks to 40000 ticks. Thus, we'd write 40000 to the MOD register. What happens afterwards? The output will still go high for 0.5ms but every 0.667ms which means that the relative duty cycle has changed to 75%...
In order to come back to our initial 50%, we need to adapt the value in FTMx_CnV, too, and set it to 20000 ticks. Now, the "high time" of the output is only 0.333ms for an overall period of 0.667, which corresponds again to a 50% duty cycle.
Thus, changing the frequency while maintaining the relative duty cycle requires changing the MOD and FTMx_CnV values, which is not covered by the analogWriteFrequency() function alone.
 
Last edited:
Let's see how things are connected together: The user does want to change the frequency of the signal while maintaining the relative duty cycle (50% in tis case).
But the FTM handles things differently. In the MOD register, it stores a number of ticks which are proportional to the overall signal period and which are thus calculated as round(timer_clock/frequency), where timer_clock is defined by F_BUS and the pre-scaler settings.
When it comes to the relative duty cycle, some math is needed, too, because the FTM has no direct setting fo it. Similar to the MOD register, the CNTIN register stores the number of ticks for which the output goes high, independent of the MOD register content. Thus, changing the frequency while maintaining the relative duty cycle requires writing to the MOD and the CNTIN register.
Let's take an example: Using a T3.6 at 180MHz will give us a F_BUS=60MHz as the FTM clock when no pre-scaler is used. We want an output frequency of 1kHz, so we set the MOD register to 60000 ticks. That means that the timer will restart every 1ms. But nothing will happen at the output as long as we do not tell the timer that we want it to go high for half of the time, which corresponds to writing 30000 to the CNTIN register. Now, the output will go high for 0.5ms every 1ms which corresponds to a 50% duty cycle.
Now let's raise the frequency to 1.5kHz. That means, that the signal period goes from 1ms to 0.667ms or from 60000 ticks to 40000 ticks. Thus, we'd write 40000 to the MOD register. What happens afterwards? The output will still go high for 0.5ms but every 0.667ms which means that the relative duty cycle has changed to 75%...
In order to come back to our initial 50%, we need to adapt the value in CNTIN, too, and set it to 20000 ticks. Now, the "high time" of the output is only 0.333ms for an overall period of 0.667, which corresponds again to a 50% duty cycle.
Thus, changing the frequency while maintaining the relative duty cycle requires changing the MOD and CNTIN values, which is not covered by the analogWriteFrequency() function alone.

Hello @Theremingenieur
My understanding is that CNTIN is the value from which the timer counts all the way up to MOD. So, to me, CNTIN is set to 0 in analogWriteFrequency(). Would your statement be more correct if you replace CNTIN with FTMx_CnV ?
 
Hello @Theremingenieur
My understanding is that CNTIN is the value from which the timer counts all the way up to MOD. So, to me, CNTIN is set to 0 in analogWriteFrequency(). Would your statement be more correct if you replace CNTIN with FTMx_CnV ?

Yes, thank you. Silly me. I edited my above post.
 
Status
Not open for further replies.
Back
Top