Hi everyone,
I'm looking into implementing Modbus RTU communication between an STM32 and an industrial device over RS-485, and one thing I'm particularly interested in is handling the timing correctly.
Sending a request seems straightforward enough, but receiving a complete response reliably is where things can get a little tricky. Modbus RTU relies on silent intervals to separate frames, and I'm wondering how others handle this in their firmware.
Do you usually use a hardware timer, UART IDLE detection, or the receiver timeout feature available on some STM32 USARTs? I'm also interested in how you manage RS-485 DE/RE control, especially making sure the transmitter is disabled only after the final byte has actually left the UART.
I came across a tutorial from PCBCool that walks through building an STM32 Modbus RTU client using HAL and an RS-485 transceiver. It covers CRC validation, frame construction, response parsing, and timeout handling.
https://pcbcool.com/technical-guides/bu ... er-rs-485/
For those who have implemented something similar, what approach has worked best for you? I'm particularly interested in solutions that remain reliable under continuous polling and occasional communication errors.
Thanks in advance for sharing your experience!
STM32 + RS-485 Modbus RTU: How do you handle frame timing and UART receive reliably?
Moderator: RoccoMarco
- Giovanni
- Site Admin
- Posts: 14892
- Joined: Wed May 27, 2009 8:48 am
- Has thanked: 1202 times
- Been thanked: 996 times
Re: STM32 + RS-485 Modbus RTU: How do you handle frame timing and UART receive reliably?
Hi,
The HAL/XHAL SIO driver implements idle detection as an event, that will probably help.
Giovanni
The HAL/XHAL SIO driver implements idle detection as an event, that will probably help.
Giovanni
Re: STM32 + RS-485 Modbus RTU: How do you handle frame timing and UART receive reliably?
This is how I implemented modbus, it has been working reliably at 4 sites:
Code: Select all
// timeout between MB frames (3.5 times a character timeout)
// for baud>19200 is 1,75 ms
uint32_t ponUsInterMsg(uint16_t baudios)
{
if( baudios > 19200U )
return 1750U;
else
return (uint32_t) (38500000/baudios);
}
// timeout between characters (1.5 times a character sending time)
// I increased to 3 times, has been more reliable for me
uint32_t ponUsMaxChr(uint16_t baudios)
{
return (uint32_t) (33000000/baudios); // antes 16500000
}
// we suppose that modbus bus are free
bool modbus::readModbus(uint8_t *buffer, uint16_t sizeOfBuffer, uint16_t *bytesReceived, sysinterval_t timeout)
{
size_t nb;
palClearLine(rxtxLine);
// I give to the first character the timeout (slave takes some time to answer)
nb = sdReadTimeout(SD,buffer,1,timeout);
*bytesReceived = nb;
if (*bytesReceived != 1)
return false;
// first character read, proceed with next characters, be strict with timeouts
do {
nb = sdReadTimeout(SD,&buffer[*bytesReceived],1,chTimeUS2I(usMaxChar));
if (nb == 0)
break;
*bytesReceived += nb;
if (*bytesReceived>=sizeOfBuffer)
return false; // message too long
} while (true);
if (*bytesReceived >= 4) // minimum size of a modbus frame
return true;
else
return false;
}
// Read input registers (FUNC04) o holding registers (FUN05)
// data are returned at bufferRx
void modbusMaster::enviaMBfunc(uint8_t funcion, uint8_t dirMB, uint16_t addressReg, uint8_t numRegs, uint8_t bufferRx[],
uint16_t sizeofbufferRx, uint16_t msDelayMax, uint16_t *msDelay, uint8_t *error)
{
uint8_t buffer[10];
uint16_t msgCRC, rxCRC, bytesReceived;
if (sizeofbufferRx <= (7 + 2*numRegs))
{
*error = 3;
return;
}
buffer[0] = dirMB;
buffer[1] = funcion;
buffer[2] = (addressReg&0xFF00)>>8;
buffer[3] = addressReg&0xFF;
buffer[4] = (numRegs & 0xFF00) >>8;
buffer[5] = numRegs & 0xFF;
msgCRC = CRC16(buffer, 6);
buffer[6] = msgCRC & 0xFF;
buffer[7] = (msgCRC & 0xFF00) >>8;
bufferRx[0] = 0;
bufferRx[1] = 0;
// send order to the slave device
envioStrModbus(buffer, 8, chTimeMS2I(100));
systime_t start = chVTGetSystemTime();
// read response
bool hayMsg = readModbus(bufferRx, sizeofbufferRx,&bytesReceived, chTimeMS2I(msDelayMax));
sysinterval_t duracion = chVTTimeElapsedSinceX(start);
if (!hayMsg || bytesReceived!=(5+2*numRegs))
{
*error = 2;
return;
}
msgCRC = CRC16(bufferRx, bytesReceived-2);
rxCRC = (bufferRx[bytesReceived-1]<<8) + bufferRx[bytesReceived-2];
if (msgCRC!=rxCRC || dirMB!= bufferRx[0] || bufferRx[1]!=funcion)
{
*error = 3;
return;
}
*msDelay = chTimeI2MS(duracion);
// supress first three characters from buffer, to get useful data
for (uint8_t i=3;i<bytesReceived-2;i++)
bufferRx[i-3] = bufferRx[i];
*error = 0;
return;
}