<?xml version="1.0"?>
<rss version="2.0"><channel><title>Articles: Industrial Machines</title><link>https://ithub.uno/statiarticles/11_industrial-machines/?d=1</link><description>Articles: Industrial Machines</description><language>en</language><item><title>&#x427;&#x430;&#x441;&#x442;&#x43E;&#x442;&#x43D;&#x44B;&#x435; &#x43F;&#x440;&#x435;&#x43E;&#x431;&#x440;&#x430;&#x437;&#x43E;&#x432;&#x430;&#x442;&#x435;&#x43B;&#x438;: &#x43F;&#x43E;&#x43B;&#x43D;&#x43E;&#x435; &#x440;&#x443;&#x43A;&#x43E;&#x432;&#x43E;&#x434;&#x441;&#x442;&#x432;&#x43E; &#x43F;&#x43E; &#x432;&#x44B;&#x431;&#x43E;&#x440;&#x443;, &#x43F;&#x43E;&#x434;&#x43A;&#x43B;&#x44E;&#x447;&#x435;&#x43D;&#x438;&#x44E; &#x438; &#x43D;&#x430;&#x441;&#x442;&#x440;&#x43E;&#x439;&#x43A;&#x435;</title><link>https://ithub.uno/statiarticles/11_industrial-machines/%D1%87%D0%B0%D1%81%D1%82%D0%BE%D1%82%D0%BD%D1%8B%D0%B5-%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D0%B8-%D0%BF%D0%BE%D0%BB%D0%BD%D0%BE%D0%B5-%D1%80%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83-%D0%BF%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D1%8E-%D0%B8-%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B5-r89/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/kak-pravilno-podklyuchit-chastotniy-preobrazovatel.webp.9e28d3fa84895c082cfe37899e288640.webp" /></p>
<h2>Введение: зачем вообще нужен частотник</h2><p>Представьте насос, который гоняет воду в системе водоснабжения. Без частотного преобразователя он работает в одном режиме — на полную мощность. Давление в сети выросло — открывается байпасный клапан и лишняя энергия тупо рассеивается. Это как ехать на машине с педалью газа в полу и тормозить ногой одновременно.</p><p>Частотный преобразователь (ЧП, частотник, VFD — Variable Frequency Drive, инвертор) решает эту проблему радикально: он плавно регулирует скорость электродвигателя, меняя частоту и напряжение питания. Насосу нужно меньше давления — он просто крутится медленнее. Результат: экономия электроэнергии 30–70%, меньше износ оборудования, мягкий пуск без ударных нагрузок.</p><p>Сегодня частотники стоят везде: насосы, вентиляторы, конвейеры, компрессоры, станки, лифты, краны. Если в вашем производстве есть асинхронный электродвигатель — с вероятностью 80% он или уже управляется частотником, или должен управляться.</p><hr><h2>Принцип работы: что происходит внутри</h2><p>Понимание принципа работы — ключ к правильной настройке и диагностике. Внутри любого частотника три основных блока:</p><h3>1. Выпрямитель (Rectifier)</h3><p>Входное переменное напряжение (380В/50Гц) выпрямляется в постоянное. Используется трёхфазный диодный мост. На выходе получаем ~540В постоянного тока (380 × √2 ≈ 537В).</p><h3>2. Звено постоянного тока (DC Bus)</h3><p>Электролитические конденсаторы большой ёмкости сглаживают пульсации выпрямленного напряжения. Именно здесь хранится энергия рекуперации при торможении. <strong>Важно:</strong> после отключения питания конденсаторы остаются заряженными до 540В в течение нескольких минут. Всегда ждите разряда перед обслуживанием!</p><h3>3. Инвертор (Inverter)</h3><p>IGBT-транзисторы переключаются по алгоритму ШИМ (широтно-импульсная модуляция) с частотой 2–16 кГц, формируя синусоидальный ток нужной частоты и амплитуды. Именно здесь и происходит "магия" — создание переменного тока с произвольными параметрами.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Сеть 380В/50Гц → [Выпрямитель] → 540В DC → [Инвертор ШИМ] → Переменный ток 0-400Гц / 0-380В → Двигатель
</code></pre><hr><h2>Типы управления: скалярное vs векторное</h2><p>Это первое принципиальное решение при настройке. Выбор не правильный — получите либо плохую динамику, либо нестабильную работу.</p><h3>Скалярное управление (V/f = const)</h3><p>Самый простой алгоритм: соотношение напряжения к частоте держится постоянным. Поднимаем частоту с 50Гц до 25Гц — напряжение тоже снижается вдвое (с 380В до 190В). Магнитный поток ротора остаётся примерно постоянным.</p><p><strong>Когда использовать:</strong></p><ul><li><p>Насосы и вентиляторы (вентиляторная нагрузка)</p></li><li><p>Момент не нужен на малых скоростях</p></li><li><p>Несколько двигателей на одном частотнике</p></li><li><p>Простые конвейеры без точного позиционирования</p></li></ul><p><strong>Ограничения:</strong> При малых скоростях момент проседает. Нет контроля скорости под нагрузкой — при увеличении нагрузки двигатель чуть замедляется.</p><h3>Векторное управление (Vector Control)</h3><p>Алгоритм управляет не просто частотой и напряжением, а непосредственно вектором магнитного потока и моментом. Два варианта:</p><p><strong>Без датчика скорости (Sensorless Vector):</strong> Математическая модель двигателя внутри частотника оценивает скорость ротора и момент. Требует настройки параметров двигателя (автотюнинг). Точность: ±0.5–2% от номинала.</p><p><strong>С датчиком скорости (Closed Loop Vector):</strong> Энкодер на валу двигателя даёт точную информацию о скорости. Точность: ±0.01–0.1%. Используется в станках, намоточных машинах, лифтах.</p><p><strong>Когда использовать векторное:</strong></p><ul><li><p>Нужен полный момент при нулевой скорости (краны, экструдеры)</p></li><li><p>Точное поддержание скорости под переменной нагрузкой</p></li><li><p>Быстрая динамика разгона/торможения</p></li><li><p>Один двигатель — один частотник</p></li></ul><hr><h2>Выбор частотника: критерии и расчёт</h2><h3>Мощность</h3><p>Главное правило: <strong>мощность частотника ≥ мощности двигателя</strong>. Для большинства применений выбирают одноступенчатое превышение по каталогу. Например, двигатель 11 кВт — берём частотник 11 кВт или 15 кВт.</p><p><strong>Поправочные коэффициенты:</strong></p><div class="tmiRichText__table-wrapper"><table style="width: 610px;"><colgroup><col style="width:372px;"><col style="width:238px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Условие</p></th><th colspan="1" rowspan="1"><p>Коэффициент к мощности</p></th></tr><tr><td colspan="1" rowspan="1"><p>Длинный кабель (&gt;50м)</p></td><td colspan="1" rowspan="1"><p>×1.1–1.2</p></td></tr><tr><td colspan="1" rowspan="1"><p>Тяжёлый пуск (компрессор)</p></td><td colspan="1" rowspan="1"><p>×1.25–1.5</p></td></tr><tr><td colspan="1" rowspan="1"><p>Частые пуски/остановы</p></td><td colspan="1" rowspan="1"><p>×1.25</p></td></tr><tr><td colspan="1" rowspan="1"><p>Высокая температура (&gt;40°C)</p></td><td colspan="1" rowspan="1"><p>Деrating по паспорту</p></td></tr><tr><td colspan="1" rowspan="1"><p>Высота &gt;1000м над уровнем моря</p></td><td colspan="1" rowspan="1"><p>Derating 1% на каждые 100м</p></td></tr></tbody></table></div><h3>Ток</h3><p>Номинальный ток частотника должен быть <strong>не меньше</strong> номинального тока двигателя с учётом пиковых нагрузок. Многие приводы имеют режим перегрузки 150% на 60 секунд или 200% на 3 секунды.</p><h3>Серии популярных производителей</h3><p><strong>Бюджетный сегмент (учёба, простые задачи):</strong></p><ul><li><p>Delta VFD-E, VFD-M — отличное соотношение цена/качество</p></li><li><p>Hyundai N700E — популярны в России</p></li><li><p>Веспер Е5 — отечественный производитель</p></li></ul><p><strong>Средний сегмент (промышленность):</strong></p><ul><li><p>ABB ACS310, ACS550 — надёжные, хорошая документация</p></li><li><p>Danfoss FC-051, FC-102 — особенно хороши для насосов</p></li><li><p>Schneider Electric Altivar 312, 320</p></li></ul><p><strong>Высокий сегмент (серводрайвы, точное позиционирование):</strong></p><ul><li><p>Siemens SINAMICS G120, S120</p></li><li><p>ABB ACS880 — промышленный стандарт</p></li><li><p>Yaskawa A1000, GA700</p></li></ul><hr><h2>Подключение: схема и важные нюансы</h2><h3>Силовая часть</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Сеть 3ф 380В
    |
[Автоматический выключатель] ← Защита от КЗ, НЕ защита от перегрузки!
    |
[Сетевой реактор] ← ОБЯЗАТЕЛЬНО при искажённой сети или мощности &gt;15кВт
    |
[Частотник]
L1 L2 L3 — вход питания
PE      — заземление (обязательно!)
U V W   — выход на двигатель
    |
[Моторный дроссель] ← при кабеле &gt;20м, защита от перенапряжений
    |
[Двигатель]
</code></pre><p><strong>Критические ошибки подключения:</strong></p><ol><li><p><strong>Никогда не подключайте выход ЧП к сети!</strong> U,V,W — только на двигатель.</p></li><li><p><strong>Никогда не ставьте контактор между ЧП и двигателем</strong> без специальной схемы — IGBT умирает мгновенно.</p></li><li><p><strong>Заземление обязательно</strong> — без него ЧП работает через паразитные ёмкости и сгорит от статики или помех.</p></li><li><p><strong>Разделяйте силовые и сигнальные кабели</strong> — минимум 20–30 см между ними.</p></li></ol><h3>Управляющая часть</h3><p>Типовое подключение аналогового задания скорости (0–10В):</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>ПЛК/потенциометр → AVI (Analog Voltage Input) — задание частоты
ПЛК/кнопка       → DI1 (Digital Input 1)     — команда ПУСК
ПЛК/кнопка       → DI2 (Digital Input 2)     — команда СТОП
ЧП → DO1 (Digital Output 1) → ПЛК            — сигнал "работает"
ЧП → DO2 (Digital Output 2) → ПЛК/лампа      — сигнал "авария"
</code></pre><p>Токовое задание (4–20мА) более помехоустойчиво для длинных кабелей. При 4мА = 0 об/мин, при 20мА = максимальные обороты. Потеря сигнала (обрыв кабеля) → 0мА → частотник видит это как аварию.</p><hr><h2>Основные параметры настройки</h2><p>Каждый производитель имеет свои номера параметров, но логика везде одинакова. Покажем на примере логики настройки:</p><h3>Группа 1: Параметры двигателя</h3><p>Это самые важные параметры — частотник должен "знать" двигатель:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>P_мощность   = 7.5 кВт    (с шильдика двигателя)
P_напряжение = 380 В
P_ток        = 15.5 А
P_частота    = 50 Гц
P_скорость   = 1440 об/мин (или 1450, 1460 — смотреть шильдик)
P_cos_phi    = 0.86
</code></pre><p>После ввода этих данных — <strong>обязательно запустите автотюнинг</strong> (Auto-tuning). Частотник сам измерит активное сопротивление обмоток, индуктивность и другие параметры. Занимает 30–120 секунд. Двигатель при этом либо неподвижен (статический тюнинг), либо вращается (динамический — точнее).</p><h3>Группа 2: Ограничения</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Мин. частота = 5–10 Гц    (ниже нельзя — перегрев двигателя)
Макс. частота = 50–60 Гц  (можно выше, но нужен расчёт подшипников)
Макс. ток     = 110–120% от номинала двигателя
</code></pre><h3>Группа 3: Разгон и торможение</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Время разгона  = 5–30 с    (чем тяжелее механизм — тем больше)
Время торможения = 5–30 с
Тип кривой    = S-образная (плавнее для конвейеров, насосов)
</code></pre><p>Слишком короткое время торможения → ошибка OV (перенапряжение в звене DC) → нужен тормозной резистор или увеличить время.</p><h3>Группа 4: Источник задания и управление</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Источник задания частоты: аналог 0–10В / 4–20мА / цифровые входы / Modbus
Источник команды пуск/стоп: цифровые входы / пульт / Modbus
Режим управления: скалярный / векторный без датчика / с датчиком
</code></pre><hr><h2>ПИД-регулятор в частотнике</h2><p>Большинство современных ЧП имеют встроенный ПИД-регулятор. Это позволяет строить замкнутую систему управления без внешнего контроллера.</p><p><strong>Типичный пример: насос с поддержанием давления</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Датчик давления (4–20мА) → Вход обратной связи ЧП
Уставка давления         → Задание (аналог или цифровое значение)
Выход ПИД               → Управляет частотой насоса
</code></pre><p>Настройка ПИД (упрощённый метод Циглера-Николса):</p><ol><li><p>Установить I=0, D=0, поднимать P до возникновения устойчивых колебаний</p></li><li><p>Записать критический коэффициент Kc и период колебаний Tc</p></li><li><p>P = 0.6×Kc, I = 2×Tc, D = Tc/8</p></li></ol><p>Для насосов и вентиляторов (инерционная нагрузка) типичные значения:</p><ul><li><p>P (пропорциональная составляющая): 20–50%</p></li><li><p>I (интегральная): 2–10 секунд</p></li><li><p>D (дифференциальная): 0–1 секунда (часто не нужна)</p></li></ul><p><strong>Важно:</strong> Включите функцию Sleep/Wake — при малом потреблении (ночное время) насос останавливается, при снижении давления — запускается. Экономия 15–30% электроэнергии.</p><hr><h2>Защитные функции и их настройка</h2><p>Современный частотник — это не просто регулятор, это полноценная система защиты двигателя и механизма.</p><h3>Тепловая защита двигателя (Motor Thermal Protection)</h3><p>Электронный тепловой реле внутри ЧП моделирует нагрев двигателя на основе тока и времени. Задаётся номинальный ток двигателя (I_nom) и тепловая постоянная времени (обычно 30–600 секунд). Гораздо точнее биметаллических реле — учитывает режим работы.</p><h3>Защита от перенапряжения (OV — Over Voltage)</h3><p>Срабатывает когда напряжение в звене DC превышает порог (~800В для 380В-сетей). Причины: резкое торможение (обратная ЭДС двигателя), выброс в сети. Решения: увеличить время торможения, поставить тормозной резистор, включить функцию "регулирование торможения по напряжению DC".</p><h3>Защита от перегрузки (OC — Over Current)</h3><p>Ток превысил допустимый предел. Причины: механическое заклинивание, слишком короткое время разгона, неправильно введены параметры двигателя. Никогда не повышайте порог защиты бездумно — это приведёт к перегреву или сгоранию двигателя.</p><h3>Потеря фазы (Input/Output Phase Loss)</h3><p>Отсутствует одна из фаз входного питания или обрыв в кабеле до двигателя. Критически важная защита — трёхфазный двигатель на двух фазах перегревается за секунды.</p><hr><h2>Коммуникация: Modbus RTU на практике</h2><p>Почти все промышленные частотники поддерживают Modbus RTU через RS-485. Это позволяет управлять приводом с ПЛК или SCADA без аналоговых сигналов.</p><h3>Типичные Modbus-регистры (адреса условные, смотрите документацию вашего ЧП):</h3><div class="tmiRichText__table-wrapper"><table style="min-width: 686px;"><colgroup><col style="min-width:20px;"><col style="width:158px;"><col style="width:508px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Регистр</p></th><th colspan="1" rowspan="1"><p>Тип</p></th><th colspan="1" rowspan="1"><p>Описание</p></th></tr><tr><td colspan="1" rowspan="1"><p>40001</p></td><td colspan="1" rowspan="1"><p>Holding</p></td><td colspan="1" rowspan="1"><p>Управляющее слово (пуск/стоп/реверс)</p></td></tr><tr><td colspan="1" rowspan="1"><p>40002</p></td><td colspan="1" rowspan="1"><p>Holding</p></td><td colspan="1" rowspan="1"><p>Задание частоты (×0.01 Гц, т.е. 5000 = 50.00 Гц)</p></td></tr><tr><td colspan="1" rowspan="1"><p>40003</p></td><td colspan="1" rowspan="1"><p>Input</p></td><td colspan="1" rowspan="1"><p>Статусное слово (работает/авария)</p></td></tr><tr><td colspan="1" rowspan="1"><p>40004</p></td><td colspan="1" rowspan="1"><p>Input</p></td><td colspan="1" rowspan="1"><p>Текущая частота</p></td></tr><tr><td colspan="1" rowspan="1"><p>40005</p></td><td colspan="1" rowspan="1"><p>Input</p></td><td colspan="1" rowspan="1"><p>Ток двигателя (×0.1А)</p></td></tr><tr><td colspan="1" rowspan="1"><p>40006</p></td><td colspan="1" rowspan="1"><p>Input</p></td><td colspan="1" rowspan="1"><p>Напряжение DC-шины</p></td></tr><tr><td colspan="1" rowspan="1"><p>40007</p></td><td colspan="1" rowspan="1"><p>Input</p></td><td colspan="1" rowspan="1"><p>Код последней аварии</p></td></tr></tbody></table></div><h3>Пример управления через Python (для тестирования и прототипирования):</h3><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>import minimalmodbus
import time

# Настройка соединения
vfd = minimalmodbus.Instrument('/dev/ttyUSB0', 1)  # COM-порт, адрес устройства = 1
vfd.serial.baudrate = 9600
vfd.serial.bytesize = 8
vfd.serial.parity   = 'N'
vfd.serial.stopbits = 1
vfd.serial.timeout  = 0.5
vfd.mode = minimalmodbus.MODE_RTU

def vfd_start():
    """Команда ПУСК вперёд"""
    vfd.write_register(0x2000, 0x0002, functioncode=6)  # Control word: Run Forward

def vfd_stop():
    """Команда СТОП (свободный выбег)"""
    vfd.write_register(0x2000, 0x0001, functioncode=6)  # Control word: Stop

def vfd_set_freq(freq_hz: float):
    """Задать частоту в Гц (0.0 - 50.0)"""
    value = int(freq_hz * 100)  # Например 50.0 Гц → 5000
    vfd.write_register(0x2001, value, functioncode=6)

def vfd_get_status() -&gt; dict:
    """Считать текущее состояние"""
    status_word = vfd.read_register(0x2100, functioncode=3)
    freq        = vfd.read_register(0x2101, functioncode=3) / 100.0
    current     = vfd.read_register(0x2102, functioncode=3) / 10.0
    voltage_dc  = vfd.read_register(0x2103, functioncode=3)
    
    return {
        'running':   bool(status_word &amp; 0x0001),
        'fault':     bool(status_word &amp; 0x0008),
        'frequency': freq,
        'current':   current,
        'dc_voltage': voltage_dc
    }

# Пример использования
try:
    print("Запуск двигателя...")
    vfd_set_freq(30.0)  # Задаём 30 Гц
    vfd_start()
    
    for i in range(10):
        time.sleep(2)
        status = vfd_get_status()
        print(f"Частота: {status['frequency']} Гц, Ток: {status['current']} А")
        
        if status['fault']:
            print("АВАРИЯ! Проверьте частотник.")
            break
    
    print("Остановка...")
    vfd_stop()

except Exception as e:
    print(f"Ошибка связи: {e}")
</code></pre><hr><h2>Типичные аварии и их устранение</h2><h3>F001 / OC — Сверхток при пуске</h3><p><strong>Симптомы:</strong> Ошибка возникает сразу при подаче команды пуск или в первые секунды разгона.</p><p><strong>Причины и решения:</strong></p><ul><li><p>Слишком короткое время разгона → Увеличить в 2–3 раза</p></li><li><p>Механическое заклинивание → Проверить механику, проворачивается ли вал вручную</p></li><li><p>КЗ в кабеле или обмотках → Мегомметром проверить изоляцию кабеля (500В) и двигателя</p></li><li><p>Неверные параметры двигателя → Перепроверить ток, мощность, cos_phi</p></li><li><p>Включён режим векторного управления без автотюнинга → Запустить автотюнинг</p></li></ul><h3>F002 / OV — Перенапряжение в DC-шине</h3><p><strong>Симптомы:</strong> Ошибка при торможении или при резком снижении нагрузки.</p><p><strong>Причины и решения:</strong></p><ul><li><p>Слишком короткое время торможения → Увеличить время</p></li><li><p>Нет тормозного резистора при большом маховике → Установить резистор</p></li><li><p>Высокое напряжение сети (&gt;415В) → Проверить сеть, возможно нужен трансформатор</p></li><li><p>Включить функцию "Voltage regulation during deceleration" — ЧП сам замедляет торможение</p></li></ul><h3>F003 / OH — Перегрев</h3><p><strong>Симптомы:</strong> После длительной работы или в жаркую погоду.</p><p><strong>Причины и решения:</strong></p><ul><li><p>Загрязнён радиатор → Очистить сжатым воздухом (не водой!)</p></li><li><p>Сломан вентилятор охлаждения → Заменить</p></li><li><p>Недостаточно места для вентиляции → Минимум 10 см сверху и снизу</p></li><li><p>Температура в шкафу &gt;40°C → Добавить принудительную вентиляцию шкафа</p></li><li><p>Слишком высокая частота ШИМ → Снизить с 8кГц до 4кГц (будет чуть громче, но прохладнее)</p></li></ul><h3>F004 / LV — Пониженное напряжение</h3><p><strong>Симптомы:</strong> При просадке сети или при запуске мощного оборудования рядом.</p><p><strong>Решения:</strong></p><ul><li><p>Проверить напряжение сети мультиметром под нагрузкой</p></li><li><p>Установить сетевой реактор — сглаживает просадки</p></li><li><p>Настроить время повторного пуска после восстановления питания (Auto-restart)</p></li></ul><hr><h2>Энергосбережение: реальные цифры</h2><p>Закон куба: мощность вентилятора/насоса пропорциональна кубу скорости. Снизили скорость на 20% → потребление упало на 49%!</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>P2/P1 = (n2/n1)³

