cancel
Showing results for 
Search instead for 
Did you mean: 

Problems using USART with idle line interrupt and dma to send and receive data.

JHöfl.1
Associate II

I'm trying to use the idle line interrupt in combination with dma to send and receive data from the serial port to my STM32F417VG based board.

I got as far as being able to send with dma successfuly and also reveive data via the idle line interrupt. The problem is that I get only one correct receive and after the hole thing breaks.

Here is my init code for usart and dma:

void init_usart(void)
{
	RCC->APB2ENR |= (RCC_APB2ENR_USART1EN);
    
	__NVIC_SetPriority(USART1_IRQn, 0);
	__NVIC_EnableIRQ(USART1_IRQn);
    
	USART1->CR1 |= (USART_CR1_RE |
			USART_CR1_TE | 
			USART_CR1_IDLEIE);
 
        USART1->CR3 |= ((USART_CR3_DMAR) | (USART_CR3_DMAT));
	USART1->CR1 |= (USART_CR1_UE);
 
        RCC->AHB1ENR |= (RCC_AHB1ENR_GPIOAEN);
 
	GPIOC->MODER |= (2 << 18);  // PA9 (USART1-TX)
	GPIOC->MODER |= (2 << 20); // PA10 (USART1-RX)
	GPIOC->AFR[1] |= (7 << 4); // PA9 Alternate Function: USART1 (AF7)
	GPIOC->AFR[1] |= (7 << 8); // PA10 Alternate Function: USART1 (AF7)
}
 
