cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F4: EXTI: Fails to capture rising edge interrupt sometimes

MSuhe
Associate III

In out project we are making use of EXTI for interfacing a radio (RF) ASIC with STM32F411. Whenever, the radio has some data to be sent host it interrupts the host using the I/O line (drives the line high). This I/O line stays high till data is read by STM32 over SPI.

Now, we have configured this line to be external interrupt line with EXTI. And the functionality works as expected most of the time. But, we do see some scenarios where, EXTI controller does not raise an interrupt even though the I/O line is high.

To overcome this issue, we are monitoring the state of the I/O line to check if it is high and if true, try and read the data from radio over SPI.

A quick check at the state of the EXTI registers whenever this happens shows PR (pending register) set to 0 (no interrupt pending) and IMR (interrupt mask register) set to 0x400, which mean the associated interrupt being enabled.

Is there any reason why the EXTI might fail to capture a rising edge on the I/O line? STM32F4 does not support level sensitive interrupts, otherwise we could have configured the external interrupt as level sensitive.

Please do share your inputs.

15 REPLIES 15
MSuhe
Associate III

Hi Bob,

Please find the ISR handler and ISR kick functions as below. Yes, from the radio side interrupt single is level sensitive & not edge sensitive. BTW, is it possible to have edge sensitive external interrupts with STM32 by any means? I know EXTI does not support level sensitive interrupts. But, is there any possible way to have level sensitive interrupts?

static void radio_irq(void)   /* EXTI interrupt handler. */
{
    do {
             rf215_isr_handler(data->phy[index]);
        /* Check the status of external interrupt line and if it is still
         * high go back and run the ISR handler again. Radio might have
         * new events to report. */
    } while (radio_irq_check_pin(dev));
} /* radio_irq */
 
void radio_isr_kick(void)
{
    radio_irq_disable();
    radio_isr_handler();
    radio_irq_enable();
} /* radio_isr_kick */
 
void
radio_isr_handler(void)
{
   if (!radio_irq_check_pin(drv->device)) {
        return;
    }
 
    /* Read the IRQ registers - Radio will clear the interrupt line, once all the interrupt flags are 
        read. */
    radio_update_irq_status();
    
   /* ------------ Handle different interrupt events. ------------*/
}

