There is a UART driver issue that is present in SDK_2.7.0. While I have not tested it, I believe it is still present in SDK_2.8. I am using a MKE14F512VLL16. In my case I am also using FreeRTOS and have one task handling sending data out the UART and another task reading data from the UART, concurrently.
Things usually appear to work fine. However, we have witnessed a small number of mysterious device lockups and so I was tasked with looking into it. What I found was that in this bad state, the device would repeatedly receive a Tx interrupt. Inside of LPUART_TransferHandleIRQ() , the kLPUART_TxDataRegEmptyFlag would be set in STAT, and the kLPUART_TxDataRegEmptyInterruptEnable flag would be set in CTRL. However, the handle->txDataSize field would unexpectedly be zero. This caused an endless interrupt loop.
I believe the root cause of this problem is a small window of opportunity where a new Rx request would be made by the application (via LPUART_TransferReceiveNonBlocking()). Among other things, this request calls LPUART_DisableInterrupts() and LPUART_EnableInterrupts() to clear/set various interrupt flags. Normally this is fine. However, there is a problem in the following scenario:
1. IF within LPUART_EnableInterrupts (for example), the following line starts to be executed:
base->CTRL |= mask;
2. A Tx interrupt immediately occurs causing the last byte of a transfer to be written out. Note that in this case, the LPUART_TransferHandleIRQ() will do the following:
base->CTRL = (base->CTRL & ~LPUART_CTRL_TIE_MASK);
which (correctly) clears the Tx interrupt flag.
But, because the operation in #1 is not atomic (*), if the interrupt occurred after the read instruction of the read/modify/write instruction sequence in #1, but before the write instruction, when the ISR returns and the application call continues within LPUART_EnableInterrupts(), it will write stale data back to CTRL, with the Tx interrupt flag still set. This causes the lockup.
There might be a more elegant way to fix this, but one fix for this case is to disable the UART IRQs at the beginning of LPUART_DisableInterrupts() and LPUART_EnableInterrupts() and then reenable them before the function returns. For example:
void LPUART_EnableInterrupts(LPUART_Type *base, uint32_t mask)
{
// First NEW CODE block start
uint32_t instance = LPUART_GetInstance(base);
#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ
DisableIRQ(s_lpuartTxIRQ[instance]);
DisableIRQ(s_lpuartRxIRQ[instance]);
#else
DisableIRQ(s_lpuartIRQ[instance]);
#endif
// First NEW CODE block end
base->BAUD |= ((mask << 8U) & (LPUART_BAUD_LBKDIE_MASK | LPUART_BAUD_RXEDGIE_MASK));
#if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO
base->FIFO = (base->FIFO & ~(LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK)) |
((mask << 8U) & (LPUART_FIFO_TXOFE_MASK | LPUART_FIFO_RXUFE_MASK));
#endif
mask &= 0xFFFFFF00U;
base->CTRL |= mask;
// Second NEW CODE block start
#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ
EnableIRQ(s_lpuartRxIRQ[instance]);
EnableIRQ(s_lpuartTxIRQ[instance]);
#else
EnableIRQ(s_lpuartIRQ[instance]);
#endif
// Second NEW CODE block end
}
-------------------------------------------------------------------------------------
(*) The disassembly of base->CTRL |= mask shows the following (non-atomic):
00066a58: ldr r3, [r7, #4]
00066a5a: ldr r2, [r3, #24] // copies base->CTRL into temporary register
00066a5c: ldr r3, [r7, #0] // WINDOW OF OPPORTUNITY exists here if interrupt
00066a5e: orrs r2, r3 // occurs and ISR modifies CTRL flag. If it does,
00066a60: ldr r3, [r7, #4] // our CTRL copy will now be stale!
00066a62: str r2, [r3, #24] // writes potentially stale result of OR back to base->CTRL,
666 } // potentially losing ISR change