void init_dma(void)
{  
        RCC->AHB1ENR |= (RCC_AHB1ENR_DMA2EN);
 
	__NVIC_SetPriority(DMA2_Stream7_IRQn, 0);
	__NVIC_EnableIRQ(DMA2_Stream7_IRQn);
 
        /* DMA2_Stream2: RX */
        DMA2_Stream2->CR |= ((4 << DMA_SxCR_CHSEL_Pos) |
                        (2 << DMA_SxCR_PL_Pos) |
                        (DMA_SxCR_MINC);
 
	DMA2_Stream2->PAR = (uint32_t) &(USART1->DR);
 
        /* DMA2_Stream7: TX */
        DMA2_Stream7->CR |= ((4 << DMA_SxCR_CHSEL_Pos) |
                        (1 << DMA_SxCR_PL_Pos) |
                        (DMA_SxCR_MINC) |
                        (1 << DMA_SxCR_DIR_Pos) |    // Direction: memory to peripheral
                        (DMA_SxCR_TCIE);
 
	DMA2_Stream7->PAR = (uint32_t) &(USART1->DR);
}
 
void usart_send_dma(uint8_t *msg, uin16_t len)
{
        transferComplete = 0;
        DMA2_Stream7->M0AR = msg;
        DMA2_Stream7->NDTR = len;
	DMA2_Stream7->CR |= (DMA_SxCR_EN);
}
 
void DMA2_Stream7_IRQHandler(void)
{
	if (DMA2->HISR & (DMA_HISR_TCIF7))
	{
		DMA1->HIFCR |= (DMA_HIFCR_CTCIF7);
                transferComplete = 1;
	        DMA2_Stream7->CR &= ~(DMA_SxCR_EN);
	}
}
 
void usart_rcv_idle_dma(void)
{
        DMA2_Stream2->CR &= ~(DMA_SxCR_EN);
 
        DMA2_Stream2->M0AD = (uint32_t) rxBuf;
        DMA2_Stream2->NDTR = RX_BUF_SIZE;
 
        DMA2_Stream2->CR |= (DMA_SxCR_EN);
}
 
void USART1_IRQHandler(void)
{
        if (USART1->SR & (USART_SR_IDLE))
        {
            uint8_t dummyRead = USART1->DR;    // clears IDLE interrupt flag
            com.IdleLineCallback();    // a method for handling the incomming data
        }
}

I always get the first receive but after that the dma doesn't copy the data into my buffer anymore. I will provide some screenshots tomorrow.

6 REPLIES 6
TDK
Guru

Look at the UART and DMA registers in the "stuck" condition. Probably an error flag is set, or the DMA isn't enabled.

> DMA2_Stream2->CR |= ...

Careful with assuming default values. Better to set the entire register if you know what it should be.

> DMA1->HIFCR |= (DMA_HIFCR_CTCIF7);

This will clear TCIF7, but it will also clear every other flag set in that register. To clear only TCIF7:

DMA1->HIFCR = (DMA_HIFCR_CTCIF7);

If you feel a post has answered your question, please click "Accept as Solution".

Thanks for the hint with the HIFCR reg. Is it because I read the whole register? I thought it only clears where I write a "1". Basically like the BSSR of the GPIOs. I still do unnecessary read and modify operations, so I will change that.

How do I enable the "stuck" condition? Do you mean pausing the program in step through debugging and look into the registers? I did that and the DMA registers seemd fine. I didn't look into the interrupt flags though.

I usually do

USART1->CR1 = 0x00;

for example but I wrote the code from memory so I left that out.

I also use CubeMX to init the USART1 so I don't have to bother with setting the BRR reg.

> Thanks for the hint with the HIFCR reg. Is it because I read the whole register?

No, it's because the read-modify-write operation you're doing writes 1 to every bit that is currently set.

> Do you mean pausing the program in step through debugging and look into the registers?

Yes.

> I did that and the DMA registers seemd fine.

"seemd fine" is not a phrase that inspires confidence. Show them. The registers tell the whole story, so if the peripheral isn't working, the answer can be found within the registers (barring less common exceptions).

> but I wrote the code from memory so I left that out.

Copy/pasting code is the best thing to do. Don't write it in separate places based on memory. I don't want to look through one piece of code with potential typos to troubleshoot another piece of the same code with different potential typos.

If you feel a post has answered your question, please click "Accept as Solution".
Muhammed Güler
Senior III

I am using DMA and idle line detection to communicate with the Nextion display. Below is the HAL library version of the serial port part. I hope it helps.

void nexInit(void)
{
    HAL_UART_Receive_DMA(&nexSerial,Nextion_Rx_Buffer,255);
    __HAL_UART_ENABLE_IT(&nexSerial, UART_IT_IDLE);
    ///Nexion init stuff here
}
 
void USART3_IRQHandler(void)
{
	/* USER CODE BEGIN USART3_IRQn 0 */
	if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_IDLE))
	{
		HAL_UART_DMAStop(&huart3);
		//Calculate the length of the received data
		Nextion_Rx_Index  = 255 - __HAL_DMA_GET_COUNTER(&hdma_usart3_rx);
		if((Nextion_Rx_Buffer[Nextion_Rx_Index-1]==0xFF) 
			&& (Nextion_Rx_Buffer[Nextion_Rx_Index-2]==0xFF)
			&& (Nextion_Rx_Buffer[Nextion_Rx_Index-3]==0xFF))
		{
			Nextion_Transfer_Flag = 1;//transfer complete, data is ready to read
		}
		//Restart to start DMA transmission of 255 bytes of data at a time
		HAL_UART_Receive_DMA(&huart3, (uint8_t*)Nextion_Rx_Buffer, 255);
	}
	/* USER CODE END USART3_IRQn 0 */
	HAL_UART_IRQHandler(&huart3);
	/* USER CODE BEGIN USART3_IRQn 1 */
	
	/* USER CODE END USART3_IRQn 1 */
}

Thanks Muhammed! I decided against using the HAL for my projects but if I can't find an appropriate solution I will look into it.

JHöfl.1
Associate II

After some more debugging and inspecting the Error Flags especially, I fond a solution to the Problem.

Here is the exact code I use for this:

#define RX_BUF_SIZE 64
#define TX_BUF_SIZE 64
 
volatile uint8_t transferComplete = 0;
volatile uint16_t numRcv = 0;
 
uint8_t rxBuf[RX_BUF_SIZE];
uint8_t txBuf[TX_BUF_SIZE];
 
void SystemClock_Config(void);
 
void init_usart(void)
{
	__NVIC_SetPriority(USART1_IRQn, 0);
	__NVIC_EnableIRQ(USART1_IRQn);
 
	USART1->CR1 |= ((USART_CR1_TE) | (USART_CR1_RE) | (USART_CR1_IDLEIE));
	USART1->CR3 |= ((USART_CR3_DMAT) | (USART_CR3_DMAR));
	USART1->CR1 |= (USART_CR1_UE);
 
	RCC->AHB1ENR |= (RCC_AHB1ENR_GPIOAEN);
}
 
