1. Тема дня
Сегодня разбираем промышленный последовательный транспорт:
UART
→ RS-485 transceiver
→ двухпроводная half-duplex шина
→ управление DE/~RE
→ границы кадров
→ CRC
→ Modbus RTU или собственный протоколГлавная мысль: RS-485 определяет электрический интерфейс, но не определяет кадры, адреса и команды. Протокол вроде Modbus RTU должен отдельно решить, кто и когда получает право передавать, где заканчивается кадр и что делать после ошибки.
2. Зачем это нужно в твоих проектах
RS-485 может использоваться для связи с удалёнными IO-модулями, релейными блоками, контроллерами подсветки, HIL-стендом, промышленной панелью или ПЛК. Для контроллера фаз светофора удалённый модуль может отдавать:
регистр 0: текущая фаза
регистр 1: raw input mask
регистр 2: conflict flags
регистр 3: glitch counter
регистр 4-5: напряжение питания
регистр 6-7: sequenceНужно отличать:
устройство не ответило;
CRC неверен;
кадр оборван;
линия занята;
ответ пришёл от другого адреса;
UART потерял байты;
DE выключился слишком рано.3. Теория
3.3.1 3.1. UART и RS-485 - разные уровни
UART формирует start/data/parity/stop биты. RS-485 transceiver преобразует логические уровни MCU в дифференциальную пару A/B.
MCU UART_TX/RX/DE
→ RS-485 transceiver
→ A/B twisted pairЧасто DE и ~RE соединяют вместе:
0: transmitter disabled, receiver enabled
1: transmitter enabled, receiver disabledНо при такой схеме узел не слышит собственную передачу и хуже обнаруживает коллизии.
3.3.2 3.2. Half-duplex требует владельца шины
На двухпроводной линии все устройства используют одну пару и для TX, и для RX. В каждый момент времени передавать должен только один узел. Типовая модель:
client/master создаёт запрос
server/slave отвечает только на свой адрес3.3.3 3.3. DMA complete и UART Transmission Complete - не одно и то же
DMA complete означает, что DMA перенёс последний байт в UART data register/FIFO. Но UART ещё может передавать последний data byte, parity bit и stop bit. Правильная TX-последовательность:
1. DE = 1.
2. Подождать включение трансивера.
3. Запустить UART TX DMA.
4. Дождаться DMA complete.
5. Дождаться UART TC - Transmission Complete.
6. DE = 0.
7. Перейти в RX.3.3.4 3.4. State machine передатчика
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;Нельзя разрешать нескольким задачам напрямую вызывать UART TX. Один rs485_task или modbus_task должен владеть UART, DE, RX framing, TX queue, request timeout и текущей транзакцией. 3.3.5 3.5. Character time
Для формата 8E1:
1 start + 8 data + 1 parity + 1 stop = 11 битВремя одного символа:
Tchar = character_bits / baudrateПример для 9600 baud, 11 бит:
Tchar = 11 / 9600 ≈ 1.146 мс
t1.5 ≈ 1.719 мс
t3.5 ≈ 4.010 мсHelper:
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
Кадр:
Address | Function | Data | CRC16Границы задаются тишиной:
до начала кадра: не менее 3.5 символов тишины
внутри кадра: пауза не более 1.5 символов
после кадра: не менее 3.5 символов тишиныЕсли внутри кадра пауза больше t1.5, кадр считается неполным и должен быть отброшен. Для скоростей выше 19200 bit/s часто используют фиксированные значения:
t1.5 = 750 мкс
t3.5 = 1750 мкс3.3.7 3.7. Почему одного UART IDLE недостаточно
UART IDLE часто срабатывает примерно после одного character time. Modbus требует t3.5. Поэтому IDLE можно использовать только как ранний сигнал:
UART IDLE
→ зафиксировать DMA position
→ запустить one-shot timer t3.5
→ если новых байтов нет до timeout
→ frame complete3.3.8 3.8. CRC16 Modbus
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;
}CRC проверяется до изменения прикладного состояния.
3.3.9 3.9. Broadcast
Broadcast-запрос не должен порождать ответ. Иначе все серверы ответят одновременно и создадут коллизию.
3.3.10 3.10. ESP32 RS-485 mode
ESP-IDF позволяет настроить UART в half-duplex RS-485 mode, где драйвер управляет RTS/DE:
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);После этого другие модули не должны вручную переключать тот же DE GPIO.
4. Схемотехнические ограничения
RS-485 требует правильной топологии:
termination termination
│ │
────┴───────────┬──────────┬─────────────┴──── trunk
│ │
short stub short stubTermination должна быть только на двух концах магистрали, не на каждом модуле. Biasing/polarization также должна быть в одной контролируемой точке.
5. Типичные ошибки
1. Выключить DE по DMA complete.
2. Использовать vTaskDelay() для управления DE.
3. Считать UART IDLE концом Modbus-кадра.
4. Делить UART между несколькими задачами.
5. Не проверять CRC до выполнения команды.
6. Отвечать на broadcast.
7. Терминировать каждый узел.
8. Установить bias на каждой плате.
9. Перепутать A/B.
10. Не учитывать Common.
11. После RX overrun продолжать parser.
12. Использовать software timeout с низкой точностью.6. Практическое задание на 30-60 минут
Создай RS485_MODBUS_POLICY.md:
# 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.Добавь тест CRC для запроса:
01 03 00 00 00 0AПолный кадр должен быть:
01 03 00 00 00 0A C5 CDДобавь диагностику:
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. Что почитать или попробовать дальше
- ESP-IDF UART/RS-485 documentation.
- STM32 HAL/LL UART DMA, UART TC, IDLE detection и hardware DE.
- MODBUS over Serial Line Specification and Implementation Guide V1.02.
Короткий итог
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Задание
DMA complete сработал, пока UART ещё передаёт последний stop bit. Объясните отказ при немедленном DE release и правильный state transition.
Критерии самопроверки: Отделить DMA completion от UART completion и назначить одного DE owner; произвольный task delay не является доказательством.
Показать ответ автора
DE release может обрезать frame. DMA completion означает завершение memory-to-UART transfer, а не wire transmission. Держать DE до UART Transmission Complete, затем отпустить и перейти в RX по transceiver timing contract.
Задание
UART IDLE произошёл до Modbus t3.5 boundary. Определите framing при следующем byte и при достижении требуемой тишины.
Критерии самопроверки: Определить оба timing cases и invalid-frame path; не приравнивать IDLE к protocol completion.
Показать ответ автора
Использовать IDLE как раннее наблюдение, учитывать byte arrival и framing timer, объявлять complete frame только после нужной тишины. Отклонить неполный frame при нарушении internal timing t1.5; проверить CRC/address/function до изменения application state.