Почему CAN Bus? История одного протокола
1983 год. Инженеры Bosch смотрят на жгут проводки в Mercedes-Benz W126 и понимают, что так продолжаться не может. 1000+ метров провода, сотни коннекторов — система ненадёжна, дорога и тяжела. Им нужна шина данных, по которой все блоки управления могут общаться.
В 1986 году появляется CAN (Controller Area Network). В 1991 году Mercedes внедряет CAN в S-класс. Сегодня нет ни одного автомобиля без CAN. И не только автомобиля: промышленные роботы, строительная техника, медицинское оборудование, поезда, самолёты.
Секреты успеха:
Надёжность — дифференциальный сигнал, устойчив к помехам
Детерминизм — приоритетная схема без коллизий
Простота — только 2 провода (CANH и CANL)
Скорость — до 1 Мбит/с (Classic CAN), до 8 Мбит/с (CAN FD)
Физический уровень: как это работает
CAN использует дифференциальную пару: два провода CANH и CANL. Информация кодируется разностью напряжений, а не абсолютным значением.
Рецессивный бит (логическая 1):
CANH ≈ 2.5В, CANL ≈ 2.5В → разность ≈ 0В
Доминантный бит (логический 0):
CANH ≈ 3.5В, CANL ≈ 1.5В → разность ≈ 2В
Дифференциальный сигнал нечувствителен к синфазным помехам — если на оба провода наводится шум, разность остаётся неизменной. Именно поэтому CAN работает в двигательном отсеке автомобиля рядом с высоковольтной проводкой зажигания.
Топология
Строго линейная шина с терминаторами 120 Ом на концах:
[Узел A]──────[Узел B]──────[Узел C]──────[Узел D]
120Ом 120Ом
Максимальная длина зависит от скорости:
Скорость | Макс. длина |
|---|---|
1 Мбит/с | 25 м |
500 Кбит/с | 100 м |
250 Кбит/с | 250 м |
125 Кбит/с | 500 м |
10 Кбит/с | 5000 м |
Структура CAN-фрейма (Standard 11-bit)
SOF│ Identifier (11 бит) │RTR│IDE│r0│ DLC (4) │ Data (0-8 байт) │ CRC │ACK│ EOF
1 11 1 1 1 4 0-64 15 2 7
SOF (Start of Frame): 1 доминантный бит — все узлы синхронизируются.
Identifier (ID, 11 бит): Идентифицирует тип сообщения, а не адрес отправителя/получателя. Одновременно определяет приоритет — чем меньше ID, тем выше приоритет. ID=0 — наивысший приоритет.
RTR (Remote Transmission Request): Запрос данных от другого узла (редко используется).
DLC (Data Length Code): Количество байт данных, 0–8.
Data: Полезные данные, 0–8 байт.
CRC (15 бит): Контрольная сумма для обнаружения ошибок.
ACK: Все узлы, успешно принявшие фрейм, устанавливают доминантный бит в поле ACK. Отправитель проверяет — если ACK не получен, повторяет передачу.
Extended Frame (29-bit ID)
Для приложений где 2048 идентификаторов мало (J1939, CANopen):
SOF│ ID_A (11) │SRR│IDE=1│ ID_B (18 бит) │RTR│...
29-битный ID даёт 536 870 912 возможных идентификаторов.
Битовый арбитраж: без коллизий
Самая элегантная часть CAN. Когда два узла начинают передачу одновременно — нет коллизии, как в Ethernet. Побеждает тот, у кого ID меньше (приоритетнее).
Механизм: каждый передающий узел одновременно читает шину. Пока он видит то, что передаёт — продолжает. Как только видит расхождение (отправил рецессивный 1, а на шине доминантный 0 — значит другой узел передаёт доминантный бит с более высоким приоритетом) — немедленно прекращает передачу и переходит в режим приёма.
Узел A: 0 0 0 1 0 ... (ID = 0b00010...)
Узел B: 0 0 0 0 1 ... (ID = 0b00001...)
Бит 4:
Узел A передаёт рецессивный (1)
Узел B передаёт доминантный (0)
Шина показывает доминантный (0)
→ Узел A видит расхождение и ОСТАНАВЛИВАЕТСЯ
→ Узел B продолжает передачу
Узел B выиграл арбитраж! Нет потерянных данных, нет задержек.
Обработка ошибок и состояния узла
CAN имеет развитую систему самодиагностики. Каждый узел ведёт два счётчика:
TEC (Transmit Error Counter)
REC (Receive Error Counter)
Состояния узла:
Error Active (TEC<128, REC<128) — Нормальная работа
↓ TEC или REC ≥ 128
Error Passive (TEC≥128 или REC≥128) — Узел работает, но:
- Не посылает Active Error Flags
- Ждёт 8 рецессивных бит между передачами
↓ TEC ≥ 256
Bus Off — Узел ОТКЛЮЧЁН от шины
(требует программного сброса или 128×11 рецессивных бит)
Это важно: неисправный узел не "разваливает" шину, а сначала становится пассивным, затем отключается — остальные продолжают работать.
STM32: встроенный CAN-контроллер
STM32F103 имеет встроенный bxCAN (Basic Extended CAN). Пины: PA11/PA12 или PB8/PB9.
#include "stm32f1xx_hal.h"
CAN_HandleTypeDef hcan;
// ===== ИНИЦИАЛИЗАЦИЯ CAN 500 Кбит/с =====
void CAN_Init_500kbps(void)
{
hcan.Instance = CAN1;
// Тайминг для 500 Кбит/с при тактовой 36 МГц
// Bit time = Prescaler × (1 + BS1 + BS2)
// 36 МГц / 4 / (1+7+2) = 900 Кбит/с ... нет, подберём:
// 36 МГц / 9 / (1+3+2) = 500 Кбит/с ← правильно
hcan.Init.Prescaler = 9;
hcan.Init.Mode = CAN_MODE_NORMAL; // CAN_MODE_LOOPBACK для теста!
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_3TQ; // BS1 = 3 TQ
hcan.Init.TimeSeg2 = CAN_BS2_2TQ; // BS2 = 2 TQ
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = ENABLE; // Автоматический выход из Bus-Off
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE; // Автоповтор при ошибках
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority= DISABLE;
HAL_CAN_Init(&hcan);
// ===== ФИЛЬТР ПРИЁМА =====
CAN_FilterTypeDef filter = {0};
// Принимать ВСЕ сообщения (маска 0 — все биты любые)
filter.FilterBank = 0;
filter.FilterMode = CAN_FILTERMODE_IDMASK;
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterIdHigh = 0x0000;
filter.FilterIdLow = 0x0000;
filter.FilterMaskIdHigh = 0x0000; // Маска 0 = принимать всё
filter.FilterMaskIdLow = 0x0000;
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan, &filter);
// Активировать прерывания приёма
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_CAN_Start(&hcan);
}
// ===== ОТПРАВКА СООБЩЕНИЯ =====
HAL_StatusTypeDef CAN_SendMessage(uint32_t id, uint8_t *data, uint8_t len)
{
CAN_TxHeaderTypeDef txHeader;
uint32_t txMailbox;
txHeader.StdId = id; // 11-битный ID
txHeader.ExtId = 0;
txHeader.IDE = CAN_ID_STD;
txHeader.RTR = CAN_RTR_DATA;
txHeader.DLC = len;
txHeader.TransmitGlobalTime = DISABLE;
return HAL_CAN_AddTxMessage(&hcan, &txHeader, data, &txMailbox);
}
// ===== ПРИЁМ ЧЕРЕЗ ПРЕРЫВАНИЕ =====
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_ptr)
{
CAN_RxHeaderTypeDef rxHeader;
uint8_t rxData[8];
if (HAL_CAN_GetRxMessage(hcan_ptr, CAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK) {
uint32_t id = (rxHeader.IDE == CAN_ID_STD) ? rxHeader.StdId : rxHeader.ExtId;
// Обработка по ID
switch (id) {
case 0x100: // Состояние узла 1
process_node1_status(rxData, rxHeader.DLC);
break;
case 0x200: // Измерения температуры
process_temperature_data(rxData, rxHeader.DLC);
break;
default:
// Неизвестное сообщение
break;
}
}
}
// Пример обработки температурных данных
// Договорённость: 2 байта = температура × 10 (signed)
void process_temperature_data(uint8_t *data, uint8_t len)
{
if (len < 2) return;
int16_t raw = (int16_t)((data[0] << 8) | data[1]);
float temperature = raw / 10.0f;
if (temperature > 80.0f) {
// Высокая температура — принять меры
activate_cooling();
}
}
// ===== ПРИМЕР: ПЕРИОДИЧЕСКАЯ ОТПРАВКА ДАННЫХ УЗЛА =====
void CAN_SendNodeStatus(void)
{
uint8_t data[8];
// Байт 0: статусные биты
data[0] = 0x00;
if (motor_running) data[0] |= 0x01;
if (fault_active) data[0] |= 0x02;
if (io_ready) data[0] |= 0x04;
// Байты 1-2: скорость двигателя (об/мин × 10, unsigned)
uint16_t speed_raw = (uint16_t)(motor_speed_rpm * 10.0f);
data[1] = speed_raw >> 8;
data[2] = speed_raw & 0xFF;
// Байты 3-4: ток (А × 100, signed)
int16_t current_raw = (int16_t)(motor_current_a * 100.0f);
data[3] = current_raw >> 8;
data[4] = current_raw & 0xFF;
// Байты 5-6: температура (°C × 10, signed)
int16_t temp_raw = (int16_t)(temperature_c * 10.0f);
data[5] = temp_raw >> 8;
data[6] = temp_raw & 0xFF;
// Байт 7: номер пакета (для обнаружения потерь)
static uint8_t packet_num = 0;
data[7] = packet_num++;
CAN_SendMessage(0x100, data, 8);
}
Arduino + MCP2515: добавляем CAN
Arduino не имеет встроенного CAN. Используем MCP2515 — внешний CAN-контроллер с SPI.
#include <SPI.h>
#include <mcp2515.h> // Библиотека arduino-mcp2515
MCP2515 mcp2515(10); // CS pin
struct can_frame rxMsg, txMsg;
void setup() {
Serial.begin(115200);
SPI.begin();
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ); // 500 Кбит/с, кварц 8 МГц
mcp2515.setNormalMode(); // или setLoopbackMode() для теста без шины
Serial.println("CAN Bus Ready");
}
// Отправка измерений температуры
void sendTemperature(float temp_celsius) {
txMsg.can_id = 0x200; // ID нашего сообщения
txMsg.can_dlc = 2; // 2 байта данных
// Упаковываем: температура × 10, int16
int16_t raw = (int16_t)(temp_celsius * 10.0f);
txMsg.data[0] = raw >> 8;
txMsg.data[1] = raw & 0xFF;
mcp2515.sendMessage(&txMsg);
}
void loop() {
// Приём сообщений
if (mcp2515.readMessage(&rxMsg) == MCP2515::ERROR_OK) {
Serial.print("ID: 0x");
Serial.print(rxMsg.can_id, HEX);
Serial.print(", DLC: ");
Serial.print(rxMsg.can_dlc);
Serial.print(", Data: ");
for (int i = 0; i < rxMsg.can_dlc; i++) {
Serial.print("0x");
Serial.print(rxMsg.data[i], HEX);
Serial.print(" ");
}
Serial.println();
// Обработка сообщения статуса узла
if (rxMsg.can_id == 0x100 && rxMsg.can_dlc >= 3) {
bool running = rxMsg.data[0] & 0x01;
bool fault = rxMsg.data[0] & 0x02;
uint16_t speed_raw = (rxMsg.data[1] << 8) | rxMsg.data[2];
float speed = speed_raw / 10.0f;
Serial.print("Узел 1: ");
Serial.print(running ? "Работает" : "Стоит");
if (fault) Serial.print(" [АВАРИЯ]");
Serial.print(", Скорость: ");
Serial.print(speed);
Serial.println(" об/мин");
}
}
// Отправка своих данных каждые 100 мс
static uint32_t lastSend = 0;
if (millis() - lastSend >= 100) {
lastSend = millis();
float temp = 25.0f + analogRead(A0) * 0.05f;
sendTemperature(temp);
}
}
Linux SocketCAN: мощный инструментарий
На Linux (Raspberry Pi, промышленные PC) CAN работает через SocketCAN — часть ядра с 2009 года.
# Настройка CAN интерфейса
# Если есть USB-CAN адаптер (Peak PCAN, Kvaser, SeedStudio)
sudo ip link set can0 up type can bitrate 500000
# Или через модуль ядра для MCP2515 на Raspberry Pi
# /boot/config.txt:
# dtoverlay=mcp2515-can0,oscillator=8000000,interrupt=25
# Просмотр трафика
candump can0
# Отправка сообщения: ID=0x100, 3 байта
cansend can0 100#010203
# Фильтрация - только ID 0x100-0x1FF
candump can0 100~1FF
# Статистика ошибок
ip -details -statistics link show can0
# Python + python-can
# pip install python-can
import can
import struct
import time
# Создаём интерфейс
bus = can.interface.Bus(channel='can0', bustype='socketcan')
# Отправка
def send_temperature(temp: float):
raw = struct.pack('>h', int(temp * 10)) # big-endian signed short
msg = can.Message(arbitration_id=0x200, data=raw, is_extended_id=False)
bus.send(msg)
# Приём с фильтрацией по ID
bus.set_filters([
{"can_id": 0x100, "can_mask": 0x7FF, "extended": False}, # Только 0x100
{"can_id": 0x200, "can_mask": 0x7FF, "extended": False}, # И 0x200
])
print("Ожидаем CAN-сообщения...")
for msg in bus:
if msg.arbitration_id == 0x100:
status = msg.data[0]
speed = struct.unpack('>H', bytes(msg.data[1:3]))[0] / 10.0
current = struct.unpack('>h', bytes(msg.data[3:5]))[0] / 100.0
temp = struct.unpack('>h', bytes(msg.data[5:7]))[0] / 10.0
print(f"Узел 100: {'Работает' if status & 1 else 'Стоит'}, "
f"n={speed} об/мин, I={current}А, T={temp}°C")
Протоколы высшего уровня
J1939 — для грузовой техники и дизелей
J1939 — стандарт SAE для коммуникации в коммерческом транспорте и тяжёлой технике. Работает поверх CAN с 29-битными ID.
Структура J1939 ID (29 бит):
Приоритет (3б) │ Reserved (1б) │ Data Page (1б) │ PGN (8б) │ Source Address (8б)
Популярные PGN (Parameter Group Number):
PGN 0xF004 (EEC1)— данные двигателя: обороты, момент, нагрузкаPGN 0xFEF1 (CCVS)— скорость, круиз-контрольPGN 0xFEE5 (HOURS)— моточасыPGN 0xFEE6 (TIME)— время и дата
CANopen — для промышленной автоматизации
CANopen — стандарт для промышленного оборудования: частотники, серводрайвы, I/O модули. Определяет:
Словарь объектов (Object Dictionary) — структурированное хранилище всех параметров устройства
PDO (Process Data Object) — быстрая передача данных реального времени (заменяет аналог 4-20мА)
SDO (Service Data Object) — медленная конфигурация и чтение параметров
NMT (Network Management) — управление состоянием узлов
Heartbeat / Node Guarding — контроль жизнеспособности узлов
Пример: частотник с CANopen. Через SDO читаем/пишем параметры (коэффициент разгона, макс. частота). Через PDO каждые 10 мс обмениваемся уставкой и текущими значениями.
Диагностика сети CAN
Признаки проблем:
Симптом | Вероятная причина |
|---|---|
Bus Off у одного узла | Неисправный узел, помехи на кабеле |
Много ошибок CRC | Неправильный биттайминг, плохая линия |
Узел не видит свои сообщения в ACK | Он один на шине, некому подтверждать |
Периодические потери сообщений | Нет терминаторов или два на одном конце |
Все узлы в Bus Off | Неисправная нагрузка на шине |
Инструменты:
PEAK PCAN-USB (~€80) + PCAN-View (бесплатно) — лучший бюджетный вариант
Kvaser Leaf Light — популярен с CANalyzer
Vector CANalyzer — профессиональный инструмент для автомобильных применений
Linux candump / cansniffer — бесплатно, для socketcan-интерфейсов
Wireshark с плагином SocketCAN — анализ на Linux
CAN FD: следующее поколение
CAN FD (Flexible Data-rate) — развитие стандарта 2012 года. Нет обратной совместимости на физическом уровне, но концептуально тот же подход.
Отличия от Classic CAN:
До 64 байт данных в одном фрейме (против 8)
До 8 Мбит/с в поле данных (при сохранении 1 Мбит/с для арбитража)
Обязательная CRC 21-бит для надёжности при высоких скоростях
CAN FD активно внедряется в новых автомобилях (все автомобили с AUTOSAR) и промышленной автоматизации. STM32G4, STM32H7 имеют встроенный FDCAN-контроллер.
Практический чеклист для CAN-системы
□ Терминаторы 120 Ом на ОБОИХ концах шины (и только там)
□ Экранированная витая пара (для помехонагруженных сред)
□ Максимальная длина ответвлений (stub) < 0.3 м
□ Все узлы имеют ОДИНАКОВУЮ скорость и биттайминг
□ Адреса узлов (если используются) уникальны
□ Заземление: один общий провод + заземление экрана в одной точке
□ Проверить напряжение CANH/CANL (рецесс.: оба ~2.5В, домин.: разность ~2В)
□ Подключить анализатор и убедиться в отсутствии ошибок
□ Задокументировать все ID и значения данных
Заключение
CAN Bus — это элегантное инженерное решение, выдержавшее испытание десятилетиями. Детерминизм без коллизий, встроенная обработка ошибок, устойчивость к помехам — всё это делает CAN первым выбором для распределённых систем управления с жёсткими требованиями к надёжности.
Для старта: MCP2515 + Arduino даёт минимальный стенд за $5. SocketCAN на Raspberry Pi — бесплатный анализатор. PCAN-USB — профессиональный инструмент за разумные деньги.
Знание CAN открывает двери в automotive-разработку, промышленную автоматизацию и встраиваемые системы. Это инвестиция, которая окупается.
Create an account or sign in to leave a review
There are no reviews to display.