void init_dma(void)
{
	RCC->AHB1ENR |= (RCC_AHB1ENR_DMA2EN);
 
	__NVIC_SetPriority(DMA2_Stream7_IRQn, 0);
	__NVIC_EnableIRQ(DMA2_Stream7_IRQn);
 
	__NVIC_SetPriority(DMA2_Stream2_IRQn, 0);
	__NVIC_EnableIRQ(DMA2_Stream2_IRQn);
 
	/* DMA for TX */
	DMA2_Stream7->CR = 0x00;
	DMA2_Stream7->CR |= ((4 << DMA_SxCR_CHSEL_Pos) | (2 << DMA_SxCR_PL_Pos)
			| (DMA_SxCR_MINC) | (1 << DMA_SxCR_DIR_Pos) | (DMA_SxCR_TCIE));
	DMA2_Stream7->PAR = (uint32_t) &(USART1->DR);
 
	/* DMA for RX */
	DMA2_Stream2->CR = 0x00;
	DMA2_Stream2->CR |= ((4 << DMA_SxCR_CHSEL_Pos) |
			(2 << DMA_SxCR_PL_Pos) |
			(DMA_SxCR_TCIE) |
			(DMA_SxCR_MINC));
	DMA2_Stream2->FCR |= (DMA_SxFCR_FEIE);
	DMA2_Stream2->M0AR = (uint32_t) rxBuf;
	DMA2_Stream2->PAR = (uint32_t) &(USART1->DR);
}
 
void usart_send_dma(uint8_t *msg, uint16_t len)
{
	transferComplete = 0;
	DMA2_Stream7->NDTR = len;
	DMA2_Stream7->M0AR = (uint32_t) msg;
	DMA2_Stream7->CR |= (DMA_SxCR_EN);
}
 
void usart_rcv_idle_dma(void)
{
	numRcv = 0;
 
	DMA2_Stream2->CR &= ~(DMA_SxCR_EN);
	DMA2_Stream2->NDTR = RX_BUF_SIZE;
	DMA2_Stream2->CR |= (DMA_SxCR_EN);
}
 
void DMA2_Stream2_IRQHandler(void)
{
	if (DMA2->LISR & (DMA_LISR_TCIF2))
	{
		DMA2->LIFCR = (DMA_LIFCR_CTCIF2);
	}
	if (DMA2->LISR & (DMA_LISR_FEIF2))
	{
		DMA2->LIFCR = (DMA_LIFCR_CFEIF2);
	}
}
 
void DMA2_Stream7_IRQHandler(void)
{
	if (DMA2->HISR & (DMA_HISR_TCIF7))
	{
		DMA2->HIFCR = (DMA_HIFCR_CTCIF7);
		transferComplete = 1;
		DMA2_Stream7->CR &= ~(DMA_SxCR_EN);
	}
	if (DMA2->HISR & (DMA_HISR_FEIF7))
	{
		DMA2->HIFCR = (DMA_HIFCR_CFEIF7);
	}
}
 
void USART1_IRQHandler(void)
{
	if (USART1->SR & (USART_SR_IDLE))
	{
		uint8_t dummyRead = USART1->DR;
		numRcv = RX_BUF_SIZE - DMA2_Stream2->NDTR;
	}
}
 
int main(void)
{
 
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	MX_USART1_UART_Init();
 
	init_usart();
	init_dma();
 
	char msg[] = "Hello World!\n\r";
	usart_send_dma((uint8_t*) msg, strlen(msg));
 
	while (1)
	{
		usart_rcv_idle_dma();
		while (!numRcv)
			;
		usart_send_dma(rxBuf, numRcv);
 
		while (!transferComplete);
		for (int i = 0; i < numRcv; i++)
		{
			rxBuf[i] = 0;
		}
	}
}

I removed the comments from CubeMX to make It more condesed.

The solution to my problem was, that for some reason the TCIF2 for DMA2_Stream2 (RX) was set after

void usart_rcv_idle_dma(void)
{
	numRcv = 0;
 
	DMA2_Stream2->CR &= ~(DMA_SxCR_EN);
	DMA2_Stream2->NDTR = RX_BUF_SIZE;          // <=====
	DMA2_Stream2->CR |= (DMA_SxCR_EN);
}

executing this line.

After I enabled the IRQ for DMA2_Stream2 and cleared it. the Problem was gone.

One more thing I don't understand atm: I always get FIFO error flags for DMA2_Stream7 (TX) along the desired TCIF7. I looked into the ref and it says something about configuring MBURST to the correct value. Use direct mode so what ever I write in there will always be set to "0". Can I just ignore this?