Up to 8 channel delay timer

Status
Not open for further replies.

luni

Well-known member
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.
 
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?
 
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.
 
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:
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!
 
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.
 
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);
}
 
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/TeensyDel...2539e06f88d5a0b307ff15906eb/src/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.
 
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
 
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:
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.
    Unbenannt.PNG

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:
Status
Not open for further replies.
Back
Top