Interrupt registers names on Teensy 4.1

Status
Not open for further replies.

AleMonti

Member
Hi all,
I'm converting a big led project from the Mega to the Teensy 4.1. Everything has gone smooth so far except for a fundamental piece of code that handles the timing. Basically I have a lot of leds to sync to a (long) video so I was using the 32K output of a DS3231 RTC to generate interrupts every 1/25 of a second to run a frame timer. The problem is that the interrupts registers have (of course) different names on the Mega and I'm having trouble identifying what to change them to.
The original code obviously uses the ATmega2560's registers. For the Teensy I only found this page but it doesn't contain enough information. Could you help me porting the following code?

Here's a test sketch with the code I was running on the Mega:
Code:
#include <FastLED.h>
#include "RTClib.h"

RTC_DS3231 rtc;
/* connections:
32K --> 47
SCL --> 21SCL
SDA --> 20SDA
VCC --> 5V
GND --> GND
*/

volatile uint32_t frameCounter = 0;
uint32_t frameCount;
uint32_t frameCountPrev;

void setup() {
  Serial.begin(115200);
  Serial.println("startup");
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }
  
  if (!rtc.isEnabled32K()) { //verify 32K output is enabled
    rtc.enable32K();
  }
  
  TCCR5A = 0; //clear control register A
  TCCR5B = 0; //clear control register B
  TCNT5 = 0;  //clear counter
  OCR5A = 1309; //set value for output compare register A  (32768Hz * 1/25 second) - 1 = 1309
  TCCR5B |= (1 << WGM52); //Set CTC mode (WGM5 = 0100);
  TCCR5B |= (1 << CS52) | (1 << CS51) | (1 << CS50) ; //External Clock mode using D47 as input
  TIMSK5 |= (1 << OCIE5A); //Set the interrupt request
  interrupts(); //Enable interrupt
}


void loop() {
  //temporarily disable interrupts while making a copy of frameCounter
  noInterrupts();
  frameCount = frameCounter;
  interrupts();

  if (frameCount != frameCountPrev) { //only execute once per frame
    if ((frameCount - frameCountPrev) != 1) {
      Serial.println("missed frame");
      while (1) {};
    }
    frameCountPrev = frameCount;

    //do stuff, update leds

    Serial.print(frameCount);
    //print millis every 25 frames to test discrepancy
    if ((frameCount % 25) == 0) {
      Serial.print('\t');
      Serial.print(millis());
    }
    Serial.println();
  }
}

ISR(TIMER5_COMPA_vect) {   //This is the interrupt request
  static byte cycleCount = 24;
  //adjustment to compensate for 32768 not being evenly divisible by 25
  //counter will count ( (7 * 1310) + (18 * 1311) ) = 32768 pulses over 25 frames
  if ((cycleCount & 0x03) == 0) {
    OCR5A = 1309; //1310 clock pulses
  } else {
    OCR5A = 1310; //1311 clock pulses
  }
  if (cycleCount == 0) {
    cycleCount = 25;
  }
  cycleCount--;

  frameCounter++; //actual frame counter
}


I've added those first two lines (as indicated in the page I linked) and connected the RTC as follows:
Code:
#include <avr/io.h>
#include <avr/interrupt.h>
#include "RTClib.h"
RTC_DS3231 rtc;
/* connections:
32K --> 18 //can be anything else if needed
SCL --> 19SCL
SDA --> 18SDA
VCC --> 3.3V
GND --> GND
*/

Thanks
 
Perhaps a quick start and usable solution would be setting up an IntervalTimer with time of 1,000,000/25 for the interrupt rate to call the function?

You could study that code and set it up - or just use that tested Teensy provided library,
 
Indeed for that code, using IntervalTimer is the best way, since all it's doing with the timer is running a function at a regular interval.

Regarding the original question, the interrupt names are defined in imxrt.h.

https://github.com/PaulStoffregen/c...adbddb4bceee04f55f96318299/teensy4/imxrt.h#L8

For the actual functions, there are no pre-defined names. Instead, you create whatever function name you like and then use attachInterruptVector(irq_number, function_name) to cause a particular interrupt to run it. Usually interrupts then need to be enabled both in the peripheral generating the interrupt and also in the NVIC controller, using NVIC_ENABLE_IRQ(). Of course you also have to dive into the peripheral's registers to figure out how to configure it to work the way you want.

But for just running a function on a precise interval, you don't need to go to all that trouble. IntervalTimer does that for you, and it comes with the nice advantage of dynamically allocating the timer from a pool of 4 hardware timers, so your program can automatically work together with others using IntervalTimer without the usual hardware conflicts, as long as the total number of timers used isn't more than 4.
 
Might work like this:

Code:
#include <FastLED.h>
#include "RTClib.h"

IntervalTimer myTimer;

RTC_DS3231 rtc;
/* connections:
32K --> 47
SCL --> 21SCL
SDA --> 20SDA
VCC --> 5V
GND --> GND
*/

volatile uint32_t frameCounter = 0;
uint32_t frameCount;
uint32_t frameCountPrev;

void setup() {
  Serial.begin(115200);
  Serial.println("startup");
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }
  [B]myTimer.begin( isrTimer, 1000000/25 );[/B]

}

