2Hz analogWriteFrequency on Teensy 3.1

neurostar

New member
I wonder what is the lowest frequency for analogWriteFrequency on Teensy 3.1.
When I set it to 20Hz, it works fine. PWM Frequency was 5.xxHz and pulse width was much shorter than set when it was set it to 2Hz.
If the limit of frequency setting is < 2Hz, is there work around using intervaltimer?
 
I'm curious about lower limits for PWM output too. Is there a practical limit where it makes more sense to switch to using a software-based implementation of PWM? For my application, I will be modulating a solenoid water valve, so the fastest PWM cycle I will want is ~1 Hz. I'm interested both in (1) whether I can simply use a hardware PWM function for this and (2) whether there are reasons to do it in software even if hardware would work.
 
Just have a look into the K20 Sub-Family Reference Manual, pages 769ff.: With F_BUS (36MHz if the Teensy runs at 72MHz, 48MHz if the Teensy runs at 48MHz) as a clock source and a maximum reload value for the Modulo counter of 0xFFFF, the "natural" lowest PWM frequencies when using one of the FTMs are 549.316Hz or 732.422Hz. Using the maximum prescale divider of 128, you get 5.722Hz or 7.629Hz as the lowest possible PWM frequency.
Lower frequencies may be obtained by using a different clock source for the FTM, but this would require that you modify the analogWriteFrequency(uint8_t pin, float frequency) function in pins_teensy.c or that you write your own PWM code using the Fixed Frequency Clock (MCGFFCLK) which is only 31.250kHz and which would allow a PWM frequency of 0.477Hz without prescaler and 0.004Hz with a prescale divider of 128.
 
Last edited:
The lower PWM frequency limit is imposed by the maximum prescaler setting, which is only 128, the maximum timer period, which is 65536, and the F_BUS frequency. So the slowest PWM you can get is F_BUS / 2^23.

Normally F_BUS is 48 MHz when Teensy clocks at 48 or 96 MHz, or 36 MHz when Teensy runs at 72, or 24 MHz. So simply setting Tools > CPU Speed to 24 MHz will give you a lower-reaching range, but at the expense of CPU performance.

The hardware creates F_BUS as an integer division of the PLL. If you want to get into deeper fiddling, you could edit the code in mk20dx128.c which sets up all the clocks. Also edit kinetis.h, which defines F_BUS based on F_CPU... but be aware, the actual code in mk20dx128.c is what really controls F_BUS. The defs in kinetis.h are merely based on what that code does.

Almost all the on-chip peripherals use F_BUS. Some, like the hardware serial will adapt to (almost) any F_BUS number. Others like I2C have sets of #ifdef, so you can only expect certain F_BUS settings to work.... pretty much only the ones already in the list. That means the next integer divide step down from 24 MHz is likely 8 MHz, since 12 isn't on the list.

Beyond F_BUS, there may be ways to get the FTM timers (the ones responsible for PWM generation) to clock from something other than F_BUS. So far, I've not personally tried to run the FTMs that way, so I can't offer much direct advise. Well, other than reading Freescale's reference manual, and hopefully sharing whatever you might learn.
 
I suggest to try to add to pins_teensy.c file as below. Just use that function to set your (low) write frequency and then you may use the standard analogWrite() function.

Code:
void analogWriteFrequencySlow(uint8_t pin, float frequency)
{
	uint32_t prescale, mod;
	float minfreq;

	for (prescale = 0; prescale < 7; prescale++) {
		minfreq = (float)(31250 >> prescale) / 65536.0f;
		if (frequency >= minfreq) break;
	}
	mod = (float)(31250 >> prescale) / frequency - 0.5f;
	if (mod > 65535) mod = 65535;
	if (pin == FTM1_CH0_PIN || pin == FTM1_CH1_PIN) {
		FTM1_SC = 0;
		FTM1_CNT = 0;
		FTM1_MOD = mod;
		FTM1_SC = FTM_SC_CLKS(2) | FTM_SC_PS(prescale);
	} else if (pin == FTM0_CH0_PIN || pin == FTM0_CH1_PIN
	  || pin == FTM0_CH2_PIN || pin == FTM0_CH3_PIN
	  || pin == FTM0_CH4_PIN || pin == FTM0_CH5_PIN
#ifdef FTM0_CH6_PIN
	  || pin == FTM0_CH6_PIN || pin == FTM0_CH7_PIN
#endif
	  ) {
		FTM0_SC = 0;
		FTM0_CNT = 0;
		FTM0_MOD = mod;
		FTM0_SC = FTM_SC_CLKS(2) | FTM_SC_PS(prescale);
	}
#ifdef FTM2_CH0_PIN
	  else if (pin == FTM2_CH0_PIN || pin == FTM2_CH1_PIN) {
		FTM2_SC = 0;
		FTM2_CNT = 0;
		FTM2_MOD = mod;
		FTM2_SC = FTM_SC_CLKS(2) | FTM_SC_PS(prescale);
	}
#endif
}
 
