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:
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:
if (HAL_UART_Receive(&huart1, &b, 1, 10) == HAL_OK) {
parser_feed_byte(b);
}Prefer:
CLI/low speed: interrupt per byte -> ring buffer
stream/Modbus: DMA circular RX + IDLE/timer -> parserInterrupt per byte:
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:
DI - MCU TX
RO - MCU RX
DE - driver enable
/RE - receiver enable
A/B - differential busDo 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:
t1.5 - pause within a frame, frame incomplete
t3.5 - end of frameAt baud > 19200, 750 us and 1.750 ms are often used. Architecture:
rs485_uart_driver -> rs485_link_service -> modbus_rtu_parser -> application4. Common mistakes
- Releasing DE immediately after writing the bytes.
- Holding DE enabled too long.
- Ignoring ORE/FE/NE/PE.
- Implementing Modbus RTU without timers.
- Letting the application call HAL_UART directly on the bus UART.
- Ignoring termination/bias/ESD/topology.
5. Practical task
Create STM32_UART_RS485_POLICY.md.
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.
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.