2010-02-04 02:44 PM
DMA->USART transmission intermittently does not start
2011-05-17 04:39 AM
Okay, i'll try replying again... After typing out a response last time the bloody forum threw an exception and lost my message.
We use 25 Cortex M3's in our product, of which all 25 use at least one USART, some using two, one using three. 20 of the devices run at 1.5Mbps with new messages every 2ms. Each packet can be anywhere from 20-180 bytes long. The other devices run their USARTs at 115K2. Can you please post the code sections/functions that... Handle the DMA IRQ Handle the USART IRQ Setup the DMA/USART (Re)Load the DMA TX/RX2011-05-17 04:39 AM
Hi Ken,
While I haven't tried it with DMA, I have programmed the USART using the interrupt on this part. I have multiple USARTS running at 115200 baud. I too have observed conditions where it hangs in this manner, it seems to be a service speed issue as increasing the CPU speed makes it go away. I try to run the thing low to stress the system. Specifically I think there is a race condition between TE and TC, the former being when the transmit buffer is moved to the shifter, and the latter when the last bit has hit the wire. Touching the TC bit in the debugger's perpherial view, and off every thing goes again for a while. Given the way my code works I'd hazard that there is a condition when TE can be clear (not ready for new data) but the transmit register is actually empty. I'm servicing the TE interrupt while I have data to send, and turn it off when I don't (so it doesn't generate an interrupt storm), and periodically restart transmission when I have new data to send. The only way this wouldn't recover transparently is if the USART said it was busy. I will try to reproduce it again tomorrow. -Clive2011-05-17 04:39 AM
Thanks for the responses.
The code loads the DMA buffer and then kicks off the DMA transfer. When the transfer is complete DMA is disabled and the USART TC interrupt is then enabled. The purpose of this is to chain events to minimize CPU interrupts and still detect the exact end of transfer so the RS-485 transmitter can then be disabled. The macros that begin with CCB are replaced with the IRQ number, DMA channel, USART number, etc. from the firmware library at compile time based on the USART in use (it's a library used by several products). The code snippets below are listed in the order of events. Init USART----------------------------------------------------------------------------------------------------------------- void CCBInitCCBUSART( void ) { GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; USART_InitTypeDef USART_InitStructure; // Configure USART Tx as alternate function open-drain GPIO_InitStructure.GPIO_Pin = CCB_TxPin; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(CCB_GPIO, &GPIO_InitStructure); // Enable the CCB USART Interrupt NVIC_InitStructure.NVIC_IRQChannel = CCB_USART_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // Initialize USART USART_InitStructure.USART_BaudRate = CCB_BAUD_RATE; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(CCB_USART, &USART_InitStructure); USART_ITConfig(CCB_USART, USART_IT_IDLE, ENABLE); // Don't care about these interrupts, make sure they're disabled USART_ITConfig(CCB_USART, USART_IT_TXE, DISABLE); USART_ITConfig(CCB_USART, USART_IT_RXNE, DISABLE); // Clear any pending TC flag to eliminate spurious TC interrupt when enabled USART_ClearFlag( CCB_USART, USART_FLAG_TC ) ; // Enable the USART USART_Cmd(CCB_USART, ENABLE); } // end CCBInitCCBUSART() Init Tx DMA -------------------------------------------------------------------------------------------------------------- void CCBInitTxDMA( void ) { NVIC_InitTypeDef NVIC_InitStructure; // Initialize base TX DMA structure. // Per-message values will be set when message is transmitted by CCBTxMsg() CCBGbl.TxDMA_InitStructure.DMA_PeripheralBaseAddr = CCB_TX_REG_BASE; CCBGbl.TxDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; CCBGbl.TxDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; CCBGbl.TxDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; CCBGbl.TxDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; CCBGbl.TxDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; CCBGbl.TxDMA_InitStructure.DMA_Mode = DMA_Mode_Normal; CCBGbl.TxDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; CCBGbl.TxDMA_InitStructure.DMA_M2M = DMA_M2M_Disable; /* Enable the DMX USART TX Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = CCB_DMA_TX_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } // end CCBInitTxDMA() Code that loads DMA and kicks off transmission -------------------------------------------------------------- DMA_DeInit(CCB_DMA_TX_CH); CCBGbl.TxDMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) CCBGbl.TxBuffer ; CCBGbl.TxDMA_InitStructure.DMA_BufferSize = CCBGbl.TxLen ; DMA_Init(CCB_DMA_TX_CH, &CCBGbl.TxDMA_InitStructure); // Set up DMA for transfer DMA_ClearFlag( CCB_DMA_TX_IRQ_FLAGS ) ; // Clear any interrupt flags DMA_ITConfig( CCB_DMA_TX_CH, DMA_IT_TC | DMA_IT_TE, ENABLE ) ; CCB_RS485Transmitter(ENABLE); USART_DMACmd(CCB_USART, USART_DMAReq_Tx, ENABLE); // Enable USART DMA DMA_Cmd(CCB_DMA_TX_CH, ENABLE); // Kick of DMA transmit DMA interrupt handler. ------------------------------------------------------------------------------------------------- void CCB_DMA_TX_HANDLER(void) { // Check for Tx complete condition if( DMA_GetITStatus( CCB_DMA_TX_TC_FLAG ) == SET ) { CCBQueueEvent( E_TX_DMA_TC ) ; } else // Check for Tx Error condtion if( DMA_GetITStatus( CCB_DMA_TX_TE_FLAG ) == SET ) { CCBQueueEvent( E_TX_DMA_ERR ) ; } // Disable DMA Interrupts DMA_ITConfig( CCB_DMA_TX_CH, DMA_IT_TC | DMA_IT_TE, DISABLE ) ; DMA_Cmd(CCB_DMA_TX_CH, DISABLE); // Enable USART TC interrupts USART_ClearFlag(CCB_USART, USART_FLAG_TC ) ; // Clear USART interrupt USART_ITConfig(CCB_USART, USART_IT_TC, ENABLE); } // end CCB TX DMA interrupt handler2011-05-17 04:39 AM
Part of this problem appears to be related to a break condition on the serial lines. The behavior of what happens when a break condition occurs during a DMA transfer is something of a mystery.
2011-05-17 04:39 AM
I traced my problems to a overrun condition on the receive side, receive register needs to be read to clear, RXNE was clear, receiver stops accepting new data. USART interrupts don't get serviced quickly enough when writing to flash memory, and receiving firmware at 115200 at speeds some what less than 64 MHz. Part desperately needs Rx and Tx FIFOs like a 16550. Using DMA when you don't know how much data is coming seems fraught with race conditions and latency issues.
-Clive2011-05-17 04:39 AM
Writing to flash ''stalls'' the CPU according to documentation, so if you limit the amount you write each time that would free the core to process interrupts... I think :)
As for the unkown message size, we have the same scenario. We transmit and receive at 1.5Mbps on the usarts. We don't use the DMA RX interrupt at all (since you don't know what size you're receiving you can't set it). Instead, the transmitter does this... DMA TC interrupt, enables the USART TXC interrupt (whichever one indicates tx complete, NOT the one that indicates TX empty) USART TXC interrupt, this means tx has completely sent, nothing else required, this is the point in time when the receiver detects the idle state The receiver uses the DMA to receive with a buffer larger than your largest message/packet (i.e. it should never fill up completely). The receiving USART enables idle interrupts. When you are expecting to receive a message (we leave our modules in this state by default), all the receiver waits for is the idle interrupt. By the time that has happened, your dma has already handled the entirety of the incoming data... Calculate how much data came in using something like this, length_received = usart_configuration[port].receive_buffer_length_u8; length_received -= DMA_GetCurrDataCounter(USART_RX_DMA_Channelx[port]); As soon as you've removed the data from that buffer (alternatively you can double buffer and switch buffers as soon as the interrupt occurrs) you can reenable the receive process. Switching two receive buffers has the advantage of not needing to copy the data to another location and would speed things up for larger transfers. Maybe that will give you a few ideas of things to try. The RX IDLE interrupt definitely works and is our sole method of detecting that a receive has completed (with the exception of the RX DMA TC interrupt for unusual scenarios where buffer may fill up due to lack of idle) Darcy2011-05-17 04:39 AM
Darcy, that's exactly what we are doing.
2011-05-17 04:39 AM
Found the problem and wanted to close it out here.
We're doing RS-485, using DMA for both transmit and receive, and performing collision detection. Following a collision the TX DMA count register was not being reinitialized with the proper value and if the value was zero the TX DMA would ''hang'' when enabled (no TC/TE/HT interrupt would be generated). This was clearly our fault but it would have been helpful if the DMA would generate a TC interrupt when it is enabled with a count of zero.