A more elegant way would be to convince Paul to modify the original analogWriteFrequency as follows, so that the alternative clock would be used automatically when setting very low frequencies:
Code:
void analogWriteFrequency(uint8_t pin, float frequency)
{
	uint32_t prescale, mod, ftmClock, ftmClockSource;
 	float minfreq;

        if (frequency < (float)(F_TIMER >> 7) / 65536.0f) {
                ftmClockSource = 2;
                ftmClock = 31250;
        } else {
                ftmClockSource = 1;
                ftmClock = F_TIMER;
        }

	for (prescale = 0; prescale < 7; prescale++) {
		minfreq = (float)(ftmClock >> prescale) / 65536.0f;
		if (frequency >= minfreq) break;
	}
	mod = (float)(ftmClock >> prescale) / frequency - 0.5f;
	if (mod > 65535) mod = 65535;
	if (pin == FTM1_CH0_PIN || pin == FTM1_CH1_PIN) {
		FTM1_SC = 0;
		FTM1_CNT = 0;
		FTM1_MOD = mod;
		FTM1_SC = FTM_SC_CLKS(ftmClockSource) | FTM_SC_PS(prescale);
	} else if (pin == FTM0_CH0_PIN || pin == FTM0_CH1_PIN
	  || pin == FTM0_CH2_PIN || pin == FTM0_CH3_PIN
	  || pin == FTM0_CH4_PIN || pin == FTM0_CH5_PIN
#ifdef FTM0_CH6_PIN
	  || pin == FTM0_CH6_PIN || pin == FTM0_CH7_PIN
#endif
	  ) {
		FTM0_SC = 0;
		FTM0_CNT = 0;
		FTM0_MOD = mod;
		FTM0_SC = FTM_SC_CLKS(ftmClockSource) | FTM_SC_PS(prescale);
	}
#ifdef FTM2_CH0_PIN
	  else if (pin == FTM2_CH0_PIN || pin == FTM2_CH1_PIN) {
		FTM2_SC = 0;
		FTM2_CNT = 0;
		FTM2_MOD = mod;
		FTM2_SC = FTM_SC_CLKS(ftmClockSource) | FTM_SC_PS(prescale);
	}
#endif
}
 
This is fantastic, thank you. I need to spend some time with the reference manual to understand your code and what else may be impacted by changing the clock source for the FTM. No doubt it may mean that I should have chosen certain pins for the outputs of interest. (My board uses pins 23 and 32 as designed.) And when I have a chance to play around with it, I will report back.
 
modify the original analogWriteFrequency as follows

I can confirm that this works as desired, thank you so much.

Am I correct that, based on section 36.3.3 of the Reference Manual (pp. 780-81), each FTM may select a different clock and/or prescale? But each channel within a given FTM shares that clock and prescale selection (based on 36.3.6)? So to efficiently accomplish slow PWM for only two outputs, those should be on the same FTM (e.g., pins 3/4 for FTM1 or pins 25/32 for FTM2)? (I may be able to accommodate that in the next revision of my board.)

Do you have advice for how I sort out what other Teensyduino functions use FTM? I may need somewhat faster PWM but likely not. What about the SPI/I2C/UART peripherals? As I understand it, they do not use FTM, so will be safe?
 
I can confirm that this works as desired, thank you so much.

Good news. I've updated the website with a link to this thread. Hopefully anyone else who needs this will find the code.

At this time, my general feeling is very few people will use such low PWM speeds, so I'm not really inclined to add bloat to the official analogWriteFrequency().

Am I correct that, based on section 36.3.3 of the Reference Manual (pp. 780-81), each FTM may select a different clock and/or prescale? But each channel within a given FTM shares that clock and prescale selection (based on 36.3.6)? So to efficiently accomplish slow PWM for only two outputs, those should be on the same FTM (e.g., pins 3/4 for FTM1 or pins 25/32 for FTM2)?

Yes, that's how it works. This is explained on the web page: "The PWM signals are created by hardware timers. PWM pins common to each timer always have the same frequency, so if you change one pin's PWM frequency, all other pins for the same timer change."

Do you have advice for how I sort out what other Teensyduino functions use FTM? I may need somewhat faster PWM but likely not. What about the SPI/I2C/UART peripherals? As I understand it, they do not use FTM, so will be safe?

Unfortunately, there simply isn't a comprehensive list of which things use which hardware. Maybe there should be someday?

The FTM timers are generally not used by any communication stuff, except AltSoftSerial. Some timer-based libraries use FTM, like PulsePosition and FreqMeasure. There are a lot of different timer types, so a good number of features actually use the other timers.

