Teensy4 IntervalTimer Channels Problem

Status
Not open for further replies.

quadrupel

Well-known member
Just got my T4s this week and I'm putting th T4 through some basic tests. I wrote a very basic IntervalTime sketch using 4 channels of the IntervalTimer and discovered that it will only handle two channels. The other two (any combination of two) are ignored.

Is this a known problem, or did I blow it somewhere?
Here is the sketch:

Update: It should also be noted that the last two instances do return false indicating resources are not available, but I don't understand why this may be happening in such a basic demonstration.

Code:
IntervalTimer timer_led1;
IntervalTimer timer_led2;
IntervalTimer timer_led3;
IntervalTimer timer_led4;

const uint8_t led1 = 1;
const uint8_t led2 = 2;
const uint8_t led3 = 3;
const uint8_t led4 = 4;

void toggle( const uint8_t pin ) { digitalWrite( pin, !digitalRead(pin) ); }

void led1_isr() { toggle( led1 ); }
void led2_isr() { toggle( led2 ); }
void led3_isr() { toggle( led3 ); }
void led4_isr() { toggle( led4 ); }


//--------------------------------------------------------------------------------------//

void setup()
{
  pinMode( led1, OUTPUT );
  pinMode( led2, OUTPUT );
  pinMode( led3, OUTPUT );
  pinMode( led4, OUTPUT );

  timer_led1.begin( led1_isr, 100000 );
  timer_led2.begin( led2_isr, 250000 );
  timer_led3.begin( led3_isr, 500000 );
  timer_led4.begin( led4_isr, 1000000 );
}

void loop() {}
 
Last edited:
I've observed the same exact behavior.

I don't see where the other two timers are going (looks like interval timer 0 and 1 are in use or not initialized properly)... or what happens if they are disabled/reassigned but this code makes them available for me:

Code:
  // clear/make available all PIT timers
  CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
  PIT_MCR = 1;
  PIT_TCTRL0=0;
  PIT_TCTRL1=0;
  PIT_TCTRL2=0;
  PIT_TCTRL3=0;
 
I'm also confused by the overloaded methods casting microseconds from long to int in IntervalTimer.h begin(), possibly I don't understand the code but it looks like a bug to me. I use floats for the period in my application so this isn't much of a concern.
 
Strange. for some reason TCTRL0 and TCTRL1 have non-zero values (power on should set them to 0). I haven't yet found where in the core those registers are set. here is my test sketch
Code:
#define PRREG(x) Serial.printf(#x" 0x%x\n",x);
IntervalTimer t1, t2, t3, t4;
volatile uint32_t t1c, t2c, t3c, t4c;

void t1isr() {
  t1c++;
}
void t2isr() {
  t2c++;
}
void t3isr() {
  t3c++;
}
void t4isr() {
  t4c++;
}
void setup() {
  Serial.begin(9600);
  while (!Serial);
  CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
  PRREG(PIT_TCTRL0);
  PRREG(PIT_TCTRL1);
  PRREG(PIT_TCTRL2);
  PRREG(PIT_TCTRL3);

 // PIT_MCR = 1;
  PIT_TCTRL0 = 0;
  PIT_TCTRL1 = 0;
  //  PIT_TCTRL2=0;
  //  PIT_TCTRL3=0;
  t1.begin(t1isr, 500000);
  t2.begin(t2isr, 200000);
  t3.begin(t3isr, 100000);
  t4.begin(t4isr, 1000);


}

void loop() {
  Serial.printf("%d %d %d %d\n", t1c, t2c, t3c, t4c);
  delay(1000);

}
I confirm setting TCTRL0 and TCTRL1 to 0 allow all 4 PIT timers to be used in IntervalTimer ... more research required.

Production T4 (1062) using 1.8.8 1.47 or 1.8.9 1.47

EDIT: PIT registers should be 0 at power on/reset, but for T4 (1062), regs are
Code:
PIT_MCR 0x2
PIT_TCTRL0 0x1
PIT_TCTRL1 0x5
PIT_TCTRL2 0x0
PIT_TCTRL3 0x0
PIT_LDVAL0 0xffffffff
PIT_LDVAL1 0xffffffff
PIT_LDVAL2 0x0
PIT_LDVAL3 0x0
It looks like PIT was configured for 64-bit cycle counting (channel 1 chained to channel 0). I still don't know why regs aren't all 0.

FWIW, see https://github.com/manitou48/teensy4/blob/master/pit_micros64.ino
 
