FTM Channel compare interrupts (Teensy 3.5)

Status
Not open for further replies.

noisymime

Active member
So I thought I'd do a bit of a write up around some of the work I'm doing with the FTM timer in the hopes of potentially making life easier for others wanting to do this in the future. The background is that I'm adapting an existing project (http://speeduino.com) to support Teensy as well as the AVR platform. This code makes extensive use of the AVRs timer compares to trigger outputs for controlling fuel and ignition in a 'PWM like' type way. I say 'PWM like' as, unlike normal PWM, both the pulse width and the frequency are continually variable rather than just the pulse width.

With the Teensy 3.5, the FTM timers are terrific for this and offer a number of applicable benefits compared to the AVR (Such as 8 channels per timer rather than 3). Whilst there are a few posts and things around the timers, most of them appear to be either for input capture or edge aligned PWM modes rather than just compare mode. I put together the following test sketch, just to get a handle on how to setup the FTM and its ISR. It simply outputs a fixed frequency, 50% duty cycle pulse, on 1 channel, but it demonstrates the ability to have 8 separate interrupts that can each be set individually to a given target point in the future (Provided it is sooner than the maximum duration of the FTM).

Code:
volatile bool alternate = 0;
byte outputPin = 32 ;

#define MAX_TIMER_PERIOD 139808 // 2.13333333uS * 65535
#define uS_TO_TIMER_COMPARE(uS) ((uS * 15) >> 5) //Fixed point equivalent of uS / 2.13333. Converts a given number of uS into the required number of timer ticks until that time has passed. Note this is specific to this prescaler value. If you change the prescaler or clock, you must update this macro accordingly

void ftm0_isr(void) 
{
  
  if(FTM0_C0SC & FTM_CSC_CHF) { FTM0_C0SC &= ~FTM_CSC_CHF; } //DO STUFF FOR CHANNEL 1 HERE
  if(FTM0_C1SC & FTM_CSC_CHF) { FTM0_C1SC &= ~FTM_CSC_CHF; } //DO STUFF FOR CHANNEL 2 HERE
  if(FTM0_C2SC & FTM_CSC_CHF) { FTM0_C2SC &= ~FTM_CSC_CHF; } //DO STUFF FOR CHANNEL 3 HERE
  if(FTM0_C3SC & FTM_CSC_CHF) { FTM0_C3SC &= ~FTM_CSC_CHF; } //DO STUFF FOR CHANNEL 4 HERE
  if(FTM0_C4SC & FTM_CSC_CHF) { FTM0_C4SC &= ~FTM_CSC_CHF; } //DO STUFF FOR CHANNEL 5 HERE
  if(FTM0_C5SC & FTM_CSC_CHF) { FTM0_C5SC &= ~FTM_CSC_CHF; } //DO STUFF FOR CHANNEL 6 HERE
  if(FTM0_C6SC & FTM_CSC_CHF) { FTM0_C6SC &= ~FTM_CSC_CHF; } //DO STUFF FOR CHANNEL 7 HERE
  if(FTM0_C7SC & FTM_CSC_CHF) { FTM0_C7SC &= ~FTM_CSC_CHF; } //DO STUFF FOR CHANNEL 8 HERE

  //Demo for actually doing something:
  if(alternate)
  {
    digitalWrite(outputPin, HIGH);
    alternate = 0;
    FTM0_C0V += uS_TO_TIMER_COMPARE(1000);
  }
  else
  {
    digitalWrite(outputPin, LOW);
    alternate = 1;
    FTM0_C0V += uS_TO_TIMER_COMPARE(1000);
  }
}

void setup() {

  pinMode(outputPin, OUTPUT);
  
  FTM0_MODE |= FTM_MODE_WPDIS; // Write Protection Disable
  FTM0_MODE |= FTM_MODE_FTMEN; // Flex Timer module enable
  FTM0_MODE |= FTM_MODE_INIT; // Perform init functions

  FTM0_SC = 0x00; // Set this to zero before changing the modulus
  FTM0_CNTIN = 0x0000; //Shouldn't be needed, but just in case
  FTM0_CNT = 0x0000; // Reset the count to zero
  FTM0_MOD = 0xFFFF; // max modulus = 65535
  
  /*
   * Enable the clock for FTM0 
   * 00 No clock selected. Disables the FTM counter.
   * 01 System clock
   * 10 Fixed frequency clock
   * 11 External clock
   */  
  FTM0_SC |= FTM_SC_CLKS(0b1);

  /*  
   * Set Prescaler 
   * This is the slowest that the timer can be clocked (Without used the slow timer, which is too slow). It results in ticks of 2.13333uS on the teensy 3.5:
   * 60000000 Hz = F_BUS
   * 128 * 1000000uS / F_BUS = 2.133uS
   * 
   * 000 = Divide by 1
   * 001 Divide by 2
   * 010 Divide by 4
   * 011 Divide by 8
   * 100 Divide by 16
   * 101 Divide by 32
   * 110 Divide by 64
   * 111 Divide by 128
   */
  FTM0_SC |= FTM_SC_PS(0b111);

  //Setup the channels (See Pg 1014 of K64 DS).
  //FTM0_C0SC &= ~FTM_CSC_ELSB; //Probably not needed as power on state should be 0
  //FTM0_C0SC &= ~FTM_CSC_ELSA; //Probably not needed as power on state should be 0
  //FTM0_C0SC &= ~FTM_CSC_DMA; //Probably not needed as power on state should be 0
  FTM0_C0SC &= ~FTM_CSC_MSB; //According to Pg 965 of the datasheet, this should not be needed as MSB is reset to 0 upon reset, but the channel interrupt fails to fire without it
  FTM0_C0SC |= FTM_CSC_MSA; //Enable Compare mode
  FTM0_C0SC |= FTM_CSC_CHIE; //Enable channel compare interrupt
  //Duplicate the above 3 lines for each compare channel you want active

  // enable IRQ Interrupt
  NVIC_ENABLE_IRQ(IRQ_FTM0);

  //Set the initial compare value
  FTM0_C0V = 200;
  
  // initialize serial
  Serial.begin(115200);
}

void loop() 
{
  //Nothing here
}

A couple of things to note about this:
  • The F_BUS frequency appears to be 60Mhz, which is what I had assumed it would be on the 120Mhz 3.5, but I hadn't seen this confirmed anywhere
  • I bashed my head against the desk for a few hours until I found that the FTM_CSC_MSB bit has to be manually turned off for each channel. This appears (at least to me) to be counter to the datasheet which specifies a 0 value for this upon reset, but without the manual setting of this, the compare interrupt will NOT fire. No idea if I missed something somewhere in the sheet that mentions why MSB would be set already upon reset :confused:
  • It's a slight shame that the maximum prescaler is 128. 256 would've made my life a lot easier in having common schedule logic between the Teensy and the AVR, but its a small thing
 
Last edited:
Status
Not open for further replies.
Back
Top