Teensy 4 Interval Timer

Status
Not open for further replies.

jusack

New member
Hello,

I am trying to use the IntervalTimer on a Teensy 4 to schedule two tasks periodically with different priority. It seems like even though I set two priorities for my two instances of the IntervalTimer the faster (and higher priority) one is not able to interrupt the other function. It seems like there is just a single priority for PIT_IRQ, even though the PIT has four channels so I guess my plan will not work.
What are possible alternatives to solve the problem of two periodic tasks (40us and 1ms) where the faster one can interrupt the other on the Teensy 4?

Thanks in advance for your help!
 
priority levels?
There are in steps of 16 (at least for T3.6, not checked yet for T4)
e.g levels 16 to 31 are the same level
 
Looking further into the code of the IntervalTimer library and the documentation of the processor, I found out that all four channels of the IntervalTimer trigger the same interrupt and therefore must have the same priority.
I solved my problem having two periodic tasks using one of the General Purpose Timers that are available on the Teensy 4. I looked into the reference manual of the processor and some code, that I found on this forum to produce the following:

Code:
void setup()
{
    Serial.begin(1);
    uint32_t us = 1000;

    CCM_CCGR1 |= CCM_CCGR1_GPT(CCM_CCGR_ON); //enable clock for General Purpose Timer 1
    GPT1_CR = 0;
    GPT1_PR = 0;
    GPT1_SR = 0x3F;                         // clear all prior status
    GPT1_IR = GPT_IR_OF1IE;                 // enable GPT1 COMPARE 1 Interrupt
    GPT1_CR = GPT_CR_EN | GPT_CR_CLKSRC(1); // sets timer clock to 24 MHz (Teensy 4)
    GPT1_OCR1 = 24000000 / 1000000 * us;    // set compare value

    attachInterruptVector(IRQ_GPT1, timer_test);
    NVIC_SET_PRIORITY(IRQ_GPT1, 0);
    NVIC_ENABLE_IRQ(IRQ_GPT1);
}

void timer_test()
{
    GPT1_SR = GPT_SR_OF1; //clear the compare flag, the timer resets automatically
    Serial.println(micros());
}

void loop()
{
}

This way I have a different interrupt (GPT1_IRQ), that I can assign a different priority to. This allows the IntervalTimer to interrupt or be interrupted by the other task.

Is this the way you would do it, or has the General Purpose Timer (GPT) some drawbacks in comparison to the Periodic Interrupt Timer (PIT) that is used in the IntervalTimer library? Should I use the PIT for the faster Task where timing needs to be more accurate or is there no difference?
 
I had to invest a couple of hours to find out what is wrong with the above GPT example and my version :) When toggling a pin in the ISR routine you see strange results. The pin doesn't stay up until it's toggled in the next interrupt. It goes down much faster. Reason seems to be a behaviour of the M7 (see the description of the macro __DSB()).

First, add "__DSB();" at the end of the ISR routine.
Second subtract one from GPT_OCR1, otherwise your duration is 1us to long

My Makefile example:

Code:
#include "Arduino.h"
#include "debug/printf.h"
#include "arm_math.h"

void GPT1_GPT_IRQHANDLER(void);

int main(void)
{
    pinMode(23, OUTPUT);

    uint32_t us = 1;

    CCM_CCGR1 |= CCM_CCGR1_GPT(CCM_CCGR_ON); //enable clock for General Purpose Timer 1
    GPT1_CR = 0;
    GPT1_PR = 0;
    GPT1_SR = 0x3F;                         // clear all prior status
    GPT1_IR = GPT_IR_OF1IE;                 // enable GPT1 COMPARE 1 Interrupt
    GPT1_CR = GPT_CR_EN | GPT_CR_CLKSRC(1); // sets timer clock to 24 MHz (Teensy 4)
    GPT1_OCR1 = 24000000 / 1000000 * us - 1 ;    // set compare value

    attachInterruptVector(IRQ_GPT1, GPT1_GPT_IRQHANDLER);
    NVIC_SET_PRIORITY(IRQ_GPT1, 0);
    NVIC_ENABLE_IRQ(IRQ_GPT1);

    printf_debug_init();

    for(;;)
    {
    }
}

void GPT1_GPT_IRQHANDLER(void)
{
    GPT1_SR = GPT_SR_OF1;
    GPIO6_DR_TOGGLE = (1 << 25);
    __DSB();
}
 
dbuergin-Would you please help me understand why the _DSB() is needed here. I know it inserts and asm dsb but not sure why needed. I am not very assembly language savvy.

I am also having trouble finding a reference for the use of printf_debug. Can you point me somewhere that explains this capability?

Thanks
 
Hi Neal,

First of all, i'm also learning :cool: i'm coming from the STM32F407 and try to migrate my project to the M7 on the Teensy 4.1 Board. Actually i don't know if i want to work under fully Arduino control or Makefile based but with the Teensyduino libs from PRJC (great work by the way) or based on the SDK from MCUXpresso for the MIMXRT10162 CPU. So i'm experimenting around with everything.
When i use the "IntervalTimer" lib, i don't need __DSB() simply because it works without. But this lib uses the PIT Timers and not the GPT Timers in the CPU, read the docu for the difference. If i used the generic GPT example from "jusak", i got the strange behaviour. Then i checked the GPT example from the MCUXpresso SDK and found this __DSB() in every example. So i added it to my example and voila it works.
Then i google'd for __DSB() and found: https://developer.arm.com/documentation/dai0321/a/ , download the PDF and read it. My undestanding is, that it has something to do with the multiple execution pipelines the M7 has.

printf_debug: If you work with the Arduino IDE or with the Makefile provided from PRJC ([your ardunio path]\hardware\teensy\avr\cores\teensy4), you can simple add "#define PRINT_DEBUG_STUFF" to your code or to the Makefile as a "-DPRINT_DEBUG_STUFF" and you have the debug output on Pin 17 (TX4), add a SerialTTL to USB Converter and connect it to your Computer and use Putty or HTerm or whatever tool you use for serial connections.
In your code you can simply use "printf("");" to output on the debug connection. Don't use "Serial.print..." this will not use the debug Interface

Hope that helps.

Regards, Daniel
 
@Neal : Need for _DSB() is a puzzle - but it makes sure the bus and I/O completes before the _isr exits. When that is not done each interrupt can complete twice. The interrupt flag needs to be unset to prevent it doubling, seeing it still set on exit it will re-enter. Perhaps that is handled in the IntervalTimer() code, and when you hand code the timer it must be in that code.

That is the value in using the tested, refined and provided code versus hand coding timers.

@luni - on github @luni64 has a more expansive Timer library set for Teensy good perhaps for reference or use as it exposes all types of timers AFAIK.
 
The dsb is needed to make sure the reset of the interrupt flag propagated through the caches and busses to the actual timer hardware. Without dsb and a very short ISR (like the one you have) the ISR would be left before the interrupt flag is actually reset. Thus, the ISR would be called again, immediately after you left it.


EDIT: Always those crossposts with defragster :)
 
The dsb is needed to make sure the reset of the interrupt flag propagated through the caches and busses to the actual timer hardware. ...
EDIT: Always those crossposts with defragster :)

And as usual I said it more quickly and you said it better :)
 
Status
Not open for further replies.
Back
Top