При n1 = 50 Гц, n2 = 40 Гц (снижение на 20%):
P2/P1 = (40/50)³ = 0.512 → экономия 48.8%!
</code></pre><p>Реальный пример из практики: вентилятор системы вентиляции цеха, 55 кВт, работал 24/7 на 50 Гц. После установки частотника с датчиком CO2 и ПИД-регулятором:</p><ul><li><p>Среднесуточная частота работы: 35–40 Гц</p></li><li><p>Фактическое потребление: снизилось с 55 кВт до 22–28 кВт</p></li><li><p>Годовая экономия: ~250 000 кВт·ч</p></li><li><p>При тарифе 6 руб/кВт·ч: 1 500 000 руб/год</p></li><li><p>Стоимость частотника 55 кВт: ~180 000 руб</p></li><li><p>Срок окупаемости: <strong>6 недель</strong></p></li></ul><hr><h2>Чек-лист при вводе в эксплуатацию</h2><p>Перед первым пуском обязательно проверить:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>□ Напряжение питания соответствует номиналу ЧП (380В ±10%)
□ Заземление подключено и проверено (&lt;4 Ом)
□ Кабели L1-L2-L3 и U-V-W не перепутаны местами
□ Нет КЗ между фазами и на землю (мегомметром)
□ Введены параметры двигателя (шильдик)
□ Проведён автотюнинг
□ Настроены ограничения: мин/макс частота, макс ток
□ Настроены времена разгона/торможения
□ Проверена правильность направления вращения (на малой скорости 5–10 Гц)
□ Настроены аварийные выходы и тестирована их реакция
□ Записаны все изменённые параметры в документацию
</code></pre><hr><h2>Заключение</h2><p>Частотный преобразователь — один из самых универсальных и окупаемых инструментов в промышленной автоматизации. Правильно подобранный и настроенный, он одновременно экономит электроэнергию, продлевает ресурс двигателя и механического оборудования, даёт возможность тонкого управления технологическим процессом.</p><p>Ключевые принципы, которые стоит запомнить: выбирайте тип управления под задачу, всегда вводите точные параметры двигателя и делайте автотюнинг, не пренебрегайте сетевыми и моторными дросселями, настраивайте защиты адекватно нагрузке. И помните про безопасность — конденсаторы DC-шины хранят смертельное напряжение ещё несколько минут после отключения питания.</p><p>Изучите документацию на ваш конкретный привод — производители вкладывают в неё годы опыта тысяч инсталляций. Это лучший источник правильных решений для конкретного устройства.</p>]]></description><guid isPermaLink="false">89</guid><pubDate>Sat, 21 Mar 2026 16:40:33 +0000</pubDate></item><item><title>&#x41F;&#x440;&#x43E;&#x433;&#x440;&#x430;&#x43C;&#x43C;&#x438;&#x440;&#x43E;&#x432;&#x430;&#x43D;&#x438;&#x435; &#x41F;&#x41B;&#x41A;: &#x43E;&#x442; &#x43D;&#x443;&#x43B;&#x44F; &#x434;&#x43E; &#x43F;&#x435;&#x440;&#x432;&#x43E;&#x439; &#x440;&#x430;&#x431;&#x43E;&#x447;&#x435;&#x439; &#x43F;&#x440;&#x43E;&#x433;&#x440;&#x430;&#x43C;&#x43C;&#x44B;</title><link>https://ithub.uno/statiarticles/11_industrial-machines/%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BB%D0%BA-%D0%BE%D1%82-%D0%BD%D1%83%D0%BB%D1%8F-%D0%B4%D0%BE-%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B9-%D1%80%D0%B0%D0%B1%D0%BE%D1%87%D0%B5%D0%B9-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D1%8B-r92/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/preview.jpg.f278a87c27083427569f6803d307df9a.jpg" /></p>
<h2>Что такое ПЛК и почему это не просто "мощный Arduino"</h2><p>ПЛК (Программируемый Логический Контроллер) — это специализированный промышленный компьютер, разработанный для управления технологическими процессами в реальном времени. Главное отличие от обычного компьютера или Arduino — <strong>детерминизм</strong>: гарантированное время реакции на входные сигналы, независимо от загрузки процессора.</p><p>Когда на производстве нужно, чтобы насос включился <strong>строго через 50 мс</strong> после срабатывания датчика — это ПЛК. Когда допустима задержка в секунду — можно обойтись более дешёвыми решениями. Когда вопрос в надёжности и работе в условиях вибраций, пыли, температур от -40 до +70°C — снова ПЛК.</p><p><strong>Другие ключевые отличия:</strong></p><ul><li><p><strong>Цикличность:</strong> программа выполняется повторяющимися циклами (обычно 1–100 мс). Каждый цикл: считать все входы → выполнить программу → записать все выходы</p></li><li><p><strong>Гальваническая развязка входов/выходов:</strong> промышленные сигналы (24В DC, 220В AC) изолированы от внутренней логики</p></li><li><p><strong>Горячая замена модулей:</strong> во многих системах можно менять I/O-модули без остановки контроллера</p></li><li><p><strong>Встроенная диагностика:</strong> ПЛК сам следит за собственным здоровьем</p></li></ul><hr><h2>Архитектура ПЛК: как это устроено</h2><h3>Центральный процессорный модуль (CPU)</h3><p>Выполняет программу, управляет обменом данных, содержит основную память. В S7-1200 CPU также имеет встроенные входы/выходы, Ethernet порт и возможность расширения.</p><h3>Модули ввода-вывода (I/O Modules)</h3><p><strong>Дискретные входы (DI):</strong> Воспринимают сигналы "есть напряжение / нет напряжения". Обычно 24В DC или 220В AC. Примеры источников: кнопки, концевые выключатели, датчики приближения (индуктивные, ёмкостные), фотодатчики, реле.</p><p><strong>Дискретные выходы (DO):</strong> Управляют исполнительными устройствами. Бывают транзисторные (24В DC, быстрые) и релейные (любое напряжение до 250В AC, медленные, но универсальные).</p><p><strong>Аналоговые входы (AI):</strong> Принимают непрерывные сигналы: 4–20мА, 0–10В, термопары (тип K, J, T...), термосопротивления (Pt100, Pt1000). Преобразуют в число (обычно 0–27648 для диапазона 0–100%).</p><p><strong>Аналоговые выходы (AO):</strong> Выдают аналоговые сигналы для управления частотниками, регулирующими клапанами, позиционерами.</p><h3>Память ПЛК (на примере Siemens)</h3><div class="tmiRichText__table-wrapper"><table style="width: 903px;"><colgroup><col style="width:164px;"><col style="width:251px;"><col style="width:488px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Область</p></th><th colspan="1" rowspan="1"><p>Обозначение</p></th><th colspan="1" rowspan="1"><p>Описание</p></th></tr><tr><td colspan="1" rowspan="1"><p>Входы</p></td><td colspan="1" rowspan="1"><p>I, %IX</p></td><td colspan="1" rowspan="1"><p>Образ входов, обновляется каждый цикл</p></td></tr><tr><td colspan="1" rowspan="1"><p>Выходы</p></td><td colspan="1" rowspan="1"><p>Q, %QX</p></td><td colspan="1" rowspan="1"><p>Образ выходов, пишется в физику после цикла</p></td></tr><tr><td colspan="1" rowspan="1"><p>Меркеры</p></td><td colspan="1" rowspan="1"><p>M, %MX</p></td><td colspan="1" rowspan="1"><p>Внутренние биты/байты/слова — "рабочая память"</p></td></tr><tr><td colspan="1" rowspan="1"><p>Таймеры</p></td><td colspan="1" rowspan="1"><p>T (S7-classic) / IEC-timer</p></td><td colspan="1" rowspan="1"><p>Отсчёт времени</p></td></tr><tr><td colspan="1" rowspan="1"><p>Счётчики</p></td><td colspan="1" rowspan="1"><p>C (S7-classic) / IEC-counter</p></td><td colspan="1" rowspan="1"><p>Счёт импульсов</p></td></tr><tr><td colspan="1" rowspan="1"><p>Data Blocks</p></td><td colspan="1" rowspan="1"><p>DB</p></td><td colspan="1" rowspan="1"><p>Блоки данных: рецепты, уставки, история</p></td></tr></tbody></table></div><hr><h2>Стандарт МЭК 61131-3: пять языков программирования</h2><p>Международный стандарт определяет пять языков для ПЛК. Хороший инженер знает минимум два-три.</p><h3>1. Ladder Diagram (LD) — Релейно-контактная схема</h3><p>Исторически первый язык — имитация схем из физических реле. Читается слева направо, как электрическая цепь. Левая шина — "фаза", правая — "ноль". Ток "течёт" если путь замкнут.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>         I0.0        I0.1        Q0.0
Пуск     Стоп (НЗ)   Выход насос
 ──┤ ├────┤/├──────────( )──
   
   Q0.0    (самоподхват)
 ──┤ ├──
</code></pre><p>Эта схема — классика: кнопка ПУСК (I0.0) запускает насос (Q0.0), контакт самоподхвата удерживает его включённым, кнопка СТОП (I0.1, нормально-закрытый) его отключает.</p><p><strong>Преимущества LD:</strong> Понятен электрикам без IT-образования, визуально отображает логику цепей, легко отлаживать онлайн (подсвечиваются активные цепи).</p><p><strong>Недостатки:</strong> Громоздкий для сложных вычислений и работы с данными.</p><h3>2. Function Block Diagram (FBD) — Диаграмма функциональных блоков</h3><p>Программа строится из готовых блоков (AND, OR, NOT, таймеры, счётчики, ПИД-регуляторы), соединённых сигнальными линиями. Хорошо подходит для управления потоками сигналов.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>I0.0 ──┐
       ├──[AND]──── Q0.0
I0.1 ──┘
</code></pre><h3>3. Structured Text (ST) — Структурированный текст</h3><p>Язык высокого уровня, похожий на Pascal/Ada. Самый мощный для математических вычислений, работы с массивами, строками.</p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>(* Программа управления насосом с ПИД *)
IF Start AND NOT Stop THEN
    Running := TRUE;
END_IF;

IF Stop THEN
    Running := FALSE;
END_IF;

(* Расчёт ПИД *)
IF Running THEN
    Error    := Setpoint - Feedback;
    Integral := Integral + Error * CycleTime;
    Integral := LIMIT(-100.0, Integral, 100.0); (* Ограничение интеграла *)
    
    Output := Kp * Error + Ki * Integral + Kd * (Error - PrevError) / CycleTime;
    Output := LIMIT(0.0, Output, 100.0);
    
    PrevError := Error;
ELSE
    Output   := 0.0;
    Integral := 0.0;
END_IF;
</code></pre><h3>4. Instruction List (IL) — Список инструкций</h3><p>Ассемблер для ПЛК. Устаревший язык, в новом стандарте МЭК 61131-3 третьей редакции официально deprecated. Знать необязательно.</p><h3>5. Sequential Function Chart (SFC) — Диаграмма последовательных функций</h3><p>Похоже на блок-схему или граф состояний. Идеален для описания технологических последовательностей: шаг 1 → условие перехода → шаг 2 → условие → шаг 3...</p><hr><h2>Реальный проект: управление насосной станцией</h2><p>Разберём полноценный пример — система управления двумя насосами с чередованием и защитами.</p><h3>Техническое задание:</h3><ul><li><p>2 насоса, работают поочерёдно для равномерного износа</p></li><li><p>Автоматическое включение второго при отказе первого</p></li><li><p>Защита от сухого хода (датчик уровня)</p></li><li><p>Защита от давления (реле давления)</p></li><li><p>Ручной/автоматический режим</p></li><li><p>Счётчик моточасов каждого насоса</p></li></ul><h3>Распределение входов/выходов:</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>ВХОДЫ:
I0.0 — Кнопка ПУСК (автоматический режим)
I0.1 — Кнопка СТОП
I0.2 — Переключатель Авт/Ручной
I0.3 — Датчик уровня (нижний предел — сухой ход)
I0.4 — Датчик уровня (верхний предел — бак полон)
I0.5 — Реле давления насос 1 (авария — нет давления)
I0.6 — Реле давления насос 2
I0.7 — Тепловое реле насос 1 (перегрев)
I1.0 — Тепловое реле насос 2
I1.1 — Ручное управление насос 1 (в ручном режиме)
I1.2 — Ручное управление насос 2

ВЫХОДЫ:
Q0.0 — Контактор насос 1
Q0.1 — Контактор насос 2
Q0.2 — Лампа "Работа авто"
Q0.3 — Лампа "Авария"
Q0.4 — Сирена (авария критическая)
</code></pre><h3>Программа на Structured Text (ST):</h3><pre spellcheck="" class="tmiCode" data-language="pascal"><code>PROGRAM PumpStation

(* ===== ПЕРЕМЕННЫЕ ===== *)
VAR
    // Входы
    btnStart         AT %I0.0 : BOOL;
    btnStop          AT %I0.1 : BOOL;
    swAutoManual     AT %I0.2 : BOOL;  // TRUE = авто
    snsLevelLow      AT %I0.3 : BOOL;  // TRUE = уровень низкий (авария)
    snsLevelHigh     AT %I0.4 : BOOL;  // TRUE = бак полон
    relPressure1     AT %I0.5 : BOOL;  // FALSE = нет давления (авария)
    relPressure2     AT %I0.6 : BOOL;
    thmRelay1        AT %I0.7 : BOOL;  // TRUE = перегрев (авария)
    thmRelay2        AT %I1.0 : BOOL;
    btnPump1Manual   AT %I1.1 : BOOL;
    btnPump2Manual   AT %I1.2 : BOOL;
    
    // Выходы
    outPump1         AT %Q0.0 : BOOL;
    outPump2         AT %Q0.1 : BOOL;
    lampAutoRun      AT %Q0.2 : BOOL;
    lampFault        AT %Q0.3 : BOOL;
    siren            AT %Q0.4 : BOOL;
    
    // Внутренние переменные
    SystemRun        : BOOL := FALSE;
    ActivePump       : INT  := 1;      // Какой насос сейчас основной
    Fault            : BOOL := FALSE;
    FaultCode        : INT  := 0;
    Pump1Fault       : BOOL := FALSE;
    Pump2Fault       : BOOL := FALSE;
    
    // Таймеры
    TimerPumpStart   : TON;            // Задержка пуска насоса
    TimerRotation    : TON;            // Таймер чередования (8 часов)
    TimerAlarmDelay  : TON;            // Задержка подтверждения аварии
    
    // Счётчики моточасов
    Hours_Pump1      : DINT := 0;
    Hours_Pump2      : DINT := 0;
    TimerH_Pump1     : TON;
    TimerH_Pump2     : TON;
END_VAR

(* ===== ЛОГИКА АВАРИЙ ===== *)
// Авария сухого хода — критическая, немедленная остановка
IF snsLevelLow THEN
    Fault     := TRUE;
    FaultCode := 1;  // Сухой ход
    SystemRun := FALSE;
END_IF;