Lots of details are missing so I'll have to make some assumptions.

  • radio_irq() passes "dev" to radio_irq_check_pin() to (I presume) see if the receiver (EDITED, was "ADXL" but I that was from a different thread) interrupt signal is still asserted, and radio_isr_handler() passes "drv->device". You do not show how "dev" is set in radio_irq(). Verify that the value passed in radio_irq() is the same value that radio_isr_handler() passes as "drv->device" (i.e. make sure you are reading the correct pin, and the SAME pin in both function calls).
  • Your code does not show when you clear the EXTI "pending" bit
  • The parameter you pass to rf215_isr_handler() suggests that there are multiple devices/radios that you can talk to. Is that the case? If so, are there multiple devices wire-ORed into the single EXTI input?
  • Why does radio_isr_kicker() call DIFFERENT code to service the interrupt? Or does radio_update_irq_status() eventually trigger a call to radio_isr() by forcing the interrupt (like setting a bit in the "software interrupt event register"? Ideally you never want 2 different functions performing the same function - it is a maintenance nightmare when one function gets updated and the other doesn't. In addition, forcing the interrupt via the software interrupt means you don't need the radio_irq_disable() and radio_irq_enable() calls in radio_isr_kicker().

As for handling a level sensitive interrupt input - you *MAY* be able to use a timer in "Slave mode, Gated mode". I haven't tried this on the STM32 family, but have used this feature in other CPU families. In this mode the timer only counts when the input signal is high or low (you get to choose which one). Then you can set the ARR to 1 so that the timer generates an overflow interrupt immediately when it starts counting. This way you will continue to get timer overflow interrupts as long as your "interrupt" signal is asserted. But I would try to figure our why your current code isn't working before trying this (presuming you have a timer available, and can get the interrupt signal to the appropriate timer input pin).

> As for handling a level sensitive interrupt input - you *MAY* be able to use a timer in "Slave mode, Gated mode". [...] Then you can set the ARR to 1 so that

> the timer generates an overflow interrupt immediately when it starts counting.

Nice trick, Bob, thanks!

Jan

MSuhe
Associate III

Hi Bob,

I tried to keep the code simple that's why removed all the parameters passed to individual function call & also renamed function names (but, i left some API names unmodified i guess). Please find the cleaned code as below:

Regarding your queries:

  • We clear the EXTI PR register right before radio_irq() is called by the IRQ parent EXTI. When EXTI captures the edge.
  • Yes, the radio we have has a single wired interface (SPI & external interrupt line etc...) but supports two different baseband controllers one for 2.4GHz band & other for sub-GHz band.

Hope this clears the confusion.

static void radio_irq(void)   /* EXTI interrupt handler. */
{
    do {
             radio_isr_handler();
        /* Check the status of external interrupt line and if it is still
         * high go back and run the ISR handler again. Radio might have
         * new events to report. */
    } while (radio_irq_check_pin());
} /* radio_irq */
 
void radio_isr_kick(void)
{
    radio_irq_disable();
    radio_isr_handler();
    radio_irq_enable();
} /* radio_isr_kick */
 
void
radio_isr_handler(void)
{
   if (!radio_irq_check_pin()) {
        return;
    }
 
    /* Read the IRQ registers - Radio will clear the interrupt line, once all the interrupt flags are 
        read. */
    radio_update_irq_status();
    
   /* ------------ Handle different interrupt events. ------------*/
}

Earlier you said that your ISR was sometimes missing data from the receiver, so you added your "kicker" function. And you instrumented your code and verified that sometimes your kicker function indeed found that the interrupt line was high and not serviced by your interrupt function. Then you added code in your ISR that looped as long as the EXTI/GPIO signal is asserted (and I can only PRESUME that your radio_irq-check_pin() actually reads the GPIO->IDR register).

What I now see in you radio_isr_kick() function is that you disable the radio IRQ, manually call your ISR, then re-enable the radio IRQ. Presuming that radio_irq_disable() disables the EXTI interrupt, you have a race condition here. There will be times where the radio asserts its interrupt signal just after your call to radio_irq-disable() and before your call to radio_isr_handler(). Which will make it look like your kicker function found a case where the interrupt didn't work. But in reality, your kicker code prevented the interrupt from working.

So as a test - remove/comment out the 3 lines from radio_isr_kick() - the disable IRQ call, ISR call and enable IRQ call. See if you still get to a point where you miss data from the radio. I suspect (hope :) that your ISR will now correctly handle data from the radio.

If indeed you still miss data, and if you really do need your "kicker" function, change it. DO NOT disable interrupts then manually call your interrupt code. Instead, as I've mentioned before, manually trigger the interrupt using the EXTI software interrupt event trigger (SWIER) register. This removes the race condition in the existing kicker function and (even better) guarantees that you always service the radio's interrupts using the exact same code sequence.

P.S. I know that I mentioned earlier that when you post code you could remove radio-specific stuff presumably not directly related to this issue. And I know this can be a tough choice of what to include in the post and what to take out. However, the more you edit/change names of functions, remove parameters, etc., the more you hide possible issues with your ACTUAL code, as opposed to your posted code.

Hello!

A concept behind these Atmel radios is to have fast enough code to take the received data primarily and other necessary information from it before next IRQ occurs.

IRq pin from this device is cleared (LO) only if you read all four (4) interrupt status registers using the SPI. So, supposing that code reads first of all these rfic registers to take action , you must ensure that you use fast code to take all the data before next IRQ occurs, else what? overwritten? lose data?

A practice is to configure the rfic not to generate interrupts in IRQ pin for everything but for some absolute necessary reasons eg "Receiver Frame End" and not e.g. for "Receiver Frame Start".

And to solve this ambiguity about lost edges, configure a timer as an external input counter counting on rising edges with zero prescaller and full ARR value and connect the input pin to IRQ pin (EXTI_GPIO input, TIm_Input an IRQ pin tied together)

Timer will count the rise edges. Define a global integer variable, initialize it to zero and increase it by one inside the radio_irq. Compare the values of timer's counter with this variable to see if finaly EXTI Fails to capture rising edges sometimes. HW timers tell always the truth.