ГлавнаяАкадемияОсновы умного дома → CAN-шина

CAN-шина

Урок 2 · Основы умного дома · 30 мин · theory

Введение в CAN-шину: от автомобилей до умных зданий

Изначально разработанная компанией Bosch в 1980-х годах для автомобильной промышленности, шина CAN (Controller Area Network) стала настоящей революцией. Ее задача состояла в том, чтобы заменить сложные и тяжелые жгуты проводки в автомобилях, где каждый датчик и исполнительный механизм требовал отдельной линии до центрального блока управления. CAN предложила элегантное решение: единую двухпроводную шину, к которой все устройства могли подключаться параллельно.

Успех в автомобильной отрасли, где требования к надежности и устойчивости к электромагнитным помехам чрезвычайно высоки, быстро привлек внимание инженеров из других сфер. Промышленная автоматизация, медицинское оборудование и, конечно, системы управления зданиями (BMS) увидели в CAN-шине ключевые преимущества:

> 🔗 Связанный материал: Физический уровень CAN-шины, использующий дифференциальную передачу сигнала, имеет много общего с RS-485. Рекомендуем освежить знания об этом интерфейсе, обратившись к уроку COURSE-01-M04-L02 "RS-485 и Modbus RTU".

Фундаментальное отличие CAN от ранее рассмотренных протоколов заключается в его мульти-мастер архитектуре. В сети Modbus RTU есть только одно активное устройство (Master), которое инициирует все обмены данными. Остальные (Slave) лишь отвечают на запросы. В сети CAN все узлы равноправны. Любой узел может начать передачу, как только шина освободится. Если два или более узлов попытаются сделать это одновременно, в действие вступает уникальный механизм разрешения конфликтов, называемый арбитражем. Это делает CAN идеальным решением для распределенных систем управления, где важна быстрая и гарантированная доставка критически важных событий.

---

Принцип работы: Арбитраж доступа и структура кадра

Сердцем CAN-протокола и его главным конкурентным преимуществом является механизм недеструктивного арбитража доступа к шине. Чтобы понять, как он работает, необходимо познакомиться с несколькими ключевыми концепциями.

Механизм доступа: CSMA/CD+AMP