// Задержанные аварии давления (3 секунды для исключения ложных срабатываний)
TimerAlarmDelay(IN := (outPump1 AND NOT relPressure1) OR
                       (outPump2 AND NOT relPressure2),
                PT := T#3S);

IF TimerAlarmDelay.Q THEN
    IF outPump1 AND NOT relPressure1 THEN
        Pump1Fault := TRUE;
        FaultCode  := 2;  // Нет давления насос 1
    END_IF;
    IF outPump2 AND NOT relPressure2 THEN
        Pump2Fault := TRUE;
        FaultCode  := 3;  // Нет давления насос 2
    END_IF;
END_IF;

// Тепловая защита
IF thmRelay1 THEN
    Pump1Fault := TRUE;
    FaultCode  := 4;  // Перегрев насос 1
END_IF;
IF thmRelay2 THEN
    Pump2Fault := TRUE;
    FaultCode  := 5;  // Перегрев насос 2
END_IF;

// Оба насоса в аварии
IF Pump1Fault AND Pump2Fault THEN
    Fault     := TRUE;
    SystemRun := FALSE;
END_IF;

(* ===== КОМАНДЫ ПУСК/СТОП ===== *)
IF btnStart AND NOT Fault THEN
    SystemRun := TRUE;
END_IF;

IF btnStop OR snsLevelHigh THEN  // Стоп или бак полон
    SystemRun := FALSE;
END_IF;

// Квитирование аварии (нажать СТОП для сброса)
IF btnStop THEN
    Fault      := FALSE;
    FaultCode  := 0;
    Pump1Fault := FALSE;
    Pump2Fault := FALSE;
END_IF;

(* ===== АВТОМАТИЧЕСКИЙ РЕЖИМ ===== *)
IF swAutoManual AND SystemRun THEN
    
    // Чередование каждые 8 часов
    TimerRotation(IN := SystemRun, PT := T#28800S);  // 8 часов = 28800 секунд
    IF TimerRotation.Q THEN
        IF ActivePump = 1 THEN
            ActivePump := 2;
        ELSE
            ActivePump := 1;
        END_IF;
        TimerRotation(IN := FALSE);  // Сброс таймера
        TimerRotation(IN := TRUE);
    END_IF;
    
    // При аварии основного насоса — переключаемся на резервный
    IF ActivePump = 1 AND Pump1Fault AND NOT Pump2Fault THEN
        ActivePump := 2;
    ELSIF ActivePump = 2 AND Pump2Fault AND NOT Pump1Fault THEN
        ActivePump := 1;
    END_IF;
    
    // Задержка пуска 1 секунда (защита от дребезга)
    TimerPumpStart(IN := SystemRun, PT := T#1S);
    
    outPump1 := TimerPumpStart.Q AND (ActivePump = 1) AND NOT Pump1Fault;
    outPump2 := TimerPumpStart.Q AND (ActivePump = 2) AND NOT Pump2Fault;

(* ===== РУЧНОЙ РЕЖИМ ===== *)
ELSIF NOT swAutoManual THEN
    outPump1 := btnPump1Manual AND NOT Pump1Fault;
    outPump2 := btnPump2Manual AND NOT Pump2Fault;
    
ELSE
    outPump1 := FALSE;
    outPump2 := FALSE;
END_IF;

(* ===== СЧЁТЧИКИ МОТОЧАСОВ ===== *)
TimerH_Pump1(IN := outPump1, PT := T#1S);
IF TimerH_Pump1.Q THEN
    Hours_Pump1 := Hours_Pump1 + 1;
    TimerH_Pump1(IN := FALSE);
    TimerH_Pump1(IN := TRUE);
END_IF;

TimerH_Pump2(IN := outPump2, PT := T#1S);
IF TimerH_Pump2.Q THEN
    Hours_Pump2 := Hours_Pump2 + 1;
END_IF;

(* ===== ИНДИКАЦИЯ ===== *)
lampAutoRun := SystemRun AND swAutoManual;
lampFault   := Fault OR Pump1Fault OR Pump2Fault;
siren       := Fault;  // Сирена только при критической аварии

END_PROGRAM
</code></pre><hr><h2>Таймеры и счётчики: подробно</h2><p>Таймеры и счётчики — основа любой программы ПЛК. В стандарте МЭК 61131-3 они реализованы как функциональные блоки.</p><h3>Типы таймеров</h3><p><strong>TON (Timer On Delay) — таймер с задержкой включения:</strong></p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>TimerFan(IN := MotorRun, PT := T#5S);
// Q становится TRUE через 5 секунд после включения MotorRun
FanStart := TimerFan.Q;
</code></pre><p><strong>TOF (Timer Off Delay) — таймер с задержкой выключения:</strong></p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>TimerFan(IN := MotorRun, PT := T#30S);
// Q остаётся TRUE ещё 30 секунд после выключения MotorRun
// Используется для дополнительного охлаждения после остановки
FanRun := TimerFan.Q;
</code></pre><p><strong>TP (Timer Pulse) — таймер импульса:</strong></p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>TimerBuzzer(IN := AlarmNew, PT := T#2S);
// При фронте AlarmNew генерирует импульс 2 секунды
// Независимо от того, сколько ещё держится AlarmNew
Buzzer := TimerBuzzer.Q;
</code></pre><h3>Счётчики</h3><p><strong>CTU (Count Up) — счётчик вперёд:</strong></p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>CounterBottles(CU := BottleSensor, R := ResetButton, PV := 100);
// Считает бутылки, при достижении 100 — Q=TRUE
BatchComplete := CounterBottles.Q;
CurrentCount  := CounterBottles.CV;  // Текущее значение
</code></pre><p><strong>CTD (Count Down) — счётчик назад:</strong></p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>CounterProducts(CD := ProductSensor, LD := LoadButton,
                PV := OrderQty);  // PV загружается при LD=TRUE
OrderComplete := CounterProducts.Q;  // Q=TRUE когда CV=0
</code></pre><hr><h2>Работа с аналоговыми сигналами</h2><h3>Масштабирование аналогового входа</h3><p>Аналоговый модуль S7-1200 возвращает значение 0–27648 для диапазона 0–100% входного сигнала (4–20мА или 0–10В). Для получения реального значения нужно масштабирование:</p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>FUNCTION_BLOCK ScaleAnalog
VAR_INPUT
    RawValue    : INT;    // Сырое значение от АЦП (0..27648)
    RawMin      : INT;    // Мин значение АЦП (обычно 0 или 5530 для 4мА)
    RawMax      : INT;    // Макс значение АЦП (обычно 27648)
    PhysMin     : REAL;   // Физический минимум (например, 0.0 бар)
    PhysMax     : REAL;   // Физический максимум (например, 16.0 бар)
END_VAR
VAR_OUTPUT
    PhysValue   : REAL;   // Результат в физических единицах
    Broken      : BOOL;   // Обрыв линии (значение ниже 4мА)
END_VAR

// Проверка обрыва линии (для 4-20мА: ниже ~5% = обрыв)
Broken := (RawValue &lt; (RawMin - 1000));

IF NOT Broken THEN
    // Линейное масштабирование
    PhysValue := PhysMin + (REAL(RawValue - RawMin) / REAL(RawMax - RawMin))
                           * (PhysMax - PhysMin);
    // Ограничение выхода
    PhysValue := MAX(PhysMin, MIN(PhysMax, PhysValue));
ELSE
    PhysValue := PhysMin;  // При обрыве — безопасное значение
END_IF;

END_FUNCTION_BLOCK
</code></pre><p>Использование:</p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>// Датчик давления 4-20мА, диапазон 0-16 бар
PressureSensor(
    RawValue := %IW64,      // Адрес аналогового входа
    RawMin   := 5530,       // 4мА соответствует 5530
    RawMax   := 27648,      // 20мА соответствует 27648
    PhysMin  := 0.0,
    PhysMax  := 16.0
);

Pressure       := PressureSensor.PhysValue;  // Давление в барах
PressureAlarm  := PressureSensor.Broken;     // Авария обрыва линии

IF Pressure &gt; 12.0 THEN
    HighPressureAlarm := TRUE;
END_IF;
</code></pre><hr><h2>Организационные блоки и структура программы</h2><p>Профессиональная программа ПЛК разделена на функциональные блоки:</p><h3>Организация в TIA Portal (Siemens):</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>OB1 (Main) — Главный цикл
├── FC10: ReadInputs         — Чтение и нормализация входов
├── FC20: SafetyLogic        — Приоритетные защиты (всегда первыми!)
├── FB30: PumpControl [DB30] — Управление насосами (с памятью)
├── FB40: PIDControl [DB40]  — ПИД-регулятор
├── FC50: WriteOutputs       — Запись выходов
└── FC60: Diagnostics        — Диагностика и коммуникации

OB35 (Cyclic Interrupt, 10ms) — Быстрые задачи
└── FB35: FastCounter        — Высокоскоростной счётчик

OB82 (I/O Error) — Обработка ошибок I/O-модулей

OB121 (Programming Error) — Обработка ошибок программы
</code></pre><p>Принцип: <strong>защиты и аварии — всегда в начале</strong> главного цикла. Они должны отработать независимо от состояния остальной программы.</p><hr><h2>Отладка и диагностика программы</h2><h3>Онлайн-мониторинг</h3><p>В TIA Portal при подключении к ПЛК все блоки отображают реальные значения переменных. В Ladder Diagram активные цепи подсвечиваются зелёным — мгновенно видно что работает.</p><h3>Форсирование переменных (Force)</h3><p>Можно принудительно установить значение входа или переменной для тестирования. <strong>Внимание:</strong> принудительные значения перекрывают реальные физические сигналы. Не забудьте снять форсирование перед вводом в эксплуатацию!</p><h3>Трассировка</h3><p>Запись значений переменных в реальном времени с временной меткой. Незаменима для поиска редко возникающих ошибок — включаете запись и ждёте появления проблемы.</p><h3>Типичные ошибки начинающих:</h3><p><strong>1. Использование выходных катушек несколько раз в Ladder</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>НЕПРАВИЛЬНО:
Цепь 1: I0.0 → Q0.0 (катушка)
Цепь 2: I0.1 → Q0.0 (катушка снова!)

Вторая катушка перезаписывает первую. Результат Q0.0 определяется только второй цепью.

ПРАВИЛЬНО:
Цепь 1: I0.0 ──┐
               ├── Q0.0
Цепь 2: I0.1 ──┘
</code></pre><p><strong>2. Гонка состояний при SET/RESET</strong></p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>// ОПАСНО: порядок операций важен
IF Condition1 THEN  SET(Coil);    END_IF;
IF Condition2 THEN  RESET(Coil);  END_IF;
// Если оба условия TRUE — RESET побеждает (последний)
// Убедитесь, что это желаемое поведение!
</code></pre><p><strong>3. Деление на ноль</strong></p><pre spellcheck="" class="tmiCode" data-language="pascal"><code>// ВСЕГДА проверяйте делитель
IF Denominator &lt;&gt; 0.0 THEN
    Result := Numerator / Denominator;
ELSE
    Result := 0.0;  // Безопасное значение
END_IF;
</code></pre><hr><h2>Коммуникация ПЛК с внешним миром</h2><h3>Modbus TCP (через Ethernet)</h3><p>Большинство современных ПЛК поддерживают Modbus TCP "из коробки". Это самый распространённый протокол для связи с SCADA, HMI и частотниками.</p><p>В S7-1200 для Modbus TCP используются системные функциональные блоки:</p><ul><li><p><code>MB_CLIENT</code> — ПЛК как Modbus-мастер (опрашивает устройства)</p></li><li><p><code>MB_SERVER</code> — ПЛК как Modbus-сервер (отвечает на запросы SCADA)</p></li></ul><h3>OPC UA</h3><p>Современный открытый протокол для промышленной коммуникации. Поддерживает семантику данных, безопасность, публикацию/подписку. Все серьёзные ПЛК последних поколений имеют встроенный OPC UA сервер.</p><h3>PROFINET/EtherCAT</h3><p>Промышленные реальном-временные сети. PROFINET — стандарт Siemens и Profibus International. EtherCAT — от Beckhoff, исключительно быстрый (цикл 250 мкс). Используются для связи с распределёнными I/O-модулями, сервоприводами, vision-системами.</p><hr><h2>Советы по надёжности программы</h2><ol><li><p><strong>Всегда инициализируйте переменные</strong> — не полагайтесь на "нулевое" начальное значение</p></li><li><p><strong>Используйте watchdog-таймер</strong> — если программа "зависла", таймер переводит выходы в безопасное состояние</p></li><li><p><strong>Документируйте каждую переменную</strong> — через полгода вы забудете что значит переменная <code>b47</code></p></li><li><p><strong>Разделяйте задачи по функциональным блокам</strong> — один блок = одна задача</p></li><li><p><strong>Тестируйте на симуляторе</strong> перед загрузкой в реальный ПЛК</p></li><li><p><strong>Делайте резервные копии</strong> программы перед каждым изменением — версионирование спасало многих</p></li><li><p><strong>Стандартизируйте именование:</strong> <code>btnStart</code> — кнопка, <code>snsLevel</code> — датчик, <code>outPump</code> — выход, <code>tmrDelay</code> — таймер</p></li></ol><hr><h2>Заключение</h2><p>Программирование ПЛК — это отдельная инженерная дисциплина на стыке электротехники, автоматики и программирования. Ключевой принцип: <strong>программа управляет реальным оборудованием</strong>, и любая ошибка может привести к аварии или травме. Поэтому надёжность, защиты и понятность кода здесь важнее красоты архитектуры.</p><p>Начните с Ladder Diagram — он прозрачен и хорошо отлаживается онлайн. Освойте Structured Text для сложных вычислений. Используйте SFC для технологических последовательностей. И всегда: сначала безопасность, потом функциональность.</p><p>Хорошая программа ПЛК должна безопасно остановить оборудование при <strong>любой</strong> нештатной ситуации — потере связи, пропадании питания, выходе из строя датчика. Проектируйте с расчётом на отказ.</p>]]></description><guid isPermaLink="false">92</guid><pubDate>Sat, 21 Mar 2026 16:42:30 +0000</pubDate></item><item><title>&#x41F;&#x440;&#x43E;&#x442;&#x43E;&#x43A;&#x43E;&#x43B; Modbus: &#x43F;&#x43E;&#x43B;&#x43D;&#x43E;&#x435; &#x43F;&#x440;&#x430;&#x43A;&#x442;&#x438;&#x447;&#x435;&#x441;&#x43A;&#x43E;&#x435; &#x440;&#x443;&#x43A;&#x43E;&#x432;&#x43E;&#x434;&#x441;&#x442;&#x432;&#x43E;</title><link>https://ithub.uno/statiarticles/11_industrial-machines/%D0%BF%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB-modbus-%D0%BF%D0%BE%D0%BB%D0%BD%D0%BE%D0%B5-%D0%BF%D1%80%D0%B0%D0%BA%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5-%D1%80%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-r95/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/cover_v2.jpg.71f78a147f4587e25711895308e985bb.jpg" /></p>
<h2>Почему Modbus жив и актуален спустя 45 лет</h2><p>Modbus был разработан компанией Modicon в 1979 году для связи ПЛК по последовательной шине. Прошло почти полвека — а протокол по-прежнему является самым распространённым в промышленной автоматизации. По различным оценкам, более 30 миллионов устройств в мире используют Modbus.</p><p>Секрет долголетия прост: <strong>протокол исключительно прост в понимании и реализации</strong>. Он работает по схеме "мастер-слейв", имеет открытую спецификацию, не требует лицензирования, поддерживается абсолютно всеми промышленными устройствами.</p><p>Сегодня существуют три основные реализации:</p><ul><li><p><strong>Modbus RTU</strong> — по последовательной шине (RS-232, RS-485)</p></li><li><p><strong>Modbus ASCII</strong> — текстовое представление, устарело</p></li><li><p><strong>Modbus TCP</strong> — поверх TCP/IP Ethernet</p></li></ul><hr><h2>Архитектура: мастер и слейвы</h2><p>Modbus — строго мастер-слейв (Master-Slave) протокол:</p><ul><li><p><strong>Мастер (Master):</strong> Инициирует все запросы. Только один мастер в сети. Обычно это ПЛК, SCADA-сервер, промышленный компьютер.</p></li><li><p><strong>Слейв (Slave):</strong> Отвечает на запросы мастера. В сети RS-485 может быть до 247 слейвов с адресами 1–247. Адрес 0 — широковещательный (слейвы не отвечают).</p></li></ul><p><strong>Важно:</strong> Слейв никогда не инициирует передачу! Он только отвечает.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Мастер                    Слейв 1    Слейв 2   Слейв 3
  |                          |          |          |
  |── Request → Addr=1 ─────&gt;|          |          |
  |&lt;── Response ────────────|          |          |
  |                          |          |          |
  |── Request → Addr=2 ──────────────&gt;|           |
  |&lt;── Response ──────────────────────|           |
</code></pre><hr><h2>Типы данных (регистры)</h2><p>Modbus оперирует четырьмя типами данных:</p><h3>1. Coils (Coil Status) — Дискретные выходы</h3><ul><li><p><strong>Размер:</strong> 1 бит</p></li><li><p><strong>Доступ:</strong> Чтение и запись мастером</p></li><li><p><strong>Функциональные коды:</strong> FC01 (читать), FC05 (записать один), FC15 (записать несколько)</p></li><li><p><strong>Адреса:</strong> 00001–09999 (в Modbus-нотации), 0x0000–0xFFFF (в PDU)</p></li><li><p><strong>Применение:</strong> Состояния реле, клапанов, двигателей</p></li></ul><h3>2. Discrete Inputs (Input Status) — Дискретные входы</h3><ul><li><p><strong>Размер:</strong> 1 бит</p></li><li><p><strong>Доступ:</strong> Только чтение мастером (данные поступают от физических входов)</p></li><li><p><strong>Функциональные коды:</strong> FC02 (читать)</p></li><li><p><strong>Адреса:</strong> 10001–19999</p></li><li><p><strong>Применение:</strong> Состояния кнопок, датчиков, концевиков</p></li></ul><h3>3. Holding Registers — Регистры хранения</h3><ul><li><p><strong>Размер:</strong> 16 бит (2 байта), беззнаковое целое 0–65535</p></li><li><p><strong>Доступ:</strong> Чтение и запись мастером</p></li><li><p><strong>Функциональные коды:</strong> FC03 (читать), FC06 (записать один), FC16 (записать несколько)</p></li><li><p><strong>Адреса:</strong> 40001–49999</p></li><li><p><strong>Применение:</strong> Уставки, параметры настройки, команды управления</p></li></ul><h3>4. Input Registers — Входные регистры</h3><ul><li><p><strong>Размер:</strong> 16 бит</p></li><li><p><strong>Доступ:</strong> Только чтение мастером</p></li><li><p><strong>Функциональные коды:</strong> FC04 (читать)</p></li><li><p><strong>Адреса:</strong> 30001–39999</p></li><li><p><strong>Применение:</strong> Измеренные значения датчиков, счётчики</p></li></ul><h3>Хранение чисел с плавающей точкой</h3><p>16 бит для float недостаточно. Для передачи float используют <strong>два последовательных регистра (32 бит = IEEE 754)</strong>:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Регистр 40001 (HIGH word): первые 16 бит float
Регистр 40002 (LOW word):  вторые 16 бит float

Значение 3.14159:
IEEE 754: 0x40490FDB
HIGH: 0x4049
LOW:  0x0FDB
</code></pre><p><strong>Важная ловушка:</strong> порядок байт и слов (endianness) различается у разных производителей! Четыре варианта: Big-Endian, Little-Endian, Big-Endian Byte Swap, Little-Endian Byte Swap. Смотрите документацию устройства.</p><hr><h2>Modbus RTU: структура фрейма</h2><p>RTU (Remote Terminal Unit) — бинарный формат, максимально компактный.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>┌─────────┬──────────────┬──────┬────────────────────┬───────┐
│ Address │ Function Code│ Data │        Data        │  CRC  │
│  1 байт │    1 байт    │  ... │        ...         │ 2 байт│
└─────────┴──────────────┴──────┴────────────────────┴───────┘
</code></pre><p><strong>Пример запроса FC03 (читать Holding Registers):</strong></p><p>Запрос: "Слейв №1, дай мне 2 регистра начиная с адреса 0x0064 (100)"</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>01 03 00 64 00 02 85 D5

01    — Адрес слейва
03    — Код функции (читать Holding Registers)
00 64 — Начальный адрес (0x0064 = 100)
00 02 — Количество регистров (2)
85 D5 — CRC16 (контрольная сумма)
</code></pre><p><strong>Ответ слейва:</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>01 03 04 01 F4 00 0A 2B 11

01    — Адрес слейва
03    — Код функции
04    — Количество байт данных (2 регистра × 2 байта = 4)
01 F4 — Значение регистра 100 (0x01F4 = 500)
00 0A — Значение регистра 101 (0x000A = 10)
2B 11 — CRC16
</code></pre><p><strong>Ответ при ошибке (Exception Response):</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>01 83 02 C0 F1

01    — Адрес слейва
83    — Код функции + 0x80 (признак ошибки)
02    — Код исключения (02 = Illegal Data Address)
C0 F1 — CRC16
</code></pre><h3>Коды исключений:</h3><div class="tmiRichText__table-wrapper"><table style="min-width: 761px;"><colgroup><col style="min-width:20px;"><col style="width:268px;"><col style="width:473px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Код</p></th><th colspan="1" rowspan="1"><p>Название</p></th><th colspan="1" rowspan="1"><p>Описание</p></th></tr><tr><td colspan="1" rowspan="1"><p>01</p></td><td colspan="1" rowspan="1"><p>Illegal Function</p></td><td colspan="1" rowspan="1"><p>Устройство не поддерживает данный FC</p></td></tr><tr><td colspan="1" rowspan="1"><p>02</p></td><td colspan="1" rowspan="1"><p>Illegal Data Address</p></td><td colspan="1" rowspan="1"><p>Запрошенный адрес не существует</p></td></tr><tr><td colspan="1" rowspan="1"><p>03</p></td><td colspan="1" rowspan="1"><p>Illegal Data Value</p></td><td colspan="1" rowspan="1"><p>Недопустимое значение данных</p></td></tr><tr><td colspan="1" rowspan="1"><p>04</p></td><td colspan="1" rowspan="1"><p>Server Device Failure</p></td><td colspan="1" rowspan="1"><p>Внутренняя ошибка устройства</p></td></tr><tr><td colspan="1" rowspan="1"><p>06</p></td><td colspan="1" rowspan="1"><p>Server Device Busy</p></td><td colspan="1" rowspan="1"><p>Устройство занято, повторите позже</p></td></tr></tbody></table></div><hr><h2>Расчёт CRC16</h2><p>CRC16 (Cyclic Redundancy Check) — контрольная сумма для обнаружения ошибок передачи. Алгоритм несложный, но важный:</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>uint16_t ModbusCRC16(uint8_t *buffer, uint16_t length)
{
    uint16_t crc = 0xFFFF;
    
    for (uint16_t i = 0; i &lt; length; i++) {
        crc ^= (uint16_t)buffer[i];
        
        for (uint8_t j = 0; j &lt; 8; j++) {
            if (crc &amp; 0x0001) {
                crc &gt;&gt;= 1;
                crc ^= 0xA001;  // Полином Modbus
            } else {
                crc &gt;&gt;= 1;
            }
        }
    }
    
    return crc;  // Младший байт первый в фрейме!
}

// Использование:
uint8_t frame[] = {0x01, 0x03, 0x00, 0x64, 0x00, 0x02};
uint16_t crc = ModbusCRC16(frame, 6);
// Добавить в конец фрейма: (crc &amp; 0xFF), (crc &gt;&gt; 8)
</code></pre><p><strong>Важно:</strong> В Modbus RTU CRC передаётся <strong>младшим байтом вперёд (Little-Endian)</strong>!</p><hr><h2>RS-485: физический уровень</h2><p>Modbus RTU работает поверх RS-485 — дифференциальной последовательной шины.</p><h3>Параметры сети:</h3><ul><li><p><strong>Длина:</strong> до 1200 м (при скорости 9600 бод), до 100 м (при 115200 бод)</p></li><li><p><strong>Устройств:</strong> до 32 без репитеров, до 247 с репитерами</p></li><li><p><strong>Скорости:</strong> 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 бод</p></li><li><p><strong>Линия:</strong> витая пара, лучше экранированная (STP)</p></li><li><p><strong>Терминаторы:</strong> 120 Ом на каждом конце шины</p></li></ul><h3>Топология — только шина!</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Мастер ──────┬──────────┬──────────┬──────── Терминатор 120Ом
    120Ом ───┘  Слейв1   Слейв2    Слейв3
</code></pre><p><strong>НЕЛЬЗЯ делать "звезду"</strong> — отражения сигнала разрушат связь.</p><h3>Типичные проблемы RS-485:</h3><p><strong>Проблема 1: Нет терминирующих резисторов</strong> Симптомы: связь работает на малой скорости, не работает на высокой. Или работает с одним устройством, не работает с несколькими. Решение: 120 Ом строго на двух концах шины — и только там.</p><p><strong>Проблема 2: Земля не подключена</strong> RS-485 — дифференциальный сигнал (A-B), не требует общего провода теоретически. Практически — без общей земли при большой разнице потенциалов (грозозащита, разные здания) трансиверы сгорают. Третий провод "GND" обязателен.</p><p><strong>Проблема 3: Смешаны A и B</strong> Сигнальные линии перепутаны местами. Ошибка типичная при ручном монтаже. Симптом: нет ответа вообще или постоянные ошибки CRC.</p><p><strong>Проблема 4: Нет pull-up/pull-down резисторов на линии</strong> Когда все устройства молчат (пауза между транзакциями), линия "висит в воздухе". Нужны подтягивающие резисторы: A через ~560 Ом на +5В, B через ~560 Ом на GND. Многие USB-RS485 адаптеры имеют их встроенными.</p><hr><h2>Modbus TCP: Ethernet-версия</h2><p>Modbus TCP — это Modbus RTU без адреса устройства и без CRC, завёрнутый в TCP/IP пакет.</p><h3>Структура Modbus TCP фрейма:</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>┌─────────────┬──────────────────────────────────────────────┐
│  MBAP Header (7 байт)  │    PDU (Protocol Data Unit)       │
├──────┬───────┬──────────┬───────┬──────────────────────────┤
│TrID  │ Proto │  Length  │ UnitID│ FC  │      Data          │
│2 байт│ 2 байт│  2 байта │ 1 байт│1 байт│    N байт         │
└──────┴───────┴──────────┴───────┴──────┴──────────────────┘

TrID   — Transaction Identifier (любое число, повторяется в ответе)
Proto  — 0x0000 (всегда)
Length — длина оставшейся части (Unit ID + FC + Data)
UnitID — адрес устройства (для RTU-TCP шлюзов)
</code></pre><p><strong>Порт:</strong> 502 (стандартный, зарезервирован IANA)</p><p>TCP обеспечивает надёжную доставку — CRC не нужен. Но помните: <strong>TCP не обеспечивает реальное время</strong>. Задержки могут варьироваться от единиц миллисекунд до нескольких секунд при перегрузке сети.</p><hr><h2>Практика: Modbus на Python</h2><h3>Библиотека pymodbus (полноценная реализация)</h3><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code># Установка: pip install pymodbus

from pymodbus.client import ModbusTcpClient, ModbusSerialClient
import struct
import time

# ========== MODBUS TCP ==========

def read_vfd_status_tcp(host: str, port: int = 502, unit_id: int = 1) -&gt; dict:
    """
    Читаем параметры частотника по Modbus TCP.
    Пример для ABB ACS550.
    """
    client = ModbusTcpClient(host=host, port=port, timeout=3)
    
    if not client.connect():
        raise ConnectionError(f"Не удалось подключиться к {host}:{port}")
    
    try:
        # Читаем Input Registers 30001-30006 (адрес 0-5)
        result = client.read_input_registers(
            address=0,      # Начальный адрес (0 = регистр 30001)
            count=6,        # Количество регистров
            slave=unit_id
        )
        
        if result.isError():
            raise Exception(f"Ошибка Modbus: {result}")
        
        regs = result.registers
        
        return {
            'status_word':   regs[0],           # Слово состояния
            'speed_rpm':     regs[1],           # Скорость об/мин
            'frequency_hz':  regs[2] / 100.0,  # Частота (0.01 Гц)
            'current_a':     regs[3] / 10.0,   # Ток (0.1 А)
            'voltage_v':     regs[4],           # Напряжение
            'power_kw':      regs[5] / 10.0,   # Мощность (0.1 кВт)
            'running':       bool(regs[0] &amp; 0x0001),
            'fault':         bool(regs[0] &amp; 0x0008),
        }
        
    finally:
        client.close()


def write_vfd_command_tcp(host: str, freq_hz: float, run: bool, unit_id: int = 1):
    """Управление частотником через Modbus TCP"""
    client = ModbusTcpClient(host=host, port=502, timeout=3)
    
    if not client.connect():
        raise ConnectionError(f"Не удалось подключиться")
    
    try:
        # Задаём частоту (Holding Register 40002, адрес 1)
        freq_raw = int(freq_hz * 100)  # 50.00 Гц → 5000
        client.write_register(address=1, value=freq_raw, slave=unit_id)
        
        # Команда пуск/стоп (Holding Register 40001, адрес 0)
        control_word = 0x0002 if run else 0x0001  # 2=Run, 1=Stop
        client.write_register(address=0, value=control_word, slave=unit_id)
        
        print(f"VFD: {'Пуск' if run else 'Стоп'}, частота {freq_hz} Гц")
        
    finally:
        client.close()


# ========== MODBUS RTU ==========

def create_rtu_client(port: str, baudrate: int = 9600) -&gt; ModbusSerialClient:
    """Создаём RTU клиент для RS-485"""
    return ModbusSerialClient(
        port=port,            # '/dev/ttyUSB0' или 'COM3'
        baudrate=baudrate,
        bytesize=8,
        parity='N',           # N=None, E=Even, O=Odd
        stopbits=1,
        timeout=1.0
    )


def scan_modbus_rtu_network(port: str, baudrate: int = 9600) -&gt; list:
    """
    Сканирование Modbus RTU сети — ищем все активные устройства.
    Возвращает список адресов ответивших устройств.
    """
    client = create_rtu_client(port, baudrate)
    client.connect()
    
    found_devices = []
    
    print(f"Сканирование Modbus RTU на {port}, {baudrate} бод...")
    
    for address in range(1, 248):
        try:
            # Пробуем прочитать 1 регистр — если устройство есть, оно ответит
            result = client.read_holding_registers(0, 1, slave=address)
            
            if not result.isError():
                found_devices.append(address)
                print(f"  </code></pre><p><code><span class="tmiEmoji" title="">✅</span> Найдено устройство: адрес {address}")         </code></p><p><code>except Exception:             pass  # Таймаут — устройства нет                  </code></p><p><code>time.sleep(0.05)  # Пауза между запросами          </code></p><p><code>client.close()     print(f"Найдено устройств: {len(found_devices)}")     </code></p><p><code>return found_devices   # ========== РАБОТА С FLOAT ==========  def registers_to_float(high_reg: int, low_reg: int,                          byte_order: str = 'big') -&gt; float:     """     Конвертация двух Modbus-регистров в float IEEE 754.     byte_order: 'big' (ABCD), 'little' (DCBA),                  'big_swap' (BADC), 'little_swap' (CDAB)     """     if byte_order == 'big':         raw = struct.pack('&gt;HH', high_reg, low_reg)     elif byte_order == 'little':         raw = struct.pack('&lt;HH', low_reg, high_reg)     elif byte_order == 'big_swap':         raw = struct.pack('&gt;HH',              ((high_reg &amp; 0xFF) &lt;&lt; 8) | (high_reg &gt;&gt; 8),             ((low_reg &amp; 0xFF) &lt;&lt; 8)  | (low_reg &gt;&gt; 8))     else:         raw = struct.pack('&gt;HH', high_reg, low_reg)          return struct.unpack('&gt;f', raw)[0]   def float_to_registers(value: float, byte_order: str = 'big') -&gt; tuple:     """Конвертация float в два Modbus-регистра"""     raw = struct.pack('&gt;f', value)     high_reg, low_reg = struct.unpack('&gt;HH', raw)          if byte_order == 'big':         return high_reg, low_reg     elif byte_order == 'little':         return low_reg, high_reg          return high_reg, low_reg   # ========== ПРИМЕР OPROS SCADA ==========  class ModbusDataCollector:     """     Циклический опрос Modbus-устройств для SCADA/мониторинга.     """          def __init__(self, host: str):         self.client = ModbusTcpClient(host=host, port=502, timeout=5)         self.data = {}              def poll_all_devices(self) -&gt; dict:         """Опросить все устройства и вернуть данные"""         if not self.client.connect():             return {'error': 'connection_failed'}                  try:             results = {}                          # Насос 1 (Unit ID = 1)             pump1 = self.client.read_input_registers(0, 4, slave=1)             if not pump1.isError():                 r = pump1.registers                 results['pump1'] = {                     'running':   bool(r[0] &amp; 1),                     'fault':     bool(r[0] &amp; 8),                     'freq_hz':   r[1] / 100.0,                     'current_a': r[2] / 10.0,                     'power_kw':  r[3] / 10.0,                 }                          # Датчик давления (Unit ID = 5, счётчик давления)             pressure = self.client.read_input_registers(0, 2, slave=5)             if not pressure.isError():                 r = pressure.registers                 results['pressure_bar'] = registers_to_float(r[0], r[1])                          # Расходомер (Unit ID = 6)             flow = self.client.read_input_registers(0, 4, slave=6)             if not flow.isError():                 r = flow.registers                 results['flow'] = {                     'instant_m3h': registers_to_float(r[0], r[1]),                     'total_m3':    registers_to_float(r[2], r[3]),                 }                          return results                      finally:             self.client.close()   # ========== ЗАПУСК ========== if __name__ == "__main__":     # Пример использования     collector = ModbusDataCollector('192.168.1.100')          while True:         data = collector.poll_all_devices()         print(f"Давление: {data.get('pressure_bar', 0):.2f} бар")         print(f"Насос 1: {'Работает' if data.get('pump1', {}).get('running') else 'Стоит'}, "               f"{data.get('pump1', {}).get('freq_hz', 0):.1f} Гц")         time.sleep(1) </code></p><hr><h2>Реализация Modbus Slave на микроконтроллере (C)</h2><p>Иногда нужно сделать собственное устройство с Modbus-интерфейсом. Вот минимальная реализация для STM32/Arduino:</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>#include &lt;stdint.h&gt;
#include &lt;string.h&gt;

#define MODBUS_ADDRESS    1      // Адрес нашего устройства
#define HOLDING_REG_COUNT 20     // Количество Holding Registers
#define INPUT_REG_COUNT   10     // Количество Input Registers

// Данные регистров
static uint16_t holding_regs[HOLDING_REG_COUNT] = {0};
static uint16_t input_regs[INPUT_REG_COUNT]     = {0};

// Расчёт CRC16
static uint16_t crc16(uint8_t *buf, uint16_t len)
{
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i &lt; len; i++) {
        crc ^= buf[i];
        for (uint8_t j = 0; j &lt; 8; j++) {
            if (crc &amp; 1) { crc = (crc &gt;&gt; 1) ^ 0xA001; }
            else           { crc &gt;&gt;= 1; }
        }
    }
    return crc;
}

// Отправка ответа (реализуйте под вашу платформу)
extern void uart_send(uint8_t *data, uint16_t len);

// Обработка Modbus запроса
void modbus_process_request(uint8_t *request, uint16_t req_len)
{
    uint8_t response[256];
    uint16_t resp_len = 0;
    
    // Проверяем адрес
    if (request[0] != MODBUS_ADDRESS) return;
    
    // Проверяем CRC
    uint16_t received_crc = (request[req_len-1] &lt;&lt; 8) | request[req_len-2];
    uint16_t calculated_crc = crc16(request, req_len - 2);
    if (received_crc != calculated_crc) return;  // CRC ошибка — игнорируем
    
    uint8_t fc    = request[1];
    uint16_t addr = (request[2] &lt;&lt; 8) | request[3];
    uint16_t count = (request[4] &lt;&lt; 8) | request[5];
    
    response[resp_len++] = MODBUS_ADDRESS;
    response[resp_len++] = fc;
    
    switch (fc)
    {
        case 0x03:  // Читать Holding Registers
        {
            if (addr + count &gt; HOLDING_REG_COUNT) {
                // Исключение: неверный адрес
                response[1] = fc | 0x80;
                response[resp_len++] = 0x02;
                break;
            }
            
            response[resp_len++] = count * 2;  // Количество байт
            
            for (uint16_t i = 0; i &lt; count; i++) {
                response[resp_len++] = holding_regs[addr + i] &gt;&gt; 8;
                response[resp_len++] = holding_regs[addr + i] &amp; 0xFF;
            }
            break;
        }
        
        case 0x04:  // Читать Input Registers
        {
            if (addr + count &gt; INPUT_REG_COUNT) {
                response[1] = fc | 0x80;
                response[resp_len++] = 0x02;
                break;
            }
            
            response[resp_len++] = count * 2;
            
            for (uint16_t i = 0; i &lt; count; i++) {
                response[resp_len++] = input_regs[addr + i] &gt;&gt; 8;
                response[resp_len++] = input_regs[addr + i] &amp; 0xFF;
            }
            break;
        }
        
        case 0x06:  // Записать один Holding Register
        {
            if (addr &gt;= HOLDING_REG_COUNT) {
                response[1] = fc | 0x80;
                response[resp_len++] = 0x02;
                break;
            }
            
            holding_regs[addr] = (request[4] &lt;&lt; 8) | request[5];
            
            // Эхо запроса как ответ
            memcpy(response + 2, request + 2, 4);
            resp_len += 4;
            break;
        }
        
        case 0x10:  // Записать несколько Holding Registers
        {
            if (addr + count &gt; HOLDING_REG_COUNT) {
                response[1] = fc | 0x80;
                response[resp_len++] = 0x02;
                break;
            }
            
            uint8_t byte_count = request[6];
            for (uint16_t i = 0; i &lt; count; i++) {
                holding_regs[addr + i] = (request[7 + i*2] &lt;&lt; 8) | request[8 + i*2];
            }
            
            // Ответ: адрес и количество записанных регистров
            response[resp_len++] = request[2];
            response[resp_len++] = request[3];
            response[resp_len++] = request[4];
            response[resp_len++] = request[5];
            break;
        }
        
        default:
        {
            // Неизвестная функция
            response[1] = fc | 0x80;
            response[resp_len++] = 0x01;  // Illegal Function
            break;
        }
    }
    
    // Добавляем CRC
    uint16_t resp_crc = crc16(response, resp_len);
    response[resp_len++] = resp_crc &amp; 0xFF;
    response[resp_len++] = resp_crc &gt;&gt; 8;
    
    // Отправляем ответ
    uart_send(response, resp_len);
}

// Обновление Input Registers из реальных данных
void modbus_update_inputs(uint16_t temp_x10, uint16_t pressure_x100,
                          uint16_t status_bits)
{
    input_regs[0] = temp_x10;       // Температура × 10 (250 = 25.0°C)
    input_regs[1] = pressure_x100;  // Давление × 100 (1013 = 10.13 бар)
    input_regs[2] = status_bits;    // Биты состояния
}
</code></pre><hr><h2>Диагностика сети Modbus: практические инструменты</h2><h3>Wireshark для Modbus TCP</h3><p>Wireshark понимает Modbus TCP "из коробки". Фильтр для захвата:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>modbus.func_code  — фильтр по функциональному коду
tcp.port == 502   — весь Modbus TCP трафик
modbus.exception_code  — только ошибки
</code></pre><h3>ModRSsim2 / Diagslave — эмуляторы слейва</h3><p>Незаменимы при разработке — тестируете мастер без реального оборудования.</p><h3>Свой анализатор на Python:</h3><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>import socket
import struct

def modbus_tcp_sniffer(host: str, port: int = 502):
    """Простой анализатор Modbus TCP запросов"""
    
    FC_NAMES = {
        1: 'Read Coils',
        2: 'Read Discrete Inputs',
        3: 'Read Holding Registers',
        4: 'Read Input Registers',
        5: 'Write Single Coil',
        6: 'Write Single Register',
        15: 'Write Multiple Coils',
        16: 'Write Multiple Registers',
    }
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))
    
    while True:
        # Читаем MBAP-заголовок (7 байт)
        header = sock.recv(7)
        if len(header) &lt; 7:
            break
        
        transaction_id = struct.unpack('&gt;H', header[0:2])[0]
        protocol_id    = struct.unpack('&gt;H', header[2:4])[0]
        length         = struct.unpack('&gt;H', header[4:6])[0]
        unit_id        = header[6]
        
        # Читаем PDU
        pdu = sock.recv(length - 1)
        fc  = pdu[0]
        
        fc_name = FC_NAMES.get(fc, f'Unknown FC {fc}')
        
        if fc &lt; 0x80:  # Запрос или нормальный ответ
            if len(pdu) &gt;= 5:
                addr  = struct.unpack('&gt;H', pdu[1:3])[0]
                count = struct.unpack('&gt;H', pdu[3:5])[0]
                print(f"[{transaction_id}] Unit={unit_id} | {fc_name} | "
                      f"Addr={addr} Count={count}")
        else:  # Ошибка
            exc_code = pdu[1]
            print(f"[{transaction_id}] EXCEPTION: FC={fc &amp; 0x7F} Code={exc_code}")
</code></pre><hr><h2>Типичные проблемы и решения</h2><h3>"Устройство не отвечает"</h3><p>Алгоритм диагностики:</p><ol><li><p>Проверьте физику: контакты, полярность A/B, терминаторы</p></li><li><p>Проверьте параметры порта: baudrate, parity, stopbits — должны совпадать с устройством</p></li><li><p>Проверьте адрес — он правильно задан в устройстве? Не все устройства имеют адрес "1" по умолчанию</p></li><li><p>Используйте осциллограф или логический анализатор — видны ли данные на линии?</p></li><li><p>Попробуйте другой кабель</p></li></ol><h3>"Иногда не отвечает, CRC-ошибки"</h3><ul><li><p>Слишком длинная линия без репитера</p></li><li><p>Отсутствуют или не там стоят терминаторы</p></li><li><p>Несколько устройств с одинаковым адресом</p></li><li><p>Помехи от силового оборудования — проложите кабель отдельно</p></li></ul><h3>"Данные неверные"</h3><ul><li><p>Неправильный порядок байт (endianness) для float</p></li><li><p>Смещение адреса: в документации адреса часто указываются с 1 (40001), а в запросе нужно с 0 (0x0000)</p></li><li><p>Неправильный масштабный коэффициент (×10, ×100, ×0.01...)</p></li></ul><hr><h2>Заключение</h2><p>Modbus — это фундамент промышленной коммуникации. Несмотря на почтенный возраст, он остаётся стандартом де-факто и будет таковым ещё долгие годы. Понимание структуры фрейма, типов данных и физического уровня RS-485 — это базовый навык любого инженера автоматики.</p><p>Для новых проектов, где нет ограничений совместимости, стоит рассматривать OPC UA или MQTT как более современные альтернативы. Но если перед вами стоит задача интегрировать любое промышленное оборудование — с вероятностью 90% оно имеет Modbus, и знание этого протокола решит задачу быстро и надёжно.</p>]]></description><guid isPermaLink="false">95</guid><pubDate>Sat, 21 Mar 2026 16:44:53 +0000</pubDate></item><item><title>&#x41F;&#x440;&#x43E;&#x43C;&#x44B;&#x448;&#x43B;&#x435;&#x43D;&#x43D;&#x44B;&#x439; IoT: MQTT, &#x442;&#x435;&#x43B;&#x435;&#x43C;&#x435;&#x442;&#x440;&#x438;&#x44F; &#x438; &#x43C;&#x43E;&#x43D;&#x438;&#x442;&#x43E;&#x440;&#x438;&#x43D;&#x433; &#x43E;&#x431;&#x43E;&#x440;&#x443;&#x434;&#x43E;&#x432;&#x430;&#x43D;&#x438;&#x44F;</title><link>https://ithub.uno/statiarticles/11_industrial-machines/%D0%BF%D1%80%D0%BE%D0%BC%D1%8B%D1%88%D0%BB%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9-iot-mqtt-%D1%82%D0%B5%D0%BB%D0%B5%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%8F-%D0%B8-%D0%BC%D0%BE%D0%BD%D0%B8%D1%82%D0%BE%D1%80%D0%B8%D0%BD%D0%B3-%D0%BE%D0%B1%D0%BE%D1%80%D1%83%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-r107/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/Element-IIoT-2.jpg.96a0c9a7cf5ae7b2d0c90f56b5141a9b.jpg" /></p>
<h2>IIoT: не просто модное слово</h2><p>Промышленный IoT (IIoT — Industrial Internet of Things) — это не умный чайник и не фитнес-браслет. Это системы, которые собирают данные с реального производственного оборудования, анализируют их и помогают принимать решения.</p><p>Реальный кейс: завод по производству подшипников. Раньше плановое ТО каждые 3 месяца — меняли подшипники в редукторах "по графику". После внедрения IIoT (вибродатчики на каждом редукторе + MQTT + аналитика): 30% редукторов работали нормально и менялись зря, 5% уже имели износ и могли выйти из строя раньше графика. Экономия на расходниках — 28%, аварийных остановок из-за поломки — минус 4 в год.</p><p>Это и есть предиктивное обслуживание. И начинается оно с правильного сбора данных.</p><hr><h2>Архитектура IIoT-системы</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Уровень 0 — Полевые устройства:
    Датчики (температура, вибрация, давление, ток)
    Исполнительные механизмы

Уровень 1 — Агрегаторы / Граничные узлы (Edge):
    ESP32, Raspberry Pi, промышленные шлюзы
    Протоколы: Modbus, 1-Wire, I2C, SPI, 4-20мА

Уровень 2 — Брокер сообщений:
    MQTT Broker (Mosquitto, EMQX, HiveMQ)
    Нормализация данных, маршрутизация

Уровень 3 — Обработка и хранение:
    Node-RED / Python — логика, алармы
    InfluxDB / TimescaleDB — временные ряды
    PostgreSQL — конфигурация, справочники

Уровень 4 — Визуализация и аналитика:
    Grafana — дашборды, алерты
    Jupyter Notebook — анализ данных
    ML модели — предиктивная аналитика
</code></pre><hr><h2>MQTT: почему именно он</h2><p>MQTT (Message Queuing Telemetry Transport) — лёгкий протокол публикации/подписки, разработанный IBM в 1999 году для телеметрии нефтепроводов через спутник. Идеален для IoT:</p><ul><li><p><strong>Лёгкий:</strong> заголовок всего 2 байта, работает при 2G-соединении</p></li><li><p><strong>Асинхронный:</strong> устройства не опрашиваются, а сами публикуют данные</p></li><li><p><strong>QoS (Quality of Service):</strong> три уровня надёжности доставки</p></li><li><p><strong>Retain:</strong> брокер хранит последнее значение, новые подписчики сразу его получают</p></li><li><p><strong>Last Will:</strong> автоматическое сообщение при потере связи с устройством</p></li></ul><h3>Уровни QoS:</h3><ul><li><p><strong>QoS 0 (At most once):</strong> Отправил и забыл. Быстро, но сообщение может потеряться. Для частых нечувствительных данных (телеметрия каждую секунду).</p></li><li><p><strong>QoS 1 (At least once):</strong> Гарантированная доставка, но возможны дубликаты. Для алармов и важных событий.</p></li><li><p><strong>QoS 2 (Exactly once):</strong> Ровно один раз. Медленнее, для критичных команд управления.</p></li></ul><h3>Структура топиков (best practices):</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>factory/                        ← Завод
  ├── line1/                    ← Производственная линия 1
  │   ├── conveyor1/            ← Конвейер 1
  │   │   ├── telemetry         ← Данные датчиков (JSON, часто)
  │   │   ├── status            ← Состояние (работает/стоит)
  │   │   ├── alarms            ← Аварии
  │   │   └── commands          ← Команды управления
  │   └── robot1/
  │       └── telemetry
  └── utilities/
      ├── compressor1/
      │   └── telemetry
      └── hvac/
          └── telemetry

Примеры топиков:
factory/line1/conveyor1/telemetry
factory/line1/robot1/status
factory/+/+/alarms              ← Подписка на все аварии всей линии 1
factory/#                       ← Подписка на ВСЁ (осторожно!)
</code></pre><hr><h2>Установка и настройка Mosquitto</h2><pre spellcheck="" class="tmiCode language-bash" data-language="Bash"><code># Ubuntu/Debian
sudo apt update
sudo apt install mosquitto mosquitto-clients

# Конфигурация /etc/mosquitto/mosquitto.conf:
listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd

# TLS (обязательно для производства!):
listener 8883
cafile /etc/ssl/certs/ca-certificates.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
require_certificate false

# WebSocket для Node-RED и браузерных клиентов:
listener 9001
protocol websockets

# Логирование:
log_dest file /var/log/mosquitto/mosquitto.log
log_type all

# Создание пользователя:
sudo mosquitto_passwd -c /etc/mosquitto/passwd username

sudo systemctl enable mosquitto
sudo systemctl start mosquitto

# Тест:
mosquitto_sub -h localhost -u user -P pass -t "factory/#" -v &amp;
mosquitto_pub -h localhost -u user -P pass -t "factory/test" -m "hello"
</code></pre><hr><h2>ESP32: узел сбора данных</h2><p>ESP32 — идеальный Edge-узел: WiFi/BT, 240 МГц, 520 КБ RAM, куча периферии, цена $3–5.</p><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;WiFi.h&gt;
#include &lt;PubSubClient.h&gt;
#include &lt;ArduinoJson.h&gt;
#include &lt;Wire.h&gt;
#include "Adafruit_BME280.h"

// ===== КОНФИГУРАЦИЯ =====
const char* WIFI_SSID     = "Factory_WiFi";
const char* WIFI_PASSWORD = "secretpass";
const char* MQTT_SERVER   = "192.168.1.100";
const int   MQTT_PORT     = 1883;
const char* MQTT_USER     = "esp32_node1";
const char* MQTT_PASS     = "nodepass";
const char* DEVICE_ID     = "conveyor1";

// Топики
const char* TOPIC_TELEMETRY = "factory/line1/conveyor1/telemetry";
const char* TOPIC_STATUS    = "factory/line1/conveyor1/status";
const char* TOPIC_ALARMS    = "factory/line1/conveyor1/alarms";
const char* TOPIC_COMMANDS  = "factory/line1/conveyor1/commands";
const char* TOPIC_WILL      = "factory/line1/conveyor1/status";

WiFiClient   espClient;
PubSubClient mqtt(espClient);
Adafruit_BME280 bme;

// Состояние
bool motorRunning   = false;
float setpoint      = 50.0f;
uint32_t lastPublish = 0;
uint32_t uptime_sec  = 0;

// ===== ПОДКЛЮЧЕНИЕ =====
void connectWiFi() {
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.print("WiFi...");
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println(" OK");
    Serial.println(WiFi.localIP());
}

void connectMQTT() {
    while (!mqtt.connected()) {
        Serial.print("MQTT...");
        
        // Last Will &amp; Testament — сообщение при потере связи
        const char* willMsg = "{\"online\":false}";
        
        if (mqtt.connect(DEVICE_ID, MQTT_USER, MQTT_PASS,
                         TOPIC_WILL, 1, true, willMsg)) {
            Serial.println(" OK");
            
            // Сообщение о подключении
            mqtt.publish(TOPIC_STATUS, "{\"online\":true}", true);
            
            // Подписываемся на команды
            mqtt.subscribe(TOPIC_COMMANDS, 1);  // QoS 1
            
        } else {
            Serial.printf(" Ошибка: %d\n", mqtt.state());
            delay(5000);
        }
    }
}

// ===== ОБРАБОТКА КОМАНД =====
void mqttCallback(char* topic, byte* payload, unsigned int length) {
    // Парсим JSON команду
    StaticJsonDocument&lt;256&gt; doc;
    DeserializationError error = deserializeJson(doc, payload, length);
    
    if (error) {
        Serial.println("JSON error");
        return;
    }
    
    String topicStr = String(topic);
    
    if (topicStr == TOPIC_COMMANDS) {
        // Команда пуск/стоп
        if (doc.containsKey("run")) {
            motorRunning = doc["run"].as&lt;bool&gt;();
            Serial.printf("Команда: %s\n", motorRunning ? "ПУСК" : "СТОП");
        }
        
        // Изменение уставки
        if (doc.containsKey("setpoint")) {
            setpoint = doc["setpoint"].as&lt;float&gt;();
            Serial.printf("Уставка: %.1f\n", setpoint);
        }
        
        // Сброс аварии
        if (doc["reset_alarm"].as&lt;bool&gt;()) {
            Serial.println("Сброс аварии");
        }
    }
}

// ===== ПУБЛИКАЦИЯ ДАННЫХ =====
void publishTelemetry() {
    // Читаем датчики
    float temperature = bme.readTemperature();
    float humidity    = bme.readHumidity();
    float pressure    = bme.readPressure() / 100.0f;
    int   current_raw = analogRead(34);  // 4-20мА через ACS712
    float current_a   = current_raw * 25.0f / 4095.0f;  // 0-25А
    
    // Формируем JSON
    StaticJsonDocument&lt;512&gt; doc;
    doc["device_id"]   = DEVICE_ID;
    doc["timestamp"]   = millis() / 1000;
    doc["uptime"]      = uptime_sec;
    doc["running"]     = motorRunning;
    doc["setpoint"]    = setpoint;
    
    JsonObject sensors = doc.createNestedObject("sensors");
    sensors["temperature"] = round(temperature * 10) / 10.0;
    sensors["humidity"]    = round(humidity * 10) / 10.0;
    sensors["pressure"]    = round(pressure * 10) / 10.0;
    sensors["current"]     = round(current_a * 100) / 100.0;
    
    // Диагностика устройства
    JsonObject diag = doc.createNestedObject("diagnostics");
    diag["wifi_rssi"]   = WiFi.RSSI();
    diag["free_heap"]   = ESP.getFreeHeap();
    diag["cpu_freq"]    = ESP.getCpuFreqMHz();
    
    // Сериализация и публикация
    char payload[512];
    serializeJson(doc, payload);
    
    mqtt.publish(TOPIC_TELEMETRY, payload, false);  // QoS 0, не retain
    
    // Проверка алармов
    if (temperature &gt; 80.0f) {
        StaticJsonDocument&lt;128&gt; alarm;
        alarm["type"]    = "high_temperature";
        alarm["value"]   = temperature;
        alarm["limit"]   = 80.0f;
        alarm["message"] = "Превышена температура двигателя!";
        
        char alarmPayload[128];
        serializeJson(alarm, alarmPayload);
        mqtt.publish(TOPIC_ALARMS, alarmPayload, true);  // retain = true
    }
}

// ===== SETUP / LOOP =====
void setup() {
    Serial.begin(115200);
    
    Wire.begin(21, 22);
    if (!bme.begin(0x76)) {
        Serial.println("BME280 не найден!");
    }
    
    connectWiFi();
    mqtt.setServer(MQTT_SERVER, MQTT_PORT);
    mqtt.setCallback(mqttCallback);
    mqtt.setBufferSize(1024);  // Увеличиваем буфер для больших сообщений
    
    connectMQTT();
}

void loop() {
    // Переподключение при потере связи
    if (!mqtt.connected()) {
        if (WiFi.status() != WL_CONNECTED) {
            connectWiFi();
        }
        connectMQTT();
    }
    
    mqtt.loop();
    
    // Публикация каждые 5 секунд
    if (millis() - lastPublish &gt;= 5000) {
        lastPublish = millis();
        uptime_sec += 5;
        publishTelemetry();
    }
}
</code></pre><hr><h2>Python: обработка и алармы</h2><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>import paho.mqtt.client as mqtt
import json
import time
from datetime import datetime
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS

# ===== КОНФИГУРАЦИЯ =====
MQTT_BROKER   = "192.168.1.100"
MQTT_PORT     = 1883
MQTT_USER     = "backend"
MQTT_PASS     = "backendpass"

INFLUX_URL    = "http://localhost:8086"
INFLUX_TOKEN  = "your-influx-token"
INFLUX_ORG    = "factory"
INFLUX_BUCKET = "telemetry"

# ===== ИНИЦИАЛИЗАЦИЯ =====
influx_client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = influx_client.write_api(write_options=SYNCHRONOUS)

# Состояние алармов (дедупликация)
active_alarms = {}

def on_message(client, userdata, msg):
    topic = msg.topic
    
    try:
        data = json.loads(msg.payload.decode())
    except json.JSONDecodeError:
        print(f"Ошибка JSON в топике {topic}")
        return
    
    # Маршрутизация по топику
    if "/telemetry" in topic:
        handle_telemetry(topic, data)
    elif "/alarms" in topic:
        handle_alarm(topic, data)
    elif "/status" in topic:
        handle_status(topic, data)


def handle_telemetry(topic: str, data: dict):
    """Запись телеметрии в InfluxDB"""
    
    # Извлекаем путь устройства из топика
    # factory/line1/conveyor1/telemetry → ['factory', 'line1', 'conveyor1', 'telemetry']
    parts = topic.split('/')
    if len(parts) &lt; 4:
        return
    
    location = parts[1]   # line1
    device   = parts[2]   # conveyor1
    
    sensors = data.get('sensors', {})
    
    # Формируем точку данных для InfluxDB
    point = (
        Point("telemetry")
        .tag("location", location)
        .tag("device", device)
        .tag("device_id", data.get('device_id', device))
        .field("temperature", float(sensors.get('temperature', 0)))
        .field("humidity",    float(sensors.get('humidity', 0)))
        .field("pressure",    float(sensors.get('pressure', 0)))
        .field("current",     float(sensors.get('current', 0)))
        .field("running",     int(data.get('running', False)))
        .field("wifi_rssi",   int(data.get('diagnostics', {}).get('wifi_rssi', 0)))
    )
    
    try:
        write_api.write(bucket=INFLUX_BUCKET, record=point)
    except Exception as e:
        print(f"InfluxDB ошибка: {e}")
    
    # Проверка пороговых значений
    temp = sensors.get('temperature', 0)
    if temp &gt; 85.0:
        send_alert(device, "critical", f"Критическая температура: {temp}°C")
    elif temp &gt; 75.0:
        send_alert(device, "warning", f"Высокая температура: {temp}°C")


def handle_alarm(topic: str, data: dict):
    """Обработка аларм-сообщений от устройства"""
    parts = topic.split('/')
    device = parts[2] if len(parts) &gt;= 3 else "unknown"
    
    alarm_type = data.get('type', 'unknown')
    alarm_key  = f"{device}_{alarm_type}"
    
    # Дедупликация: не спамим одинаковые алармы
    now = time.time()
    if alarm_key in active_alarms:
        if now - active_alarms[alarm_key] &lt; 300:  # 5 минут
            return
    
    active_alarms[alarm_key] = now
    
    print(f"</code></pre><p><code><span class="tmiEmoji" title="">🚨</span></code></p><p><code> АВАРИЯ [{device}]: {data.get('message', alarm_type)}")          # Здесь можно добавить отправку в Telegram, email, SMS     send_notification(         f"<span class="tmiEmoji" title="">⚠️</span> Авария на {device}\n"         f"Тип: {alarm_type}\n"         f"Значение: {data.get('value', 'N/A')}\n"         f"Время: {datetime.now().strftime('%H:%M:%S')}"     )   def handle_status(topic: str, data: dict):     """Отслеживание онлайн/офлайн устройств"""     parts = topic.split('/')     device = parts[2] if len(parts) &gt;= 3 else "unknown"     online = data.get('online', False)          print(f"{'<span class="tmiEmoji" title="">🟢</span>' if online else '<span class="tmiEmoji" title="">🔴</span>'} {device}: {'онлайн' if online else 'офлайн'}")          if not online:         send_alert(device, "critical", f"Устройство {device} потеряло связь!")   def send_alert(device: str, level: str, message: str):     """Отправка алерта (пример — в лог)"""     timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")     print(f"[{timestamp}] {level.upper()} [{device}]: {message}")   def send_notification(text: str):     """Здесь интеграция с Telegram Bot API"""     # import requests     # requests.post(f"https://api.telegram.org/bot{TOKEN}/sendMessage",     #               json={"chat_id": CHAT_ID, "text": text})     print(f"NOTIFICATION: {text}")   # ===== ЗАПУСК ===== client = mqtt.Client(client_id="backend_processor") client.username_pw_set(MQTT_USER, MQTT_PASS) client.on_message = on_message  client.connect(MQTT_BROKER, MQTT_PORT, 60) client.subscribe("factory/#", qos=1)  # Подписка на всё  print("Backend запущен, ожидаем данные...") client.loop_forever() </code></p><hr><h2>InfluxDB + Grafana: красивые дашборды</h2><h3>Установка через Docker Compose:</h3><pre spellcheck="" class="tmiCode language-yaml" data-language="YAML"><code># docker-compose.yml
version: '3.8'

services:
  mosquitto:
    image: eclipse-mosquitto:2
    ports:
      - "1883:1883"
      - "9001:9001"
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - mosquitto_data:/mosquitto/data

  influxdb:
    image: influxdb:2.7
    ports:
      - "8086:8086"
    environment:
      DOCKER_INFLUXDB_INIT_MODE: setup
      DOCKER_INFLUXDB_INIT_USERNAME: admin
      DOCKER_INFLUXDB_INIT_PASSWORD: secretpassword
      DOCKER_INFLUXDB_INIT_ORG: factory
      DOCKER_INFLUXDB_INIT_BUCKET: telemetry
      DOCKER_INFLUXDB_INIT_RETENTION: 30d  # Хранение 30 дней
    volumes:
      - influxdb_data:/var/lib/influxdb2

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: grafanapass
      GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: "yesoreyeram-infinity-datasource"
    volumes:
      - grafana_data:/var/lib/grafana
    depends_on:
      - influxdb

  node-red:
    image: nodered/node-red:latest
    ports:
      - "1880:1880"
    volumes:
      - nodered_data:/data

volumes:
  mosquitto_data:
  influxdb_data:
  grafana_data:
  nodered_data:
</code></pre><h3>Flux-запрос в Grafana (температура за последний час):</h3><pre spellcheck="" class="tmiCode" data-language="flux"><code>from(bucket: "telemetry")
  |&gt; range(start: -1h)
  |&gt; filter(fn: (r) =&gt; r._measurement == "telemetry")
  |&gt; filter(fn: (r) =&gt; r._field == "temperature")
  |&gt; filter(fn: (r) =&gt; r.device == "conveyor1")
  |&gt; aggregateWindow(every: 1m, fn: mean, createEmpty: false)
  |&gt; yield(name: "mean_temperature")
</code></pre><hr><h2>Предиктивная аналитика: пример с вибрацией</h2><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>import numpy as np
from scipy.fft import fft, fftfreq
from scipy.stats import zscore

def analyze_vibration(samples: list, sample_rate: int = 1000) -&gt; dict:
    """
    Анализ вибрации для обнаружения износа подшипников.
    samples: список отсчётов акселерометра
    sample_rate: частота дискретизации, Гц
    """
    data = np.array(samples, dtype=float)
    n = len(data)
    
    # Временные характеристики
    rms   = np.sqrt(np.mean(data**2))      # Эффективное значение (RMS)
    peak  = np.max(np.abs(data))           # Пиковое значение
    crest = peak / rms if rms &gt; 0 else 0  # Пик-фактор (norma: 1.4–2.5, износ: &gt;4)
    kurtosis = float(np.mean((data - np.mean(data))**4) / (np.std(data)**4 + 1e-10))
    
    # Спектральный анализ (FFT)
    spectrum = np.abs(fft(data))[:n//2]
    freqs    = fftfreq(n, 1.0 / sample_rate)[:n//2]
    
    # Поиск доминирующих частот
    top_indices = np.argsort(spectrum)[-5:][::-1]
    dominant_freqs = [(float(freqs[i]), float(spectrum[i])) for i in top_indices]
    
    # Оценка состояния
    # Crest Factor: &lt;2.5 — норма, 2.5–4 — внимание, &gt;4 — износ
    # Kurtosis: &lt;3 — норма (гауссов шум), &gt;6 — дефект (удары)
    
    if crest &gt; 4.0 or kurtosis &gt; 6.0:
        status = "FAULT"
        recommendation = "Замените подшипник в течение 48 часов"
    elif crest &gt; 2.5 or kurtosis &gt; 4.5:
        status = "WARNING"
        recommendation = "Запланируйте замену при следующем ТО"
    else:
        status = "OK"
        recommendation = "Оборудование в норме"
    
    return {
        'rms':             round(rms, 4),
        'peak':            round(peak, 4),
        'crest_factor':    round(crest, 2),
        'kurtosis':        round(kurtosis, 2),
        'dominant_freqs':  dominant_freqs[:3],
        'status':          status,
        'recommendation':  recommendation,
    }

# Пример использования с MQTT:
def on_vibration_data(client, userdata, msg):
    data = json.loads(msg.payload)
    samples = data['samples']
    device  = data['device_id']
    
    analysis = analyze_vibration(samples, sample_rate=data.get('sample_rate', 1000))
    
    # Публикуем результат анализа
    result_topic = f"factory/analytics/{device}/bearing_health"
    client.publish(result_topic, json.dumps(analysis), retain=True)
    
    if analysis['status'] != 'OK':
        print(f"</code></pre><p><code><span class="tmiEmoji" title="">⚠️</span></code></p><p><code>  {device}: {analysis['recommendation']}")         # Отправить уведомление... </code></p><hr><h2>Безопасность IIoT</h2><p><strong>Это не опционально.</strong> Промышленные системы с интернет-подключением — лакомая цель для атак.</p><p>Минимальный стандарт:</p><ol><li><p><strong>TLS везде</strong> — Mosquitto с сертификатами, никакого plaintext</p></li><li><p><strong>Аутентификация</strong> — уникальный логин/пароль для каждого устройства, или X.509 сертификаты</p></li><li><p><strong>Авторизация по ACL</strong> — устройство <code>conveyor1</code> пишет только в <code>factory/line1/conveyor1/#</code>, не может читать чужие команды</p></li><li><p><strong>Сегментация сети</strong> — IoT-устройства в отдельном VLAN, без прямого доступа в интернет</p></li><li><p><strong>OTA-обновления</strong> — возможность удалённого обновления прошивки при обнаружении уязвимостей</p></li><li><p><strong>Мониторинг аномалий</strong> — необычное количество сообщений, соединения с нестандартных IP</p></li></ol><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code># Mosquitto ACL файл /etc/mosquitto/acl:
# Устройство conveyor1 — пишет только в свои топики
user esp32_conveyor1
topic write factory/line1/conveyor1/+
topic read  factory/line1/conveyor1/commands

# Backend — читает всё, пишет команды
user backend
topic readwrite factory/#
</code></pre><hr><h2>Заключение</h2><p>IIoT с MQTT — это доступная и проверенная технология, которую можно внедрить даже на небольшом предприятии с минимальными затратами. ESP32 + Mosquitto + InfluxDB + Grafana — весь стек работает на одном Raspberry Pi 4 или бюджетном сервере.</p><p>Главные принципы: данные должны быть точными (правильная калибровка датчиков), надёжными (QoS, переподключение, buffering), безопасными (TLS, ACL) и полезными (не просто собирать, а анализировать и действовать).</p><p>Начните с малого: один датчик температуры, один MQTT-брокер, один Grafana-дашборд. После первого успешного графика желание расширять систему появится само.</p>]]></description><guid isPermaLink="false">107</guid><pubDate>Sat, 21 Mar 2026 16:52:39 +0000</pubDate></item><item><title>RS-485: &#x43F;&#x440;&#x43E;&#x43C;&#x44B;&#x448;&#x43B;&#x435;&#x43D;&#x43D;&#x430;&#x44F; &#x43F;&#x43E;&#x441;&#x43B;&#x435;&#x434;&#x43E;&#x432;&#x430;&#x442;&#x435;&#x43B;&#x44C;&#x43D;&#x430;&#x44F; &#x441;&#x435;&#x442;&#x44C; &#x43E;&#x442; &#x410; &#x434;&#x43E; &#x42F;</title><link>https://ithub.uno/statiarticles/11_industrial-machines/rs-485-%D0%BF%D1%80%D0%BE%D0%BC%D1%8B%D1%88%D0%BB%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F-%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F-%D1%81%D0%B5%D1%82%D1%8C-%D0%BE%D1%82-%D0%B0-%D0%B4%D0%BE-%D1%8F-r131/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/2690794.jpg.9609f146f678951db326253d18045598.jpg" /></p>
<h2>Почему RS-485, а не что-то современное</h2><p>Каждый год появляются новые промышленные протоколы: EtherCAT, PROFINET, IO-Link, TSN. Но RS-485 не умирает. По данным IHS Markit, ежегодно продаётся более 1 миллиарда чипов RS-485. Новые установки продолжают использовать этот интерфейс.</p><p>Причины живучести просты:</p><ul><li><p><strong>Дешевизна:</strong> кабель — витая пара $0.1/м, трансивер MAX485 — $0.3</p></li><li><p><strong>Надёжность:</strong> дифференциальный сигнал устойчив к помехам, работает на расстояниях до 1200 м</p></li><li><p><strong>Простота:</strong> понять и реализовать RS-485 можно за один день</p></li><li><p><strong>Совместимость:</strong> поддерживается абсолютно всеми промышленными устройствами</p></li></ul><p>Modbus RTU, BACnet MS/TP, DMX512, DALI — всё это работает поверх RS-485. Если вы занимаетесь промышленной автоматизацией, знание RS-485 обязательно.</p><hr><h2>Сравнение: RS-232 vs RS-485</h2><div class="tmiRichText__table-wrapper"><table style="width: 884px;"><colgroup><col style="width:326px;"><col style="width:274px;"><col style="width:284px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Параметр</p></th><th colspan="1" rowspan="1"><p>RS-232</p></th><th colspan="1" rowspan="1"><p>RS-485</p></th></tr><tr><td colspan="1" rowspan="1"><p>Тип сигнала</p></td><td colspan="1" rowspan="1"><p>Однополярный, ±3–15В</p></td><td colspan="1" rowspan="1"><p>Дифференциальный, ±200мВ–5В</p></td></tr><tr><td colspan="1" rowspan="1"><p>Количество устройств</p></td><td colspan="1" rowspan="1"><p>1:1 (точка-точка)</p></td><td colspan="1" rowspan="1"><p>1:32 без репитеров (до 247 с)</p></td></tr><tr><td colspan="1" rowspan="1"><p>Максимальное расстояние</p></td><td colspan="1" rowspan="1"><p>15 м</p></td><td colspan="1" rowspan="1"><p>1200 м</p></td></tr><tr><td colspan="1" rowspan="1"><p>Скорость</p></td><td colspan="1" rowspan="1"><p>До 115 200 бод (практически)</p></td><td colspan="1" rowspan="1"><p>До 10 Мбит/с (при короткой линии)</p></td></tr><tr><td colspan="1" rowspan="1"><p>Устойчивость к помехам</p></td><td colspan="1" rowspan="1"><p>Низкая</p></td><td colspan="1" rowspan="1"><p>Высокая</p></td></tr><tr><td colspan="1" rowspan="1"><p>Дуплекс</p></td><td colspan="1" rowspan="1"><p>Полный (отдельные TX/RX)</p></td><td colspan="1" rowspan="1"><p>Полу (одна пара) или полный (2 пары)</p></td></tr><tr><td colspan="1" rowspan="1"><p>Применение</p></td><td colspan="1" rowspan="1"><p>Отладка, локальные устройства</p></td><td colspan="1" rowspan="1"><p>Промышленные сети, длинные линии</p></td></tr></tbody></table></div><hr><h2>Физический уровень: как работает дифференциальный сигнал</h2><p>RS-485 использует <strong>дифференциальную пару</strong> проводников A и B:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Состояние MARK (логическая 1, рецессивное):
  A &gt; B: разность (A-B) = +200мВ...+5В

Состояние SPACE (логическая 0, доминантное):
  B &gt; 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В — сигнал не изменился!
</code></pre><p><strong>Пороги приёмника:</strong> если разность (A-B) &gt; +200мВ — принимает "1"; если (A-B) &lt; -200мВ — принимает "0". Диапазон ±200мВ — мёртвая зона (неопределённость).</p><hr><h2>Выбор трансивера RS-485</h2><h3>Бюджетные (для начала):</h3><p><strong>MAX485 / MAX485E (Maxim)</strong></p><ul><li><p>Самый популярный, $0.3–0.5</p></li><li><p>Полудуплекс, 2.5 Мбит/с</p></li><li><p>Нет защиты от ESD (добавьте TVS-диоды!)</p></li><li><p>Нет защиты от перегрева</p></li><li><p>Питание 5В</p></li></ul><p><strong>SP3485 (Sipex/Exar)</strong></p><ul><li><p>Клон MAX485, питание 3.3В</p></li><li><p>Совместимость с STM32, ESP32 напрямую (5В-tolerant входы)</p></li></ul><h3>Профессиональные (для промышленности):</h3><p><strong>MAX3485 / MAX3488</strong></p><ul><li><p>Расширенный диапазон ESD: ±15 кВ (HBM)</p></li><li><p>Работает от 3.3В</p></li></ul><p><strong>SN65HVD1780 (Texas Instruments)</strong></p><ul><li><p>Встроенная защита от отказа шины (failsafe)</p></li><li><p>ESD: ±16 кВ IEC 61000-4-2</p></li><li><p>Для жёстких промышленных условий</p></li></ul><p><strong>ADM2587E (Analog Devices)</strong></p><ul><li><p>Встроенная гальваническая изоляция 2500 VRMS</p></li><li><p>Изолированный DC-DC для питания изолированной стороны</p></li><li><p>Для применений с разным заземлением узлов</p></li></ul><hr><h2>Схема подключения: правильно и неправильно</h2><h3>Минимальная схема (Arduino + MAX485):</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>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
</code></pre><h3>Ключевые правила:</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code></code></pre><p><code><span class="tmiEmoji" title="">✅</span> ПРАВИЛЬНО:    [Устройство A]──────[Устройство B]──────[Устройство C]    [Term 120Ом]                              [Term 120Ом]        Строго линейная шина, терминаторы только на концах     </code></p><p><code><span class="tmiEmoji" title="">❌</span> НЕПРАВИЛЬНО — "звезда":           [Устройство A]                 │    [Уст.B]──[Hub]──[Уст.C]                 │           [Устройство D]        Отражения на каждом разветвлении разрушат сигнал!     </code></p><p><code><span class="tmiEmoji" title="">❌</span> НЕПРАВИЛЬНО — терминаторы не там:    [Term]──[Уст.A]──[Уст.B]──[Term]──[Уст.C]        Терминатор посередине создаёт проблемы! </code></p><h3>Допустимые ответвления (stub):</h3><p>Короткие отводы от основной шины допустимы при условии:</p><ul><li><p>Длина stub &lt; λ/10, где λ — длина волны на рабочей скорости</p></li><li><p>При 9600 бод: λ ≈ 12 км → stub до 1200 м (практически неограничен)</p></li><li><p>При 115200 бод: stub не более 1 м</p></li><li><p>При 1 Мбит/с: stub не более 15 см!</p></li></ul><hr><h2>Управление направлением передачи</h2><p>RS-485 в полудуплексном режиме требует переключения между передачей и приёмом через сигнал DE/RE:</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// 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();
}
</code></pre><h3>Аппаратное управление DE/RE (лучше!):</h3><p>На STM32 USART имеет аппаратный сигнал DE для RS-485. Переключение происходит автоматически — с точностью до такта, без программных задержек:</p><pre spellcheck="" class="tmiCode language-c" data-language="C"><code>// 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(&amp;huart2, data, len, 100);
// STM32 сам поднял DE перед передачей и снял после!
</code></pre><hr><h2>Расчёт нагрузки на шину</h2><p>RS-485 трансивер создаёт нагрузку на шину. Стандарт RS-485 определяет "единицу нагрузки" (Unit Load, UL) = 12 кОм.</p><p>Драйвер должен обеспечить минимум 32 UL. Это значит: <strong>максимум 32 "классических" устройства</strong> на шине.</p><p>Современные трансиверы с низким потреблением имеют 1/8 UL или 1/4 UL:</p><div class="tmiRichText__table-wrapper"><table style="min-width: 60px;"><colgroup><col style="min-width:20px;"><col style="min-width:20px;"><col style="min-width:20px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Тип трансивера</p></th><th colspan="1" rowspan="1"><p>Нагрузка</p></th><th colspan="1" rowspan="1"><p>Устройств на шине</p></th></tr><tr><td colspan="1" rowspan="1"><p>Стандартный (MAX485)</p></td><td colspan="1" rowspan="1"><p>1 UL</p></td><td colspan="1" rowspan="1"><p>32</p></td></tr><tr><td colspan="1" rowspan="1"><p>1/2 UL (MAX3430)</p></td><td colspan="1" rowspan="1"><p>0.5 UL</p></td><td colspan="1" rowspan="1"><p>64</p></td></tr><tr><td colspan="1" rowspan="1"><p>1/4 UL (MAX3471)</p></td><td colspan="1" rowspan="1"><p>0.25 UL</p></td><td colspan="1" rowspan="1"><p>128</p></td></tr><tr><td colspan="1" rowspan="1"><p>1/8 UL (MAX3491)</p></td><td colspan="1" rowspan="1"><p>0.125 UL</p></td><td colspan="1" rowspan="1"><p>256</p></td></tr></tbody></table></div><p>Также нагрузку создают терминирующие резисторы:</p><ul><li><p>2 × 120 Ом = 60 Ом = 200 UL (!) — это доминирующая нагрузка</p></li><li><p>Учитывайте это при расчёте суммарной нагрузки</p></li></ul><hr><h2>Кабель: выбор и прокладка</h2><h3>Требования к кабелю RS-485:</h3><p><strong>Обязательно:</strong></p><ul><li><p>Витая пара (не просто два провода!)</p></li><li><p>Волновое сопротивление 120 Ом (терминируется парными резисторами 120 Ом)</p></li></ul><p><strong>Рекомендуемые типы кабелей:</strong></p><ul><li><p><strong>КВВГЭ 1×2×0.75</strong> — отечественный, экранированная витая пара</p></li><li><p><strong>Belden 9842</strong> — американский стандарт, 120 Ом, двойной экран</p></li><li><p><strong>LiYCY 2×0.5 мм²</strong> — гибкий, для подвижных установок</p></li><li><p><strong>Cat5e / Cat6</strong> — работает! (120 Ом, но без промышленной изоляции)</p></li></ul><h3>Сечение проводника:</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Падение напряжения на кабеле:
ΔU = 2 × R_кабеля × I_нагрузка

