3.26. Шина CAN в коде

machine.CAN оборачивает аппаратный контроллер CAN. Инициализируйте его с идентификатором шины и скоростью передачи в битах:

from machine import CAN

can = CAN(1, 500_000)
can.set_filters(None)        # accept all incoming IDs

Скорость передачи в битах должна точно совпадать у всех остальных узлов на шине — 125_000, 250_000, 500_000 и 1_000_000 являются распространёнными значениями. set_filters() настраивает, какие ID контроллер будет пропускать; None означает принимать всё (полезно при поднятии канала, менее полезно, когда шина загружена).

3.26.1. Внутри контроллера

Между Python-кодом камеры и шиной находятся три аппаратных элемента — почтовые ящики TX, фильтр приёма и FIFO приёма. Каждый из них соответствует части API CAN, используемой ниже.

Блок-схема контроллера CAN. На стороне TX (вверху) can.send() помещает кадры в один из трёх почтовых ящиков TX, а блок арбитража выбирает, какой ящик следующим выйдет на шину. На стороне RX (внизу) входящие кадры проходят через фильтр приёма; принятые кадры попадают в FIFO приёма, а can.recv() читает FIFO с его самого старого конца.

Почтовые ящики TX, фильтр приёма и FIFO приёма контроллера между программным обеспечением и шиной.

  • Почтовые ящики TX. Небольшой набор аппаратных слотов (обычно три), которые хранят исходящие кадры, переданные камерой через send(), но ещё не достигшие шины. Когда шина свободна, контроллер выбирает почтовый ящик с ID наименьшего номера (наивысший приоритет) и проводит за него арбитраж на шине. Камера не выбирает почтовый ящик; контроллер назначает его сам и возвращает его индекс из send().

  • Фильтр приёма. Настраиваемая аппаратура, которая сравнивает ID каждого входящего кадра со списком шаблонов и отбрасывает всё, что не совпадает. Прошедшие кадры продолжают путь в FIFO приёма; отклонённые кадры отбрасываются контроллером, так и не достигнув Python. set_filters() настраивает эти шаблоны.

  • FIFO приёма. Очередь first-in, first-out (первым пришёл — первым ушёл): первый вошедший кадр выходит первым, как очередь в кассу. Контроллер добавляет принятые кадры в хвост очереди по мере их поступления, а recv() извлекает их с головы в том же порядке. Очередь важна, потому что шина ловит кадры в фоновом режиме, пока Python занят чем-то другим; затем камера опустошает их по одному, не теряя ни одного, пока FIFO не переполнен.

3.26.2. Отправка кадра

send() помещает кадр в очередь на передачу:

can.send(0x123, b"\x01\x02\x03\x04")

Первый аргумент — это ID (11-битное целое число для стандартного кадра); второй — полезная нагрузка (от 0 до 8 байт для CAN Classic). Вызов возвращает небольшой целочисленный индекс, идентифицирующий аппаратный почтовый ящик, в который попал кадр. Контроллер проводит арбитраж с любыми другими передатчиками на шине и при необходимости повторяет передачу без дальнейшего участия программного обеспечения.

Для расширенных (29-битных) ID добавьте флаг CAN.FLAG_EXT_ID через побитовое ИЛИ в третий аргумент:

can.send(0x18FF1234, b"hello", CAN.FLAG_EXT_ID)

3.26.3. Приём кадров

При запуске контроллер не имеет установленного фильтра и отбрасывает каждый входящий кадр. Прежде чем recv() вернёт что-либо, вызовите set_filters() один раз — простейшая форма это None, которая принимает любой ID:

can.set_filters(None)   # accept every frame

recv() затем возвращает следующий кадр в FIFO приёма или None, если ничего не ожидает:

msg = can.recv()
if msg is not None:
    can_id, data, flags, errs = msg
    print("got", hex(can_id), bytes(data))

Шина заполняет FIFO приёма в фоновом режиме, поэтому главный цикл просто опустошает его так быстро, как итерируется. Пока FIFO глубже, чем самый длинный промежуток между опустошениями, кадры не теряются.

3.26.4. Фильтры

Реальная шина обычно занята кадрами, до которых камере нет дела. Аппаратные фильтры позволяют контроллеру отбрасывать нежелательные ID, прежде чем они достигнут FIFO. set_filters() принимает список кортежей (id, mask, flags); кадр проходит фильтр, если его ID, наложенный маской mask, совпадает с настроенным id:

# Accept only IDs 0x100 - 0x10F (mask off the bottom 4 bits)
can.set_filters(((0x100, 0x7F0, 0),))

# Accept IDs 0x300 and 0x700 exactly
can.set_filters(((0x300, 0x7FF, 0),
                 (0x700, 0x7FF, 0)))

Несовпавшие кадры отбрасываются контроллером и никогда не появляются в recv(), что экономит и пространство буфера, и процессорное время.

3.26.5. Состояния ошибок и восстановление

Реальная шина CAN накапливает ошибки передачи — замыкания на землю, отсутствующие узлы, электрические помехи, искажающие биты. Контроллер ведёт два счётчика, которые отслеживают это поведение: счётчик ошибок передачи (TEC) и счётчик ошибок приёма (REC); каждый увеличивается, когда контроллер обнаруживает ошибку, и уменьшается после успешной передачи. Значения счётчиков переводят контроллер в одно из четырёх состояний:

  • Error Active (TEC и REC оба ниже 96). Нормальная работа. Когда узел обнаруживает ошибку шины, он передаёт доминантный активный кадр ошибки, который заставляет каждый другой узел отбросить текущий кадр, чтобы отправитель мог повторить попытку.

  • Error Warning (любой из счётчиков достигает 96). По-прежнему полностью активен на шине — состояние предупреждения это программный сигнал о том, что ошибки накапливаются, а не изменение поведения.

  • Error Passive (любой из счётчиков достигает 128). Узел всё ещё на шине, но прекращает отправку доминантных кадров ошибок; ошибки теперь сигнализируются пассивными (рецессивными) кадрами ошибок, чтобы неисправный узел не мог продолжать разрушать работу шины для всех остальных.

  • Bus Off (TEC достигает 256). Контроллер решил, что этот узел слишком ненадёжен для участия. Он отключается от шины, прекращает передачу и подтверждение и остаётся вне работы, пока программное обеспечение явно его не перезапустит.

Первые три перехода полностью автоматические — по мере того как счётчики уменьшаются после успешных кадров, контроллер сам возвращается к Error Active без вмешательства.

Bus Off — единственное состояние, требующее действия со стороны программного обеспечения. restart() сбрасывает контроллер и возвращает его в Error Active. Типичный приём — проверять состояние из главного цикла и перезапускать после короткой задержки, чтобы дать шине время устаканиться:

import time
from machine import CAN

can = CAN(1, 500_000)
can.set_filters(None)

while True:
    if can.state() == CAN.STATE_BUS_OFF:
        time.sleep_ms(100)
        can.restart()
    # ... rest of the loop

Текущие значения счётчиков доступны через get_counters() для диагностики — TEC, который стабильно растёт на в остальном тихой шине, обычно указывает на проводку, согласование или неправильно настроенную скорость передачи.