TimerOne -- setPeriod not working inside ISR function [Teensy 3.2]

jukedude

Member
Hi all!

My setup: Teensy 3.2 @ 96MHz. I wanted to setup a Timer interrupt whose interval would vary -- whenever the interrupt fires, I wish to change the Timer period. I've used the TimerOne library to do this on an Arduino Uno (16MHz). The code ran as expected, on the teensy however it did not.
I've only just started working with a teensy, so either I don't understand how to use TimerOne to do this OR TimerOne does not support this at all.

Here's the code:
Code:
#include <TimerOne.h>
void setup(void)
{
  pinMode(13, OUTPUT);
  pinMode(15, OUTPUT):
  Timer1.initialize(9000);
  Timer1.attachInterrupt(blinkLED);
  Serial.begin(115200);
}
// The interrupt will blink the LED, and
// mainloop will print FTM1 configuration

unsigned long last_event_micros = 0;
volatile uint8_t lstate = 0, wflag = 0;

void blinkLED(void)
{
  digitalWrite(15, HIGH);
  lstate = !lstate;
  wflag = 1;
  digitalWrite(13, lstate);
  /*
  if (lstate == 0){
    FTM1_MOD = 900;
  }
  else{
    FTM1_MOD = 65000;
  }
  */
  //Timer1.setPeriod(9000);
  digitalWrite(15, LOW);
}

void loop(void)
{
  if (wflag == 1){
    Serial.print(micros() - last_event_micros);
    last_event_micros = micros();
    Serial.print(" ");
    Serial.print(FTM1_MOD);
    Serial.print(" ");
    Serial.println(FTM1_SC & 0x7);
    wflag = 0;
  }
}
Run the code as is first. Then uncomment either setPeriod() or the FTM1_MOD direct access -- in both cases the mainloop would show that the MOD is updated but the micros() print-out or a scope would tell you that it didn't work.
It seems MOD is updated, with the correct value too; but the interrupt triggers at the wrong time. I do not understand why. See attached photos.
In both images, the timer is initialized to 50us. Please ignore other signals, they don't matter. Pin 15 is toggled by the ISR function, so we can "see" the ISR active. When I comment setPeriod(), the ISR fires every 50us as expected. With setPeriod uncommented, the interrupt seems to be firing fast, at 5us. I tried different initial timer periods but the ISR still fired @ 5us, ie, ther's no correlation between the 50us and 5us.