R = ρ × L / S = 0.0175 (Ом·мм²/м) × 1000 м / 0.5 мм² = 35 Ом

При токе утечки 100 мА:
ΔU = 2 × 35 × 0.1 = 7В — это уже критично!

Для длинных линий выбирайте кабель 1.0 мм² и более.
</code></pre><h3>Экранирование:</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Правила заземления экрана:
</code></pre><p><code><span class="tmiEmoji" title="">✅</span> Заземлять в ОДНОЙ точке — предотвращает контурные токи    Обычно: на стороне мастера/ПЛК     </code></p><p><code><span class="tmiEmoji" title="">❌</span> Заземлять с ОБОИХ концов — контурный ток протекает по экрану!    При разных потенциалах земли создаёт синфазные помехи.     Исключение: при частотах &gt; 100 кГц экран заземляют с обоих концов (через конденсатор 10 нФ с одной стороны). </code></p><hr><h2>Практика: полный пример Modbus RTU на Python</h2><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>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) -&gt; int:
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                crc = (crc &gt;&gt; 1) ^ 0xA001 if crc &amp; 1 else crc &gt;&gt; 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) -&gt; Optional[bytes]:
        """Приём данных с таймаутом"""
        deadline = time.time() + timeout
        buf = b''
        
        while time.time() &lt; deadline:
            chunk = self.ser.read(expected_len - len(buf))
            buf += chunk
            if len(buf) &gt;= 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) -&gt; Optional[list]:
        """
        Чтение Holding (FC=3) или Input (FC=4) регистров.
        Возвращает список значений или None при ошибке.
        """
        # Формируем запрос
        request = struct.pack('&gt;BBHH', slave, func, start_addr, count)
        crc = self._crc16(request)
        request += struct.pack('&lt;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('&lt;H', response[-2:])[0]
        calc_crc = self._crc16(response[:-2])
        if recv_crc != calc_crc:
            return None  # CRC ошибка
        
        # Проверка Exception
        if response[1] &amp; 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'&gt;{count}H', response[3:3+byte_count]))
        return values
    
    def modbus_write_register(self, slave: int, addr: int, value: int) -&gt; bool:
        """Запись одного Holding регистра (FC=6)"""
        request = struct.pack('&gt;BBHH', slave, 6, addr, value)
        crc = self._crc16(request)
        request += struct.pack('&lt;H', crc)
        
        self.send_raw(request)
        
        # Ответ = эхо запроса (8 байт)
        response = self.recv_raw(8)
        if response is None:
            return False
        
        recv_crc = struct.unpack('&lt;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 &amp; 0x0001)
                faulted = bool(status &amp; 0x0008)
                
                print(f"{'</code></pre><p><code><span class="tmiEmoji" title="">▶</span></code></p><p><code>' if running else '<span class="tmiEmoji" title="">⏹</span>'} "                       f"f={freq_hz:.1f}Гц  "                       f"I={curr_a:.1f}А  "                       f"U={volts}В  "                       f"P={power_kw:.1f}кВт  "                       f"{'<span class="tmiEmoji" title="">🔴</span>АВАРИЯ' if faulted else ''}")                                  if faulted:                     print(f"Код аварии: {fault}")                          time.sleep(1.0)          except KeyboardInterrupt:         print("Остановлено")     finally:         master.close()  demo_poll_vfd() </code></p><hr><h2>Диагностика: осциллограф и мультиметр</h2><h3>Измерения мультиметром:</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Линия A-B без сигнала (все устройства молчат):
  Должно быть: A &gt; B на 200мВ+ (если есть pull-up/down)
  Плохо:       A = B (неопределённое состояние — нужны резисторы смещения)

Во время передачи:
  Осциллограф: чёткие уровни ±3..4В, без выбросов
  Плохо: размытые фронты → слишком длинная линия или нет терминаторов
  Плохо: выбросы &gt;±7В → нет снаббера или плохое заземление
  
Измерение дифференциального сигнала:
  Щуп A → канал 1, Щуп B → канал 2
  Включить Math: CH1 - CH2
  Должен быть чёткий прямоугольник ±(3..5)В
</code></pre><h3>Типичные осциллограммы проблем:</h3><p><strong>Нет терминаторов:</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>    ┌──────┐
    │      │  ← Нормальный фронт
    │      │╲  ← Отражение (заброс)
────┘      └─╲──────
                └──  ← Повторное отражение
</code></pre><p><strong>Слишком длинный stub:</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>    ┌──────┐
    │      ╲___/  ← Паразитное колебание после фронта
────┘            ────
</code></pre><p><strong>Хорошая линия:</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>    ┌──────────┐
────┘          └────  ← Чёткие фронты без выбросов
</code></pre><hr><h2>Гальваническая развязка: когда обязательна</h2><p>В промышленных установках "земля" в разных точках может иметь разный потенциал — десятки и даже сотни вольт. Без развязки:</p><ul><li><p>Контурный ток по GND-проводу шины RS-485 разрушает трансиверы</p></li><li><p>Помехи от силового оборудования проникают в логику</p></li><li><p>Один неисправный узел выводит из строя всю сеть</p></li></ul><p><strong>Когда нужна обязательно:</strong></p><ul><li><p>Устройства питаются от разных источников</p></li><li><p>Расстояние между устройствами &gt; 50 м</p></li><li><p>Рядом с шиной есть мощные электродвигатели или сварочное оборудование</p></li><li><p>Разные здания или разные распределительные щиты</p></li></ul><p><strong>Варианты реализации:</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Вариант 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 Мбит/с, без светодиодов (долговечнее оптопар)
</code></pre><hr><h2>Число устройств больше 32: репитеры и конвертеры</h2><p>Если нужно более 32 устройств или протяжённость линии превышает допустимую:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Репитер RS-485:
[Сегмент 1: 32 уст., 600 м]──[Репитер]──[Сегмент 2: 32 уст., 600 м]

Репитер переусиливает и переформирует сигнал.
Каждый сегмент — отдельная нагрузка.

Популярные репитеры:
- ADAM-4510 (Advantech): изолированный, DIN-рейка
- Moxa MB-9000: с диагностикой
- Homemade: MAX487 + MAX487 с DE/RE управлением
</code></pre><hr><h2>Защита от грозовых разрядов и ESD</h2><p>При длинных линиях между зданиями — молниезащита обязательна:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Схема защиты 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/аналоги
</code></pre><hr><h2>Частые вопросы и ответы</h2><p><strong>Q: Можно ли использовать обычный кабель UTP Cat5e вместо специального?</strong> A: Можно, и это работает на практике. Cat5e имеет волновое сопротивление 100 Ом (термinator нужен 100 Ом), паразитные ёмкости немного хуже. До 300м/115200 бод — без проблем. Но в промышленной среде с помехами — лучше экранированный кабель.</p><p><strong>Q: Нужен ли третий провод GND?</strong> A: Для корректной работы трансивера входная синфазная помеха не должна выходить за пределы -7В...+12В (Vcm). При длинных линиях и разных заземлениях это нарушается. GND-провод удерживает синфазное напряжение в пределах допустимого. <strong>Включайте GND во все промышленные установки</strong>.</p><p><strong>Q: У устройства нет RS-485, только UART. Как подключить?</strong> A: Добавить внешний трансивер MAX485/SP3485 + резистор 300 Ом на DE/RE от GPIO.</p><p><strong>Q: Что делать если устройства разных производителей не видят друг друга?</strong> A: Проверить: 1) Скорость/parity/stopbits совпадают. 2) Полярность A/B (иногда производители маркируют наоборот). 3) Адреса устройств уникальны. 4) Нет конфликта адресов.</p><hr><h2>Заключение</h2><p>RS-485 — это не устаревший протокол, а надёжный, проверенный инструмент для промышленных приложений. Правильная линейная топология с терминаторами, экранированная витая пара, правильное управление DE/RE и гальваническая развязка там, где нужно — всё это обеспечит годы надёжной работы.</p><p>Вложите время в понимание физического уровня: осциллограф + хорошая книга по физике передачи сигналов. Большинство проблем RS-485 решаются на физическом уровне, а не в программе.</p>]]></description><guid isPermaLink="false">131</guid><pubDate>Sat, 21 Mar 2026 20:41:06 +0000</pubDate></item><item><title>&#x41F;&#x440;&#x43E;&#x43C;&#x44B;&#x448;&#x43B;&#x435;&#x43D;&#x43D;&#x44B;&#x435; &#x434;&#x430;&#x442;&#x447;&#x438;&#x43A;&#x438;: &#x432;&#x44B;&#x431;&#x43E;&#x440;, &#x43F;&#x43E;&#x434;&#x43A;&#x43B;&#x44E;&#x447;&#x435;&#x43D;&#x438;&#x435; &#x438; &#x43A;&#x430;&#x43B;&#x438;&#x431;&#x440;&#x43E;&#x432;&#x43A;&#x430;</title><link>https://ithub.uno/statiarticles/11_industrial-machines/%D0%BF%D1%80%D0%BE%D0%BC%D1%8B%D1%88%D0%BB%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D0%B4%D0%B0%D1%82%D1%87%D0%B8%D0%BA%D0%B8-%D0%B2%D1%8B%D0%B1%D0%BE%D1%80-%D0%BF%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8-%D0%BA%D0%B0%D0%BB%D0%B8%D0%B1%D1%80%D0%BE%D0%B2%D0%BA%D0%B0-r146/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/datchiki.png.13485475144a796be64f4529135037e6.png" /></p>
<h2>Правильный датчик — половина успеха автоматизации</h2><p>Самый умный ПЛК и лучший алгоритм управления ничего не стоят, если датчик даёт неверные данные. Выбор, монтаж и калибровка датчиков — часто недооцениваемая область, которая определяет качество всей системы.</p><p>Промышленный датчик — не просто "измерительное устройство". Это прибор, который должен работать 24/7 годами в условиях вибраций, агрессивных сред, перепадов температур и электромагнитных помех. И при этом давать достоверные показания.</p><hr><h2>Классификация выходных сигналов</h2><p>Прежде чем выбирать датчик — определитесь с типом сигнала:</p><h3>Аналоговые сигналы</h3><p><strong>4–20 мА (токовая петля):</strong></p><ul><li><p>Самый распространённый промышленный стандарт</p></li><li><p>4 мА = 0% диапазона, 20 мА = 100%</p></li><li><p>Устойчив к помехам (ток, не напряжение)</p></li><li><p>Обнаружение обрыва кабеля: &lt; 3.8 мА = авария</p></li><li><p>Длина линии до нескольких км (ограничено напряжением питания)</p></li><li><p>Двухпроводная схема: датчик питается от той же петли!</p></li></ul><p><strong>0–10 В:</strong></p><ul><li><p>Проще в подключении, но чувствителен к помехам</p></li><li><p>Нет встроенного обнаружения обрыва</p></li><li><p>Ограничение длины кабеля (~50 м)</p></li><li><p>Используется в HVAC, Building Automation</p></li></ul><p><strong>0–5 В / ±10 В:</strong></p><ul><li><p>Распространён для ускорений, давлений в автомотиве</p></li></ul><h3>Дискретные сигналы</h3><p><strong>NPN / PNP:</strong></p><ul><li><p>NPN: выход тянет к GND при активации (сигнал = LOW)</p></li><li><p>PNP: выход тянет к питанию (сигнал = HIGH)</p></li><li><p>Подключение к ПЛК: важно соответствие типа входного модуля!</p></li></ul><p><strong>NO / NC (нормально открытый / нормально закрытый):</strong></p><ul><li><p>NO: контакт разомкнут в норме, замыкается при срабатывании</p></li><li><p>NC: контакт замкнут в норме, размыкается при срабатывании</p></li><li><p>Для безопасности: NC предпочтительнее (обрыв провода = срабатывание защиты)</p></li></ul><h3>Цифровые/протокольные</h3><p><strong>IO-Link, HART, Profibus PA, Foundation Fieldbus, EtherCAT</strong> — для умных датчиков с диагностикой и конфигурированием по линии.</p><hr><h2>Температура: термопары и PT100</h2><h3>Термопары (Thermocouple)</h3><p><strong>Принцип:</strong> эффект Зеебека — два разных металла, соединённых в двух точках, генерируют ЭДС при разнице температур.</p><div class="tmiRichText__table-wrapper"><table style="min-width: 536px;"><colgroup><col style="min-width:20px;"><col style="width:237px;"><col style="width:259px;"><col style="min-width:20px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Тип</p></th><th colspan="1" rowspan="1"><p>Диапазон</p></th><th colspan="1" rowspan="1"><p>Материал</p></th><th colspan="1" rowspan="1"><p>Применение</p></th></tr><tr><td colspan="1" rowspan="1"><p>K</p></td><td colspan="1" rowspan="1"><p>-200..+1372°C</p></td><td colspan="1" rowspan="1"><p>NiCr-NiAl</p></td><td colspan="1" rowspan="1"><p>Универсальный, самый популярный</p></td></tr><tr><td colspan="1" rowspan="1"><p>J</p></td><td colspan="1" rowspan="1"><p>-210..+1200°C</p></td><td colspan="1" rowspan="1"><p>Fe-CuNi</p></td><td colspan="1" rowspan="1"><p>Печи, старые установки</p></td></tr><tr><td colspan="1" rowspan="1"><p>T</p></td><td colspan="1" rowspan="1"><p>-270..+400°C</p></td><td colspan="1" rowspan="1"><p>Cu-CuNi</p></td><td colspan="1" rowspan="1"><p>Криогеника, пищевая промышленность</p></td></tr><tr><td colspan="1" rowspan="1"><p>N</p></td><td colspan="1" rowspan="1"><p>-270..+1300°C</p></td><td colspan="1" rowspan="1"><p>NiCrSi-NiSi</p></td><td colspan="1" rowspan="1"><p>Стабильнее K при высоких T</p></td></tr><tr><td colspan="1" rowspan="1"><p>S</p></td><td colspan="1" rowspan="1"><p>0..+1768°C</p></td><td colspan="1" rowspan="1"><p>Pt10Rh-Pt</p></td><td colspan="1" rowspan="1"><p>Металлургия (эталон)</p></td></tr><tr><td colspan="1" rowspan="1"><p>B</p></td><td colspan="1" rowspan="1"><p>+250..+1820°C</p></td><td colspan="1" rowspan="1"><p>Pt30Rh-Pt6Rh</p></td><td colspan="1" rowspan="1"><p>Высокотемпературные печи</p></td></tr></tbody></table></div><p><strong>Ключевые особенности термопар:</strong></p><ul><li><p>Генерируют малую ЭДС (0–80 мВ) — нужен прецизионный усилитель</p></li><li><p>Требуют компенсации холодного спая (CJC — Cold Junction Compensation)</p></li><li><p>Длинные термопарные кабели из компенсационных проводов (дороже!)</p></li><li><p>Нелинейная характеристика (полиномиальная аппроксимация по ГОСТ)</p></li></ul><p><strong>Подключение к микроконтроллеру через MAX31855/MAX6675:</strong></p><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>#include &lt;Adafruit_MAX31855.h&gt;
#include &lt;SPI.h&gt;

