2017-03-14 09:25 AM
Hi Everybody,
I'm using a STM32f205RG on a personnal electronic board. I would like to communicate with an external equipment using modbus RTU on RS485 link. So, I'm trying to implement USART 1 with DMA.
For the moment, I have set everything (DMA, UART...). My board is the slave. So, I can see frames from the master (read holding registers). These frame have only 8 bytes.
So, at the first frame, the 8 bytes are located in my array from 0 to 7. When I receive the second frame, this one is located from 8 to 15.
So when I calculate the size of my frame, I found 16 instead of 8, and then my checksum is wrong, and the frame is not validated.
So, I'm looking for a solution to 'reset' this array.
Please find my code :
--------------------INITIALIZATION---------------------------
/* USER CODE END USART1_MspInit 0 */
/* Peripheral clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); /**USART1 GPIO Configuration PB6 ------> USART1_TX PB7 ------> USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* Peripheral DMA init*/
hdma_usart1_rx.Instance = DMA2_Stream5; hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_rx.Init.Mode = DMA_NORMAL; hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW; hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) { Error_Handler(); }__HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);
hdma_usart1_tx.Instance = DMA2_Stream7;
hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_tx.Init.Mode = DMA_NORMAL; hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW; //hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK) { Error_Handler(); }__HAL_LINKDMA(huart,hdmatx,hdma_usart1_tx);
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);-----------------------------IT--------------------------------------------
/**
* @brief This function handles DMA2 Stream7 global interrupt.*/void DMA2_Stream7_IRQHandler(void){ HAL_DMA_IRQHandler(&hdma_usart1_tx); if((HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_READY) || (HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_ERROR)) { Modbus_IT_Transmit(&modbusNET1); }}/**
* @brief This function handles USART6 global interrupt.*/void USART1_IRQHandler(void){ HAL_UART_IRQHandler(&huart1); if((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) && (__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_IDLE) != RESET)) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); __HAL_UART_DISABLE_IT(&huart1, UART_IT_IDLE); Modbus_IT_Receive_Data(&modbusNET1); }}If you have any idea, I would be very happy.
Regards,
Fabrice Péden
2017-03-14 09:50 AM
>>If you have any idea, I would be very happy.
If the data buffer is a circular loop, you could sync and fish the data out at HT and TC interrupts of the DMA.
USART RX DMA is most practical for constant streams where the goal is to decimate interrupt loading. For low bandwidth/sporadic comms, a more simple IRQ and state-machine model may work better at synchronizing and processing small packets.
2017-03-14 12:38 PM
Unfortunately the 'F205 isn't one of the variants with built-in Modbus support. Since you are using RTU format you have to detect the end of frame by the inter-message gap. That requires using a timer to monitor the incoming RX serial data, and timing out when the inter-message gap time is reached while idle. ST has an app note on using a hardware timer for RX DMA.
You need to connect a timer channel input to the RxD serial in pin. Use a timer that supports external reset (look at TIM9). Configure the timer to reset when the channel detects an edge. Program the timer with the inter-message gap time. Set up your receive for IRQ on first byte, where the interrupt handler then starts RX DMA for the rest of the frame, along with starting the timer. As each byte arrives it resets the timer with bit changes (start bit guarantees a reset). At the end of frame the timer is not reset and times out.
The timer interrupt handler terminates the RX DMA. You have a complete RTU frame (including first byte from IRQ) in the DMA buffer. Since Modbus is request-reply you have time to process the current message during line turnaround, and then send a reply. While turning around the line again you have time to set up for the next incoming message.
Complicated but it can sustain high baud rates since there's no per byte interrupt latency. TX DMA to send replies is just a simple DMA transfer with a fixed length, except you have to wait for the USART TC interrupt to complete the last shift out after the DMA TC interrupt. Otherwise the line turnaround cuts off the last byte. TX DMA guarantees individual bytes will be within the inter-character gap for RTU, more reliable than IRQ where there might be jitter due to latency.
2017-03-21 06:43 AM
Thank you for your replies.
For the moment, I didn't got time to check what you suggest. I'll come back to you when I'll have tested these solutions.
Thank you.