Jump to content
View in the app

A better way to browse. Learn more.

T.M.I IThub

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Почему 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-разработку, промышленную автоматизацию и встраиваемые системы. Это инвестиция, которая окупается.

User Feedback

Create an account or sign in to leave a review

There are no reviews to display.

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.