In the first year of Teensy 3.0, I made a huge effort to port all the widely used libraries using different timers, so nearly everything could be used together, even in combinations that conflict on regular Arduino. But obviously that couldn't be kept up forever.
 
Happy that I could help. Since the extended functionality of my analogWriteFrequency() does not eat much bread (8 bytes more RAM for the two additional variables) and one if/else statement to set these depending on the desired frequency, it could, I think, well be integrated into further teensyduino versions without compatibility issues.

BTW: My compliments, Paul, for the "pseudo logarithm in a loop" to determine the optimal prescaler value. Simple and elegant!
 
Last edited:
What happens if you have analogWriteFrequency() set to a value lower than it's minimum value? I have an application where I need a PWM signal that is adjustable from 1-20Hz, I have it running using analogWriteFrequency with a user input to change it's value. Setting it to 1Hz and simply watching an led blink on and off it appears to be "about" 1Hz and it responds with higher and lower duty cycle in a way that appears (by eye at least) to be working. I don't need super precise timing on this so I figured "good enough" without trying to actually measure what it's doing, but I'm wondering if I might run into problems doing this.
 
Why not just use theremingenieur's code, which will accommodate the frequency range.

Because I don't really understand how it works. :)

I'll probably give it a try. If nothing else it can be a learning experience, but for the sake of understanding what's going on better, I'm curious what the actual problem is or if I even have a problem. For example if it's something as simple as the pwm frequency just won't go that low, it will only go down to it's minimum value (very easy to check), or something more subtle like maybe the duty cycle will be inaccurate (also not terribly difficult to measure, but also probably liveable for my application).

I can answer the question for myself maybe by sticking a scope on the output and seeing what happens. I'll probably do that too.

Just want to understand more if possible. :cool:
 
To understand the principle of operation of Paul's code, with or without my extension, you just need to have a look into the K20 Sub-Family Reference Manual. Pages from 769 describe exactly the FTM configurations which will allow you to understand how the FTM clock, the FTM prescaler value, the FTM modulo value, and the FTM channel control value work together to obtain a PWM with specific frequency. There are basically two possible clock sources from which one does depend on F_CPU and one is fixed. Then there are 8 prescaler values. That means that there are only very few specific PWM frequencies at which native full 16bit resolution PWM is possible. And there are also limits for the PWM frequency (highest prescaler and modulo values). For all other frequencies, the modulo value has to be reduced to obtain the desired frequency which unfortunately reduces the resolution, too. That's why the analogWrite code scales the input value depending on analogWriteResolution() to match the reality.
Kudos to Paul for this approach which is easy to use, abstract, and thus most Arduino-ish because you can just set two parameters, frequency and resolution, and then start writing PWM values to a pin and that's it, fully without knowing which FTM is involved, at which clock source and with which prescaler and modulo value it runs and which resolution would theoretically be possible at that frequency. Unfortunately, that is totally intransparent, too, because without deeper knowledge of the FTM mechanisms, you never know and understand what really happens in the FTM, how your input value is processed to obtain the desired result, and where the limits are. And there is no simple access to other great FTM features like synchronized or center-aligned PWM, dead time insertion, and so on....
 
I'm using low frequency stuff (FYI, for the sake of head count)

I'm going to be using this code a lot for low frequency stuff, just a quick note for the sake of head count. Thanks so much for this contribution Theremingenieur! Once again Teensy, and the Teensy community, has saved my ass.

Good news. I've updated the website with a link to this thread. Hopefully anyone else who needs this will find the code.

At this time, my general feeling is very few people will use such low PWM speeds, so I'm not really inclined to add bloat to the official analogWriteFrequency().
 
Just to confirm. I no longer need to overload the analogWriteFrequency function with Theremingenieur changes as it's been updated as of 1.23 Teensyduino? The 1.23 notes say that it allows sub 1hz "input". But I am assuming you meant output... http://www.pjrc.com/teensy/td_download.html

Unfortunately, you'll have to continue to overload the analogWriteFrequency() code. I think that the mention "analogWriteFrequency() now allow precise sub-1 Hz input" on the website means that the frequency parameter can now be a float number (with decimals) instead of an integer.

I can understand that Paul doesn't want as he said "add bloat" to that function, but we are talking here about a meaningful extension of the usable range. And my "bloat" adds just if/then clause to select the appropriate clock source and a variable which allows the following code to determine the prescaler and mod values as before. I will make another effort and do that stuff again on github, followed by a pull request to make the decision easier for Paul. :)
 
I'm a doofus. Failed to notice that Theremingenieur wrote a new function, not overloading the original analogWriteFrequency function.
 
But I should just be able to reference a hand-spun analogWriteFrequencySlow function that you have written in my into my code and not have to overload anything, right?
 
Back
Top