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

Thread: Interrupt registers names on Teensy 4.1

  1. #1
    Junior Member
    Join Date
    Aug 2021
    Posts
    8

    Interrupt registers names on Teensy 4.1

    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

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,078
    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,

  3. #3
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    25,050
    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/co...sy4/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.

  4. #4
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,078
    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);
      }
      myTimer.begin( isrTimer, 1000000/25 );
    
    }
    
    /* // 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
      }
    */
    
    void isrTimer() {
      static byte cycleCount = 24;
      if (cycleCount == 0) {
        cycleCount = 25;
      }
      cycleCount--;
    
      frameCounter++; //actual frame counter
    }

  5. #5
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    25,050
    Also as mentioned on your other threads, if you use Teensy's non-blocking libraries OctoWS2811 or WS2812Serial, you probably won't need this interrupt code at all.

    You can just use millis() or elapsedMillis for the 25ms timing if you use those libraries for the LEDs.

  6. #6
    Junior Member
    Join Date
    Aug 2021
    Posts
    8
    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.

  7. #7
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Location
    Cheltenham, UK
    Posts
    412
    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.

  8. #8
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,623
    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);
        }
    }
    Click image for larger version. 

Name:	Screenshot 2021-09-12 151642.gif 
Views:	14 
Size:	34.9 KB 
ID:	25824

    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.

  9. #9
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,623
    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);
        }
    }
    Click image for larger version. 

Name:	Screenshot 2021-09-12 153348.jpg 
Views:	16 
Size:	21.0 KB 
ID:	25825

    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...

Posting Permissions

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