cancel
Showing results for 
Search instead for 
Did you mean: 

USART2_LPUART2_IRQHandler fired when no interrupts are enabled in control register

kta
Associate II

Hello,

The cpu is STM32G0B1. I am developing the interrupt-based UART driver using FIFO and software circular buffers. The main point is, that I don't fully understand the mechanism of interrupts. I enabled two interrupts in CR3 register using RXFTIE and TXFTIE bits. The thresholds I set to: RX - 1 byte present in fifo, TX - 0 bytes present in fifo (empty). As I mentioned earlier, I am also using software circular buffer with several bytes of size. The mechanism is rather simple - interface with periodic writing/reading to/from circular buffer and then interrupts are handling the rest. First thing is - if there is no data to be sent or, vice-versa, the software RX buffer is full, I do not want to touch TDR/RDR register - so in order to stop interrupts from triggering over and over (eventually triggering the watchdog) I am using RXFTIE and TXFTIE bits to disable the interrupts. I then enable them again in periodic read/write functions when there is some data to be transmitted/received. However, what is super weird, is that sometimes in the interrupt routine I check for the RXFTIE and TXFTIE and they are both zeros. How is that possible?

They are "ghost" interrupts. It is also possible to have interrupt fired with RXFT == 0 and TXFT == 0 in ISR register.

My interrupt routine looks like this:

if (RXFT)

    Read whole RX fifo to circular buffer

else if (TXFT)

    Write whole TX fifo with data from circular buffer

else

    Count for "ghost" interrupts -> if more than 5 raise an error

6 REPLIES 6

While I'm not sure this is the reason for what you experience, it may be a similar effect than the "interrupts called second time if interrupt source is cleared late in the ISR" issue. If this is the case, you can safely ignore the "ghost" interrupts.

JW

As Jan indicates there is a tail-chaining-nvic hazard that causes reentry in some circumstances if the source is cleared immediately prior to exiting. You should always check and qualify sources.

The Handler here is also tasked with other sources.

The triggers can also come as a result of noise/overflow/framing type errors which must be explicitly cleared. My handlers check an clear these.

The if-else-if-else structuring is also flawed. Multiple sources can be triggering concurrently, so it's better to if (x), if (y), if (z) to handle all the different cases individually. All error, and pending RX and TX get serviced in ONE call of the IRQHandler.

Another thing to consider is that the UART flags it's internal state regardless of what you enable to generate an interrupt. The mask simply AND's across all the sources, and ORs them together to poke the NVIC for service.

For example TXE will always be set if it's not busy, you can mask it so it stops causing an IRQ, and then when you reenable it you'll immediately get an interrupt. So stuff new content in your ring/fifo buffer, then enable and  off it goes again. A fraction of a second later the new data will start shifting out, and the TDR will already be vacated, with TC firing as the last bit hits the wire and the STOP bit goes out.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

Thanks for the replies.

As far as I checked it's not the tail-chaining hazard, because I added wait time of 1 us at the end of IRQ routine and problem still exists. 

As I understood, if the speicifc pair of bits is 1 at the same time (e.g. TXE and TXEIE), the interrupt request is pushed to the NVIC and if corresponding interrupt is enabled in NVIC, the IRQ routine is fired - please feel free to correct me.

Question is, because there is only one interrupt routine to be called per peripheral (e.g. USART1_IRQn), is it possible to queue several interrupts of the same type (USART1_IRQn)? Or is the 'queue' only 1 element long? So I can have pending e.g. USART1_IRQn and USART2_LPUART2_IRQn, but after they are handled the condition is checked again?

I am trying to understand how is this possible to have no interrupts 'active' (as active here I mean that 2 corresponding bits are 1 per each interrupt e.g. TXFT AND TXFTIE), but still the IRQ is fired - this tail-chaining sounded promising.

"The triggers can also come as a result of noise/overflow/framing type errors which must be explicitly cleared. My handlers check an clear these." - I would be really grateful if you could elaborate more on this topic. I do not have any interrupts ever enabled, but RXFTIE and TXFTIE (also I would not expect errors happening so often)

Raja_Kaaliraj
Associate III

 

When developing an interrupt-based UART driver using FIFOs and circular buffers on the STM32G0B1, understanding the nuances of the UART interrupt mechanism is crucial. The issues you're experiencing, such as "ghost" interrupts and discrepancies in interrupt enable bits, can be attributed to several factors including improper handling of the interrupt flags, synchronization issues, and potential errata in the UART hardware.

Understanding UART Interrupts and Flags

For the STM32G0B1, UART interrupt handling typically involves:

  1. Interrupt Enable Bits: These are configured in the CR1, CR2, or CR3 registers (e.g., RXFTIE, TXFTIE).
  2. Interrupt Flags: These are status flags set by the hardware, found in the ISR (Interrupt Status Register) (e.g., RXFT, TXFT).
  3. Interrupt Clearing: Some flags might need to be cleared by reading from or writing to specific registers, or they might be auto-cleared under certain conditions.

