Problem with direct interrupt / zero latency interrupt on nRF5340

Hello,

I'm currently trying to get a realtime regulation algorithm for the PWM0 module running. I use bare metal programming for the PWM and interrupts. My intention is to trigger an interrupt after each PWM period end, so that I can manually calculate the next PWM cycle (simple calculation within only a couple of cycles). My PWM counter top is configured to 1023, prescaler running at DIV_1 which means 16 MHz. So the interrupt should be triggered with a period of 16 kHz. The PWM generation works just fine (I can update the PWM memory location from within a Zephyr task, but this doesn't satisfy our realtime requirements.

Now I tried to add the interrupt. According to the documentation, I used the following Zephyr declaration:

ISR_DIRECT_DECLARE(pwm_isr)
{
    //NVIC_ClearPendingIRQ(PWM0_IRQn);
    m_pwm_interrupt_cnt++;
    return 0;
}

For now, the interrupt shall just increase a memory variable and then return to the calling context.

I configured and enabled the interrupt this after configuring and starting PWM generation (last three lines of the following code block):

    // configure PWM0 [...]
  NRF_PWM0->INTENCLR = 0xFFFFFFFF;
    NRF_PWM0->SHORTS = (PWM_SHORTS_LOOPSDONE_SEQSTART1_Enabled << PWM_SHORTS_LOOPSDONE_SEQSTART1_Pos);
    NRF_PWM0->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
    while (!NRF_PWM0->ENABLE);                              // wait until enabled
    NRF_PWM0->TASKS_SEQSTART[1] = 1;                        // start PWM generation
    while (!NRF_PWM0->EVENTS_SEQSTARTED[1]);                // wait until started
    // enable PWM0 period interrupt for regulation algorithm
    IRQ_DIRECT_CONNECT(PWM0_IRQn, 0, pwm_isr, 0);
    NRF_PWM0->INTENSET = PWM_INTENSET_PWMPERIODEND_Msk;
    irq_enable(PWM0_IRQn);

Just to be sure, I activated Zero Latency Interrupts by adding the following line to prj.conf (if you have a look above, I set the interrupt priority to 0 which is highest priority):

CONFIG_ZERO_LATENCY_IRQS=y

The interrupt is triggered, I can debug the code and set a breakpoint at the interrupt handler. The variable is also increased steadily (the interrupt handler is calles repeatedly). Now, the problem is that the interrupt seems to be starving the whole system. My threads are not executed anymore, the call stack at the interrupt handler's breakpoint shows the same layout all the time. If I use the "Step Over" function of the debugger, I end up in my thread's code for a short moment, but immediately execution is interrupted again and the statement does not advance anymore. (Some times in the beginning, the thread seemed to advance one line between each interrupt (or couple of interrupts), but then it stopped.)

I tried adding the NVIC_Cllear_Pending_IRQ() call to my interrupt handler, but it did not help. I also tried to set the prescaler of my PWM configuration to DIV_16 (which should lower the interrupt period to 1 ms), but this also did not help.

Does anyone have an idea what is going wrong here? How can I continue processing my thread's code? For a 128 MHz MCU, a 16 kHz interrupt which just increments a single memory location should not be an issue in my opinion. Especially if I further decrese the period to 1 kHz.

The Nordic plugin also does not help very much. I tried to check the NVIC peripheral, but Visual Studio Code just shows some garbage values that don't make any sense for the register content (all ISER/ICER registers have the same value):

I also tried to check the CLOCK configuration (which is a bit confusing because the module shown in the debugger screen is called CLOCK_NS... I hope the data is still valid? HFCLKSTAT suggests that the HFCLK is not running at all?????

By the way, I started my implementation based on the sample with three threads which was suggested on one of the "getting started" pages from Nordic. At least some weeks ago, it wasn't possible to start a new project if you didn't use an existing project as a template. Maybe this information can help tracking down my problem...

I really hope for quick help on this topic, as my colleagues are waiting for the interrupt based software framework. Thank you very much

  • The isr function must reset the condition that trigerred the interrupt. Otherwise the processor just keeps tail-chanining into the function again.

    Your function does not do that, and runs at highest possible priority - so nothing else can preempt it either.

  • You mean the PWMPERIODEND event in the pwm module? I could try that...

    Is the NVIC state (clear pending interrupt) handled automatically by the macro definition? (If yes, will the pending flag be reset in the header or the footer? Just to be sure what happens in the case of a timeslice overrun of the interrupt code).

    And concerning the NVIC screenshot during debug, do you know what's the issue with it? Is this a nRF Connect SDK bug?

  • Hi Michael

    It is true that you need to clear the event manually in the interrupt handler (clearing the pending flag in the NVIC is not required). 

    Potentially an interrupt could be triggered by many different events, and the event handler will typically check the status of each event in turn, and clear it if set, before running the associated code. 

    Secondly, when using the ISR_DIRECT_DECLARE() macro you should also use the ISR_DIRECT_PM() macro at the end of the interrupt handler. 

    For an example of this please have a look at this interrupt handler from the ESB library. 

    If you look at the implementation of the radio_irq_handler() function, here, you can see that it checks if the event is enabled and clears it, as described above. It also checks if the event interrupt is enabled, but you can skip this if you are not planning to enable or disable interrupts for the event dynamically. 

    For the debugger issue, I was able to reproduce this here. For some reason the VSCode debugger doesn't properly interpret the ISER and ICER registers as an array, and they all point to the same register (ISER0). 
    If you try to read the register in the code, or debug with Ozone, the register values are correct. 

    I will report this to the VSCode team and ask if they can look into it. 

    Best regards
    Torbjørn

  • Clearing the PWMPERIODEND event in my interrupt handler was actually what made it work. Thanks for the hint, Turbo J!

    I was falsely assuming that the PWM end is just communicated as an event (edge based) to the NVIC to trigger the interrupt bending bit, just like it does when triggering a task (which works fine in the background even without clearing the event bit).

    ISR_DIRECT_DECLARE(pwm_isr)
    {
        NRF_PWM0->EVENTS_PWMPERIODEND = 0;      // This is needed to reset the pending interrupt and return to the calling context
        m_pwm_interrupt_cnt++;
        return 0;
    }
  • Thanks for the detailed reply, Torbjørn. Clearing the event helped.

    As far as I know, I have to call ISR_DIRECT_PM() only if I want to trigger the scheduler (e.g. my interrupt triggers some context switch to a higher priority thread). This is not the case in my implementation, so I don't call the macro according to the documentation that I have read. Especially as my interrupt is running at 16 kHz and the system tick is only 1 kHz if I remember correctly

    Can you confirm that I don't need ISR_DIRECT_PM() in my scenario and that it is not mandatory to call it in EVERY direct isr implementation?

    Also thanks for forwarding the plugin bug to the VSCode team.

Related