1. Today’s topic

Today we examine industrial serial transport:

text
UART
→ RS-485 transceiver
→ two-wire half-duplex bus
→ DE/~RE control
→ frame boundaries
→ CRC
→ Modbus RTU or a custom protocol

The central idea: RS-485 defines the electrical interface, not frames, addresses, or commands. A protocol such as Modbus RTU must separately determine who may transmit and when, where a frame ends, and what to do after an error.

2. Why this matters in your projects

RS-485 can connect remote IO modules, relay units, illumination controllers, a HIL bench, an industrial panel, or a PLC. For a traffic-light phase controller, a remote module may return:

text
register 0: current phase
register 1: raw input mask
register 2: conflict flags
register 3: glitch counter
register 4-5: supply voltage
register 6-7: sequence

Distinguish:

text
the device did not respond;
CRC is incorrect;
the frame is truncated;
the line is busy;
the response came from another address;
UART lost bytes;
DE switched off too early.

3. Theory

3.3.1 3.1. UART and RS-485 are different layers

UART forms start/data/parity/stop bits. The RS-485 transceiver converts MCU logic levels to the differential A/B pair.

text
MCU UART_TX/RX/DE
→ RS-485 transceiver
→ A/B twisted pair

DE and ~RE are often connected together:

text
0: transmitter disabled, receiver enabled
1: transmitter enabled, receiver disabled

But with this arrangement the node cannot hear its own transmission and detects collisions less effectively.

3.3.2 3.2. Half-duplex requires a bus owner

On a two-wire line, all devices share one pair for TX and RX. Only one node should transmit at a time. Typical model:

text
client/master creates a request
server/slave responds only to its own address

3.3.3 3.3. DMA complete and UART Transmission Complete are different

DMA complete means DMA transferred the last byte into the UART data register/FIFO. UART may still be transmitting the final data byte, parity bit, and stop bit. Correct TX sequence:

text
1. DE = 1.
2. Wait for the transceiver to enable.
3. Start UART TX DMA.
4. Wait for DMA complete.
5. Wait for UART TC - Transmission Complete.
6. DE = 0.
7. Enter RX.

3.3.4 3.4. Transmitter state machine

c
typedef enum {
    RS485_STATE_RX_IDLE = 0,
    RS485_STATE_WAIT_BUS_IDLE,
    RS485_STATE_DE_ASSERT,
    RS485_STATE_TX_DMA,
    RS485_STATE_WAIT_UART_TC,
    RS485_STATE_DE_RELEASE,
    RS485_STATE_TURNAROUND,
    RS485_STATE_WAIT_RESPONSE,
    RS485_STATE_ERROR,
} rs485_state_t;

Do not allow several tasks to call UART TX directly. One rs485_task or modbus_task should own UART, DE, RX framing, the TX queue, request timeout, and the current transaction. 3.3.5 3.5. Character time

For the 8E1 format:

text
1 start + 8 data + 1 parity + 1 stop = 11 bits

The duration of one character:

text
Tchar = character_bits / baudrate

Example at 9600 baud, 11 bits:

text
Tchar = 11 / 9600 ≈ 1.146 ms
t1.5 ≈ 1.719 ms
t3.5 ≈ 4.010 ms

Helper:

c
typedef struct {
    uint32_t baudrate;
    uint8_t data_bits;
    bool parity_enabled;
    uint8_t stop_bits;
} serial_format_t;
static uint32_t serial_char_time_us(const serial_format_t *fmt)
{
    uint32_t bits = 1U + fmt->data_bits +
                    (fmt->parity_enabled ? 1U : 0U) +
                    fmt->stop_bits;
    return (uint32_t)(((uint64_t)bits * 1000000ULL +
                       fmt->baudrate - 1U) /
                      fmt->baudrate);
}

3.3.6 3.6. Modbus RTU framing

Frame:

text
Address | Function | Data | CRC16

Silence defines the boundaries:

text
before the frame: at least 3.5 character times of silence
inside the frame: a gap no longer than 1.5 character times
after the frame: at least 3.5 character times of silence

If an in-frame gap exceeds t1.5, the frame is considered incomplete and must be discarded. Above 19200 bit/s, fixed values are often used:

text
t1.5 = 750 us
t3.5 = 1750 us

3.3.7 3.7. Why UART IDLE alone is insufficient

UART IDLE often occurs after approximately one character time. Modbus requires t3.5. Therefore IDLE can be used only as an early signal:

text
UART IDLE
→ record DMA position
→ start one-shot timer t3.5
→ if no new bytes arrive before timeout
→ frame complete

3.3.8 3.8. Modbus CRC16