Key Points to Address in Your Implementation

  1. Proper Enabling/Disabling of Interrupts:

    • Ensure that you correctly enable and disable the RXFTIE and TXFTIE interrupts. Disabling interrupts inside the ISR can cause synchronization issues since the interrupt might be processed before the enable bits are updated.
  2. Handling Ghost Interrupts:

    • Ghost interrupts can occur if the interrupt enable/disable mechanism is not synchronized with the interrupt status flags. This might happen if there's a delay or if the interrupt is still in the queue when you disable it.
    • Use volatile variables to ensure memory consistency between interrupt service routines and main code.
  3. Circular Buffer Management:

    • Ensure the circular buffer handling in the ISR and main loop is correctly synchronized. Improper handling can lead to buffer overflows or underflows, causing unpredictable behavior.
  4. Interrupt Priorities and Preemption:

    • Configure interrupt priorities properly to ensure critical interrupts are handled first and avoid conflicts with other interrupts.

Example Implementation

Here's a detailed example based on your description:

UART Initialization

Ensure the UART is properly initialized with interrupts enabled.

 

 

 

 

void UART_Init(void)
{
// Configure UART registers, baud rate, etc.

// Enable UART interrupt for RX and TX in CR3
USART1->CR3 |= USART_CR3_RXFTIE | USART_CR3_TXFTIE;

// Enable RX and TX FIFOs and set thresholds
USART1->CR3 |= USART_CR3_RXFTCFG_0; // RXFTCFG: 1 byte threshold
USART1->CR3 |= USART_CR3_TXFTCFG_0; // TXFTCFG: FIFO empty

// Enable UART
USART1->CR1 |= USART_CR1_UE;

// Enable USART1 global interrupt in NVIC
NVIC_EnableIRQ(USART1_IRQn);
}

 

 

ISR Implementation

Ensure the ISR correctly handles the interrupts and modifies the enable bits as needed

 

 

 

volatile uint8_t txBuffer[TOTAL_SIZE];
volatile uint8_t rxBuffer[TOTAL_SIZE];
volatile uint8_t txHead = 0, txTail = 0, rxHead = 0, rxTail = 0;

void USART1_IRQHandler(void)
{
if (USART1->ISR & USART_ISR_RXFT)
{
// Read the whole RX FIFO into the circular buffer
while (USART1->ISR & USART_ISR_RXNE)
{
if ((rxHead + 1) % TOTAL_SIZE != rxTail)
{
rxBuffer[rxHead] = USART1->RDR;
rxHead = (rxHead + 1) % TOTAL_SIZE;
}
else
{
// Buffer overflow, handle error
// Optionally disable RX interrupt
USART1->CR3 &= ~USART_CR3_RXFTIE;
break;
}
}
}

if (USART1->ISR & USART_ISR_TXFT)
{
// Write data from circular buffer into the TX FIFO
while (USART1->ISR & USART_ISR_TXE)
{
if (txTail != txHead)
{
USART1->TDR = txBuffer[txTail];
txTail = (txTail + 1) % TOTAL_SIZE;
}
else
{
// Buffer empty, disable TX interrupt
USART1->CR3 &= ~USART_CR3_TXFTIE;
break;
}
}
}

// Check for ghost interrupts
if (!(USART1->ISR & (USART_ISR_RXFT | USART_ISR_TXFT)))
{
static uint8_t ghostCount = 0;
ghostCount++;
if (ghostCount > 5)
{
// Handle ghost interrupt error
}
}
else
{
ghostCount = 0;
}
}

 

 

 

Handling in the Main Loop

Enable interrupts only when data is available.

 

c

 

 

void UART_Transmit(uint8_t data)
{
__disable_irq(); // Prevent race condition
txBuffer[txHead] = data;
txHead = (txHead + 1) % TOTAL_SIZE;
USART1->CR3 |= USART_CR3_TXFTIE; // Enable TX interrupt
__enable_irq();
}

void UART_Receive(void)
{
__disable_irq(); // Prevent race condition
if (rxTail != rxHead)
{
uint8_t data = rxBuffer[rxTail];
rxTail = (rxTail + 1) % TOTAL_SIZE;
// Process data
}
USART1->CR3 |= USART_CR3_RXFTIE; // Enable RX interrupt if buffer not full
__enable_irq();
}

 

 

Documentation and Resources

  1. STM32G0 Reference Manual:

  2. HAL Documentation:

  3. STM32G0 Datasheet:

  4. Community and Support:

Conclusion

By carefully managing the UART interrupts, ensuring synchronization, and correctly handling the FIFO and circular buffer, you can mitigate "ghost" interrupts and ensure reliable UART communication. Use the provided code examples as a reference, and make sure to refer to official documentation for detailed implementation guidance.

 

Many thanks for this example. My implementation is almost identical to yours, here I key differences:

- I am using 

while (USART1->ISR & USART_ISR_RXFNE) - RX FIFO not empty
while (USART1->ISR & USART_ISR_TXFNF) - TX FIFO not full

 but it still should do the job

- I am also using if/else if construct when checking for RXFT/TXFT instead of two ifs