// MAX31855: усилитель термопары типа K с SPI интерфейсом
Adafruit_MAX31855 thermocouple(D5, D6, D7);  // CLK, CS, DO

void setup() {
    Serial.begin(115200);
    if (!thermocouple.begin()) {
        Serial.println("MAX31855 не найден!");
        while (1);
    }
}

void loop() {
    double hot_temp  = thermocouple.readCelsius();  // Температура термопары
    double cold_temp = thermocouple.readInternal();  // Температура холодного спая (чип)
    
    if (isnan(hot_temp)) {
        uint8_t fault = thermocouple.readError();
        Serial.print("Ошибка: ");
        if (fault &amp; MAX31855_FAULT_OPEN)   Serial.println("обрыв термопары");
        if (fault &amp; MAX31855_FAULT_SHORT_GND) Serial.println("КЗ на GND");
        if (fault &amp; MAX31855_FAULT_SHORT_VCC) Serial.println("КЗ на VCC");
    } else {
        Serial.printf("T = %.2f°C (холодный спай: %.2f°C)\n", hot_temp, cold_temp);
    }
    
    delay(1000);
}
</code></pre><h3>Термосопротивления PT100/PT1000</h3><p><strong>Принцип:</strong> сопротивление платинового проводника линейно растёт с температурой.</p><ul><li><p>PT100: 100 Ом при 0°C, α = 0.00385 Ом/(Ом·°C)</p></li><li><p>PT1000: 1000 Ом при 0°C (лучше для длинных кабелей)</p></li></ul><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>R(T) = R0 × [1 + A×T + B×T² + C×T³ × (T-100)]
R0 = 100 Ом
A = 3.9083×10⁻³
B = -5.775×10⁻⁷
C = -4.183×10⁻¹² (только при T &lt; 0°C)