Possible Work-around
I delved into the K20 data sheet and also this very useful Application Guide on FlexTimer. The guide has C programs on synchronous PWM updates and details on how to synchronise the MOD update (hardware and software triggers). After wrapping my head around the terminology and the vast configuration options available, I realised many things:
  1. TimerOne does not configure any MOD/CnV synchronisation bits
    • According to data sheet sec. 36.4.10.2 MOD register update,
      register will be updated after counter transitions from MOD to MOD-1.
      We can see that this indeed happens (serial output shows). But the ISR doen't trigger properly. So, I set FTM_MODE_FTMEN so that MOD sync is now by data sheet sec. 36.4.11.4 (I believe the datasheet is also calling this "enhanced PWM synchronisation" (FTMEN=1, FTM_SYNCONF_SYNCMODE=1) and the other one (which TimerOne uses, by default "legacy synchronisation")
    • Now, I depended on the code examples of the Guide document sec 3.11, particularly sec 3.11.3.
    • MOD (and CnV) are updated at a "load point" either one of CNTMIN or CNTMAX, (see FTM_SYNC register desc.) TimerOne does not set any "load point" bit.
    • The program below works exactly as I wanted it to. I haven't attached scope images for this program. You can even play with the FTM1_SC_PS bits and MOD in the ftm_isr()

    Code:
    volatile uint8_t lstate = 0, wflag = 0;
    unsigned long last_micros_event = 0;
    
    void setup(){
      Serial.begin(115200);
      pinMode(13, OUTPUT);
      FTM1_SC = 0;
      FTM1_FMS = 0;
      FTM1_MODE = FTM_MODE_FTMEN; // WPDIS set, all registers accessible
      FTM1_SYNC |= FTM_SYNC_CNTMIN; // CNTMIN load point
      // EDIT 1 -----------------------------------------------------------------------------------
      // Enhanced PWM sync, allow software to trigger synch via SWSYNC bit, Reset timer and load MOD as soon as SWSYNC is set by software
      // don't wait till next loading point.
      // Naturally, I set the SWSYNC bit in the ISR..
      FTM1_SYNCONF |= FTM_SYNCONF_SYNCMODE | FTM_SYNCONF_SWWRBUF [COLOR="#008000"]| FTM_SYNCONF_SWRSTCNT[/COLOR]; // EDIT1 (SWRSTCNT)
      FTM1_MOD = 60769;
      FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_PS(7) | FTM_SC_TOIE;
      NVIC_ENABLE_IRQ(IRQ_FTM1);
    }
    
    void loop(){
      if (wflag == 1){
        Serial.print(micros() - last_event_micros);
        last_event_micros = micros();
        Serial.print(" ");
        Serial.print(FTM1_MOD);
        Serial.print(" ");
        Serial.println(FTM1_SYNC);
        wflag = 0;
      }
    }
    
    void ftm1_isr(){
      // EDIT1 -----------------------------------------------------------------------------------
      // no need to do FTM1_SC = 0
      uint32_t sc = FTM1_SC;
      if (sc & 0x80) FTM1_SC = (sc & 0x7f);
      if (lstate){
        FTM1_MOD = 65535;
      }
      else{
        FTM1_MOD = 100;
      }
      digitalWrite(13, lstate);
      lstate = !lstate;
      wflag = 1;
      // this tells the synchronisation hardware that CPU has written to MOD buffer.
      // and asks it to "latch" the buffered value into MOD [COLOR="#008000"]right now because of SWRSTCNT.[/COLOR]
      FTM1_SYNC |= FTM_SYNC_SWSYNC;
    }
  2. TimerOne works in CPW aligned mode :(
    • That's a good thing because it basically doubles the max. supported period, but.. read on..
    • In the code above, the load point is set at CNTMIN. The ISR will fire at CNTMAX (always, no matter what config).
    • In CPWM, MOD will be updated (MOD - CNTIN) * timerCLK later. It should have updated ASAP. Setting load point as CNTMAX would be worse, as the update happens 2*(MOD-CNTIN) * timerCLK later, at the next load point -- whereas my application requires it ASAP.
    • In non-CPWM, one of the CNTIN or CNTMAX should be set (I have CNTMIN).
      OLD: So the MOD is updated right after ISR occurs -- exactly what I need!
      EDIT1: Due to the SWRSTCNT bit, the MOD value is loaded when SWSYNC is set, instead of waiting for the loading point that would come after SWSYNC was set. This is important when PS bits are also changed with MOD. Without SWRSTCNT, PS would get updated but MOD would update at next load point, which is atleast (oldMOD - CNTIN)*timerCLK away. See datasheet sec 36.4.11.4 for diagrams and a flowchart for MOD synchronisation.

I don't know why TimerOne (default) legacy synchronisation failed.
@Paul Did you intend to support this use case? IMO, it will be difficult to simply integrate this because of the CPWM conflict.

PS I've also tried calling setPeriod outside the ISR function with same problem. I can post the code upon request.

Thanks,
Ananya
 

Attachments

  • initialize(50)_no_setPeriod.png
    initialize(50)_no_setPeriod.png
    21.7 KB · Views: 125
  • initialize(50)_setperiod(50).png
    initialize(50)_setperiod(50).png
    22.9 KB · Views: 118
Last edited:
Also consider this program. When you run it as is, the LED would blink fast for 5 sec and then blink slowly. Works like a charm.
Uncomment the #define and you'll see what I'm talking about.
Code:
#include <TimerOne.h>

//#define DESTROY
void setup(void)
{
  pinMode(13, OUTPUT);
  Timer1.initialize(20000);
  Timer1.attachInterrupt(blinkLED);
  Serial.begin(115200);
}


// The interrupt will blink the LED, and
// mainloop will print FTM1 configuration

unsigned long last_event_micros = 0;
volatile uint8_t lstate = 0, wflag = 0;

void blinkLED(void)
{
  lstate = !lstate;
  wflag = 1;
  digitalWrite(13, lstate);
  //Timer1.setPeriod(9000);
}

void loop(void)
{
  if (wflag == 1){
    Serial.print(micros() - last_event_micros);
    last_event_micros = micros();
    Serial.print(" ");
    Serial.print(FTM1_MOD);
    Serial.print(" ");
    Serial.println(FTM1_SC & 0x7);
    wflag = 0;
    #ifdef DESTROY
      Timer1.setPeriod(120000);
    #endif
  }
  #ifndef DESTROY
    delay(5000);
    Timer1.setPeriod(120000);
  #endif
}
 
Try the following:

in TimerOne.h you find :
Code:
    uint32_t sc = FTM1_SC;
    //FTM1_SC = 0; //<<--- comment this line
    FTM1_MOD = pwmPeriod;
    FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_CPWMS | clockSelectBits | (sc & FTM_SC_TOIE);

Comment the marked line, and try your example again. Is this the wanted behaviour ?
 
No that did not work. FTM_MOD remains 49151 = 0xBFFF no matter what. The datasheet says that MOD buffer value will immediately latch if FTMx_SC_CLKS bits are zero, ie, Timer has no clock source. That is why FTM1_SC = 0 was present.
 
The speed changes because the prescaler bits changed. sePeriod computes the correct MOD and PS bits but only PS is updated. You can see all that int Serial output in the program (FTM1_SC & 0x7) are the FTM1_SC_PS[2:0].
If FTM1_MOD does get updated, perhaps later, then who is writing 0xBFFF? I really think MOD is stuck at 0xBFFF.
 
Last edited:
"Initialize the FTM counter, by writing to CNT, before writing to the MOD register to avoid confusion about when the first counter overflow will occur."

Ok.. so let's try

Code:
    FTM1_SC = 0;
    FTM1_CNT = 0; //<<-insert this
    FTM1_MOD = pwmPeriod;

Edit: FTM1_CNT = FTM1_CNT; should work too - the question is, which is better.
 
Last edited:
Important Edit
I discovered a flaw in the work-around solution provided above. I have edited the program and a few of the points I wrote below it. I've highlighted edits wherever possible
@Frank B nothing that concerns our discussion has changed
 
Hm setting the counter-value works for me, with your sketch in post #2
the only "problem" is, that it displays the half counter-value (resp. us) - but thats a problem for the first overflow only, i think.

Maybe we have to disable the up/down counting.
 
.... to disable upd/down counting:
Code:
const unsigned long cycles = (F_TIMER / [U][I][B]1000000[/B][/I][/U]) * microseconds;
.
.
.
    FTM1_CNT =FTM1_CNT;
    FTM1_MOD = pwmPeriod;
    FTM1_SC =[U][I][B] FTM_SC_CLKS(1)  | clockSelectBits | (sc & FTM_SC_TOIE);[/B][/I][/U]

Edit the bold lines.
 
Yeah that works perfectly :) Let me try this in the ISR function....
Works there too. \o/ I spent hours reading the datasheet, tried FTM1_SC etc but not FTM_CNT... It felt too easy a hack (but it is a hack).

