Teensy LC input capture test
Affiliate Disclosure: Some links on this blog are affiliate links, meaning I earn a commission at no extra cost to you. I only recommend products and services I trust and use myself.

Teensy LC input capture test

As a third step to learning the Teensy LC, I decided to tackle input capture. I discovered that there is no separate interrupt vector for input capture; it is the same vector used by the timer interrupt. This means that if we are looking for a timer overflow event as well as a pin change for input capture, we must check for that specific interrupt flag within the interrupt service routine (ISR).

To try the example, connect a button to pin 20 of the Teensy LC, and connect the other side of the button to the Teensy LC’s ground (G) pin. Run the code and open a serial terminal. Whenever you press the button, you will see the time (in nanoseconds) between edges. Push the button a number of times, and you’ll see examples of switch bounce.

/**
 * Teensy_LC_IC_Test.ino
 * Shawn Hymel
 * May 21, 2015
 *
 * Set up an interrupt service routine and input capture to 
 * display the time between edges of a button push. It will show 
 * button bounce!
 *
 * Pin 20 on the Teensy LC is PORT D, pin 5. It is pin 46 on the
 * 48-pin QFN. This is tied to TPM0_CH5 with pin function ALT4.
 *
 * Connect: Teensy LC pin 16 -> Button -> Teensy LC GND
 *
 * Teensy LC schematic: https://www.pjrc.com/teensy/schematic.html
 * MKL26Z64 datasheet: https://www.pjrc.com/teensy/KL26P121M48SF4RM.pdf
 */

volatile uint32_t count = 0;
volatile uint32_t prev_val = 0;
volatile uint32_t ovf_count = 0;
volatile uint8_t ic_flag = 0;
float gap;

void setup() {
  
  // Set up our Serial port
  Serial.begin(9600);
  
  // The order of setting the TPMx_SC, TPMx_CNT, and TPMx_MOD
  // seems to matter. You must clear _SC, set _CNT to 0, set _MOD
  // to the desired value, then you can set the bit fields in _SC.
  
  // Clear TPM0_SC register (p. 572)
  FTM0_SC = 0;
  
  // Reset the TPM0_CNT counter (p. 574)
  FTM0_CNT = 0;
  
  // Set overflow value (modulo) (p. 574)
  FTM0_MOD = 0xFFFF;
  
  // Set TPM0_SC register (p. 572)
  // Bits | Va1ue | Description
  //  8   |    0  | DMA: Disable DMA
  //  7   |    1  | TOF: Clear Timer Overflow Flag
  //  6   |    1  | TOIE: Enable Timer Overflow Interrupt
  //  5   |    0  | CPWMS: TPM in up counting mode
  // 4-3  |   01  | CMOD: Counter incrememnts every TPM clock
  // 2-0  |  000  | PS: Prescale = 1
  FTM0_SC = 0b011001000;
  
  // Set TPM0_C5SC register (Teensy LC - pin 20) (p. 575)
  // As per the note on p. 575, we must disable the channel
  // first before switching channel modes. We also introduce
  // a magical 1 us delay to allow the new value to take.
  // Bits | Va1ue | Description
  //  7   |    0  | CHF: Do nothing
  //  6   |    1  | CHIE: Enable Channel Interrupt
  // 5-4  |   00  | MS: Input capture
  // 3-2  |   11  | ELS: Capture on rising and falling edge
  //  1   |    0  | Reserved
  //  0   |    0  | DMA: Disable DMA
  FTM0_C5SC = 0;
  delayMicroseconds(1);
  FTM0_C5SC = 0b01001100;
  
  // Set PORTD_PCR5 register (Teensy LC - pin 20) (p. 199)
  // Bits | Value | Description
  // 10-8 |   100 | MUX: Alt 4 attach to TPM0_CH5 (p. 179)
  //  7   |     0 | Reserved
  //  6   |     0 | DSE: Low drive strength
  //  5   |     0 | Reserved
  //  4   |     0 | PFE: Disable input filter
  //  3   |     0 | Reserved
  //  2   |     0 | SRE: Fast slew rate if output
  //  1   |     1 | PE: Enable pull-up/down
  //  0   |     1 | PS: Internal pull-up
  PORTD_PCR5 = 0b10000000011; 
  
  // Nested Vector Interrupt Controller (NVIC) (p. 57)
  // Also: Chapter 4.2 of the Generic User Guide
  // Our FTM0 interrupt number is 17 (as per kinetis.h). We can
  // use that to set up our interrupt vector and priority.
 
  // Set the urgency of the interrupt. Lower numbers mean higher
  // urgency (they will happen first). Acceptable values are
  // 0, 64, 128, and 192. Default is 128. We set the priority
  // (2nd byte) in the register for the FTM0 interrupt (&E000_E410) 
  // to 64.
  NVIC_SET_PRIORITY(IRQ_FTM0, 64);
  
  // Enable the interrupt vector. In this case, we want to execute
  // the ISR (named "ftm0_isr()" for Teensy) every time TPM0 
  // overflows. We set bit 17 of &E000_E100.
  NVIC_ENABLE_IRQ(IRQ_FTM0);
  // Same as: NVIC_ISER0 |= (1 << 17);
}

void loop() {
  
  // If an input capture event has occurred, print the time
  // between edges
  if ( ic_flag ) {
    
    // Time between edges
    // (1 / 48MHz) * (1 prescaler) = 20.83 ns per count
    gap = count * (1 / 48.0);
    Serial.print("Time between edges: ");
    Serial.print(gap);
    Serial.println(" ns");
    
    // Reset input capture flag
    ic_flag = 0;
  }
}

// "ftm0_isr" is an interrupt vector defined for the Teensy
void ftm0_isr(void) {
  
  uint32_t val;
  
  // If we got to this ISR via timer overflow, we want to clear
  // the timer overflow flag. We also want to count the number of
  // overflows that have occurred.
  if ( FTM0_SC & (1 << 7) ) {
    FTM0_SC |= (1 << 7);
    ovf_count++;
  }
  
  // If we got here from the input capture, we want to clear the
  // channel flag.
  if ( FTM0_C5SC & (1 << 7) ) {
    FTM0_C5SC |= (1 << 7);
    
    // We should only continue if we have handled the previous
    // input capture
    if ( !ic_flag ) {
    
      // Retrieve the counter value and compare it to the last one.
      // Take into account any overflows that have occured.
      val = FTM0_C5V;
      if ( ovf_count == 0 ) {
        count = val - prev_val;
      } else {
        count = (0x10000 - prev_val) + ((ovf_count - 1) << 16) + val;
      }
      
      // Save the counter value and reset the overflow counter
      prev_val = val;
      ovf_count = 0;
      
      // Set a flag so that we can read the count in the main loop
      ic_flag = 1;
    }
  }
}

Output of Teensy LC input capture

Leave a Comment

Your email address will not be published. Marked fields are required.