Упрощённо для -50..+200°C:
R(T) ≈ 100 × (1 + 0.00385 × T)
T ≈ (R - 100) / 0.385
</code></pre><p><strong>Схемы подключения:</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>2-проводная (дёшево, но ошибка от сопротивления кабеля):
  [ПЛК]──R_кабель──[PT100]──R_кабель──[ПЛК]
  Ошибка: ΔR_кабель = 2 × ρ × L / S
  При 10 м, 0.5 мм²: ΔR ≈ 0.7 Ом ≈ 1.8°C погрешность!

3-проводная (стандарт):
  [ПЛК]──R1──[PT100]──R2──[ПЛК]
              │
              R3──────────[ПЛК]
  Измерительная схема компенсирует сопротивление одного провода

4-проводная (эталонная точность):
  [ПЛК]──I+──[PT100]──I-──[ПЛК]  (ток через PT100)
  [ПЛК]──U+──[PT100]──U-──[ПЛК]  (измерение напряжения)
  Сопротивление провода не влияет на точность
</code></pre><hr><h2>Датчики давления: 4–20 мА в промышленности</h2><h3>Основные типы:</h3><p><strong>Пьезорезистивные:</strong> мембрана с тензодатчиками, сигнал 4–20 мА. Самые распространённые.</p><p><strong>Пьезоэлектрические:</strong> для динамических давлений (удары, взрывы). Заряд пропорционален давлению.</p><p><strong>Ёмкостные:</strong> высокая точность (0.01%), дорогие. Для точных технологических процессов.</p><h3>Подключение датчика 4–20 мА:</h3><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Двухпроводная схема (+питание, -сигнал):

