Почему RS-485, а не что-то современное
Каждый год появляются новые промышленные протоколы: EtherCAT, PROFINET, IO-Link, TSN. Но RS-485 не умирает. По данным IHS Markit, ежегодно продаётся более 1 миллиарда чипов RS-485. Новые установки продолжают использовать этот интерфейс.
Причины живучести просты:
Дешевизна: кабель — витая пара $0.1/м, трансивер MAX485 — $0.3
Надёжность: дифференциальный сигнал устойчив к помехам, работает на расстояниях до 1200 м
Простота: понять и реализовать RS-485 можно за один день
Совместимость: поддерживается абсолютно всеми промышленными устройствами
Modbus RTU, BACnet MS/TP, DMX512, DALI — всё это работает поверх RS-485. Если вы занимаетесь промышленной автоматизацией, знание RS-485 обязательно.
Сравнение: RS-232 vs RS-485
Параметр | RS-232 | RS-485 |
|---|---|---|
Тип сигнала | Однополярный, ±3–15В | Дифференциальный, ±200мВ–5В |
Количество устройств | 1:1 (точка-точка) | 1:32 без репитеров (до 247 с) |
Максимальное расстояние | 15 м | 1200 м |
Скорость | До 115 200 бод (практически) | До 10 Мбит/с (при короткой линии) |
Устойчивость к помехам | Низкая | Высокая |
Дуплекс | Полный (отдельные TX/RX) | Полу (одна пара) или полный (2 пары) |
Применение | Отладка, локальные устройства | Промышленные сети, длинные линии |
Физический уровень: как работает дифференциальный сигнал
RS-485 использует дифференциальную пару проводников A и B:
Состояние MARK (логическая 1, рецессивное):
A > B: разность (A-B) = +200мВ...+5В
Состояние SPACE (логическая 0, доминантное):
B > A: разность (B-A) = +200мВ...+5В, то есть (A-B) = -200мВ...-5В
Типичное напряжение при передаче:
A ≈ +3.5В, B ≈ -3.5В → разность = +7В (гарантированная "1")
A ≈ -3.5В, B ≈ +3.5В → разность = -7В (гарантированная "0")
Устойчивость к синфазным помехам:
Помеха +5В добавляется на ОБА провода:
A = +3.5 + 5 = +8.5В, B = -3.5 + 5 = +1.5В
Разность = +8.5 - 1.5 = +7В — сигнал не изменился!
Пороги приёмника: если разность (A-B) > +200мВ — принимает "1"; если (A-B) < -200мВ — принимает "0". Диапазон ±200мВ — мёртвая зона (неопределённость).
Выбор трансивера RS-485
Бюджетные (для начала):
MAX485 / MAX485E (Maxim)
Самый популярный, $0.3–0.5
Полудуплекс, 2.5 Мбит/с
Нет защиты от ESD (добавьте TVS-диоды!)
Нет защиты от перегрева
Питание 5В
SP3485 (Sipex/Exar)
Клон MAX485, питание 3.3В
Совместимость с STM32, ESP32 напрямую (5В-tolerant входы)
Профессиональные (для промышленности):
MAX3485 / MAX3488
Расширенный диапазон ESD: ±15 кВ (HBM)
Работает от 3.3В
SN65HVD1780 (Texas Instruments)
Встроенная защита от отказа шины (failsafe)
ESD: ±16 кВ IEC 61000-4-2
Для жёстких промышленных условий
ADM2587E (Analog Devices)
Встроенная гальваническая изоляция 2500 VRMS
Изолированный DC-DC для питания изолированной стороны
Для применений с разным заземлением узлов
Схема подключения: правильно и неправильно
Минимальная схема (Arduino + MAX485):
Arduino MAX485 RS-485 шина
TX ─────────── DI
RX ─────────── RO
D2 ────┬────── DE A ──── Шина A
└────── RE_ B ──── Шина B
GND ─────────── GND GND ── Общий провод (обязательно!)
5V ──────────── Vcc
Резисторы на шине:
A ──[120 Ом]── B ← Терминатор на каждом конце шины
Pullup/Pulldown для определённого состояния в паузах:
+5В ──[560 Ом]── A ← Pull-up
B ──[560 Ом]── GND ← Pull-down
Ключевые правила:
✅ ПРАВИЛЬНО: [Устройство A]──────[Устройство B]──────[Устройство C] [Term 120Ом] [Term 120Ом] Строго линейная шина, терминаторы только на концах
❌ НЕПРАВИЛЬНО — "звезда": [Устройство A] │ [Уст.B]──[Hub]──[Уст.C] │ [Устройство D] Отражения на каждом разветвлении разрушат сигнал!
❌ НЕПРАВИЛЬНО — терминаторы не там: [Term]──[Уст.A]──[Уст.B]──[Term]──[Уст.C] Терминатор посередине создаёт проблемы!
Допустимые ответвления (stub):
Короткие отводы от основной шины допустимы при условии:
Длина stub < λ/10, где λ — длина волны на рабочей скорости
При 9600 бод: λ ≈ 12 км → stub до 1200 м (практически неограничен)
При 115200 бод: stub не более 1 м
При 1 Мбит/с: stub не более 15 см!
Управление направлением передачи
RS-485 в полудуплексном режиме требует переключения между передачей и приёмом через сигнал DE/RE:
// Arduino: управление направлением через GPIO
#define RS485_DE_RE_PIN 2
#define RS485_BAUDRATE 9600
void rs485_init() {
Serial.begin(RS485_BAUDRATE);
pinMode(RS485_DE_RE_PIN, OUTPUT);
rs485_receive_mode(); // По умолчанию — приём
}
void rs485_receive_mode() {
digitalWrite(RS485_DE_RE_PIN, LOW); // DE=0, RE=0 → приём
}
void rs485_transmit_mode() {
digitalWrite(RS485_DE_RE_PIN, HIGH); // DE=1, RE=1 → передача
}
void rs485_send(uint8_t *data, uint8_t len) {
rs485_transmit_mode();
Serial.write(data, len);
Serial.flush(); // ЖДЁМ пока все байты уйдут в UART TX буфер!
// После flush() данные ещё в UART, нужно дождаться физической передачи
// Расчёт времени: N_байт × 10_бит / baudrate
uint32_t delay_us = (uint32_t)len * 10 * 1000000UL / RS485_BAUDRATE + 100;
delayMicroseconds(delay_us);
rs485_receive_mode();
}
Аппаратное управление DE/RE (лучше!):
На STM32 USART имеет аппаратный сигнал DE для RS-485. Переключение происходит автоматически — с точностью до такта, без программных задержек:
// STM32 HAL: аппаратное управление DE через USART
// В CubeMX: USART → Mode = Asynchronous, Hardware Flow Control = RS-485 Driver Enable
// CubeMX настроит:
// huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_DE_INIT;
// huart2.AdvancedInit.DEPolarity = UART_DE_POLARITY_HIGH;
// huart2.AdvancedInit.DEAssertionTime = 16; // тактов предвключения
// huart2.AdvancedInit.DEDeassertionTime = 16; // тактов послевыключения
// После этого просто передаём — DE управляется автоматически!
HAL_UART_Transmit(&huart2, data, len, 100);
// STM32 сам поднял DE перед передачей и снял после!
Расчёт нагрузки на шину
RS-485 трансивер создаёт нагрузку на шину. Стандарт RS-485 определяет "единицу нагрузки" (Unit Load, UL) = 12 кОм.
Драйвер должен обеспечить минимум 32 UL. Это значит: максимум 32 "классических" устройства на шине.
Современные трансиверы с низким потреблением имеют 1/8 UL или 1/4 UL:
Тип трансивера | Нагрузка | Устройств на шине |
|---|---|---|
Стандартный (MAX485) | 1 UL | 32 |
1/2 UL (MAX3430) | 0.5 UL | 64 |
1/4 UL (MAX3471) | 0.25 UL | 128 |
1/8 UL (MAX3491) | 0.125 UL | 256 |
Также нагрузку создают терминирующие резисторы:
2 × 120 Ом = 60 Ом = 200 UL (!) — это доминирующая нагрузка
Учитывайте это при расчёте суммарной нагрузки
Кабель: выбор и прокладка
Требования к кабелю RS-485:
Обязательно:
Витая пара (не просто два провода!)
Волновое сопротивление 120 Ом (терминируется парными резисторами 120 Ом)
Рекомендуемые типы кабелей:
КВВГЭ 1×2×0.75 — отечественный, экранированная витая пара
Belden 9842 — американский стандарт, 120 Ом, двойной экран
LiYCY 2×0.5 мм² — гибкий, для подвижных установок
Cat5e / Cat6 — работает! (120 Ом, но без промышленной изоляции)
Сечение проводника:
Падение напряжения на кабеле:
ΔU = 2 × R_кабеля × I_нагрузка
R = ρ × L / S = 0.0175 (Ом·мм²/м) × 1000 м / 0.5 мм² = 35 Ом
При токе утечки 100 мА:
ΔU = 2 × 35 × 0.1 = 7В — это уже критично!
Для длинных линий выбирайте кабель 1.0 мм² и более.
Экранирование:
Правила заземления экрана:
✅ Заземлять в ОДНОЙ точке — предотвращает контурные токи Обычно: на стороне мастера/ПЛК
❌ Заземлять с ОБОИХ концов — контурный ток протекает по экрану! При разных потенциалах земли создаёт синфазные помехи. Исключение: при частотах > 100 кГц экран заземляют с обоих концов (через конденсатор 10 нФ с одной стороны).
Практика: полный пример Modbus RTU на Python
import serial
import struct
import time
from typing import Optional
class RS485Master:
"""
Мастер RS-485 с ручным управлением DE/RE через GPIO (для Raspberry Pi).
Или с автоматическим через RTS (для USB-RS485 адаптеров).
"""
def __init__(self, port: str, baudrate: int = 9600,
use_rts: bool = True, rts_level: bool = True):
"""
port: '/dev/ttyUSB0', 'COM3' и т.д.
use_rts: использовать RTS для управления DE/RE (USB-адаптеры)
rts_level: уровень RTS при передаче (True = HIGH)
"""
self.ser = serial.Serial(
port = port,
baudrate = baudrate,
bytesize = serial.EIGHTBITS,
parity = serial.PARITY_NONE,
stopbits = serial.STOPBITS_ONE,
timeout = 0.1
)
if use_rts:
self.ser.rts = not rts_level # Начальное состояние — приём
self.use_rts = use_rts
self.rts_level = rts_level
# Время передачи одного символа (для задержки после отправки)
self.char_time = 10.0 / baudrate # 10 бит на символ
def _tx_enable(self):
if self.use_rts:
self.ser.rts = self.rts_level
time.sleep(0.0001) # 100 мкс предвключение
def _rx_enable(self):
if self.use_rts:
# Ждём физической передачи последнего байта
time.sleep(self.char_time)
self.ser.rts = not self.rts_level
def _crc16(self, data: bytes) -> int:
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
return crc
def send_raw(self, data: bytes):
"""Отправка сырых данных"""
self.ser.reset_input_buffer() # Очищаем входной буфер перед отправкой
self._tx_enable()
self.ser.write(data)
self.ser.flush()
self._rx_enable()
def recv_raw(self, expected_len: int, timeout: float = 0.5) -> Optional[bytes]:
"""Приём данных с таймаутом"""
deadline = time.time() + timeout
buf = b''
while time.time() < deadline:
chunk = self.ser.read(expected_len - len(buf))
buf += chunk
if len(buf) >= expected_len:
break
time.sleep(0.001)
return buf if len(buf) == expected_len else None
def modbus_read_registers(self, slave: int, func: int,
start_addr: int, count: int) -> Optional[list]:
"""
Чтение Holding (FC=3) или Input (FC=4) регистров.
Возвращает список значений или None при ошибке.
"""
# Формируем запрос
request = struct.pack('>BBHH', slave, func, start_addr, count)
crc = self._crc16(request)
request += struct.pack('<H', crc) # CRC little-endian!
self.send_raw(request)
# Ожидаемый размер ответа: addr(1)+fc(1)+byte_count(1)+data(count×2)+crc(2)
expected = 5 + count * 2
response = self.recv_raw(expected)
if response is None:
return None # Таймаут
# Проверка CRC
recv_crc = struct.unpack('<H', response[-2:])[0]
calc_crc = self._crc16(response[:-2])
if recv_crc != calc_crc:
return None # CRC ошибка
# Проверка Exception
if response[1] & 0x80:
exc_code = response[2]
print(f"Modbus Exception: slave={slave}, code={exc_code}")
return None
# Распаковываем данные
byte_count = response[2]
values = list(struct.unpack(f'>{count}H', response[3:3+byte_count]))
return values
def modbus_write_register(self, slave: int, addr: int, value: int) -> bool:
"""Запись одного Holding регистра (FC=6)"""
request = struct.pack('>BBHH', slave, 6, addr, value)
crc = self._crc16(request)
request += struct.pack('<H', crc)
self.send_raw(request)
# Ответ = эхо запроса (8 байт)
response = self.recv_raw(8)
if response is None:
return False
recv_crc = struct.unpack('<H', response[-2:])[0]
return recv_crc == self._crc16(response[:-2])
def close(self):
self.ser.close()
# ===== ПРИМЕР ИСПОЛЬЗОВАНИЯ =====
def demo_poll_vfd():
"""Опрос частотника по Modbus RTU через RS-485"""
master = RS485Master('/dev/ttyUSB0', baudrate=9600, use_rts=True)
try:
print("Опрос частотника (адрес 1)...")
while True:
# Читаем 6 Input регистров: статус, частота, ток, напряжение, мощность, fault
regs = master.modbus_read_registers(slave=1, func=4,
start_addr=0, count=6)
if regs is None:
print("Нет ответа от устройства")
else:
status = regs[0]
freq_hz = regs[1] / 100.0
curr_a = regs[2] / 10.0
volts = regs[3]
power_kw = regs[4] / 10.0
fault = regs[5]
running = bool(status & 0x0001)
faulted = bool(status & 0x0008)
print(f"{'▶
' if running else '⏹'} " f"f={freq_hz:.1f}Гц " f"I={curr_a:.1f}А " f"U={volts}В " f"P={power_kw:.1f}кВт " f"{'🔴АВАРИЯ' if faulted else ''}") if faulted: print(f"Код аварии: {fault}") time.sleep(1.0) except KeyboardInterrupt: print("Остановлено") finally: master.close() demo_poll_vfd()
Диагностика: осциллограф и мультиметр
Измерения мультиметром:
Линия A-B без сигнала (все устройства молчат):
Должно быть: A > B на 200мВ+ (если есть pull-up/down)
Плохо: A = B (неопределённое состояние — нужны резисторы смещения)
Во время передачи:
Осциллограф: чёткие уровни ±3..4В, без выбросов
Плохо: размытые фронты → слишком длинная линия или нет терминаторов
Плохо: выбросы >±7В → нет снаббера или плохое заземление
Измерение дифференциального сигнала:
Щуп A → канал 1, Щуп B → канал 2
Включить Math: CH1 - CH2
Должен быть чёткий прямоугольник ±(3..5)В
Типичные осциллограммы проблем:
Нет терминаторов:
┌──────┐
│ │ ← Нормальный фронт
│ │╲ ← Отражение (заброс)
────┘ └─╲──────
└── ← Повторное отражение
Слишком длинный stub:
┌──────┐
│ ╲___/ ← Паразитное колебание после фронта
────┘ ────
Хорошая линия:
┌──────────┐
────┘ └──── ← Чёткие фронты без выбросов
Гальваническая развязка: когда обязательна
В промышленных установках "земля" в разных точках может иметь разный потенциал — десятки и даже сотни вольт. Без развязки:
Контурный ток по GND-проводу шины RS-485 разрушает трансиверы
Помехи от силового оборудования проникают в логику
Один неисправный узел выводит из строя всю сеть
Когда нужна обязательно:
Устройства питаются от разных источников
Расстояние между устройствами > 50 м
Рядом с шиной есть мощные электродвигатели или сварочное оборудование
Разные здания или разные распределительные щиты
Варианты реализации:
Вариант 1: Изолированный трансивер (ADM2587E, ISO3082)
UART ──[Изолированный трансивер]── RS-485 шина
Встроенная изоляция 2500 VRMS, нет внешних компонентов
Вариант 2: Оптопара + отдельный трансивер
UART_TX ──[HCPL2630]── MAX485 ── RS-485 шина
UART_RX ──[HCPL2630]──┘
Дешевле, но нужен изолированный DC-DC конвертер питания
Вариант 3: Цифровой изолятор + трансивер
UART ──[ISO7720]── MAX485 ── RS-485 шина
ISO7720: 5 кВ изоляция, 100 Мбит/с, без светодиодов (долговечнее оптопар)
Число устройств больше 32: репитеры и конвертеры
Если нужно более 32 устройств или протяжённость линии превышает допустимую:
Репитер RS-485:
[Сегмент 1: 32 уст., 600 м]──[Репитер]──[Сегмент 2: 32 уст., 600 м]
Репитер переусиливает и переформирует сигнал.
Каждый сегмент — отдельная нагрузка.
Популярные репитеры:
- ADAM-4510 (Advantech): изолированный, DIN-рейка
- Moxa MB-9000: с диагностикой
- Homemade: MAX487 + MAX487 с DE/RE управлением
Защита от грозовых разрядов и ESD
При длинных линиях между зданиями — молниезащита обязательна:
Схема защиты RS-485 линии:
RS-485
Шина ──[Предохранитель]──[GDT или MOV]──[TVS-диод]──[Трансивер]
A P6KE6.8CA
Уровни защиты:
1. GDT (газоразрядник) или MOV: ограничивают до 100..500В за наносекунды
2. TVS-диод P6KE6.8CA: ограничивает до ±6.8В, рассеивает 600 Вт импульсно
3. Последовательный резистор 10..22 Ом: ограничивает ток через TVS
Готовые модули молниезащиты RS-485:
- MTL5000: промышленный барьер
- Phoenix Contact TRABTECH: DIN-рейка
- УЗИП-485 (отечественный): Microsemi/аналоги
Частые вопросы и ответы
Q: Можно ли использовать обычный кабель UTP Cat5e вместо специального? A: Можно, и это работает на практике. Cat5e имеет волновое сопротивление 100 Ом (термinator нужен 100 Ом), паразитные ёмкости немного хуже. До 300м/115200 бод — без проблем. Но в промышленной среде с помехами — лучше экранированный кабель.
Q: Нужен ли третий провод GND? A: Для корректной работы трансивера входная синфазная помеха не должна выходить за пределы -7В...+12В (Vcm). При длинных линиях и разных заземлениях это нарушается. GND-провод удерживает синфазное напряжение в пределах допустимого. Включайте GND во все промышленные установки.
Q: У устройства нет RS-485, только UART. Как подключить? A: Добавить внешний трансивер MAX485/SP3485 + резистор 300 Ом на DE/RE от GPIO.
Q: Что делать если устройства разных производителей не видят друг друга? A: Проверить: 1) Скорость/parity/stopbits совпадают. 2) Полярность A/B (иногда производители маркируют наоборот). 3) Адреса устройств уникальны. 4) Нет конфликта адресов.
Заключение
RS-485 — это не устаревший протокол, а надёжный, проверенный инструмент для промышленных приложений. Правильная линейная топология с терминаторами, экранированная витая пара, правильное управление DE/RE и гальваническая развязка там, где нужно — всё это обеспечит годы надёжной работы.
Вложите время в понимание физического уровня: осциллограф + хорошая книга по физике передачи сигналов. Большинство проблем RS-485 решаются на физическом уровне, а не в программе.
Create an account or sign in to leave a review
There are no reviews to display.