2021-12-01 10:22 AM
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.
2021-12-01 11:36 AM
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);
2021-12-01 12:42 PM
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.
2021-12-01 01:38 PM
> 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.
2021-12-01 11:19 PM
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 */
}
2021-12-01 11:34 PM
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.
2021-12-01 11:46 PM
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?