Summary: Rarely, this code works perfectly and receives the expected IDLE interrupt. But, mostly it does not get the expected IDLE interrupt.
Background: I'm implementing a 9-bit RS485 slave, using address match and DMA. Because the length of the messages is variable, I want to use the line idle interrupt to abort the DMA and complete message processing.
For some reason my code's ISR almost never receives the idle interrupt. The DMA transfer matches the specified match address, transfers the address and data bytes, and much later stops when a different match address is received, all as expected. But no IDLE bit set and no IDLE interrupt after the first message, when the line goes idle for a long time (as the master RS485 node awaits a reply). So the DMA keeps transferring messages until the DMA buffer fills up, then generates an EDMA user callback.
I've reviewed code from AN12679, but it does not use 9-bit.
So, what am I doing wrong? I put a break point before EDMA is commenced and the LPUART registers all look as I expect. But the IDLE flag is never set and the ISR never called.
@Omar_Anguiano- any idea?
Thanks in advance,
Best Regards, Dave
PS: I've got the idle interrupt working fine for non-9-bit fast DMA reception with variable length (see Efficient-asynch-reception-of-bursty-data-with-DMA).
PPS: Worse, very rarely, this code works properly. I left things running last night, and this morning found a solitary message on the RS485 master showing a brief connection! Something very strange going on here, maybe timing...
Here are the important code fragments. Initialization is called early, then nspResetListener_iMXRT1024 called to actually start processing.
static bool RS485hardwareInitialized = false;
// Initialize RS485 reception hardware. Call once, after the scheduler is started.
void RS485_UART_Init() {
memset(dmaBuffer, 0, sizeof(dmaBuffer));
// Set up DMAMUX channels for LPUART (DMAMUX module is already initialized)
DMAMUX_SetSource(DMAMUX, RS485_LPUART_DMA_TX_ChannelNumber, RS485_LPUART_DMAMUX_TX_request_source);
DMAMUX_SetSource(DMAMUX, RS485_LPUART_DMA_RX_ChannelNumber, RS485_LPUART_DMAMUX_RX_request_source);
DMAMUX_EnableChannel(DMAMUX, RS485_LPUART_DMA_TX_ChannelNumber);
DMAMUX_EnableChannel(DMAMUX, RS485_LPUART_DMA_RX_ChannelNumber);
// Initialize EDMA for LPUART (EDMA module is already initialized)
EDMA_CreateHandle(&lpuartTxEdmaHandle, DMA0, RS485_LPUART_DMA_TX_ChannelNumber);
EDMA_CreateHandle(&lpuartRxEdmaHandle, DMA0, RS485_LPUART_DMA_RX_ChannelNumber);
LPUART_TransferCreateHandleEDMA(RS485_LPUART, &lpuartEdmaHandle, EDMA_UserCallback, NULL,
&lpuartTxEdmaHandle, &lpuartRxEdmaHandle);
sendXfer.data = receiveXfer.data = dmaBuffer;
sendXfer.dataSize = receiveXfer.dataSize = NSP_buffer_size;
// Initialize RS485 LPUART for NSP 9-bit communications
lpuart_config_t lpuartConfig;
LPUART_GetDefaultConfig(&lpuartConfig);
lpuartConfig.baudRate_Bps = 62500;
lpuartConfig.parityMode = kLPUART_ParityDisabled;
lpuartConfig.dataBitsCount = kLPUART_EightDataBits;
lpuartConfig.stopBitCount = kLPUART_OneStopBit;
lpuartConfig.isMsb = false;
lpuartConfig.rxIdleType = kLPUART_IdleTypeStopBit; // line idle timer starts after a stop bit
lpuartConfig.rxIdleConfig = kLPUART_IdleCharacter1; // minimum kLPUART_IdleCharacter1
lpuartConfig.rxFifoWatermark = 0; // Receive does not really use FIFO; will be overridden in eDMA setup
lpuartConfig.txFifoWatermark = FSL_FEATURE_LPUART_FIFO_SIZEn(LPUARTx_BaseAddr) - 1;
lpuartConfig.enableTx = false; // Always start in receive mode, transmit is only used during brief reply
lpuartConfig.enableRx = false; // enabled later...
lpuartConfig.enableRxRTS = false; // RTS is used for RS485 bus control, not RX function
lpuartConfig.enableTxCTS = false; // no CTS in RS485 application
LPUART_Init(RS485_LPUART, &lpuartConfig, BOARD_BOOTCLOCKRUN_UART_CLK_ROOT);
LPUART_Enable9bitMode(RS485_LPUART, true);
LPUART_SetMatchAddress(RS485_LPUART, SN10_SENSORBOX_ADDR, 0U); // only using first match address 11
LPUART_EnableMatchAddress(RS485_LPUART, true, false);
RS485_LPUART->MODIR |= LPUART_MODIR_TXRTSPOL_MASK | // Set RTS polarity: 0b1.. RTS is active high.
LPUART_MODIR_TXRTSE_MASK; // enable Transmitter request-to-send RTS enable (for duplex control)
// RS485_LPUART->FIFO |= LPUART_FIFO_RXFLUSH_MASK; // clear RX FIFO
// ??? RS485_LPUART->FIFO &= ~LPUART_FIFO_RXFE_MASK; // disable RX FIFO, otherwise idle-detect may not work ???
NVIC_SetPriority(RS485_LPUART_IRQn, (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1)); // logically 1 lower than configMAX_SYSCALL_INTERRUPT_PRIORITY
RS485hardwareInitialized = true;
}
// Stop listening until next nine-bit! Start receiver for this slave.
// May be called AFTER PassMessageToApplicationLayer callback that sends reply.
extern "C" void nspResetListener_iMXRT1024(void) { // RESET_SLAVE_LISTENER()
// NSP layer may try to reset listener before reply message is completely
// transmitted, which would disrupt transmission.
// Delay until after TX (from shift register) is actually complete.
if(RS485_State == RS485_State_T::ReplySending) {
// when reply is completely transmitted, nspResetListener_iMXRT1024 will be called again
return;
}
assert(RS485hardwareInitialized); // hardware should have been initialized early
// Set up for and start receive (wait for this node's address)
LPUART_EnableTx(RS485_LPUART,false); // disable TX
// Start DMA receive, to be terminated within idle-line interrupt handler
LPUART_ClearStatusFlags(RS485_LPUART, kLPUART_IdleLineFlag);
LPUART_EnableInterrupts(RS485_LPUART, kLPUART_IdleLineInterruptEnable);
LPUART_EnableRx(RS485_LPUART,true); // enable RX
LPUART_ReceiveEDMA(RS485_LPUART, &lpuartEdmaHandle, &receiveXfer);
RS485_State = RS485_State_T::Receiving;
EnableIRQ(RS485_LPUART_IRQn);
}