Аббревиатура CSMA/CD+AMP расшифровывается как Carrier Sense Multiple Access with Collision Detection and Arbitration on Message Priority (Множественный доступ с контролем несущей и обнаружением коллизий с арбитражем на основе приоритета сообщения). Разберем по частям:

  • CSMA (Carrier Sense Multiple Access): Перед началом передачи каждый узел "прослушивает" шину. Если шина свободна, узел начинает передачу. Если занята — ждет ее освобождения.
  • CD+AMP (Collision Detection + Arbitration on Message Priority): Это самая интересная часть. Если два или более узла начинают передачу одновременно, возникает коллизия (столкновение). В отличие от Ethernet, где коллизия — это сбой, требующий повторной отправки, в CAN это штатный и абсолютно безболезненный механизм определения, какое из сообщений важнее.
  • Для разрешения коллизий в CAN используются два логических состояния шины:

    > 💡 Подсказка: Ключевое преимущество механизма арбитража: самое приоритетное сообщение (с наименьшим значением идентификатора) гарантированно будет доставлено в предсказуемое время, даже при 100% загрузке шины. Это делает CAN идеальным для систем реального времени, таких как управление тормозами, пожарная сигнализация или системы безопасности.

    Процесс арбитража

    Представим, что два устройства, Узел А и Узел Б, одновременно начинают передачу. Каждое сообщение в CAN начинается с уникального идентификатора (CAN ID). Устройство с меньшим значением ID имеет более высокий приоритет.

  • Оба узла начинают побитово выставлять на шину свои идентификаторы, начиная со старшего бита.
  • После отправки каждого бита узел считывает состояние шины.
  • Если узел отправил рецессивный бит ("1"), а считал с шины доминантный ("0"), это означает, что другое устройство с более высоким приоритетом (с "0" в этой позиции ID) также ведет передачу.
  • Узел, "проигравший" арбитраж, немедленно прекращает свою передачу и переходит в режим приема. Он не будет мешать "победителю" и автоматически попробует передать свое сообщение снова, как только шина освободится.
  • Узел, "выигравший" арбитраж (чей ID содержал "0" в спорной позиции), даже не замечает коллизии и продолжает передачу своего кадра, как будто ничего не произошло.
  • Таким образом, данные никогда не теряются и не искажаются, а самое приоритетное сообщение всегда проходит.

    Структура CAN-кадра

    Каждое сообщение, передаваемое по шине, упаковано в кадр (frame) строго определенной структуры.

    | Поле | Назначение |

    | ------------------ | ------------------------------------------------------------------------------------------------------------- |

    | Arbitration Field | Содержит идентификатор сообщения (11 бит для стандартного кадра или 29 бит для расширенного). Используется для арбитража. |

    | Control Field | Содержит DLC (Data Length Code) — 4-битное поле, указывающее количество байт данных в кадре (от 0 до 8). |

    | Data Field | Поле данных. Содержит от 0 до 8 байт полезной нагрузки, которую передает узел. |

    | CRC Field | Контрольная сумма (15 бит), которая позволяет принимающим устройствам проверить целостность полученного кадра. |

    | ACK Field | Поле подтверждения. Узел-отправитель передает здесь рецессивный бит. Любой узел, корректно принявший кадр, "перебивает" его доминантным битом, подтверждая успешный прием. Если отправитель не видит доминантного бита в ACK-слоте, он понимает, что его сообщение никто не принял, и генерирует ошибку. |

    | End of Frame | Семь рецессивных битов, означающих конец кадра. |

    Эта жесткая структура и встроенные механизмы проверки (CRC, ACK) обеспечивают исключительно высокую надежность передачи данных.

    ---

    Практика: Настройка CAN в Linux и утилиты can-utils

    Контроллеры нашей платформы работают под управлением ОС Linux (Debian), которая имеет мощную встроенную поддержку CAN-шины через фреймворк SocketCAN. Он представляет CAN-интерфейсы в системе как обычные сетевые интерфейсы (по аналогии с Ethernet: `eth0`, `eth1`), что сильно упрощает работу с ними. Наш контроллер оснащен физическим CAN-трансивером, который в системе виден как устройство `can0`.

    > ⚠️ Внимание: Перед подключением к физической CAN-шине всегда проверяйте наличие и правильность установки терминирующих резисторов (120 Ом) на обоих концах шины. Отсутствие или неправильная установка терминаторов приведет к отражению сигнала и нестабильной работе сети.

    Настройка CAN-интерфейса

    Прежде чем начать обмен данными, необходимо сконфигурировать и активировать CAN-интерфейс. Это делается с помощью стандартных сетевых утилит Linux.

  • Установка скорости (bitrate): Все устройства на одной CAN-шине должны работать на одинаковой скорости. Распространенные скорости: 125000 (125 kbit/s), 250000 (250 kbit/s), 500000 (500 kbit/s). Для установки скорости 250 kbit/s для интерфейса `can0` выполните в терминале контроллера:
  •     # Сначала переводим интерфейс в состояние "down", чтобы изменить его параметры

    sudo ip link set can0 down

    # Устанавливаем тип can и задаем bitrate

    sudo ip link set can0 type can bitrate 250000

  • Активация интерфейса: После настройки скорости интерфейс нужно "поднять".
  •     sudo ip link set can0 up

  • Проверка статуса: Убедиться, что интерфейс настроен и активен, можно командой `ip -details link show can0`:
  •     ip -details link show can0

    В выводе вы должны увидеть `state UP` и установленный `bitrate`.

    Утилиты can-utils для диагностики

    Для работы с CAN-шиной из командной строки существует незаменимый набор утилит `can-utils`. Установим его:

    sudo apt-get update
    

    sudo apt-get install can-utils

    Теперь рассмотрим самые полезные утилиты из этого пакета:

        # Показать все кадры на интерфейсе can0
    

    candump can0

    Пример вывода:

        can0  1F3   [8]  01 02 03 04 05 06 07 08

    can0 2A0 [4] FF 00 FF 00

    can0 7DF [2] 10 11

    Здесь `1F3`, `2A0` — это CAN ID, `[8]`, `[4]` — длина данных (DLC), а далее — сами байты данных в шестнадцатеричном формате.

        # Запустить сниффер на интерфейсе can0
    

    cansniffer -c can0

    Вывод будет динамически обновляться, показывая только "живые" данные, что сильно упрощает наблюдение.

        # Отправить кадр на интерфейс can0
    

    # Формат: cansend #

    cansend can0 123#11.22.33.44.55.66.77.88

    Эта команда отправит кадр с ID `0x123` и 8 байтами данных.

    Чтобы включить реле, которое слушает команду по ID `0x250`, команда может выглядеть так:

        # Включить реле 1 (команда 0x01 в первом байте данных)

    cansend can0 250#0101

    Освоив эти три утилиты, вы получаете полный контроль над диагностикой и отладкой любой CAN-сети на объекте прямо из командной строки контроллера.

    ---

    Интеграция CAN-шины в Node-RED

    После того как CAN-интерфейс настроен на уровне операционной системы, мы можем легко интегрировать его в наши потоки автоматизации в Node-RED. Для этого используется специализированная палитра узлов.

    Установка узлов и настройка

    Основной палитрой для работы с SocketCAN является `node-red-contrib-canbus`.

  • Установка: Откройте редактор Node-RED, перейдите в `Меню -> Manage palette -> Install`, введите в поиске `node-red-contrib-canbus` и нажмите `Install`.
  • Обзор узлов: Палитра добавляет три основных узла:
  • * `can in`: Узел для приема CAN-кадров из шины.

    * `can out`: Узел для отправки CAN-кадров в шину.

    * `can log`: Узел для логирования CAN-трафика в файл (для данной задачи используется реже).

    Прием данных с помощью узла `can in`

    Предположим, у нас есть CAN-датчик температуры, который периодически отправляет свои показания с ID `0x310`. Наша задача — принять это сообщение и вывести его значение в консоль отладки.

  • Разместите на холсте узел `can in` и узел `Debug`.
  • Откройте настройки узла `can in`.
  • * CAN Bus: Укажите имя интерфейса, которое мы настроили в Linux: `can0`.

    * Use Filter: Настоятельно рекомендуется использовать фильтры, чтобы узел реагировал только на нужные ID. Это снижает нагрузку на Node-RED. Включите эту опцию.

    * CAN IDs: Добавьте новый фильтр. В поле `ID` введите `310` (можно вводить как в десятичном, так и в hex-формате, например `0x310`).

  • Соедините выход `can in` со входом узла `Debug`. Разверните поток.
  • Теперь, как только датчик отправит в шину кадр с ID `0x310`, узел `can in` сработает и сформирует сообщение `msg`. Давайте посмотрим на его структуру:

    {
    

    "_msgid": "a1b2c3d4.5e4d3c",

    "payload": {

    "ts_sec": 1678890000,

    "ts_usec": 123456,

    "id": 784,

    "ext": false,

    "rtr": false,

    "dlc": 2,

    "data": {

    "type": "Buffer",

    "data": [1, 39]

    }

    },

    "topic": ""

    }

    📋 Ключевые понятия:

    Чтобы извлечь из этого полезную информацию (температуру), нам понадобится `Function` узел для обработки `Buffer`. Если, по документации датчика, температура передается в двух байтах как целое число со знаком (signed integer) и ее нужно разделить на 10, код в узле `Function` будет следующим:

    // Получаем буфер с данными
    

    const buffer = msg.payload.data;

    // Читаем 16-битное знаковое целое число из буфера (Big Endian)

    const rawValue = buffer.readInt16BE(0);

    // Преобразуем в реальное значение

    const temperature = rawValue / 10.0;

    // Формируем новое сообщение по нашему стандартному контракту

    msg.payload = {

    value: temperature,

    unit: "°C",

    source: "can-sensor-" + msg.payload.id,

    ts: Date.now()

    };

    return msg;

    Отправка данных с помощью узла `can out`

    Теперь решим обратную задачу: отправим команду на CAN-устройство, например, на релейный модуль. Пусть для включения реле №3 необходимо отправить кадр с ID `0x400`, где первый байт данных равен `3` (номер реле), а второй — `1` (команда "включить").

  • Разместите на холсте узел `Inject` и узел `can out`.
  • В узле `can out` в поле CAN Bus укажите `can0`.
  • В узле `Inject` мы сформируем сообщение, которое поймет `can out`. Узел ожидает на входе объект `msg`, где `msg.payload` имеет схожую с выходом `can in` структуру.
  • Настройте узел `Inject` на отправку `JSON` и вставьте следующий объект:
  • {
    

    "id": 1024,

    "ext": false,

    "rtr": false,

    "dlc": 2,

    "data": [3, 1]

    }

    > ℹ️ Информация: Вместо массива чисел `data` может быть и объектом `Buffer`. Зачастую удобнее собирать `msg.payload` в узле `Function`, где можно динамически формировать данные команды.

    Соедините `Inject` и `can out`, разверните поток. При нажатии на `Inject` узел `can out` сформирует и отправит в шину `can0` кадр с нужными нам параметрами, и реле должно сработать.

    Таким образом, используя всего два узла и базовые утилиты Linux, мы получаем полный и надежный инструмент для интеграции устройств с CAN-шиной в нашу систему автоматизации на платформе HI.