2024-06-13 04:46 AM - last edited on 2024-06-13 05:00 AM by Peter BENSCH
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
2024-06-13 03:11 PM
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
2024-06-13 05:46 PM
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.
2024-06-13 10:36 PM
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)
2024-06-13 11:50 PM
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.
For the STM32G0B1, UART interrupt handling typically involves:
Proper Enabling/Disabling of Interrupts:
Handling Ghost Interrupts:
Circular Buffer Management:
Interrupt Priorities and Preemption:
Here's a detailed example based on your description:
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);
}
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;
}
}
Enable interrupts only when data is available.
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();
}
STM32G0 Reference Manual:
HAL Documentation:
STM32G0 Datasheet:
Community and Support:
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.
2024-06-14 12:13 AM
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
2024-06-14 09:34 PM
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.
Use of RXFNE and TXFNF:
while (USART1->ISR & USART_ISR_RXFNE) // Read RX FIFO
{
circularBufferWrite(USART1->RDR);
}
while (USART1->ISR & USART_ISR_TXFNF) // Write TX FIFO
{
USART1->TDR = circularBufferRead();
}
Using if/else if:
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:
__disable_irq(); // Globally disable interrupts
// Critical section code
__enable_irq(); // Re-enable interrupts
Atomic Operations for UART Interrupts:
USART1->CR3 &= ~USART_CR3_RXFTIE; // Disable RX FIFO threshold interrupt
// Read circular buffer
USART1->CR3 |= USART_CR3_RXFTIE; // Re-enable RX FIFO threshold interrupt
Handling "Ghost" Interrupts:
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
}
}
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
}
}
}
__disable_irq(); // Disable interrupts
// Read from circular buffer
__enable_irq(); // Re-enable interrupts
By following these practices, you should achieve a more robust and reliable UART interrupt handling mechanism on your STM32G0B1.