Last edited:
maybe the following is a suitable FIX in hardware/teensy/avr/cores/teensy4/IntervalTimer.cpp
Code:
...
    } else {
        CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
        //__asm__ volatile("nop"); // solves timing problem on Teensy 3.5
[B]        if (PIT_MCR == 2) { 
            // first time, fix POR values
            PIT_TCTRL0 = 0;
            PIT_TCTRL1 = 0;
        }[/B]
...
 
Last edited:
I'm also confused by the overloaded methods casting microseconds from long to int in IntervalTimer.h begin(), possibly I don't understand the code but it looks like a bug to me. I use floats for the period in my application so this isn't much of a concern.

Never mind...

I always assumed int's were forced to 16bit for "Arduino" compatibility. I see both int and long are both 32bit, learn something new every day. ;)
 
manitou, ran your test sketch. Interesting observation about TCTRL0, and TCTRL1. Added your 'fix' to IntervalTimer and now my test sketch runs all four timers. Thank you for all your help.
 
latest observation: In setup() I printed out the TCTRLx registers on T4B1 (beta 1052), and all values are 0. (1.8.8 1.47-beta1)

Hardware or software ?? i'll test with NXP EVKB boards and mbed or SDK ....
 
hjd, I've been working with Teensy for almost a year now and I'm still learning new stuff every day. I should have moved to the Teensy platform years ago. Have fun.
 
latest observation: In setup() I printed out the TCTRLx registers on T4B1 (beta 1052), and all values are 0. (1.8.8 1.47-beta1)

Hardware or software ?? i'll test with NXP EVKB boards and mbed or SDK ....

Looking forward to your observations...
 
latest observation: In setup() I printed out the TCTRLx registers on T4B1 (beta 1052), and all values are 0. (1.8.8 1.47-beta1)

Hardware or software ?? i'll test with NXP EVKB boards and mbed or SDK ....

manitou,
Out of curiosity, I created a class to play around with PIT. It's a very basic version of IntervalTimer, but I can get all 4 channels running without having to anything funny. The code snippet below shows the class constructor with the normal PIT initialization and then the addChannel method simply creates the PIT channel as you'd suspect. I didn't include the pit_isr as it is identical to the one in IntervalTimer. This may help in any investigation into the IntervalTimer behaviour.

Code:
Timer::Timer()
{
	CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
	PIT_MCR = 1;
}

bool Timer::addChannel( void (*f)(), const uint32_t us )
{
	if (channel_index > 3) return false;

	IMXRT_PIT_CHANNEL_t *channel = IMXRT_PIT_CHANNELS + channel_index;
	pit_event[channel_index] = f;

	constexpr uint8_t PIT_TIE = 0x2;
	constexpr uint8_t PIT_TEN = 0x1;

	channel->LDVAL = (F_BUS_ACTUAL / 1000000) * us - 1;
	channel->TCTRL = PIT_TIE;
	channel->TCTRL |= PIT_TEN;
	channel->TFLG = 1;

	attachInterruptVector(IRQ_PIT, &_pit_isr);
	NVIC_ENABLE_IRQ(IRQ_PIT);

	channel_index++;
	return true;
}
 
latest observation: In setup() I printed out the TCTRLx registers on T4B1 (beta 1052), and all values are 0. (1.8.8 1.47-beta1)

Hardware or software ?? i'll test with NXP EVKB boards and mbed or SDK ....

Inconclusive results for NXP eval boards. The mbed core uses the PIT timer for us_ticker() with EVKB 1052 eval board, and with the 1062 eval board the NXP SDK is using the PIT for microseconds(). So it will require messing with core files to see initial values of PIT registers -- may or may not pursue that...
 
Out of curiosity, I created a class to play around with PIT. It's a very basic version of IntervalTimer, but I can get all 4 channels running without having to anything funny.

The IntervalTimer class uses each timers TCTRL flag to indicate if a given PIT timer is in use so interval timers can be allocated/deallocated in any order as needed. At startup all four TCTRL should be set to 0 (as an "available" flag) but for some reason the first two are not.
 
hjd, I've been working with Teensy for almost a year now and I'm still learning new stuff every day. I should have moved to the Teensy platform years ago. Have fun.

I've been working with a mix of 8-bit AVR, Teensy's, ESP's, STM32 in Arduino for quite a while. Since the same code runs on all of these I developed a kind of protective amnesia WRT int's I think. ;)
 
The IntervalTimer class uses each timers TCTRL flag to indicate if a given PIT timer is in use so interval timers can be allocated/deallocated in any order as needed. At startup all four TCTRL should be set to 0 (as an "available" flag) but for some reason the first two are not.

In the IntervalTimer code, I created a constructor that initializes the PIT timer and I cleared TCTRCL0 and TCTRL1 to 0. manitou indicated that only those first two flags were not being cleared. In the beginCycles() method, I commented out 'CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON)' and 'PIT_MCR = 1' as they are now in the constructor as shown in the code snippet below. manitou's fix was removed. All four timers function correctly.

