New: the AI firewall watches the whole platform and explains each incident in plain words. See how it works

STM32 with a MAX3485 gets my own echo and loses the start of the Modbus reply

STM32 with a MAX3485 gets my own echo and loses the start of the Modbus reply

Solved 5 4

Fourteen years on the PLC side, first time doing Modbus RTU from a bare microcontroller, so the protocol is not the problem, the MCU side is.

Hardware: STM32F103 (Blue Pill), USART2, MAX3485, DE and /RE tied together on PA8. Reading a magnetic flow meter, 9600 8N1, slave 7, holding registers 0x0010 to 0x0013. Later an ESP32 next to it will send the values to Hermetry, but first I need clean reads.

On the scope my request looks correct and the meter answers, I can see its reply on A/B. What my code receives is wrong. Usually the first two or three bytes of the reply are missing. Sometimes I get my own request back in the buffer before the reply. Code, simplified:

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);    // DE on
HAL_UART_Transmit(&huart2, req, 8, 100);
HAL_Delay(5);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);  // DE off, RE on
HAL_UART_Receive(&huart2, rx, 9, 200);
♥ 0

Did you try swapping A and B?

Still waiting for the gateway to reconnect.
♥ 0

Maybe a baud rate mismatch between the two?

Measure twice, poll once.
♥ 0

Neither, the request decodes fine on the scope and the meter replies to it. It’s my side.

♥ 0

It’s the timing around DE, I had the exact same thing on a lab rig with an F103.

HAL_UART_Transmit returns when the last byte has gone into the data register, not when it has left the shift register. Then you wait 5 ms on top. At 9600 one byte is about 1 ms, and a fast meter starts answering within a few ms. While DE is high, /RE is high too, so the receiver is off and the first bytes of the reply are gone. And because HAL_UART_Receive is only called after that, anything that came in meanwhile is lost or sits half in the data register.

The echo comes from the other order: if the receive is already armed while you transmit and /RE is low for a moment, you read back your own request.

Drop DE the moment the transmit complete flag (TC) is set, and arm the receive right after:

__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_TC);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
HAL_UART_Transmit(&huart2, req, 8, 100);
while (!__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC)) {}
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
__HAL_UART_FLUSH_DRREGISTER(&huart2);
HAL_UART_Receive(&huart2, rx, 9, 200);

Better still, use interrupt or DMA receive with the idle line interrupt, then a 3.5 character gap ends the frame and you don’t need to know the length in advance. The F103 has no hardware DE pin for RS-485, newer families do, which saves all of this.

Timestamps in UTC or it didn't happen.
Accepted solution♥ 3

That was it. The HAL_Delay(5) was something I’d added while guessing, removing it and waiting on TC gives me all 9 bytes and the CRC checks out every time. No more echo either. Moving to idle line DMA next like Aoife suggested.

♥ 2
Viewing 6 posts – 1 through 6 (of 6 total)
  • You must be logged in to reply to this topic.

Join the conversation

Sign in to ask and answer, like helpful posts and mark the reply that solved your problem. Every contribution earns points and levels.

Share