The problem now is that setPeriod() doesn't work in CPWM mode. It is required to reset the counter to CNTIN for proper counting (weird)
Disabling the clock by FTM1_SC = 0 also reduces precision (though I agree it's so small that anyone can neglect it)
 
Last edited:
Is it now ok for you ?

That's really a race-condition : - frequent updates to FTM1_MOD.
But the fix above is expensive - costs half the "max" value..
 
Thanks for the help Frank! All is good for me.
You mean the Timer lost 60ms in CPWM?!

If you figure out why this fails in CPWM do share!
Do note that my "workaround" works in up counting mode only -- it forces CPWM also into up-counting, plus it's "recommended" by that guidebook and datasheet.
There is a way to use my workaround partially even in CPWM using FTM1_PWMLOAD reg, bit LDOK, as per the NxP guide. (...Nah, there isn't. See #21 below)

For anyone who wants to see sample code for as much control over MOD and CnV synchronisation, please see the NxP guide on FlexTimer

Regarding FTM1_CNT = FTM1_CNT vs 0, it doesn't matter as both the writes will set FTM1_CNT to CNTIN.
 
Last edited:
Thanks for the help Frank! All is good for me.
You mean the Timer lost 60ms in CPWM?!
Yes. Maybe there is a better way to fix it than to disable CPWM. I don't know the FTM well.

Regarding FTM1_CNT = FTM1_CNT vs 0, it doesn't matter as both the writes will set FTM1_CNT to CNTIN.
Ah, ok.. :)

The FTM is really a complicated beast.
 
I don't see how the fix is expensive.. There's no time loss. Instead of 120ms work got done in 60ms -- something ran faster. Is it possible that the counter did signed counting?
I think not beacuse CNTIN is 0... but I can't trust this datasheet anymore ;p
I don't see how this is a race condition either.

I totally agree -- FTM is a beast..
 
Last edited:
Expensive: Yes the max val of setPeriod() is only the half after that fix :(
It counted up, then down - now it counts up only.
 
The Timer overflow interrupt always occurs when the counter value reaches MOD. In CPWM the counter will continue down counting from MOD-1 to CNTIN.
When we set FTM1_CNT to CNTIN (which is zero), through setPeriod in the ISR or elsewhere, we effectively disrupt the counter -- making it jump to 0. Even though we've configured up/down counting, we force it into just up counting. That's why we saw half the period.

I think for this use case, there is no other way than to reset the timer value. Even my workaround does that, because of the FTM_SYNCONF_SWRSTCNT. The SWSYNC will reset CNT to CNTIN, while updating MOD.
I also tried the FTM_PWMLOAD_LDOK bit as suggested in the guide. It works only when prescalar is not changed, but not otherwise. Even LDOK updates the MOD value from buffer at "the next load point". That is disastrous because the PS bits would have changed instantly but MOD has not!
IMHO, the FlexTimer module cannot update MOD and PS together without resetting the counter. And FTM cannot be run in CPWM for this use case. Perhaps PIT is more suited for this...
The simple addition of FTM1_CNT = FTM1_CNT does the trick so that should be enough.

Thanks once again!
Ananya
 
Last edited:
Back
Top