I have written my own function by the name of changeFrequency as an alternative to the analogWriteFrequency function.
The problem with the analogWriteFrequency function is that its call triggers a "glitch", i.e. a LOW shortly followed by a HIGH (HIGH being the beginning of the duty cycle) on the output pin. This may be a problem for those applications where the function needs to be called frequently to refresh the frequency, for example when the pin is used to generate step pulses for stepper motor drivers when updating their desired speed: it may not be desirable that a step is generated each time the function is called.
The reason why a glitch is generated upon each function call is that the function resets the FTM (Flex Timer Module page 769) counter (FTMx_SC = 0; FTMx_CNT = 0; with x being either equal to 0,1 or 2 on a Teensy 3.2). We need a new changeFrequency function which does not reset the counter each time, but just updates the frequency of the output pin, so that its call does not trigger a LOW followed immediately by a HIGH on the pin.
There are 4 important registers (or parameters) to take into account:
- FTMx_SC_CLKS (page 781), aka clock source : either 1 = F_TIMER (36mHz on a Teensy 3.2, for frequencies above 4.3Hz) or 2 = 31.25kHz, i.e. 1152 times slower (the external 16mHz oscillator divided by 512, for frequencies below 4.29Hz).
- FTMx_SC_PS (page 781), aka prescale factor, or prescale, a 3 bit number which drives the number by which the clock source frequency is divided: divider = 2^prescale, with 0<=prescale<=7 and therefore 1<=divider<=128)
- FTMx_MOD (page 782), aka MOD for modulo, which is the value between 0 and 65535 up to which the FTM counter counts from CNTIN, namely zero, after which the counter drops to zero and counts up again (FTM counts up to MOD+1).
- FTMx_CnV (page 785), aka cval, with the channel number n being equal to 0 or 1 (or more on FTM0 on Teensy 3.2). Cval is the counter value below which the output pin is HIGH and above which the output pin is LOW (in order to drive the PWM). Cval must be greater than zero and smaller than MOD. If cval is larger than MOD, then the output pin is always HIGH, it stalls, there is no longer any frequency ! (duty cycle is exactly 100%). This cval value is neither updated by the analogWriteFrequency function nor by my new changeFrequency function, but by the analogWrite function.
Frequency is updated by updating these registers. But it is important to note that each of them will not be taken into account as soon as registers are written into: clock source, prescale and cval are taken into account immediately, or upon the next clock cycle (example page 781:
The new prescaler factor affects the clock source on the next system clock cycle after the new value is updated into the register bits), whereas MOD is only taken into account when the counter reaches the previous MOD value (page 847:
MOD register is updated after MOD register was written and the FTM counter changes from MOD to CNTIN, namely 0).
Therefore, updating the parameters may slow down considerably, or even stall, the output pin before the new frequency is taken into account. This is in particular the case when the new frequency is lower than the previous one in such a way that it increases the prescale and/or or decreases the clock source frequency. This is because it would take longer for the counter to reach its previous MOD value. Depending on the application, this may or may not be a serious issue. For example, if all frequencies are above 4.3Hz, the clock source is always 36mHz, and the worst that could happen is an interim period that would be twice as long as the previous one. If frequencies can be both above and below 4.3Hz, then the counter speed may change dramatically (by a factor at least 1152 = 36mHz/31.25KHz) and that may be an issue depending on the current MOD value. The output pin may stall depending on the previous duty cycle, namely on the previous cval value. If duty cycle is not an issue (it is not an issue in applications driving stepper motor drivers), then it is recommended to apply a low duty cycle, namely have a low cval, so that the new MOD cannot be below the old cval.
An then it is always possible to force a FTM counter reset in situations where sharp frequency changes may lead to unwanted results such as output pin temporary freeze. In my changeFrequency function, I have decided to reset FTM if frequency increases dramatically in such a way that prescale or clocksource decreases. But this can of course be adapted to cope with specific situations.
The table below shows which frequency ranges are handled by each combination of prescale and clock source:
As with the original analogWriteFrequency function, it is recommended to update the duty cycle just after the function is called.
First, the usual analogWriteFrequency function must be called at least once in order to initialize FTM properly.
analogWriteFrequency(pin,frequency); // counter initialized properly
analogWrite(pin, 1); // low duty cycle
Then:
changeFrequency(pin, frequency); // change frequency without resetting the counter
analogWrite(pin, 1); // low duty cycle
Code:
void changeFrequency(uint8_t pin, float frequency) {
// Modification of the original analogWriteFrequency function.
// This function does not reset counter systemically
// in order to avoid glitches in the output pin each time it is called
uint32_t prescale, mod, ftmClock, ftmClockSource;
float minfreq;
if (frequency < (float)(F_TIMER >> 7) / 65536.0f) {
// frequency is too low for working with F_TIMER:
ftmClockSource = 2; // Use alternative 31250Hz clock source
ftmClock = 31250; // Set variable for the actual timer clock frequency
} else {
ftmClockSource = 1; // Use default F_TIMER clock source
ftmClock = F_TIMER; // Set variable for the actual timer clock frequency
}
for (prescale = 0; prescale < 7; prescale++) {
minfreq = (float)(ftmClock >> prescale) / 65536.0f;
if (frequency >= minfreq) break; //prescale is set to the lowest possible prescale value for maximum precision
}
mod = (float)(ftmClock >> prescale) / frequency - 0.5f; // counter will count from zero to MOD+1
if (mod > 65535) mod = 65535;
if (pin == 3) { // this is FTM1 on Teensy 3.2
// alternatively, if(pin == FTM1_CH0_PIN || pin == FTM1_CH1_PIN) {
// old prescale is FTM1_SC & (uint32_t)(7)
// old ftmClockSource is (FTM1_SC>>3)&(uint32_t(3))
// old MOD is FTM1_MOD
if (prescale<(FTM1_SC & (uint32_t)(7))||ftmClockSource<((FTM1_SC>>3)&(uint32_t(3)))) {
// FTM is reset if frequency is increased sharply
FTM1_SC = 0;
FTM1_CNT = 0;
}
FTM1_MOD = mod; // if FTM had not been reset, MOD actually updated once counter has reached previous MOD
FTM1_SC = FTM_SC_CLKS(ftmClockSource) | FTM_SC_PS(prescale); // updating both clocksource and prescale immediately
FTM1_MOD = mod; // updating MOD again (recommended in certain situations)
}
if (pin == 25) { // this is FTM2 on Teensy 3.2
// alternatively, if(pin == FTM2_CH0_PIN || pin == FTM2_CH1_PIN) {
if (prescale<(FTM2_SC & (uint32_t)(7))||ftmClockSource<((FTM2_SC>>3)&(uint32_t(3)))){
FTM2_SC = 0;
FTM2_CNT = 0;
}
FTM2_MOD = mod;
FTM2_SC = FTM_SC_CLKS(ftmClockSource) | FTM_SC_PS(prescale); //Use ftmClockSource instead of 1
FTM2_MOD = mod;
}
}