2021-12-04 12:53 PM
I am using a STM32F407, and I would like to use DMA to receive data from USART1 and store in a buffer.
HAL is not being used.
USART1 is configured and is receiving the first byte of the message correctly. That is, the data shows up in USART1->DR.
Here is the DMA configuration.
//**********************************************************************
// Configure DMA2 controller for USART1_RX data transfer to memory.
// (DMA2-Stream2-Channel 4)
// PAR = Address of USART1 data register (DR)
// M0AR = Address of UART ReceiverBuf
// PSIZE = Byte size data transfers
// MSIZE set to same value as PSIZE for single transfers
// NDTR = 16 bytes to transfer.
// CR, By default, FIFO is disabled, and direct mode enabled
// CR, MINC = 1, Increment memory address pointer
// CR, PINC = 0, Peripheral address pointer remains fixed (default)
// CR, DIR = 00, Data transfer direction peripheral to memory (default)
// CR, PL = 11, Very high priority
// CR, CHSEL = 4, DMA2 Stream 2, channel 4
// CR, MBURST = 00, Single, (default)
// CR, PBURST = 00, Single, (default)
// Burst transfer modes set to "single", (default)
// We will not use interrupts to signal transfer complete.
// Instead, we will poll.
//***********************************************************************
DMA2_Stream2->PAR = &USART1->DR;
DMA2_Stream2->M0AR = &UART_ReceiveBuf[0];
DMA2_Stream2->NDTR = 16;
DMA2_Stream2->CR = \
4 << DMA_SxCR_CHSEL_BITPOS | \
DMA_SxCR_PL_VERY_HIGH | \
DMA_SxCR_PSIZE_BYTE | \
DMA_SxCR_MINC;
Here is a screen snapshot of the DMA registers.
To me, this appears to be setup correctly, and is ENabled.
USART1 registers when program was stopped.
Received data is 0x5A, but was never transferred to memory.
DMA doesn't ever appear to transfer data from USART1->DR to buffer memory in RAM.
Here is the code for a simple test loop to receive the 16 bytes of data through DMA transfer, then reconfigure and setup to receive again.
while(1)
{
DMA2_Stream2->CR |= DMA_SxCR_EN;
while( !(DMA2->LISR & DMA_LISR_TCIF2) ); // wait until data has been received
DMA2_Stream2->CR &= ~DMA_SxCR_EN; // temporarily disable this DMA stream to setup for next packet
while( (DMA2_Stream2->CR & DMA_SxCR_EN) ); // wait until EN bit is clear (xfr finished)
DMA2->LIFCR |= DMA_LIFCR_CTCIF2; // clear transfer complete interrupt flag
DMA2_Stream2->M0AR = &UART_ReceiveBuf[0]; // reset receive buffer pointer to start
DMA2_Stream2->NDTR = 16; // reload number of bytes to receive
}
I've seen a few posts that seem to suggest that USART1 interrupts have to be turned on. Is that correct ??? That doesn't seem right to me because that defeats the whole intent behind DMA. I don't want an ISR to handle the data, that is DMA's job. Shouldn't the DMA use the USART1->SR RXNE bit to trigger the receive DMA transfer ?
In any case, I seem to be stuck. Any help would be appreciated.
Thanks.
2021-12-04 02:17 PM
You need DMAR set in USART_CR3 in order to trigger the DMA transfer on RXNE.
You do not need interrupts enabled.
Note that reading DR in a debuggers will reset the RXNE flag.
2021-12-05 09:21 AM
Thank you TDK. That got me past my first hurdle. However, the DMA now works only once. That is, the expected data is received and stored in the buffer as expected (16 bytes sent by another device, and 16 bytes received and stored by the STM32F407. "Most of the time", it will only work one time, and will not work again until a reset is performed. Occasionally, it will work more than once if I am single stepping through the code. But it is unpredictable.
The initial configuration code is unchanged from previous posts.
The test code loop has changed (see below)
while(1)
{
DMA2_Stream2->CR |= DMA_SxCR_EN;
while( !(DMA2->LISR & DMA_LISR_TCIF2) ); // wait until data has been received
while( (DMA2_Stream2->CR & DMA_SxCR_EN) ); // wait until EN bit is clear (xfr finished)
USART1->CR3 &= ~USART_CR3_DMAR; // disable USART1 DMA for now
result = USART1->SR; // read USART1 status register
USART1->SR = 0; // clear all USART status bits
DMA2->LIFCR = 0x0F7D0F7D; // clear all lower DMA flags. Only stream 2 used
USART1->CR3 |= USART_CR3_DMAR; // re-enable USART1 DMA
}
Some screen snapshots are attached of the registers.
Here are the DMA and USART registers before the first transfer.
Here are the DMA and USART register immediately after the first transfer.
The DMA register are what I would have expected. However, the USART registers were a bit of a surprise in two instances.
Here are the DMA registers immediately before the 2d transfer is attempted. They are identical to registers before the 1st transfer was made.
They appear to be setup properly,as expected, and ready to go.
Here are the same registers after the 2d DMA transfer is attempted.
The DMA registers appear to show that no attempt was made to transfer data - the counter remains at 0x10. Also was somewhat expected that if no data was attempted to be moved out of the USART data register that there would be a data over-run. However, the RXNE flag does not show that any data was received.
This last result is especially puzzling to me. I know that data was sent because I capture it on a logic analyzer each time.
Is there some internal DMA mechanism that I am not satisfying ?
My apologies - I know that this is a lot of info to sort through.
Thanks.
2021-12-05 09:37 AM
If ORE is set, you're receiving characters that aren't being read by your code or by DMA.
You're not setting the length of transfer NDTR anywhere in your "test code loop". Starting DMA with NDTR=0 won't work. It's at 0 after the first transfer completes.
USARTx->SR = 0 doesn't clear all bits, only a subset of them.
2021-12-05 10:51 AM
At one time, I was setting the NDTR register to the desired count in the test loop. However, I came across this note which states that the NDTR is reloaded automatically with the previous value when the DMA is re-enabled. So I took that statement out of the loop.
You are spot on about the ORE flag being set, and that is really my point. This flag shouldn't be set at all if all the bytes had been read by the DMA, and they appear to have been read because they are in the buffer, but here we are with the ORE flag set at the end of the first transfer, and only 16 bytes were sent. ???
So here is an update.
I've changed the test loop code to the following.
while(1)
{
DMA2_Stream2->CR |= DMA_SxCR_EN;
while( !(DMA2->LISR & DMA_LISR_TCIF2) ); // wait until data has been received
while( (DMA2_Stream2->CR & DMA_SxCR_EN) ); // wait until EN bit is clear (xfr finished)
// USART1->CR3 &= ~USART_CR3_DMAR; // disable USART1 DMA for now
result = USART1->DR; // just read data register
result = USART1->SR; // read USART1 status register
// USART1->SR = 0; // clear all USART status bits
DMA2->LIFCR = 0x0F7D0F7D; // clear all lower DMA flags. Only stream 2 used
// USART1->CR3 |= USART_CR3_DMAR; // re-enable USART1 DMA
}
It appears as if disabling the DMA for USART1, the re-enabling caused some sort of problem where the DMA would only work once. Now the DMA triggers every time, but there is another problem.
The values in the ReceiveBuf are correct the first time through (value and location). But they are shifted by a byte from then on.
First time is correct.
Second time. First value is wrong, and all others are shifted by a byte.
Third time. The actual last byte received from the last message (0xA0) is in the first byte position. All others shifted down.
Nowhere in my test loop have I changed the source or destination pointers, or even the count for that matter due to the auto-reload.
Now here is something else that may or may not be an issue.
The data register for the USART is actually 9 bits [8:0]. I am not using parity, so I don't care about bit 8. The DMA is setup for byte sized transfer. I don't think that the DMA controller will know, or care about that extra bit. It just knows I've asked to transfer byte sized data. Right ?
It should be noted that as soon as I enable the DMA at the top of the test loop the second time, that I can see NDTR decrement from 0x10 to 0x0F immediately after enabling the DMA, even no data has been received yet.
2021-12-05 11:30 AM
Update.
I added code inside the test loop to reset the DMA controller, and re-init the DMA every loop - hammer approach.
while(1)
{
DMA2_Stream2->CR |= DMA_SxCR_EN;
while( !(DMA2->LISR & DMA_LISR_TCIF2) ); // wait until data has been received
while( (DMA2_Stream2->CR & DMA_SxCR_EN) ); // wait until EN bit is clear (xfr finished)
result = USART1->DR; // just read data register
result = USART1->SR; // read USART1 status register
// DMA2->LIFCR = 0x0F7D0F7D; // clear all lower DMA flags. Only stream 2 used
RCC->AHB1RSTR |= RCC_AHB1RSTR_DMA2RST; // RESET DMA. SEE IF THIS WORKS
RCC->AHB1RSTR &= ~RCC_AHB1RSTR_DMA2RST;
DMA2_Stream2->PAR = &USART1->DR; // RECONFIG DMA FROM BEGINNING
DMA2_Stream2->M0AR = &UART_ReceiveBuf[0];
DMA2_Stream2->NDTR = 16;
DMA2_Stream2->CR = \
4 << DMA_SxCR_CHSEL_BITPOS | \
DMA_SxCR_PL_VERY_HIGH | \
DMA_SxCR_PSIZE_BYTE | \
DMA_SxCR_MINC;
}
This did NOT fix the problem. This leads me to believe that this is not a DMA problem, but a USART problem in some way.
2021-12-05 11:50 AM
You're right on the NDTR issue, I didn't see that before.
I didn't dig into it much, but if ORE is set, chances are the USART is receiving a character while the DMA is not set up to handle it. I would reevaluate your assumptions here as it's unlikely to be a hardware issue. Perhaps show a scope trace with GPIO pins toggling at critical points in the code. The first transfer works, so the code you've posted is likely fine. The issue is within a transition, which isn't shown in your code snippets.
If ORE is already set by the time you start the second transfer, you've already lost.