/* // Assuming the RTC isn't needed for other reasons

  if (!rtc.isEnabled32K()) { //verify 32K output is enabled
    rtc.enable32K();
  }
  
  TCCR5A = 0; //clear control register A
  TCCR5B = 0; //clear control register B
  TCNT5 = 0;  //clear counter
  OCR5A = 1309; //set value for output compare register A  (32768Hz * 1/25 second) - 1 = 1309
  TCCR5B |= (1 << WGM52); //Set CTC mode (WGM5 = 0100);
  TCCR5B |= (1 << CS52) | (1 << CS51) | (1 << CS50) ; //External Clock mode using D47 as input
  TIMSK5 |= (1 << OCIE5A); //Set the interrupt request
  interrupts(); //Enable interrupt
}
*/

void loop() {
  //temporarily disable interrupts while making a copy of frameCounter
  noInterrupts();
  frameCount = frameCounter;
  interrupts();

  if (frameCount != frameCountPrev) { //only execute once per frame
    if ((frameCount - frameCountPrev) != 1) {
      Serial.println("missed frame");
      while (1) {};
    }
    frameCountPrev = frameCount;

    //do stuff, update leds

    Serial.print(frameCount);
    //print millis every 25 frames to test discrepancy
    if ((frameCount % 25) == 0) {
      Serial.print('\t');
      Serial.print(millis());
    }
    Serial.println();
  }
}

/* ISR(TIMER5_COMPA_vect) {   //This is the interrupt request
  //adjustment to compensate for 32768 not being evenly divisible by 25
  //counter will count ( (7 * 1310) + (18 * 1311) ) = 32768 pulses over 25 frames
  if ((cycleCount & 0x03) == 0) {
    OCR5A = 1309; //1310 clock pulses
  } else {
    OCR5A = 1310; //1311 clock pulses
  }
*/

[B]void isrTimer() {[/B]
  static byte cycleCount = 24;
  if (cycleCount == 0) {
    cycleCount = 25;
  }
  cycleCount--;

  frameCounter++; //actual frame counter
}
 
Perhaps a quick start and usable solution would be setting up an IntervalTimer with time of 1,000,000/25 for the interrupt rate to call the function?
Yes this works but defeats the purpose of the RTC. I should have explicitly explained this, but I was using the RTC because I needed very accurate timing to keep the video in sync with the leds. I've just now did a test with IntervalTimer and a 120 minutes video, and the drift is (actually I was expecting more) a bit too much. IntervalTimer finished 0.28s before the video. 0.28s is 7 frames, which would be noticeable when the lights should flash in sync with music.

if you use Teensy's non-blocking libraries OctoWS2811 or WS2812Serial, you probably won't need this interrupt code at all.
Of course. On the Mega it wasn't only that crystal wasn't accurate enough, but also the fact that FastLED's interrupts interfered with the accuracy of millis even more. Here the second problem is eliminated but I'm trying to get the best accuracy possible, thus using an RTC instead of the crystal.

Hope it's clear now.

Maybe I can get away with it by syncing twice through the video, but if it's not too complicated I would prefer to keep using the RTC.
 
IntervalTimer finished 0.28s before the video. 0.28s is 7 frames, which would be noticeable when the lights should flash in sync with music.
If you can get a handle on the amount of drift you could periodically increase/decrease your counter to compensate.
 
AFAIK the built in RTC can only generate interrupts at 1s, 1/2s, 1/4s.... so that wont help. But you can easily read out the RTC clock period counter and use this for your timing:

Here an example:
Code:
uint64_t get_RTC_periods()
{
    uint32_t hi1 = SNVS_HPRTCMR, lo1 = SNVS_HPRTCLR;
    while (true)
    {
        uint32_t hi2 = SNVS_HPRTCMR, lo2 = SNVS_HPRTCLR;
        if (lo1 == lo2 && hi1 == hi2)
        {
            return (uint64_t)hi2 << 32 | lo2;
        }
        hi1 = hi2;
        lo1 = lo2;
    }
}

void setup()
{
    while (!Serial) {}
    pinMode(0, HIGH);
}

void loop()
{
    constexpr unsigned delta = 32768 / 25;

    static uint64_t last = get_RTC_periods();
    uint64_t now = get_RTC_periods();

    if (now - last > delta)
    {
        last += delta;
        digitalWriteFast(0, HIGH);
        delayMicroseconds(1);
        digitalWriteFast(0, LOW);
    }
}

Screenshot 2021-09-12 151642.gif

As your original code this one will slightly drift because the crystal clock period (1/32768Hz) can not generate exactly 40ms. If necessary, you could improve this by changing 'last' and 'delta' to floats.
 
Here the float version. Just realized that you only need the lower 32bit of the counter which simplifies it even more:

Code:
void setup()
{
    while (!Serial) {}
    pinMode(0, HIGH);
}

void loop()
{
    constexpr float delta = 32768.0 / 25;

    static float last = SNVS_HPRTCLR;
    uint32_t now      = SNVS_HPRTCLR;

    if (now - last > delta)
    {
        last += delta;
        digitalWriteFast(0, HIGH);
        delayMicroseconds(1);
        digitalWriteFast(0, LOW);
    }
}

Screenshot 2021-09-12 153348.jpg

2000 pulses give 80.05s which looks good, but of course I don't know how precise my cheap LA is.

Edit: you need to take care of the overflow...
 
Status
Not open for further replies.
Back
Top