- I am enabling/disabling interrupts while reading/writing from/to circular buffers using NVIC_EnableIRQ/NVIC_DisableIRQ, I am not sure if this is best - what is __disable_irq() actually doing?

I am starting to understand that my problem is caring too much for the 'ghost' interrupts, as ideally I wanted to have 0 of them

Your implementation and questions provide a good insight into how you're managing interrupts and handling UART communication on the STM32. Let’s address the nuances of your approach and offer some improvements for managing "ghost" interrupts effectively.

Differences in Your Implementation

  1. Use of RXFNE and TXFNF:

    • RXFNE (Receive FIFO Not Empty) and TXFNF (Transmit FIFO Not Full) are valid and ensure the FIFO status is directly checked. They should work fine for reading from and writing to the FIFO.

 

 

 

while (USART1->ISR & USART_ISR_RXFNE) // Read RX FIFO
{
    circularBufferWrite(USART1->RDR);
}
while (USART1->ISR & USART_ISR_TXFNF) // Write TX FIFO
{
    USART1->TDR = circularBufferRead();
}

 

 

 

  1.  

    Using if/else if:

    • Using if/else if ensures only one of the conditions is processed per ISR execution. This is typically good practice to handle one interrupt condition at a time unless you have specific needs to handle multiple conditions simultaneously.

     

     

 

 

if (USART1->ISR & USART_ISR_RXFT) // Check RX FIFO threshold
{
    // Process RX FIFO
}
else if (USART1->ISR & USART_ISR_TXFT) // Check TX FIFO threshold
{
    // Process TX FIFO
}
​

 

 

 

Enabling/Disabling Interrupts with NVIC:

  • Using NVIC_EnableIRQ and NVIC_DisableIRQ is a broader approach, affecting all interrupts on the specific IRQ line. This might be heavier and not as precise as manipulating specific UART interrupt enable bits.
  • __disable_irq() globally disables all interrupts and can be used to create atomic sections where you don't want any interrupts. However, it affects the entire system and should be used with caution.

 

 

__disable_irq(); // Globally disable interrupts
// Critical section code
__enable_irq(); // Re-enable interrupts

 

 

Improved Strategy for Interrupt Management

  1. Atomic Operations for UART Interrupts:

    • Instead of globally disabling interrupts or using NVIC functions, prefer enabling/disabling UART-specific interrupts by manipulating the CR1, CR2, or CR3 registers directly. This is more efficient and less disruptive.

 

 

USART1->CR3 &= ~USART_CR3_RXFTIE; // Disable RX FIFO threshold interrupt
// Read circular buffer
USART1->CR3 |= USART_CR3_RXFTIE;  // Re-enable RX FIFO threshold interrupt

 

 

  1. Handling "Ghost" Interrupts:

    • "Ghost" interrupts could be rare but should be managed gracefully. Instead of aiming for zero "ghost" interrupts, ensure they are handled properly without affecting normal operation.

 

 

else
{
    static int ghostInterruptCount = 0;
    ghostInterruptCount++;
    if (ghostInterruptCount > 5)
    {
        // Take appropriate action for persistent ghost interrupts
        // For instance, log the error, reset specific flags, or perform a system reset
    }
}

 

 

Suggested Implementation Improvements

  • Interrupt Service Routine (ISR): Simplify and ensure atomic operations. Clear flags carefully to prevent re-entering the ISR with stale conditions.

 

 

void USART1_IRQHandler(void)
{
    if (USART1->ISR & USART_ISR_RXFT) // RX FIFO threshold reached
    {
        while (USART1->ISR & USART_ISR_RXFNE) // Read RX FIFO not empty
        {
            circularBufferWrite(USART1->RDR);
        }
        USART1->ICR = USART_ICR_RXFTCF; // Clear RXFT flag
    }
    else if (USART1->ISR & USART_ISR_TXFT) // TX FIFO threshold reached
    {
        while (USART1->ISR & USART_ISR_TXFNF) // Write TX FIFO not full
        {
            USART1->TDR = circularBufferRead();
        }
        USART1->ICR = USART_ICR_TXFTCF; // Clear TXFT flag
    }
    else
    {
        static int ghostInterruptCount = 0;
        ghostInterruptCount++;
        if (ghostInterruptCount > 5)
        {
            // Handle persistent ghost interrupts
        }
    }
}

 

 

  • Circular Buffer Operations: Ensure circular buffer operations are safe from race conditions. Use atomic operations or disable specific interrupts around critical sections if needed.

 

 

__disable_irq(); // Disable interrupts
// Read from circular buffer
__enable_irq(); // Re-enable interrupts

 

 

Summary

  • Manage Interrupts Locally: Prefer local enabling/disabling of UART-specific interrupts over global interrupt control to reduce system-wide impact.
  • Efficient Flag Handling: Clear interrupt flags promptly and accurately after handling them to avoid "ghost" interrupts.
  • Debugging Tools: Use debugging tools to inspect the ISR and register states, and check how frequently and under what conditions "ghost" interrupts occur.

By following these practices, you should achieve a more robust and reliable UART interrupt handling mechanism on your STM32G0B1.