c
uint16_t modbus_crc16(const uint8_t *data, size_t length)
{
    uint16_t crc = 0xFFFFU;
    for (size_t i = 0; i < length; i++) {
        crc ^= data[i];
        for (unsigned bit = 0; bit < 8; bit++) {
            if ((crc & 1U) != 0U) {
                crc = (uint16_t)((crc >> 1U) ^ 0xA001U);
            } else {
                crc >>= 1U;
            }
        }
    }
    return crc;
}

Check CRC before changing application state.

3.3.9 3.9. Broadcast

A broadcast request must not produce a response. Otherwise every server responds at once and creates a collision.

3.3.10 3.10. ESP32 RS-485 mode

ESP-IDF can configure UART in half-duplex RS-485 mode, where the driver controls RTS/DE:

c
uart_set_pin(UART_NUM_1, TX_GPIO, RX_GPIO, RTS_GPIO, UART_PIN_NO_CHANGE);
uart_set_mode(UART_NUM_1, UART_MODE_RS485_HALF_DUPLEX);

After this, other modules must not manually toggle the same DE GPIO.

4. Circuit-design constraints

RS-485 requires correct topology:

text
termination                           termination
    │                                     │
────┴───────────┬──────────┬─────────────┴──── trunk
                │          │
             short stub  short stub

Termination should be placed only at both ends of the trunk, not at every module. Biasing/polarization should also be at one controlled point.

5. Common mistakes

text
1. Disabling DE at DMA complete.
2. Using vTaskDelay() to control DE.
3. Treating UART IDLE as the end of a Modbus frame.
4. Sharing UART between several tasks.
5. Not checking CRC before executing a command.
6. Responding to broadcast.
7. Terminating every node.
8. Placing bias on every board.
9. Swapping A/B.
10. Not accounting for Common.
11. Continuing the parser after RX overrun.
12. Using a low-accuracy software timeout.

6. Practical assignment for 30-60 minutes

Create RS485_MODBUS_POLICY.md:

markdown
# RS-485 / Modbus policy
1. UART and DE have one owner task.
2. DE is released only after UART Transmission Complete.
3. DMA Complete is not treated as end of wire transmission.
4. RX framing follows t1.5 and t3.5 rules.
5. CRC is checked before application processing.
6. Broadcast requests never produce a reply.
7. RX loss moves parser to DESYNC.
8. Termination exists only at both ends of the trunk.
9. Biasing exists at no more than one designated location.
10. Every request has timeout, retry limit and diagnostics.

Add a CRC test for this request:

text
01 03 00 00 00 0A

The complete frame should be:

text
01 03 00 00 00 0A C5 CD

Add diagnostics:

c
typedef struct {
    uint32_t tx_frames;
    uint32_t rx_frames;
    uint32_t timeout;
    uint32_t crc_error;
    uint32_t interchar_error;
    uint32_t rx_overflow;
    uint32_t collision;
    uint32_t wrong_address;
    uint32_t wrong_function;
    uint32_t retries;
    uint32_t devices_offline;
    uint32_t max_response_us;
    uint32_t max_turnaround_us;
} modbus_diag_t;

7. Further reading or experiments

  • ESP-IDF UART/RS-485 documentation.
  • STM32 HAL/LL UART DMA, UART TC, IDLE detection, and hardware DE.
  • MODBUS over Serial Line Specification and Implementation Guide V1.02.

Brief recap

text
application request
→ modbus service queue
→ RTU encoder + CRC
→ RS-485 owner task
→ DE assert
→ UART DMA
→ UART Transmission Complete
→ DE release
→ RX DMA/ring
→ t1.5/t3.5 framer
→ CRC/address/function validation
→ immutable response event

Exercise

DMA complete fires while the final stop bit is still leaving UART. Explain the failure if DE is released immediately and define the correct state transition.

Self-check criteria: Distinguish DMA and UART completion and specify a single DE owner; do not use an arbitrary task delay as proof.

Show the supplied answer

Releasing DE may truncate the transmitted frame. DMA completion means memory-to-UART transfer is finished, not wire transmission. Keep DE asserted until UART Transmission Complete, then release it and enter RX according to the transceiver timing contract.

Exercise

A UART IDLE event occurs before the Modbus t3.5 boundary. Define framing behavior if another byte arrives and if the silence reaches the required boundary.

Self-check criteria: Define both timing cases and the invalid-frame path; do not equate IDLE with protocol completion.

Show the supplied answer

Use IDLE as an early observation, track byte arrival and the framing timer, and declare a complete frame only after the required silence. Reject an incomplete frame when its internal timing violates the t1.5 rule; validate CRC/address/function before changing application state.