1. Topic

UART/USART, RX through interrupt/DMA/ring buffer, RS-485 DE/RE, half-duplex and Modbus RTU timing. Main idea: UART is a byte stream; RS-485 adds control of transmission direction.

2. Why this matters in a project

CLI, HIL through Raspberry Pi, an EC25-like parser, RS-485/Modbus, industrial modules, DMA/IDLE and a watchdog-friendly architecture.

3. Theory

Separate the layers:

text
UART/USART - a peripheral of the MCU.
RS-485 - physical differential interface.
Modbus RTU - protocol over UART/RS-485.

Polling reception is a poor production design:

c
if (HAL_UART_Receive(&huart1, &b, 1, 10) == HAL_OK) {
    parser_feed_byte(b);
}

Prefer:

text
CLI/low speed: interrupt per byte -> ring buffer
stream/Modbus: DMA circular RX + IDLE/timer -> parser

Interrupt per byte:

c
static uint8_t s_rx_byte;
void uart_rx_start(void)
{
    HAL_UART_Receive_IT(&huart1, &s_rx_byte, 1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1) {
        ringbuf_put_from_isr(&uart1_rx_rb, s_rx_byte);
        HAL_UART_Receive_IT(&huart1, &s_rx_byte, 1);
    }
}

RS-485 transceiver:

text
DI  - MCU TX
RO  - MCU RX
DE  - driver enable
/RE - receiver enable
A/B - differential bus

Do not release DE before the final stop bit, and do not hold it too long. Prefer USART hardware DE if the MCU supports it. Modbus RTU uses silent intervals:

text
t1.5 - pause within a frame, frame incomplete
t3.5 - end of frame

At baud > 19200, 750 us and 1.750 ms are often used. Architecture:

text
rs485_uart_driver -> rs485_link_service -> modbus_rtu_parser -> application

4. Common mistakes

  1. Releasing DE immediately after writing the bytes.
  2. Holding DE enabled too long.
  3. Ignoring ORE/FE/NE/PE.
  4. Implementing Modbus RTU without timers.
  5. Letting the application call HAL_UART directly on the bus UART.
  6. Ignoring termination/bias/ESD/topology.

5. Practical task

Create STM32_UART_RS485_POLICY.md.

c
typedef enum {
    RS485_STATE_IDLE_RX = 0,
    RS485_STATE_TX_ASSERT_DE,
    RS485_STATE_TX_ACTIVE,
    RS485_STATE_TX_WAIT_TC,
    RS485_STATE_TURNAROUND,
    RS485_STATE_WAIT_REPLY,
    RS485_STATE_ERROR,
} rs485_state_t;
typedef struct {
    uint32_t tx_frames;
    uint32_t rx_frames;
    uint32_t crc_errors;
    uint32_t frame_timeouts;
    uint32_t uart_ore;
    uint32_t uart_fe;
    uint32_t uart_ne;
    uint32_t uart_pe;
    uint32_t de_late_disable;
    uint32_t rx_overflows;
    uint32_t modbus_t15_violations;
    uint32_t modbus_t35_detected;
} rs485_diag_t;

6. Further reading and experiments

STM32 HAL UART/USART, ST AN4113 on RS-485 hardware DE, Modbus over Serial Line and examples of circular DMA RX with IDLE.

DMA has written the last byte to the UART data register. When may DE be released?

Exercise

At 9600 baud with 8E1, how long is a character? Calculate 1.5 and 3.5 character times. Why does UART IDLE alone not prove that a Modbus RTU frame has ended?

Self-check criteria: Show 11 bits, all three intervals and the distinction between IDLE and RTU framing.

Show the supplied answer

8E1 has 1 start + 8 data + 1 parity + 1 stop = 11 bits. Tchar=11/9600≈1.146 ms; t1.5≈1.719 ms; t3.5≈4.010 ms. IDLE can detect a shorter gap; RTU needs explicit monitoring of its required silence interval.