Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 15 of 15

Thread: Howto use timer capture mode? Teensy 3.5

  1. #1

    Howto use timer capture mode? Teensy 3.5

    Hi,
    According to the data sheet it must be possible to create the following:
    Set up a timer to capture mode at rising and falling edges of an input. (Goal is to measure pulse length.) And to have an interrupt routine to calculate and store the last pulse length. Is there some code as a template, where I could find some information? While I am quite sure, that this must be possible, I am a bit overwhelmed by all the possibilities of the timers....
    I only need about 30ms of resolution, but an edge must not be totally missed.
    Thanks for some hints

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    23,021
    The timers are indeed very powerful but complex.

    Before jumping into the details, you might consider whether a much simpler approach with attachInterrupt() and micros() will meet your needs. The simplest way gives about 2 to 3 us accuracy, and it's usually not hard to get to 1 us accuracy by just raising one of the port change interrupt priorities. Since you said "only need about 30ms of resolution", this way might be worthwhile to try first. Just use attachInterrupt() to run a function when the pin changes, and in that function record the micros() time to a variable, and set a flag. Like with all interrupts, the variables need to be "volatile" and care is needed when reading them from the main program.

    However, that approach is sensitive to interrupt latency. If other code temporarily disable interrupts, or a higher priority interrupt is running when the pulse edge is detected, your code gets delayed until your interrupt can run. So you get the micros() timestamp somewhat later. Usually this sort of error is only a few microseconds unless you're running something bad like Adafruit_Neopixel which blocks interrupts for long times. While these errors are undesirable, if all you need is 30 ms, then a couple microseconds is almost nothing.

    The timers can do the capture all in hardware, so your interrupt can read a number from the precise moment the edge was detected. This gives you accuracy to within 1 cycle of F_BUS, which defaults to 60 MHz on Teensy 3.5.

    As far as example code, the FreqMeasure and PulsePosition libraries are the best place to start. Both of these use input capture.

    The full documentation can be found in the reference manual, at this page:

    https://www.pjrc.com/teensy/datasheets.html

    The FTM timer chapter is huge and loaded with very complex details. But the vast majority is the many special PWM features, so the first step is to ignore all that complicated stuff in the latter part of that chapter.

    Using input capture usually involves 3 steps.

    1: First you need to configure the timer to increment continuously from 0 to 65535 and roll over automatically. If you use a prescaler of 1 it increments at 60 MHz, which means it rolls over every 1.09 ms. To measure longer times, the simplest approach is to use a slower prescaler, but then you get less resolution and you might be only as good as using attachInterrupt.

    2: Next you configure one of the 8 channels (or 2 channels if using FTM1 or FTM2) to capture the counter. This is where you use the big table on page 964. While this looks complex, the thing to remember is each channel is only 2 registers. One for config and one for the actual captured count. All you need to do is write the correct config and the timer will automatically start capturing the rapidly incrementing counter when those edges happen.

    This question was about Teensy 3.5... but if using Teensy LC (as in, someone else later finding this message by search), the very similar "TPM" timers have a quirk where you have to set the config to zero and either wait a brief delay or repeatedly read it back until it's really zero, and only then can you successfully change the channel config. The TPM timers have the ability to run from very different clocks (much faster or slower than the CPU) so they have this extra step. The FTM timers on Teensy 3.x don't have this limitation, but they are limited to only running from F_BUS which is always an integer division of the main F_CPU clock.

    3: Finally, you need to handle the interrupts. You want to get that counter value read ASAP. Then figuring out what to actually do with it is the challenge. Usually you would subtract it from the prior reading. If using 16 bit unsigned math, this automatically handles the roll over and gives you an elapsed time at a 16 bit number. Usually you'd store this to a volatile variable, so some sort of volatile flag so your program can know it's new data, and get out of the interrupt as quickly as you can. The longer you remain running the interrupt, the longer you block other interrupts and your ability to get the next edge.

    One drawback is you only get 16 bits of resolution, which spans only 1.09 ms if the timer counts at 60 MHz. It's possible to also respond to overflow interrupts and increment an extended 16 bits, then combine it with the 16 bit count to give you 32 bits of resolution. This is very tricky to get right in the case where these 2 interrupts happen very close in time, so I highly recommend you look at the FreqMeasure code. It's been well tested, so use it if you need more than 16 bits resolution.

    But again, while you can get 16.7 ns precision by using the timer input capture, if you only need 30 ms you might do well to save yourself a lot of trouble and just go with attachInterrupt() and micros(). The timers have incredible capability, but it is tricky to use properly.

  3. #3

  4. #4
    Thank you very much, Paul and manitou, for your inputs! I will try the attachinterrupt and micros() approach first and see if this works good enough for this project.
    Christof

  5. #5
    Hi, and thanks again.
    your input helped to modify an existing library:
    https://forum.pjrc.com/threads/53122...ary-is-working

    This actually works with "attachInterrupt() and millis() ".
    :-) Christof :-)

  6. #6
    Panic!
    I have to use hardware input capture because the rest of the code has other interrupts and long stretches of no interrupt code. I use ICP1 on the Arduino Micro. ICP1 exists on other Arduinos so must be useful to other people.
    I thought I could use A12 and FTM1 to get the same, even better results on the Teensy 3.2 (after a lot of reading!!).
    So I connected my servo tester to A12 on the back of the teensy and set
    PORTA_PCR12 |= 3<<8;
    But I couldn't get it to work. The FTM1 timer is working correctly as I can read FTM1_CNT correctly, but always 0 from C0V and C1V.
    Then I noticed that A12 is analog only, then panic that it is 3.3V only and my tester is 5V.
    Have I killed the Teensy, or maybe hopefully only the A12?
    How else can I get an ICP1 equivalent?
    George Shering.

  7. #7
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    2,593
    as noted in post #4 PulsePosition lib demonstrates how to use FTM0 to capture pin input with 32-bit count.

    Another example derived from Teensy LC timer 32-bit capture on pin 20: https://github.com/manitou48/teensy3...ftmcapture.ino

    A12 may not be fried since you weren't doing analogRead(A12). Test it with analogRead(A12) with input of 3.3v or less.

    "PORTA_PCR12 |= 3<<8; " is pin 3 (PTA12, FTM1 CH0) on T3.2. A12 is ADC0_DP3 (analog-only pin)
    Teensy 3.2 schematic
    Last edited by manitou; 10-27-2020 at 01:41 PM.

  8. #8
    Thank you Manitou. That solved my problem. The basic problem was that I confused connection A12 on the back of the Teensy 3.2 with chip PA12. A look at the Teensy 3.2 schematic showed that pad A12 goes to ADC0_DP3. I hope it survived my 5V ministrations! In fact PA12 is connected to Teensy digital pin 3, much more convenient anyway. I had chosen FTM1 to save FTM0 with its 8 registers for a later phase of the project.
    Once re-soldered and with your input I soon had the equivalent of ICP1 on the Arduino working fine, thank you.
    I then went on to try dual edge capture, and got it working, I found the reference:
    https://www.nxp.com/docs/en/application-note/AN5142.pdf
    helpful, particularly Figure 7. Dual-edge capture mode. The thing that stuck me there was the need to set the MSA bit for continuous operation, not mentioned in their code example, and not required for ICP1 emulation.
    Thanks again, George Shering.

  9. #9
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    716
    Can you post your code for dual edge capture?

  10. #10
    // TeensyDualFTM1 - input capture test
    // Captures length of positive pulse in RxPulseWidth
    // Pulse input is on Teensy digital pin 3, microprocessor PA12

    unsigned short RxPulseWidth; // width last measured

    #define ledPin 13
    #include <LiquidCrystal.h>
    // initialize the library by associating any needed LCD interface pin
    // with the pin number it is connected to
    const int rs = 7, en = 8, d4 = 9, d5 = 10, d6 = 11, d7 = 12;
    LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

    void setup() {
    // set up the LCD's number of columns and rows:
    lcd.begin(20, 4);
    pinMode(ledPin, OUTPUT);

    // Set up FTM1 to capture the RX pulse
    FTM1_SC = 0;
    FTM1_CNT = 0;
    FTM1_CNTIN=0x00;
    FTM1_MOD = 0xFFFF;
    FTM1_MODE=0x05; //set FTMEN bit to unprotect FTM1_COMBINE register, FTM mode
    FTM1_SC = (FTM_SC_CLKS(1) | FTM_SC_PS(5)); // system clock/32, 2.25MHz
    FTM1_C0SC=0b00010100; //Capture on Rising Edge Only
    FTM1_C1SC=0b01011000; //Capture on Falling Edge, interrupt
    FTM1_COMBINE=0x04; //enable dual capture mode
    FTM1_C1SC|=0x40; //enable CH1 interrupt
    FTM1_SC|=0x08;
    FTM1_COMBINE|=0x08; //setting DECAP0 bit to launch the dual capture process
    PORTA_PCR12 = 3<<8; // set Teensy pin 3, PA12, as FTM1 trigger for CH0
    NVIC_ENABLE_IRQ(IRQ_FTM1);
    NVIC_SET_PRIORITY(IRQ_FTM1, 64);
    }

    // Interrupt service routine for dual edge capture on FTM1
    void ftm1_isr()
    {
    unsigned short capture0, capture1;
    digitalWrite(ledPin,HIGH);
    capture0=FTM1_C0V;
    capture1=FTM1_C1V;
    RxPulseWidth=capture1-capture0;
    FTM1_STATUS&=0xFD;
    digitalWrite(ledPin,LOW);
    }

    void loop() {
    lcd.setCursor(0, 0); lcd.print("RxPulseWidth "); lcd.print(RxPulseWidth);
    lcd.setCursor(0, 1); lcd.print("FTM1_CNT "); lcd.print(FTM1_CNT);
    delay(20);
    }

  11. #11
    Note: The above is for Teensy 3.2. I only have two of them. The small format is better for airborne applications. But I wish space could be found for a memory chip.

  12. #12

    Teensy 3.2 dual edge input capture on FTM1

    Quote Originally Posted by jonr View Post
    Can you post your code for dual edge capture?
    Below is the code, George Shering

    // TeensyDualFTM1 - input capture test for Teensy 3.2
    // Captures length of positive pulse in RxPulseWidth (unsigned short)
    // Pulse input is on Teensy digital pin 3, microprocessor PA12

    unsigned short RxPulseWidth; // width last measured, best convert to int before use

    #define ledPin 13
    #include <LiquidCrystal.h>
    // initialize the library by associating any needed LCD interface pin
    // with the pin number it is connected to
    const int rs = 7, en = 8, d4 = 9, d5 = 10, d6 = 11, d7 = 12;
    LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

    void setup() {
    // set up the LCD's number of columns and rows:
    lcd.begin(20, 4);
    pinMode(ledPin, OUTPUT);

    // Set up FTM1 to capture the RX pulse
    FTM1_SC = 0;
    FTM1_CNT = 0;
    FTM1_CNTIN=0x00;
    FTM1_MOD = 0xFFFF;
    FTM1_MODE=0x05; //set FTMEN bit to unprotect FTM1_COMBINE register, FTM mode
    FTM1_SC = (FTM_SC_CLKS(1) | FTM_SC_PS(5)); // system clock/32, 2.25MHz
    FTM1_C0SC=0b00010100; //Capture on Rising Edge Only
    FTM1_C1SC=0b01011000; //Capture on Falling Edge, interrupt
    FTM1_COMBINE=0x04; //enable dual capture mode
    FTM1_C1SC|=0x40; //enable CH1 interrupt
    FTM1_SC|=0x08;
    FTM1_COMBINE|=0x08; //setting DECAP0 bit to launch the dual capture process
    PORTA_PCR12 = 3<<8; // set Teensy pin 3, PA12, as FTM1 trigger for CH0
    NVIC_ENABLE_IRQ(IRQ_FTM1);
    NVIC_SET_PRIORITY(IRQ_FTM1, 64);
    }

    // Interrupt service routine for dual edge capture on FTM1
    void ftm1_isr()
    {
    unsigned short capture0, capture1;
    digitalWrite(ledPin,HIGH);
    capture0=FTM1_C0V;
    capture1=FTM1_C1V;
    RxPulseWidth=capture1-capture0;
    FTM1_STATUS&=0xFD;
    digitalWrite(ledPin,LOW);
    }

    void loop() {
    lcd.setCursor(0, 0); lcd.print("RxPulseWidth "); lcd.print(RxPulseWidth);
    lcd.setCursor(0, 1); lcd.print("FTM1_CNT "); lcd.print(FTM1_CNT);
    delay(20);
    }

  13. #13
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    2,593
    Best practice warning. When setting a variable in an ISR that is referenced elsewhere, you need to declare the variable volatile so the compiler can handle asynchronous updates.
    volatile unsigned short RxPulseWidth;

  14. #14
    Thanks Manitou. I would also suggest removing the line
    FTM1_SC|=0x08;
    As it has no function.
    The reason there are two copies is that the first two posts were sent in the morning. When I checked in the afternoon they had disappeared! So I sent the third, then later the first two reappeared?

  15. #15
    Junior Member
    Join Date
    Nov 2020
    Posts
    1
    Quote Originally Posted by manitou View Post
    Hi there. Is it still actual because I also have some problems with timer capture mode?

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •