STM32 + RS-485 Modbus RTU: How do you handle frame timing and UART receive reliably?

ChibiOS public support forum for topics related to the STMicroelectronics STM32 family of micro-controllers.

Moderator: RoccoMarco

Post Reply
rachana62
Posts: 1
Joined: Thu Oct 22, 2026 7:50 pm

STM32 + RS-485 Modbus RTU: How do you handle frame timing and UART receive reliably?

Post by rachana62 »

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!
User avatar
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?

Post by Giovanni »

Hi,

The HAL/XHAL SIO driver implements idle detection as an event, that will probably help.

Giovanni
joaquin
Posts: 50
Joined: Sat Jan 22, 2011 8:44 pm
Has thanked: 1 time
Been thanked: 3 times

Re: STM32 + RS-485 Modbus RTU: How do you handle frame timing and UART receive reliably?

Post by joaquin »

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;
}
Post Reply