GPIO IRQ Priority on Teensy 4.1

brtaylor

Well-known member
I have a Teensy 4.1 project that has two external interrupts:
  • One interrupt is tied to the data ready pin from a sensor. When this fires, I read the sensor data, perform some processing on the data, and add it to a circular buffer. The sensor is configured to fire this interrupt at 200 Hz. In the lower priority loop, this buffer is read and dumped to an SD card in 512 byte chunks.
  • One interrupt is tied to GPS timepulse, which is configured to fire at 10 Hz. I use this to reset an elapsedMicros timer and, along with the GPS data, I can timestamp my 200 Hz data frames relative to the GPS time of week with microsecond resolution. Within this interrupt, I also compare the elapsedMicros to the expected duration to compute a scale factor to correct the elapsedMicros result.
These interrupts work well individually. However, running them both, I'm noticing jitter in my GPS timepulse interrupt, which I'm assuming occurs when the GPS timepulse fires while I'm servicing the sensor data ready interrupt. I'd like to set the GPS timepulse interrupt to a higher priority than the sensor data ready interrupt.

After reading threads about how all GPIO pins are on IRQ_GPIO6789 since they are configured for fast I/O, it was suggested that one of the pins could be configured for slow I/O and put on a different IRQ.

I'm writing out the path I'm following, partly as notes for myself, but I welcome feedback on the approach and whether I'm headed in the right direction.

In startup.c, it looks like fast I/O is set here:

From Section 3.2.2 of this application note (https://www.nxp.com/docs/en/application-note/AN12240.pdf), looks like I set a bit to zero to set it for slow I/O.

I've been trying to figure out how this ties together. If I follow GPIO6_DR, that's used in core_pins.h to set the pin port registers, i.e.:

That definition comes from imxrt.h:

So, it looks to me like that I could modify the bits in startup.c to configure a pin for slow I/O and then use GPIO1_DR instead of GPIO6_DR in the port register.
 
I believe you don’t need to change startup.c; just write the registers yourself, say, in setup(). Then there’s no reliance on a modified SDK.
 
I believe you don’t need to change startup.c; just write the registers yourself, say, in setup(). Then there’s no reliance on a modified SDK.
That's cool! I'm working on decreasing the priority of pin 35. So I guess after changing the value of IOMUXC_GPR_GPR27 in setup(), I would then update the appropriate row in digital_pin_to_info_PGM in digital.c to reflect using GPIO2 instead of GPIO7?
 
You’d need to change that core file if you want the built-in pin access functions to work correctly. Or, if you didn’t want to modify the core at all, you could just copy the functionality from the pin functions you need into your own functions. That’s the kind of approach I usually take to avoid having a special version of the core.

I can’t speak to separate priorities for GPIO2 and GPIO7 because I haven’t done that myself, but I’ll add a reminder about priority numbers: only the top 4 bits (of 8) are used, for an effective 16-level priority range. For example, priority level 64 is the same as priority level 65.
 
You’d need to change that core file if you want the built-in pin access functions to work correctly. Or, if you didn’t want to modify the core at all, you could just copy the functionality from the pin functions you need into your own functions. That’s the kind of approach I usually take to avoid having a special version of the core.

I can’t speak to separate priorities for GPIO2 and GPIO7 because I haven’t done that myself, but I’ll add a reminder about priority numbers: only the top 4 bits (of 8) are used, for an effective 16-level priority range. For example, priority level 64 is the same as priority level 65.
Thanks for the help! I got that hacked into setup by copying the attachInterrupt code into my own version using IRQ_GPIO2_16_31 instead of IRQ_GPIO6789. Will have to clean up the code, but it looks like it's working as expected without needing custom cores.
 
For posterity, I wanted to decrease the priority of an interrupt attached to pin 35. So I added this to my setup():

Code:
  IOMUXC_GPR_GPR27 = 0xEFFFFFFF;
  digital_pin_to_info_PGM[35] = {&GPIO2_DR, &CORE_PIN35_CONFIG, &CORE_PIN35_PADCONFIG, CORE_PIN35_BITMASK};

And as a quick hack, I copied the attachInterrupt code while changing the IRQ:

Code:
void attachInterruptCustom(uint8_t pin, void (*function)(void), int mode)
{
    if (pin >= CORE_NUM_DIGITAL) return;
    //printf("attachInterrupt, pin=%u\n", pin);
    volatile uint32_t *gpio = portOutputRegister(pin);
    volatile uint32_t *mux = portConfigRegister(pin);
    volatile uint32_t *pad = portControlRegister(pin);
    uint32_t mask = digitalPinToBitMask(pin);

    uint32_t icr;
    switch (mode) {
        case CHANGE:  icr = 0; break;
        case RISING:  icr = 2; break;
        case FALLING: icr = 3; break;
        case LOW:     icr = 0; break;
        case HIGH:    icr = 1; break;
        default: return;
    }

  attachInterruptVector(IRQ_GPIO2_16_31, function);
  NVIC_ENABLE_IRQ(IRQ_GPIO2_16_31);

    // TODO: global interrupt disable to protect these read-modify-write accesses?
    gpio[IMR_INDEX] &= ~mask;    // disable interrupt
    *mux = 5;            // pin is GPIO
    *pad |= IOMUXC_PAD_HYS;        // use hystersis avoid false trigger by slow signals
    gpio[GDIR_INDEX] &= ~mask;    // pin to input mode
    uint32_t index = __builtin_ctz(mask);
    if (mode == CHANGE) {
        gpio[EDGE_INDEX] |= mask;
    } else {
        gpio[EDGE_INDEX] &= ~mask;
        if (index < 16) {
            uint32_t shift = index * 2;
            gpio[ICR1_INDEX] = (gpio[ICR1_INDEX] & ~(3 << shift)) | (icr << shift);
        } else {
            uint32_t shift = (index - 16) * 2;
            gpio[ICR2_INDEX] = (gpio[ICR2_INDEX] & ~(3 << shift)) | (icr << shift);
        }
    }
    gpio[ISR_INDEX] = mask;  // clear any prior pending interrupt
    gpio[IMR_INDEX] |= mask; // enable interrupt
}

Then I could attach the interrupt and change the priority:
Code:
  /* Attach IMU DRDY interrupt */
  pinMode(IMU_DRDY, INPUT);
  attachInterruptCustom(IMU_DRDY, drdy, RISING);
  NVIC_SET_PRIORITY(IRQ_GPIO2_16_31, 144);

Seems to be working well and I no longer have timing jitter on the other interrupt.
 
Back
Top