Введение
CAN (Controller Area Network) — промышленный стандарт сети, повсеместно используемый в автомобилях, промышленной автоматизации и некоторых embedded-системах для надёжной связи между множеством устройств по общей шине. В отличие от UART из Arduino/STM32-циклов (связь «точка-точка» между двумя устройствами), CAN — это именно шина, к которой одновременно подключено множество узлов, обменивающихся сообщениями. В статье разберём физические основы CAN перед переходом к практической работе с ней из Qt-приложений.
Концепция
Физически CAN-шина состоит из двух проводов (CAN_H, CAN_L), по которым передаётся дифференциальный сигнал — разница напряжений между проводами, а не абсолютное напряжение относительно земли, что даёт высокую помехоустойчивость, критичную для автомобильной электрической среды с большим количеством электромагнитных помех. Сообщения передаются кадрами (frames), каждый из которых содержит идентификатор (определяющий тип/источник сообщения и его приоритет — кадры с меньшим числовым идентификатором имеют более высокий приоритет при одновременной попытке передачи нескольких узлов), и до 8 байт данных в классическом CAN (расширенный CAN FD, статья 366, позволяет больше). Стандартные скорости передачи — 125 кбит/с, 250 кбит/с, 500 кбит/с, 1 Мбит/с, с обязательным требованием, чтобы все узлы на одной шине работали на одной и той же скорости.
Пример кода: Обзор CAN-шины
Структура классического CAN-кадра (CAN 2.0):
| SOF | Identifier (11 или 29 бит) | RTR | Control | Data (0-8 байт) | CRC | ACK | EOF |
SOF — Start of Frame, начало кадра
Identifier — идентификатор сообщения (11 бит — стандартный формат, 29 бит — расширенный)
RTR — Remote Transmission Request, флаг запроса данных без самих данных
Control — длина данных (DLC, Data Length Code)
Data — сами передаваемые данные, от 0 до 8 байт
CRC — контрольная сумма для проверки целостности
ACK — подтверждение получения от других узлов на шине
EOF — End of Frame, конец кадра
// Концептуальный пример CAN-кадра, как он представлен в большинстве CAN-библиотек/драйверов
struct can_frame {
uint32_t can_id; // идентификатор сообщения (приоритет — чем меньше число, тем выше приоритет)
uint8_t can_dlc; // длина данных в байтах (0-8 для классического CAN)
uint8_t data[8]; // сами данные
};
// Пример конкретного автомобильного сообщения — обороты двигателя (типичный, хотя и
// варьирующийся между производителями, формат для иллюстрации концепции)
struct can_frame engineRpmFrame = {
.can_id = 0x0C0, // идентификатор, специфичный для конкретного автомобиля/протокола
.can_dlc = 2,
.data = {0x1A, 0x2B} // два байта, кодирующие значение оборотов по специфичной формуле
};
Пояснения к коду
Структура CAN-кадра показывает, что само сообщение по своей природе компактно (до 8 байт полезных данных в классическом CAN) — это принципиальное архитектурное ограничение, отражающее изначальное назначение CAN для частых, коротких сообщений о состоянии системы (текущая скорость, температура, статус датчика), а не для передачи больших объёмов данных, для чего эта технология просто не предназначена. Поле can_id одновременно служит и идентификатором типа сообщения, и определяет приоритет при арбитраже — когда несколько узлов на шине одновременно начинают передачу, узел с меньшим числовым значением идентификатора аппаратно «побеждает» и продолжает передачу, тогда как остальные узлы автоматически прерывают свою попытку и повторяют её позже — этот механизм арбитража встроен на физическом уровне самого протокола, а не реализуется программно.
Подводные камни
- Восприятие CAN как протокола, аналогичного UART, просто с другим физическим уровнем — принципиальное отличие в том, что CAN — это широковещательная шина, где каждое сообщение получают все подключённые узлы одновременно (а не направленная связь между конкретными двумя устройствами), и любая логика, написанная с предположением «точка-точка» связи (как в UART-циклах), требует пересмотра для шинной топологии CAN, где сообщения нужно явно фильтровать по идентификатору, чтобы каждый узел реагировал только на интересующие его сообщения.
- Подключение узлов с разной настроенной скоростью передачи к одной физической шине — в отличие от некоторых других протоколов, способных автоматически определять скорость, CAN требует, чтобы абсолютно все узлы на шине были явно настроены на одну и ту же скорость передачи; даже один неправильно настроенный узел способен полностью нарушить связь для всех остальных узлов той же шины, а не просто не работать сам по себе.
- Отсутствие правильного терминирования шины (terminating resistors), обычно 120-омных резисторов на обоих физических концах шины CAN — без правильного терминирования сигнал может отражаться от концов кабеля, вызывая искажения и ошибки передачи, особенно заметные на больших расстояниях или высоких скоростях передачи; это требование физического уровня, не решаемое никакими программными средствами.
- Слепое копирование значений идентификаторов сообщений (
can_id) из примеров или документации для другого автомобиля/устройства без проверки, что протокол сообщений (соответствие конкретных идентификаторов конкретным типам данных) специфичен для конкретного производителя и модели, и в общем случае не стандартизирован для произвольных CAN-сетей (за исключением некоторых стандартизированных подмножеств, таких как OBD-II, статья 365) — попытка интерпретировать сообщения с непроверенными, скопированными «наугад» идентификаторами может дать полностью неверные, но похожие на правдоподобные значения данных.