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

Thread: Up to 8 channel delay timer

  1. #1
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    259

    Up to 8 channel delay timer

    In case somebody needs an interrupt driven delay library (e.g. for generating stepper pulses with long pulse width):

    https://github.com/luni64/TeensyDelay

    The library uses one of the FTM or TPM timers (selectable) and can provide up to 8 independent delay channels. Not a big thing, but might come in handy for some project.
    Basic usage:

    Code:
    #include <TeensyDelay.h>
    
    void callback()
    {
      digitalWriteFast(LED_BUILTIN,LOW);
    }
    
    void setup() 
    {
      pinMode(LED_BUILTIN,OUTPUT);
    
      TeensyDelay::begin();
      TeensyDelay::addDelayChannel(callback); // setup a delay channel and attach the callback function to it
    }
    
    void loop() 
    {
        digitalWriteFast(LED_BUILTIN,HIGH);  // switch on LED
        TeensyDelay::trigger(15);            // the callback function will switch it off 15Ás later
    
        delay(500);                          // repeat every 500ms
    }
    The library is compatible to T3.0, T3.1/2, T3.5, T3.6. Im working on compatibiltiy to T-LC

    Have fun.

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    18,364
    I've added a link to this thread from the IntervalTimer page. Hopefully it'll help people find this library if they need more interrupt-based timers.

  3. #3
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    259
    Great, thanks a lot for linking the library

  4. #4
    Senior Member
    Join Date
    Apr 2015
    Posts
    120
    Life==SAVED! Thank you! I think you might need to change your addChannel to addDelayChannel in your examples Readme.md file, btw.

    Could someone tell me what the functional difference is between Paul's timers and these? Is there a difference? I noticed that the teensy 3.2 has only 2 FTM timers, so could you use the 4 Interval timers and the 2 FTM for a total of 6?

  5. #5
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    6,870
    Looking at the linked code will show - but perhaps a single fast timer is used, and then each added channel is tracked for expiration in some fashion on each call to that timer's interrupt code.

  6. #6
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    259
    Pauls timers (if you refer to the IntervalTimers) use the PIT module of the chip. They are 32bit counters and basically generate an interrupt whenever the counter matches a given compare value.

    TeensyDelay uses a FTM timer. The FTM timers are flexible (hence the name) 16bit timers. They can be used in various modes; I'm using them in the simple TPM mode. In TPM mode each of the FTM timers consists of one counter and up to 8 channels (i.e. compare values). Each of the compare values can be used to generate an interrupt whenever its value matches the counter value. If you set the compare values carefully you get up to 8 'independent' timers per FTM module

    There is a table in config.h showing which FTM modules are available for which board, how many channels the module provides and which module is used by default.

    T3.2 by default uses FTM0 which provides 8 channels. So, together with the 4 IntervalTimers you have up to 12 usable timers. Please note that the FTM timers are only 16bit. TeensyDelay chooses the prescaler such that 1 timer tick is roundabout 1Ás. Therefore the maximum possible delay is about 65ms. If you need periodic interrupts you can reload the delayChannel in its callback function. (See the chapter "A Poor Man's Periodic Timer" in the documentation)

    Hope that helps

    I think you might need to change your addChannel to addDelayChannel in your examples Readme.md file
    Thanks, fixed it.
    Last edited by luni; 04-26-2017 at 03:17 PM.

  7. #7
    Senior Member
    Join Date
    Apr 2015
    Posts
    120
    Theoretically, couldn't you also enable the FTM1 and FTM2 after setting a different default timer and renaming the TeensyDelay to another name twice, to have a total of 16 timers for teensy 3.2? It might be nice to have an instantiation init call on the class that allows one to choose the timer, if possible.

    Either way, thanks so much for writing this! I am using this to create many independent changing (different) PWM audible tones. I haven't tested the TeensyDelay library for this yet, but it seems like it would work great!

  8. #8
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    259
    Your idea about duplicating/renaming the TeensyDelay functions to allocate another FTM might end up in a mess. If you need loads of timers for a specific project on a specific board it would probably be quicker to implement the functionality from scratch. Although the documentation for the FTM counters looks quite deterrent, they default to TPM mode which is easy to use (have a look at the TPM section of the manual). You find all the code required to setup and trigger the timers in TeensyDelay.h and TeensyDelay.cpp.

  9. #9
    I've been trying to use this library to get a 20Hz output trigger running but was running into a couple of weird issues I was wondering about

    1. My main problem is that the trigger won't go higher than roughly 40ms delay (I need 50ms). A little about that it starts aliasing to a shorter delay. I didn't figure enough of the internals yet, but IntervalTimer can go higher, so could be an issue with this specific timer.

    2. There seems to be a weird problem with nested triggers where digitalWrite stops working. I verified that all the interrupts are called by also updating a variable (and tried disabling interrupts just in case). The following works (led blinks)

    Code:
    #include <TeensyDelay.h>
    static const int signalPin = LED_BUILTIN;
    
    IntervalTimer myTimer;
    
    void Timer() {
        TeensyDelay::trigger(10000, 0);
        TeensyDelay::trigger(30000, 1);
    }
    
    void SignalUp() {
        digitalWriteFast(signalPin, HIGH);
    }
    
    void SignalDown() {
        digitalWriteFast(signalPin, LOW);
    }
    
    void setup()
    {
        pinMode(signalPin, OUTPUT);
    
        TeensyDelay::begin();
        TeensyDelay::addDelayChannel(SignalUp, 0);
        TeensyDelay::addDelayChannel(SignalDown, 1);
        
        myTimer.begin(Timer, 40000);
    }
    
    void loop() { }
    and this works

    Code:
    void SignalUp() {
        digitalWriteFast(signalPin, HIGH);
        TeensyDelay::trigger(20000, 1);
    }
    
    void SignalDown() {
        digitalWriteFast(signalPin, LOW);
        TeensyDelay::trigger(20000, 0);
    }
    
    void setup() {
    ...
        SignalUp();
    }
    as well as this

    Code:
    void SignalUp() {
        digitalWriteFast(signalPin, HIGH);
        TeensyDelay::trigger(40000, 0);
        TeensyDelay::trigger(20000, 1);
    }
    
    void SignalDown() {
        digitalWriteFast(signalPin, LOW);
    }
    
    void setup() {
    ...
        SignalUp();
    }
    But here digitalWriteFast stops working (as mentioned, it looks like the interrupts are being called by adding a counter to each, but led stops blinking)

    Code:
    void Timer() {
        TeensyDelay::trigger(10000, 0);
    }
    
    void SignalUp() {
        digitalWriteFast(signalPin, HIGH);
        TeensyDelay::trigger(20000, 1);
    }
    
    void setup() {
    ...
        myTimer.begin(Timer, 40000);
    }

  10. #10
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    259
    1. My main problem is that the trigger won't go higher than roughly 40ms delay (I need 50ms). A little about that it starts aliasing to a shorter delay. I didn't figure enough of the internals yet, but IntervalTimer can go higher, so could be an issue with this specific timer.
    As I tried to explain in #6 of this thread TeensyDelay uses a 16bit timer therefore the range of possible delay values is much smaller than that for the 32bit IntervalTimer. To cope with the small range the library tries to set the timer prescaler such that one timer tick corresponds to about 1Ás. This of course is only a very rough setting. Depending on your board and your setting for the CPU speed this time can be significantly smaller or larger. If for example your settings end up in a timer period of 0.5Ás your maximum possible delay would be 0.5Ás x 2^16 = 32ms.

    If you need longer delays and don't care about the 1Ás resolution you can change the prescaler value manually. The relevant code is in config.h https://github.com/luni64/TeensyDela.../config.h#L152

    Code:
    //-----------------------------------------------------------------------------------
        //Frequency dependent settings 
    
        constexpr unsigned _timer_frequency = isFTM ? F_BUS : 16000000;  // FTM timers are clocked with F_BUS, the TPM timers are clocked with OSCERCLK (16MHz for all teensies)
    
        // Choose prescaler such that one timer cycle corresponds to about 1Ás
        constexpr unsigned prescale = _timer_frequency > 120000000 ? 0b111 :
            _timer_frequency > 60000000 ? 0b110 :
            _timer_frequency > 30000000 ? 0b101 :
            _timer_frequency > 15000000 ? 0b100 :
            _timer_frequency > 8000000 ? 0b011 :
            _timer_frequency > 4000000 ? 0b010 :
            _timer_frequency > 2000000 ? 0b001 : 0b000;
    
        // Calculates required reload value to get a delay of mu microseconds. 
        // this will be completely evaluated by the compiler as long as mu is known at compile time 
        constexpr int microsToReload(const float mu)
        {
            return  mu * 1E-6 * _timer_frequency / (1 << prescale) + 0.5;
        }
    Just comment out the setting of the prescale value and replace it by

    Code:
    constexpr unsigned prescale = 'someLargerValue'


    2. There seems to be a weird problem with nested triggers where digitalWrite stops working.
    I can reproduce that. It seems to happen when the period of the IntervalTimer myTimer is larger than the wrap around time of the FTM timer. It does call the interrupts but SignalDown will be called immediately after SignalUp so you don't see the LED blinking. I suspect that the wrap around of the FTM does something to the channel interrupt flags which I don't handle correctly. I'll have a look at it at the weekend.

  11. #11
    Quote Originally Posted by luni View Post
    I can reproduce that. It seems to happen when the period of the IntervalTimer myTimer is larger than the wrap around time of the FTM timer. It does call the interrupts but SignalDown will be called immediately after SignalUp so you don't see the LED blinking. I suspect that the wrap around of the FTM does something to the channel interrupt flags which I don't handle correctly. I'll have a look at it at the weekend.
    I don't think that it's the wrap around issue as looking with a scope, 40ms seems to be fine (looks like wrap around is around 43ms when running at a 96MHz clock), and the other samples that work and do use 40ms work. The one that doesn't actually only goes up to 20ms.

    Also, it does fire correctly the first time but then stops.

    Thanks

  12. #12
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    259
    looks like wrap around is around 43ms when running at a 96MHz clock
    Yes, you can use this snippet to find out the auto defined prescaler and ticktime:
    Code:
    constexpr float tickTime    = 10000.0 / TeensyDelay::microsToReload(10000);
    constexpr unsigned prescale = TeensyDelay::prescale;
    
    Serial.printf("TickTime:\t%0.5f\nPrescaler:\t%d\n", tickTime, prescale);
    For 96MHz you'll get ticktime = 0.667 Ás and prescaler = 5. This will give you a maximum delay time of 0.667 x 2^16 = 43.7ms. If you manually increase the prescaler to 6 you'd get a ticktime of 1.33Ás and a max delay of 87.4ms which would would be a better fit for your application as I understood.

    I don't think that it's the wrap around issue as looking with a scope, 40ms seems to be fine (looks like wrap around is around 43ms when running at a 96MHz clock), and the other samples that work and do use 40ms work. The one that doesn't actually only goes up to 20ms.
    Thanks for the additional input. I'll have a closer look this evening.

    And... sorry for the inconvenience.
    Last edited by luni; 12-15-2017 at 06:29 AM.

  13. #13
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    259
    Should be working now:

    1. I used the FTMx_STATUS register which collects the channel flags of all the channels. However, for the Teensy 3.2 (not for the other chips) they added the note: The STATUS register should be used only in Combine mode. And in fact this register does strange things on the 3.2. I changed the code to use the channel flags of the FTMx_SC registers instead. Looks like it works now.
    2. I also added a possibility to override the prescaler value in config.h.
      Click image for larger version. 

Name:	Unbenannt.PNG 
Views:	56 
Size:	9.3 KB 
ID:	12300


    Pushed the code to the bugfix branch of TeensyDelay https://github.com/luni64/TeensyDelay/tree/bugfix
    @laughingrice Can you give it a try?
    Last edited by luni; 12-16-2017 at 03:19 PM.

Posting Permissions

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