24В ──────────┬──────── Датчик (+)
              │
           R_shunt (250 Ом)
              │
GND ──────────┴──────── Датчик (-)
              │
           Измеряем U на R_shunt
           4мА → 1.000В
           20мА → 5.000В

В ПЛК: аналоговый вход 1–5В или через преобразователь ток→напряжение
</code></pre><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>def current_to_pressure(current_ma: float,
                          pressure_min: float,
                          pressure_max: float) -&gt; float:
    """
    Преобразование тока 4-20мА в давление.
    
    Args:
        current_ma: ток в мА (4.0..20.0)
        pressure_min: давление при 4мА (нижний предел датчика)
        pressure_max: давление при 20мА (верхний предел)
    
    Returns:
        давление в единицах pressure_min/max
    """
    # Проверка обрыва линии
    if current_ma &lt; 3.8:
        raise ValueError(f"Обрыв линии или ошибка датчика: {current_ma} мА")
    
    # Проверка превышения
    if current_ma &gt; 20.5:
        raise ValueError(f"Превышение тока: {current_ma} мА")
    
    # Линейное масштабирование
    # 4мА = 0%, 20мА = 100%
    pct = (current_ma - 4.0) / 16.0
    return pressure_min + pct * (pressure_max - pressure_min)

# ADC напряжение → ток → давление
def adc_voltage_to_pressure(voltage_v: float, shunt_ohm: float = 250.0,
                             p_min: float = 0.0, p_max: float = 16.0) -&gt; float:
    current_ma = voltage_v / shunt_ohm * 1000.0
    return current_to_pressure(current_ma, p_min, p_max)

# Пример:
voltage = 2.5   # Вольт на шунте 250 Ом
current = 2.5 / 250 * 1000  # = 10 мА
pressure = current_to_pressure(current, 0, 16)  # = 7.5 бар (середина диапазона)
</code></pre><hr><h2>Индуктивные датчики: металл без контакта</h2><p>Индуктивный датчик (Inductive Proximity Sensor) обнаруживает металлические объекты без физического контакта.</p><p><strong>Принцип:</strong> высокочастотное электромагнитное поле. При попадании металла в зону — меняется амплитуда генератора → срабатывание.</p><p><strong>Дальность обнаружения</strong> зависит от металла:</p><ul><li><p>Сталь (St37): номинальная дальность × 1.0</p></li><li><p>Нержавейка (304): × 0.7–0.85</p></li><li><p>Алюминий: × 0.4–0.5</p></li><li><p>Медь, латунь: × 0.35–0.45</p></li></ul><p><strong>Типичные параметры:</strong></p><ul><li><p>Диаметр: M8, M12, M18, M30 (стандартные серии)</p></li><li><p>Дальность: 2–50 мм</p></li><li><p>Выход: NPN или PNP, NO или NC</p></li><li><p>Питание: 10–30В DC</p></li><li><p>Частота переключения: до 3000–5000 Гц</p></li></ul><p><strong>Пример выбора:</strong></p><ul><li><p>Считать металлические детали на конвейере: M12, PNP NO, Sn=4 мм</p></li><li><p>Контроль положения поршня цилиндра: M8 встраиваемый, PNP NO</p></li></ul><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Подключение PNP к ПЛК с входом 24В:
+24В ──────── Коричневый провод датчика (питание)
Синий провод ────────────────────────── GND (0В)
Чёрный провод ── DI вход ПЛК ─── </code></pre><p><code><span class="tmiEmoji" title="">↗</span>  24В при срабатывании                                 </code></p><p><code>NPN подключение к ПЛК: +24В ────────────────────────────── DI </code></p><p><code>вход ПЛК Чёрный провод ─── DI вход ПЛК ── </code></p><p><code><span class="tmiEmoji" title="">↘</span> 0В при срабатывании </code></p><hr><h2>Ёмкостные датчики: любые материалы</h2><p>Ёмкостной датчик (Capacitive) обнаруживает металлы, пластики, жидкости, порошки, дерево.</p><p><strong>Принцип:</strong> электрод датчика образует конденсатор с объектом. Приближение объекта → рост ёмкости → срабатывание.</p><p><strong>Применения:</strong></p><ul><li><p>Контроль уровня сыпучих материалов (зерно, цемент, порошки) сквозь стенку ёмкости</p></li><li><p>Обнаружение прозрачных объектов (стекло, пластик) — там, где оптика не работает</p></li><li><p>Контроль присутствия жидкости в трубе (сквозь пластик)</p></li><li><p>Счётчик таблеток/ампул на фармацевтическом конвейере</p></li></ul><p><strong>Особенность:</strong> требует настройки чувствительности под конкретный материал и расстояние (потенциометр или IO-Link).</p><hr><h2>Ультразвуковые датчики уровня</h2><p><strong>Принцип:</strong> излучают ультразвуковой импульс → принимают эхо → время до эха = расстояние.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Расстояние = v_звука × t_эхо / 2
v_звука ≈ 343 м/с при 20°C

Поправка на температуру:
v(T) = 331.5 + 0.606 × T(°C)

При T=40°C: v = 331.5 + 0.606×40 = 355.7 м/с
Ошибка без коррекции при 5м дальности: Δd = 5 × (355.7-343)/343 = 0.185 м = 18.5 см!
</code></pre><p><strong>Пример: SR-04 (HC-SR04) на Arduino:</strong></p><pre spellcheck="" class="tmiCode language-cpp" data-language="C++"><code>// HC-SR04: бюджетный датчик для прототипов (не промышленный!)
// Промышленные: Pepperl+Fuchs, Microsonic, ifm, Banner

const int TRIG_PIN = 9;
const int ECHO_PIN = 10;

void setup() {
    Serial.begin(115200);
    pinMode(TRIG_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
}

float measure_distance_cm(float temp_celsius = 20.0) {
    // Отправляем импульс 10 мкс
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    
    // Измеряем длительность эхо
    unsigned long duration_us = pulseIn(ECHO_PIN, HIGH, 30000);  // таймаут 30 мс
    
    if (duration_us == 0) {
        return -1;  // Объект не найден или слишком далеко
    }
    
    // Расчёт расстояния с коррекцией температуры
    float speed_cms = (331.5 + 0.606 * temp_celsius) * 100.0 / 1000000.0; // см/мкс
    float distance = duration_us * speed_cms / 2.0;
    
    return distance;
}

// Уровень в резервуаре:
float measure_tank_level(float tank_height_cm) {
    float distance = measure_distance_cm(25.0);
    if (distance &lt; 0 || distance &gt; tank_height_cm) return -1;
    
    float level_pct = (1.0 - distance / tank_height_cm) * 100.0;
    return max(0.0f, min(100.0f, level_pct));
}
</code></pre><p><strong>Мёртвая зона:</strong> HC-SR04 не измеряет до 2 см. Промышленные: от 1–3 мм. <strong>Проблемы:</strong> пена на поверхности жидкости, пыль, ветер, наклонные поверхности, температурные градиенты.</p><hr><h2>Расходомеры: измерение потока</h2><h3>Электромагнитный (Flowmeter/Магнитный):</h3><ul><li><p>Только электропроводящие жидкости (вода, кислоты, суспензии)</p></li><li><p>Нет движущихся частей → долговечность</p></li><li><p>Нет потерь давления</p></li><li><p>Погрешность: 0.5–1%</p></li><li><p>Сигнал: 4–20 мА + импульсный выход (число импульсов = объём)</p></li></ul><h3>Вихревой (Vortex):</h3><ul><li><p>Жидкости и газы</p></li><li><p>Измеряет частоту вихрей (пропорциональна скорости)</p></li><li><p>Минимальный расход: ограничен (при малом расходе не работает)</p></li></ul><h3>Ультразвуковой (накладной / inline):</h3><ul><li><p>Накладной: крепится снаружи трубы без врезки!</p></li><li><p>Подходит для ретрофита существующих трубопроводов</p></li><li><p>Для чистых жидкостей (пузыри искажают сигнал)</p></li></ul><h3>Coriolis:</h3><ul><li><p>Измеряет массовый расход напрямую (не объём!)</p></li><li><p>Самый точный (0.1%), самый дорогой</p></li><li><p>Работает с любыми жидкостями, суспензиями, вязкими средами</p></li></ul><hr><h2>Датчики вибрации: предиктивное обслуживание</h2><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>import numpy as np
from scipy.fft import rfft, rfftfreq
from scipy.signal import find_peaks

class VibrationAnalyzer:
    """
    Анализ вибрации для диагностики подшипников и валов.
    Акселерометр: ADXL345 (I2C) или промышленный 4-20мА.
    """
    
    def __init__(self, sample_rate: int = 1000):
        self.sample_rate = sample_rate
    
    def analyze(self, samples: np.ndarray, machine_rpm: float) -&gt; dict:
        """
        Полный анализ вибрационного сигнала.
        
        machine_rpm: скорость вращения (об/мин) для идентификации гармоник
        """
        n = len(samples)
        
        # === ВРЕМЕННЫЕ ПОКАЗАТЕЛИ ===
        rms   = np.sqrt(np.mean(samples**2))
        peak  = np.max(np.abs(samples))
        crest = peak / rms if rms &gt; 0 else 0
        
        # Эксцесс (Kurtosis): для обнаружения ударных дефектов
        mean = np.mean(samples)
        std  = np.std(samples)
        kurtosis = np.mean(((samples - mean) / std)**4) if std &gt; 0 else 0
        
        # === СПЕКТРАЛЬНЫЙ АНАЛИЗ ===
        # Оконная функция для уменьшения утечек
        window   = np.hanning(n)
        spectrum = np.abs(rfft(samples * window)) * 2 / n
        freqs    = rfftfreq(n, 1.0 / self.sample_rate)
        
        # Основная частота вращения
        f_rotation = machine_rpm / 60.0
        
        # Поиск пиков в спектре
        peaks_idx, peak_props = find_peaks(
            spectrum, height=0.01 * np.max(spectrum), distance=5
        )
        
        peaks = [(float(freqs[i]), float(spectrum[i])) for i in peaks_idx]
        peaks.sort(key=lambda x: -x[1])  # По убыванию амплитуды
        
        # Идентификация характерных частот
        identified = {}
        for freq, amp in peaks[:10]:
            # Гармоники вращения
            for harmonic in range(1, 8):
                if abs(freq - harmonic * f_rotation) &lt; 2.0:
                    identified[f"{harmonic}x_rotation"] = (freq, amp)
        
        # === ДИАГНОСТИКА ===
        # ISO 10816-3: нормы вибрации для промышленных машин
        # Класс 1 (малые): OK&lt;2.3, WARN&lt;4.5, CRIT&lt;7.1 мм/с RMS
        # Класс 2 (средние): OK&lt;4.5, WARN&lt;7.1, CRIT&lt;11.0
        
        rms_velocity_mms = rms / (2 * np.pi * f_rotation) * 1000 if f_rotation &gt; 0 else 0
        
        if rms_velocity_mms &lt; 2.3:
            iso_status = "Зона A (Хорошо)"
        elif rms_velocity_mms &lt; 4.5:
            iso_status = "Зона B (Допустимо)"
        elif rms_velocity_mms &lt; 7.1:
            iso_status = "Зона C (Внимание!)"
        else:
            iso_status = "Зона D (Опасно!)"
        
        # Диагностика по Kurtosis
        if kurtosis &gt; 6:
            bearing_diagnosis = "Дефект подшипника (ударные нагрузки)"
        elif kurtosis &gt; 4:
            bearing_diagnosis = "Начальный износ подшипника"
        else:
            bearing_diagnosis = "Норма"
        
        return {
            'rms_g':           round(rms, 4),
            'peak_g':          round(peak, 4),
            'crest_factor':    round(crest, 2),
            'kurtosis':        round(kurtosis, 2),
            'rms_velocity_mms': round(rms_velocity_mms, 2),
            'iso_status':      iso_status,
            'bearing_status':  bearing_diagnosis,
            'top_frequencies': peaks[:5],
            'identified_harmonics': identified,
        }
</code></pre><hr><h2>Калибровка: без неё данные не достоверны</h2><p>Каждый датчик имеет погрешности: смещение (offset), нелинейность, дрейф со временем и температурой. Калибровка — сравнение с эталоном и коррекция.</p><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>def two_point_calibration(raw_low: float, ref_low: float,
                           raw_high: float, ref_high: float,
                           raw_measured: float) -&gt; float:
    """
    Двухточечная линейная калибровка.
    
    raw_low, raw_high:  показания датчика в нижней и верхней точках
    ref_low, ref_high:  эталонные значения в этих точках
    raw_measured:       текущее показание датчика
    
    Пример: PT100 показывает 99.2 Ом при 0°C (вместо 100) и 138.9 при 100°C (вместо 138.5)
    calibrated = two_point_calibration(99.2, 100.0, 138.9, 138.5, current_reading)
    """
    if raw_high == raw_low:
        return ref_low
    
    # Линейная интерполяция
    slope  = (ref_high - ref_low) / (raw_high - raw_low)
    offset = ref_low - slope * raw_low
    
    return slope * raw_measured + offset
</code></pre><h3>Периодичность калибровки:</h3><ul><li><p>Термопары: ежегодно или при замене</p></li><li><p>PT100: 1–2 раза в год (дрейф незначителен)</p></li><li><p>Датчики давления: раз в год или при смене диапазона</p></li><li><p>Расходомеры: согласно паспорту (обычно 1 раз в год)</p></li></ul><hr><h2>Монтаж: правила, которые нельзя игнорировать</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Температурные датчики:</code></pre><p><code><span class="tmiEmoji" title="">✅</span> Погружение на 1/2–2/3 диаметра трубы </code></p><p><code><span class="tmiEmoji" title="">✅</span> Против потока (для лучшего теплообмена) </code></p><p><code><span class="tmiEmoji" title="">✅</span> Защитный карман (гильза) из нержавейки </code></p><p><code><span class="tmiEmoji" title="">❌</span> На наружной поверхности трубы (ошибка до 50°C!) </code></p><p><code><span class="tmiEmoji" title="">❌</span> В зоне завихрений (после колен, до 10D от колена)  </code></p><p><code>Датчики давления: </code></p><p><code><span class="tmiEmoji" title="">✅</span> Импульсные линии: уклон для самодренажа (для газа — вверх, для жидкости — вниз) </code></p><p><code><span class="tmiEmoji" title="">✅</span> Манометрический вентиль для обслуживания без остановки </code></p><p><code><span class="tmiEmoji" title="">❌</span> Прямое подключение к горячим/агрессивным средам без разделительного сосуда  </code></p><p><code>Индуктивные датчики: </code></p><p><code><span class="tmiEmoji" title="">✅</span> Расстояние &gt; 2× диаметра от соседних металлических поверхностей </code></p><p><code><span class="tmiEmoji" title="">✅</span> Осевая нагрузка — только через крепёжную гайку, не на корпус </code></p><p><code><span class="tmiEmoji" title="">❌</span> Монтаж в металлический кронштейн вплотную (ложные срабатывания) </code></p><hr><h2>Заключение</h2><p>Правильный выбор датчика — это компромисс между точностью, диапазоном, стойкостью к среде, стоимостью и сложностью подключения. Никогда не выбирайте датчик по принципу "самый дешёвый" — плохой датчик в критическом месте обойдётся дороже в простоях и ремонтах.</p><p>Всегда изучайте datasheet: реальный диапазон рабочих температур, степень защиты IP, материал контактной части. И никогда не экономьте на монтаже — половина проблем с датчиками — это неправильный монтаж, а не неисправность прибора.</p>]]></description><guid isPermaLink="false">146</guid><pubDate>Sat, 21 Mar 2026 20:55:33 +0000</pubDate></item><item><title>&#x424;&#x443;&#x43D;&#x43A;&#x446;&#x438;&#x43E;&#x43D;&#x430;&#x43B;&#x44C;&#x43D;&#x430;&#x44F; &#x431;&#x435;&#x437;&#x43E;&#x43F;&#x430;&#x441;&#x43D;&#x43E;&#x441;&#x442;&#x44C;: SIL, IEC 61508 &#x438; &#x431;&#x435;&#x437;&#x43E;&#x43F;&#x430;&#x441;&#x43D;&#x44B;&#x435; &#x41F;&#x41B;&#x41A;</title><link>https://ithub.uno/statiarticles/11_industrial-machines/%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F-%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE%D1%81%D1%82%D1%8C-sil-iec-61508-%D0%B8-%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D1%8B%D0%B5-%D0%BF%D0%BB%D0%BA-r152/</link><description><![CDATA[
<p><img src="https://ithub.uno/uploads/tmi_files_2026_03/csm_SafetyLoop_eng_a08a76041b.jpg.fa13ef5e7840fc41c8d7caa16d8115b7.jpg" /></p>
<h2>Почему функциональная безопасность — не формальность</h2><p>Техасский Сити, 2005 год. Взрыв на нефтеперерабатывающем заводе BP. 15 погибших, 180 раненых, $1.5 млрд ущерба. Причина: переполнение ректификационной колонны — датчик уровня дал неверные показания, система безопасности не сработала.</p><p>Бхопал, 1984 год. Утечка метилизоцианата. 3 787 погибших (официально), десятки тысяч раненых. Системы безопасности были отключены для экономии.</p><p>Функциональная безопасность — это не бюрократия и не документация ради документации. Это инженерная дисциплина, которая систематически снижает вероятность того, что отказ автоматики приведёт к катастрофе.</p><hr><h2>Базовые концепции</h2><h3>Опасность, риск, допустимый риск</h3><p><strong>Опасность (Hazard):</strong> потенциальный источник вреда (горючий газ, высокое давление, токсичное вещество).</p><p><strong>Риск (Risk):</strong> комбинация вероятности события и его последствий:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Риск = Вероятность × Тяжесть последствий
</code></pre><p><strong>Допустимый риск (Tolerable Risk):</strong> уровень риска, который общество считает приемлемым. Для промышленных объектов — обычно ≤10⁻⁵/год (один раз в 100 000 лет) для смертельного события.</p><h3>Независимые защитные слои (IPL — Independent Protection Layer)</h3><p>Принцип швейцарского сыра: ни один слой защиты не идеален, но несколько слоёв с отверстиями в разных местах надёжно блокируют опасность.</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Слой 1: Базовая система управления (BPCS) — ПЛК основной автоматики
  ↓ не сработала