I created a local version of the IntervalTimer named Interval_Timer so as not to interfere with the Teensy4 core version

Code:
constexpr Interval_Timer() 
{
	CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
	PIT_TCTRL0 = 0;
  	PIT_TCTRL1 = 0;
	PIT_MCR = 1;
}
 
Last edited:
Inconclusive results for NXP eval boards. The mbed core uses the PIT timer for us_ticker() with EVKB 1052 eval board, and with the 1062 eval board the NXP SDK is using the PIT for microseconds(). So it will require messing with core files to see initial values of PIT registers -- may or may not pursue that...

Latest observation of 1052 and 1062 with NXP SDK (MCUXpresso IDE). A PIT timer example on the 1052 has all TCTRL and LDVAL values 0. On the 1062 eval board, the PIT example starts with channels 0 and 1 TCTRL at 1 and 5, and the channel 0 and 1 LDVALs as 0xffffffff -- just like the T4B1 and T4. So maybe the default hardware POR values are different for the 1062? Unless both Teensy and NXP bootloader are some how using the PIT timer channels 0 and 1 chained for a 64-bit timer ....
 
Thanks for keeping me informed. It would seem you've encountered quite the rabbit hole. I've been reading Ch. 52 of the Ref. Manual, trying to get a better understanding of PIT, but I also need a better understanding of the Teensy4 core as well.
 
A feature?

Though 1052 and 1062 both have PIT "lifetime" timer capability (chained channel 0 and 1), only the 1062 is configured to start the lifetime timer as soon as PIT is enabled in CCM_CCGR1. So if startup.c enabled PIT in CCGR1, then the PIT would have a running timer of how long the core has been running (in 24mhz cycles).

so maybe a better fix is in startup.c, enable PIT, clear all of PIT registers, then disable (optional?) PIT in CCGR1, then IntervalTimer lib would work with all 4 channels.
 
A feature?

Though 1052 and 1062 both have PIT "lifetime" timer capability (chained channel 0 and 1), only the 1062 is configured to start the lifetime timer as soon as PIT is enabled in CCM_CCGR1. So if startup.c enabled PIT in CCGR1, then the PIT would have a running timer of how long the core has been running (in 24mhz cycles).

so maybe a better fix is in startup.c, enable PIT, clear all of PIT registers, then disable (optional?) PIT in CCGR1, then IntervalTimer lib would work with all 4 channels.

Installed the patch below into Teensy4 core startup.c file's ResetHandler() function. Ran my IntervalTimer test previously posted on this thread and all 4 channels behave correctly.
Code:
// manitou suggested PIT fix 
  CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
  PIT_TCTRL0 = 0;
  PIT_TCTRL1 = 0;
  PIT_TCTRL2 = 0;
  PIT_TCTRL3 = 0;
  CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_OFF);
 
Even after just digging into T4 (have has a 3.6 but never got the “round-to-it” to begin learning. The higher price of the 3.6 kept me out of the game, but going forward the T4 is only slightly more than Pololu AStar 32u4 boards.... so have become a def Teensy convert. ��

hjd, I've been working with Teensy for almost a year now and I'm still learning new stuff every day. I should have moved to the Teensy platform years ago. Have fun.
 
Installed the patch below into Teensy4 core startup.c file's ResetHandler() function. Ran my IntervalTimer test previously posted on this thread and all 4 channels behave correctly.
Code:
// manitou suggested PIT fix 
  CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
  PIT_TCTRL0 = 0;
  PIT_TCTRL1 = 0;
  PIT_TCTRL2 = 0;
  PIT_TCTRL3 = 0;
  CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_OFF);

I think to disable the PIT you need to clear the bits
Code:
// manitou suggested PIT fix 
  CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
  PIT_TCTRL0 = 0;
  PIT_TCTRL1 = 0;
  PIT_TCTRL2 = 0;
  PIT_TCTRL3 = 0;
  [B]CCM_CCGR1 &= ~CCM_CCGR1_PIT(CCM_CCGR_ON);[/B]   //optional
 
Last edited:
awesome forum

Yes - I totally agree! New T4 user been on just a week or so - just beginning to see the depth that is here.

hjd, I've been working with Teensy for almost a year now and I'm still learning new stuff every day. I should have moved to the Teensy platform years ago. Have fun.
 
@Quadrupel - where did you insert the startup patch in startup.c - that is a VERY busy file - don't want to break it. Also - I'm keeping a copy of the original..
 
Status
Not open for further replies.
Back
Top