Слой 2: SIS (Safety Instrumented System) — независимая система безопасности
  ↓ не сработала  
Слой 3: Механические защиты — предохранительный клапан, разрывной диск
  ↓ не сработала
Слой 4: Физические барьеры — обвалование, газоулавливающая система
  ↓
Катастрофа

Каждый слой снижает риск в 10–1000 раз.
Задача: снизить суммарный риск до допустимого уровня.
</code></pre><hr><h2>Стандарты: IEC 61508 и его отраслевые производные</h2><p><strong>IEC 61508</strong> — базовый стандарт функциональной безопасности для электрических/электронных/программируемых систем безопасности. 7 частей, охватывает весь жизненный цикл.</p><p>Отраслевые стандарты (выводятся из 61508):</p><ul><li><p><strong>IEC 61511</strong> — нефтехимия, газ, химия (процессные установки)</p></li><li><p><strong>IEC 62061</strong> — машиностроение</p></li><li><p><strong>EN 50128 / EN 50129</strong> — железнодорожный транспорт</p></li><li><p><strong>IEC 60601</strong> — медицинское оборудование</p></li><li><p><strong>DO-178C</strong> — авиационное ПО</p></li></ul><hr><h2>Уровни полноты безопасности (SIL)</h2><p>SIL (Safety Integrity Level) — дискретная мера целостности функции безопасности. Определяется вероятностью отказа при выполнении функции по требованию:</p><div class="tmiRichText__table-wrapper"><table style="min-width: 80px;"><colgroup><col style="min-width:20px;"><col style="min-width:20px;"><col style="min-width:20px;"><col style="min-width:20px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>SIL</p></th><th colspan="1" rowspan="1"><p>PFDavg (в режиме по требованию)</p></th><th colspan="1" rowspan="1"><p>PFH (в непрерывном режиме)</p></th><th colspan="1" rowspan="1"><p>Примеры</p></th></tr><tr><td colspan="1" rowspan="1"><p>SIL 1</p></td><td colspan="1" rowspan="1"><p>10⁻² ... 10⁻¹</p></td><td colspan="1" rowspan="1"><p>10⁻⁶ ... 10⁻⁵</p></td><td colspan="1" rowspan="1"><p>Простые защиты, блокировки</p></td></tr><tr><td colspan="1" rowspan="1"><p>SIL 2</p></td><td colspan="1" rowspan="1"><p>10⁻³ ... 10⁻²</p></td><td colspan="1" rowspan="1"><p>10⁻⁷ ... 10⁻⁶</p></td><td colspan="1" rowspan="1"><p>Большинство промышленных SIS</p></td></tr><tr><td colspan="1" rowspan="1"><p>SIL 3</p></td><td colspan="1" rowspan="1"><p>10⁻⁴ ... 10⁻³</p></td><td colspan="1" rowspan="1"><p>10⁻⁸ ... 10⁻⁷</p></td><td colspan="1" rowspan="1"><p>Нефтегаз, ядерная энергетика</p></td></tr><tr><td colspan="1" rowspan="1"><p>SIL 4</p></td><td colspan="1" rowspan="1"><p>10⁻⁵ ... 10⁻⁴</p></td><td colspan="1" rowspan="1"><p>10⁻⁹ ... 10⁻⁸</p></td><td colspan="1" rowspan="1"><p>Ядерная энергетика (редко)</p></td></tr></tbody></table></div><p><strong>PFDavg</strong> — средняя вероятность отказа в режиме ожидания (датчик не сработал когда нужно).</p><p><strong>PFH</strong> — частота опасных отказов в час (для непрерывных защит).</p><hr><h2>Расчёт SIL: упрощённый подход</h2><p>Определение требуемого SIL — через LOPA (Layer of Protection Analysis):</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Пример: высокое давление в реакторе может привести к взрыву

Частота инициирующего события: 0.1 /год (раз в 10 лет, типично для BPCS)
Вероятность последствий без защит: 1.0 (взрыв неизбежен)
Тяжесть: катастрофическая (несколько погибших)
Допустимый риск: 10⁻⁵ /год

Требуемое снижение риска:
RRF = Частота × Вероятность / Допустимый риск
RRF = 0.1 × 1.0 / 10⁻⁵ = 10 000

LOPA уже учитывает другие IPL (предохранительный клапан, оператор):
- Предохранительный клапан: снижение в 100 раз (PFD = 0.01)
- Независимый алярм оператора: снижение в 10 раз

Оставшийся RRF для SIS: 10 000 / (100 × 10) = 10
PFD_SIS = 1/10 = 0.1 → SIL 1 (10⁻² ... 10⁻¹)

Если предохранительного клапана нет: PFD_SIS = 0.001 → SIL 2
</code></pre><hr><h2>Архитектура Safety Instrumented System</h2><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Типовая SIS архитектура для SIL 2:

  Датчики (1oo2 или 2oo3)
       │
  [Логический решатель — Safety PLC]
       │
  Исполнительные устройства (финальные элементы)
  
1oo1: один из одного — нет резервирования (SIL 1)
1oo2: один из двух — высокая готовность, ложные срабатывания
2oo2: два из двух — низкая доступность к опасности, ложные отказы
2oo3: два из трёх — оптимальный баланс (SIL 2-3)

Для SIL 2 часто используют:
- Датчики: 1oo2D (один из двух с диагностикой) или 2oo3
- Logic Solver: 1oo1D (один с диагностикой) или 1oo2
- Финальные элементы: 1oo1 (один клапан) или 1oo2 (два параллельных)
</code></pre><hr><h2>Safety PLC: ключевые отличия от обычных ПЛК</h2><p>Safety PLC (FS-PLC — Fail-Safe PLC) — это не просто обычный ПЛК с "безопасным" лейблом. Конструктивные отличия:</p><p><strong>Аппаратные:</strong></p><ul><li><p>Двойное/тройное резервирование процессора</p></li><li><p>Непрерывная взаимная проверка процессоров (cross-checking)</p></li><li><p>Ошибка → переход в безопасное состояние (обычно: все выходы = 0)</p></li><li><p>Диагностика: обнаружение &gt; 99% опасных отказов (DC — Diagnostic Coverage)</p></li><li><p>Специальные I/O модули с самодиагностикой</p></li></ul><p><strong>Программные:</strong></p><ul><li><p>Память программы верифицирована (CRC/хэш)</p></li><li><p>Данные хранятся дважды или с кодом исправления ошибок</p></li><li><p>Принцип fail-safe: при любой неопределённости → безопасное состояние</p></li><li><p>Ограниченный набор инструкций (только сертифицированные блоки)</p></li></ul><p><strong>Популярные Safety PLC:</strong></p><ul><li><p>Siemens SIMATIC S7-300F / S7-400F / S7-1500F</p></li><li><p>Rockwell Allen-Bradley GuardLogix 5580</p></li><li><p>Schneider Modicon M580 Safety</p></li><li><p>ABB AC500-S</p></li><li><p>Pilz PSS 4000</p></li></ul><hr><h2>Программирование Safety PLC: особенности</h2><p>На примере Siemens S7-1500F + TIA Portal Safety:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Ключевые принципы программирования Safety:

1. Fail-safe блоки (FB) vs Standard блоки
   - Безопасный код должен использовать ТОЛЬКО сертифицированные F-блоки
   - Смешивание: Standard → F OK; F → Standard НЕЛЬЗЯ без копирования

2. F-Signature: каждый F-блок имеет уникальную подпись
   - Изменение любого бита → другая подпись → требует повторной верификации

3. Consistent data transfer:
   - При передаче данных между F и Standard зонами — специальная процедура
   - Данные защищены от случайного изменения

4. Passivation: при ошибке датчика — F-код устанавливает безопасное значение (0 или FALSE)
   - Программа должна обрабатывать пассивацию явно!
</code></pre><pre spellcheck="" class="tmiCode" data-language="pascal"><code>(* Пример F-программы на Structured Text (S7-1500F / TIA Portal) *)
(* Функция безопасности: аварийная остановка при высоком давлении *)

FUNCTION_BLOCK FB_PressureShutdown
VAR_INPUT
    PressureHigh_1 : BOOL;     // Датчик высокого давления 1 (активен = HIGH)
    PressureHigh_2 : BOOL;     // Датчик высокого давления 2 (резервный)
    EStop_1        : BOOL;     // Кнопка аварийной остановки 1
    EStop_2        : BOOL;     // Кнопка аварийной остановки 2
    Reset          : BOOL;     // Сброс защиты (после устранения причины)
    
    // Каналы датчиков (после F_DI блока)
    CH_Pressure_1  : BOOL;     // TRUE = канал исправен
    CH_Pressure_2  : BOOL;
    CH_EStop_1     : BOOL;
    CH_EStop_2     : BOOL;
END_VAR

VAR_OUTPUT
    ShutdownCommand : BOOL;    // TRUE = закрыть клапан, остановить насосы
    AlarmActive     : BOOL;    // TRUE = авария активна
    AlarmCode       : INT;     // Код аварии
    ReadyToReset    : BOOL;    // Можно сбросить (причина устранена)
END_VAR

VAR
    Trip_Pressure   : BOOL;
    Trip_EStop      : BOOL;
    Trip_ChannelFault : BOOL;
    TripLatch       : SR;      // SR-триггер (Set-Reset)
END_VAR

(* Анализ датчиков давления (1oo2 логика) *)
(* Срабатывание при ЛЮБОМ из двух датчиков *)
Trip_Pressure := (PressureHigh_1 AND CH_Pressure_1) OR
                 (PressureHigh_2 AND CH_Pressure_2);

(* Кнопка аварийной остановки (2oo2 логика для предотвращения ложных срабатываний) *)
(* Исправный канал при нажатой кнопке EStop: NC контакт → LOW *)
Trip_EStop := (NOT EStop_1 AND CH_EStop_1) OR
              (NOT EStop_2 AND CH_EStop_2);

(* Отказ канала датчика = безопасное состояние (принцип fail-safe) *)
Trip_ChannelFault := NOT CH_Pressure_1 OR NOT CH_Pressure_2 OR
                     NOT CH_EStop_1   OR NOT CH_EStop_2;

(* Защёлка аварии: Set при любой причине, Reset только при явном сбросе *)
TripLatch(
    SET1 := Trip_Pressure OR Trip_EStop OR Trip_ChannelFault,
    RESET := Reset AND NOT (Trip_Pressure OR Trip_EStop OR Trip_ChannelFault)
    (* Нельзя сбросить пока причина активна! *)
);

ShutdownCommand := TripLatch.Q1;
AlarmActive     := ShutdownCommand;

(* Определяем код аварии *)
IF Trip_ChannelFault THEN
    AlarmCode := 3;  (* Наивысший приоритет: отказ диагностики *)
ELSIF Trip_EStop THEN
    AlarmCode := 2;
ELSIF Trip_Pressure THEN
    AlarmCode := 1;
ELSE
    AlarmCode := 0;
END_IF;

(* Условие готовности к сбросу *)
ReadyToReset := AlarmActive AND
                NOT Trip_Pressure AND NOT Trip_EStop AND NOT Trip_ChannelFault;

END_FUNCTION_BLOCK
</code></pre><hr><h2>Финальные элементы: клапаны и их диагностика</h2><p>Финальный элемент (обычно отсечной клапан) — самое слабое место SIS. Клапан может "прикипеть" в открытом положении — и не закроется по команде.</p><p><strong>Диагностика финальных элементов:</strong></p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Частичное хождение клапана (Partial Valve Stroke Test, PVST):
- Раз в 3-6 месяцев в рабочем режиме
- Клапан закрывается на 10-15% от полного хода
- Проверяется отклик (позиционер, время отклика)
- Полное закрытие не происходит → нет нарушения производства
- Сокращает интервал плановой проверки → снижает PFD

Полная проверка (Full Stroke Test):
- При плановом останове (раз в год или реже)
- Полное закрытие, замер времени
- Проверка концевых выключателей
</code></pre><pre spellcheck="" class="tmiCode language-python" data-language="Python"><code>def calculate_pfd_valve_with_pvst(
    pfd_full_stroke: float,    # PFD без диагностики
    pvst_coverage:   float,    # Покрытие диагностикой PVST (обычно 0.6-0.8)
    pvst_interval_months: int, # Интервал PVST
    full_test_interval_years: int  # Интервал полной проверки
) -&gt; dict:
    """
    Расчёт PFD финального элемента с учётом PVST.
    
    Упрощённая модель (для точного расчёта — IEC 61511-1 Annex K).
    """
    
    # Частота опасных отказов (из datasheet производителя или OREDA)
    # Например, для шарового клапана DN100: lambda_D ≈ 1e-6 /час
    lambda_d = pfd_full_stroke / (full_test_interval_years * 8760 / 2)
    
    # PFD без PVST (только полная проверка раз в год)
    pfd_no_pvst = lambda_d * full_test_interval_years * 8760 / 2
    
    # PFD с PVST: диагностика снижает эффективный интервал
    # PFD_pvst ≈ PFD_full × (1 - DC_pvst) + PFD_full × DC_pvst × (Ti_pvst/Ti_full)
    pvst_interval_years = pvst_interval_months / 12
    
    pfd_with_pvst = (pfd_no_pvst * (1 - pvst_coverage) +
                     pfd_no_pvst * pvst_coverage * 
                     (pvst_interval_years / full_test_interval_years))
    
    reduction_factor = pfd_no_pvst / pfd_with_pvst if pfd_with_pvst &gt; 0 else 1
    
    return {
        'pfd_without_pvst':  round(pfd_no_pvst, 6),
        'pfd_with_pvst':     round(pfd_with_pvst, 6),
        'reduction_factor':  round(reduction_factor, 1),
        'sil_without_pvst':  1 if pfd_no_pvst &gt;= 1e-2 else 2 if pfd_no_pvst &gt;= 1e-3 else 3,
        'sil_with_pvst':     1 if pfd_with_pvst &gt;= 1e-2 else 2 if pfd_with_pvst &gt;= 1e-3 else 3,
    }

# Пример расчёта:
result = calculate_pfd_valve_with_pvst(
    pfd_full_stroke      = 0.01,   # PFD при ежегодном тестировании = SIL 1
    pvst_coverage        = 0.7,    # PVST выявляет 70% опасных отказов
    pvst_interval_months = 3,      # PVST раз в квартал
    full_test_interval_years = 1
)
print(f"PFD без PVST: {result['pfd_without_pvst']} (SIL {result['sil_without_pvst']})")
print(f"PFD с PVST:   {result['pfd_with_pvst']} (SIL {result['sil_with_pvst']})")
print(f"Снижение PFD: в {result['reduction_factor']} раз")
</code></pre><hr><h2>Жизненный цикл SIS: от концепции до вывода из эксплуатации</h2><p>По IEC 61511, жизненный цикл SIS включает 16 фаз:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Фаза 1-4: Анализ
  1. Анализ опасностей (HAZOP)
  2. Оценка рисков (SIL Determination / LOPA)
  3. Концепция безопасности
  4. Требования к SIS (SRS — Safety Requirements Specification)

Фаза 5-9: Проектирование
  5. Архитектура SIS
  6. Выбор оборудования
  7. Проектирование F-программы
  8. Factory Acceptance Test (FAT)
  9. Site Acceptance Test (SAT)

Фаза 10-13: Эксплуатация
  10. Ввод в эксплуатацию
  11. Плановое техническое обслуживание
  12. Периодическое функциональное тестирование (Proof Test)
  13. Управление изменениями (MOC — Management of Change)

Фаза 14-16: Завершение
  14. Вывод из эксплуатации
  15. Оценка соответствия (SIL Verification)
  16. Функциональная оценка (Functional Safety Assessment)
</code></pre><hr><h2>HAZOP: анализ опасностей и работоспособности</h2><p>HAZOP (Hazard and Operability Study) — методология анализа, где команда экспертов применяет "направляющие слова" к каждому параметру процесса:</p><pre spellcheck="" class="tmiCode language-plaintext" data-language="Простой текст"><code>Направляющие слова × Параметры → Отклонения → Последствия → Защиты

Параметры: расход, температура, давление, уровень, состав, время
Направляющие слова: Нет, Больше, Меньше, Другое, Обратное, Часть

Примеры:
"Нет расхода" → охладитель не подаётся → перегрев реактора → взрыв
  Защита: датчик расхода + BPCS блокировка; SIS ESD; предохранительный клапан

"Больше давления" → разрыв оборудования → утечка газа → взрыв/пожар
  Защита: HS датчик давления + SIS; предохранительный клапан на 120% рабочего

"Обратный поток" → смешение несовместимых реагентов
  Защита: обратный клапан; двойная задвижка с блоком (Double Block &amp; Bleed)
</code></pre><hr><h2>Практические советы по SIS-проектам</h2><p><strong>1. Независимость — не просто разные ПЛК</strong> Датчики SIS и BPCS должны быть на РАЗНЫХ отборах давления/температуры, разных кабелях, разных шинах питания. Один кабельный канал для обоих = нет независимости.</p><p><strong>2. Воздействие оператора — только через стандартный HMI</strong> Обход (bypass) защиты — только физический (байпасный выключатель), с аудитом и процедурой. Никакого программного обхода через меню!</p><p><strong>3. Документируйте каждое изменение через MOC</strong> Изменение уставки датчика, замена компонента, изменение программы — всё через официальную процедуру управления изменениями. Иначе следующий HAZOP или audit найдёт несоответствие.</p><p><strong>4. Proof Test — проводите регулярно и документируйте</strong> PFD рассчитан на конкретный интервал тестирования. Пропустили proof test — ваш SIL больше не действителен формально (и реально выше PFD).</p><hr><h2>Заключение</h2><p>Функциональная безопасность — это та область, где нет места импровизации. Стандарты IEC 61508/61511 написаны на основе реальных промышленных катастроф и содержат выверенные методологии.</p><p>Для старта: изучите IEC 61511 (он ориентирован на процессные установки и более практичный, чем базовый 61508). Пройдите курс TÜV Rheinland или TÜV SÜD по Functional Safety Engineer — они дают систематическое понимание и признаваемый сертификат.</p><p>И помните: в функциональной безопасности "достаточно хорошо" и "почти правильно" не существует. Либо соответствует SIL, либо нет. Промежуточных значений нет.</p>]]></description><guid isPermaLink="false">152</guid><pubDate>Sat, 21 Mar 2026 20:59:50 +0000</pubDate></item></channel></rss>
