CAN + CANOpen + CANfestival + STM32. Часть первая

CANOpen
Вы когда-нибудь участвовали в форумных склоках на тему «Что лучше — написать свое или взять готовое?». Лично я обожаю подобные вещи, причем я больше предпочитаю наблюдать, нежели участвовать. Ведь это так весело, сначала обсуждаются технические детали, потом постепенно переходят на личности, потом кого-то банят… Вы скажете, что я второсортное быдло, которому нравятся такие же второсортные развлечения? Знаете, а зачем это отрицать, зачем заниматься самообманом? Лучше принять себя таким, какой я есть, и гордо нести это как знамя: «Да, я — быдло!». Поэтому вместо самоотрицания я попытаюсь «набросить», и если мне повезет, то там, в комментариях разгорится такой спор переходящий от технический деталей к личным оскорблениям.
Так что может послужить предпосылкой такого спора? Ну вот, например, такая тема. Есть у вас в микроконтроллере замечательная штука — интерфейс CAN, помощью которого можно сделать массу замечательных вещей: шину для связи между модулями в умном доме, между узлами в собственном роботе, между модулями в ПЛК, между электроникой в автомобиле и т.д. и т.п. Но что пустить «поверх» CAN: свой самодельный протокол или взять готовый. А если готовой протокол, то что лучше свой самописный стек или готовый? Займу пожалуй одну из крайних позиций — все готовое, и протокол и стек к нему, а именно CANOpen и CANFestival.


Совсем без теории, к сожалению, не получится. Поэтому вкратце вот о чем:
  1. Пару слов о самом CAN
  2. О самом CANOpen
  3. И немного о CANfestival


Коротко о CAN


Почему именно CAN и чем он лучше, например, UART+RS485? На мой взгляд основное преимущество CAN состоит в побитовом арбитраже, который позволят «говорить» одновременно двум узлам на шине. Разумеется одновременно они говорить не будут, но благодаря этому механизму узлы «договорятся», кто скажет первый, а кто второй. Это происходит на уровне контроллера CAN и разработчику совершенно не нужно за этим следить. Это открывает возможность отправлять данные в шину асинхронно, если узлы хотят что-то передать, то «пусть говорят», когда хотят.

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


А вот с протоколом CANOpen двумя словами не отделаться.

Объектный словарь (Object dictionary) и что с ним делать

Представьте себе целую сеть из устройств, объединенных шиной CAN. Для того, чтобы однозначно различать эти устройства друг от друга, CANOpen вводит такое понятие, как Node Id (идентификатор узла). Этот Node Id во избежании недоразумений должен быть уникальным для каждого устройства на шине. Если кто-то знаком с протоколом Modbus, то он вероятно знает, что такое адрес ведомого устройства (Slave Address). Именно с этим адресом и можно провести аналогию для Node Id.
Источником данных в сети CANOpen служат объектные словари (object dictionary) узлов. Этот термин, как впрочем и все остальные, крайне редко встречается в различного рода литературе в переведенном на русский язык виде. Гораздо чаще приходится иметь дело именно с Object dictionary, а еще чаще с его аббревиатурой OD. Так вот OD это, что-то вроде двухуровневого списка или, может быть правильней, таблицы. Первый уровень пронумерован индексами от 0 до 65535. Второй уровень списка (или таблицы) пронумерован субиндексами от 0 до 255. В пунктах этого списка (или ячейках таблицы) и находятся данные, которыми обмениваются узлы в сети CANOpen. Таким образом, чтобы индексировать данные нужно два параметра индекс и субиндекс. Часто можно встретит запись в таком формате XXXXsubYY, где XXXX индекс в шестнадцатеричном формате, а YY то шестнадцатеричный субиндекс, например 1028sub03. Если продолжить строить аналогию с модбасом, то в модбасе есть карта регистров, а CANOpen есть объектный словарь OD. Вот только OD в отличии от карты регистров имеет более сложную структуру и имеет гораздо больше типов данных. Что за данные в нем лежат? Да что угодно — текущие значения аналоговых или дискретных входов, управляющие значения для выходных сигналов, настройки, сведения об устройстве и производителе и т.д. и т.п.

Для записи и чтения данных в/из OD, CANOpen предоставляет следующие сообщения:
  • SDO (service data object) — чтение и запись данных в OD по запросу.
  • PDO (process data object) — сообщения для отправки данных асинхронно (хотя не обязательно). Как правильно через эти сообщения передаются текущие измерения и управляющие сигналы


SDO



Читать и писать в объектный словарь может ведущий в сети CANOpen или, как его часто называют, «мастер» (master). Как правило, когда мастер настраивает какой-либо узел, он при помощи SDO записывает в OD интересующие его параметры. Формат сообщения с запросом на чтение или запиись зависит от типа SDO — короткое однофреймовое (expedited) или длинное многофреймовое (segmented). Expedited-формат предназначен для обмена небольшим объемом данных — до 4-ех байт, для всего что больше нужен формат segmented.

Однофреймовый expedited-запрос формируется следующим образом (см. картинку ниже):
  • 11-битный идентификатор = 0x600 + Node Id
  • Длинна = 8 байт
  • 0-ой байт данных — спецификатор команды, подробнее о нем ниже
  • 1-2 байты данных — соответственно младший и старший байты интересующего индекса OD
  • 3 байт — субиндекс OD
  • 4-7 байты — данные, если это запись

В нулевом байте css равен 2, если это команда на запись, 4, если запрос на чтение. Значение n немного не очевидное — это количество байт в сообщении, в которых НЕТ ДАННЫХ.

Ответ на такой запрос выглядит следующим образом:
  • 11-битный идентификатор = 0x580 + Node Id
  • Длинна = 8 байт
  • 0-ой байт данных — спецификатор команды
  • 1-2 байты данных — соответственно младший и старший байты интересующего индекса OD
  • 3 байт — субиндекс OD
  • 4-7 байты — данные, если это ответ на читающий запрос


Если запрос успешно выполнен то css будет равен 4, в случае же ошибки — 8. Всё остальное имеет тот же смысл, что и в запросе.
Для ясности приведу пример. Предположим, что нужно записать 4-ёх байтное число 0x12345678 в объектный словарь некоторого узла с Node Id равным 1 по индексу 0x2000 субиндексу 2. Фрейм с запросом будет выглядеть следующим образом:

ID = 0x601, len = 8, data = 0x23 0x00 0x20 0x02 0x78 0x56 0x34 0x12

Ответ в случае успеха будет выглядеть так:

ID = 0x581, len = 8, data = 0x60 0x00 0x20 0x02 0x00 0x00 0x00 0x00

Обращаю внимание, что данные во фрейме располагаются в порядке от младшего байта к старшему. Поэтому поле данных индекс представлен как 0x00 0x20, а данные идут байт за байтом 0x78 0x56 0x34 0x12.

Еще пример — запись 2-ух байт в индекс 0x2002 субиндекс 0x03:
Запрос
ID = 0x601, len = 8, data = 0x2B 0x02 0x20 0x03 0x34 0x12 0x00 0x00
Ответ
ID = 0x581, len = 8, data = 0x60 0x02 0x20 0x03 0x00 0x00 0x00 0x00

И еще пример — 1 байт в индекс 0x2003 субиндекс 0x04:
Запрос
ID = 0x601, len = 8, data = 0x2F 0x03 0x20 0x04 0x12 0x00 0x00 0x00
Ответ
ID = 0x581, len = 8, data = 0x60 0x03 0x20 0x04 0x00 0x00 0x00 0x00

Многофреймовый вариант, описывать не стану, во-первых лень, а во-вторых смысла особого нет, т.к. если использовать готовый стек (CANFestival), то он заботливо скроет от разработчика низкоуровневые подробности.

PDO


У SDO есть один недостаток вытекающий из принципа его работы — обмен данными только по запросу. Но как же преимущество CAN c возможностью спорадической отправки данных? Для передачи данных из OD спорадически используется особый тип сообщений именуемый PDO (Process Data Object). Суть его простая — на каждый узел в сети выделено по 4 формата фреймов для отправки данных (txPDO0, txPD01, txPDO2, txPDO3) и по 4 формата фреймов на прием (rxPDO0, rxPDO1, rxPDO2, rxPDO3). Формируются он достаточно просто:
txPDO0: ID = 0x180 + Node ID, Len = 8, Data = [8 байт данных]
txPDO1: ID = 0x280 + Node ID, Len = 8, Data = [8 байт данных]
txPDO2: ID = 0x380 + Node ID, Len = 8, Data = [8 байт данных]
txPDO3: ID = 0x480 + Node ID, Len = 8, Data = [8 байт данных]

rxPDO0: ID = 0x200 + Node ID, Len = 8, Data = [8 байт данных]
rxPDO1: ID = 0x300 + Node ID, Len = 8, Data = [8 байт данных]
rxPDO2: ID = 0x400 + Node ID, Len = 8, Data = [8 байт данных]
rxPDO3: ID = 0x500 + Node ID, Len = 8, Data = [8 байт данных]

Таким образом каждый узел, может в совершенно любой момент времени отправлять до 32 байт и столько же принимать. Делать он это может по собственному желанию, никто его спрашивать не будет. Очень удобно, например, для измерений — свежие данные будут отправлены, когда будет закончено измерение, а не когда их запросят.
Источником данных для PDO служит объектный словарь, точнее некоторые его ячейки. Эти ячейки настраиваются, после чего стек CANOpen должен отправлять их содержимое в PDO каждый раз, когда они изменятся или периодически, или по команде в зависимости от опций.
Аналогично на приемной стороне настраиваются ячейки OD, куда будут попадать входящие PDO.

Network Management (NMT)


Каждый узел в сети CANOpen может находится в одном из многочисленных состояний:
  • Initialization — состояние сразу подачи питания;
  • PreOperational — «предбоевое» состояние. В это режиме узел уже шлет Heartbeat'ы (о них ниже), но еще не шлет и не принимает PDO;
  • Operational — «боевой» режим, в котором узел шлет и принимает PDO;
  • Stopped — режим полной остановки, узел будет недоступен даже по SDO. Предполагается, что в этот режим будут загонять устройство перед выключением.


Управление этими режимами осуществляется с помощью специальных сообщений имя которым NMT. Их формат следующий:

ID = 0x00, Len = 2, data = [cmd (1 байт)] [nodeId (1 байт)]

cmd принимает значения 1 — для перевода в режим Operational, 2 — Stopped, 0x80 — PreOperational; nodeId — это Node Id узла, режим которого хотят изменить.

Heartbeat'ы и Node Guarding


Вот в Modbus'е в некотором смысле все просто, если на узел отправили запрос, а он не ответил, то можно считать что он (узел) «помер». Но CANOpen запросы SDO используются только, чтобы настроить устройство, а в «боевом» режиме ведомое устройство может только «слушать» входящие PDO, если это какой-то модуль вывода сигналов, при этом совершенно ничего не отправляя в ответ. Как же понять, что подобный узел вообще в данный момент присутствует на шине? Для этого в CANOpen есть два механизма Heartbeat и Node Guarding. Как правило применяется один из двух, технически конечно можно зарядить и тот и другой, но я такого ни разу не видел.
Механизм Heartbeat'ов достаточно простой: каждый узел в сети с определенным периодом отправляет CAN фреймы следующего содержания:

ID = 0x700 + Node, ID Len = 1, data = [Mode (1 байт)]

Mode принимает значения 0x7F, если узел в режиме PreOperational, 0x05 — Operational, 0x00 — BootUp, 0x04 — Stopped. Значение BootUp отправляется самый первый раз, когда узел появляется на шине. Если от узла перестали приходить Heartbeat'ы, то можно диагностировать потерю связи.

Механизм Node Guarding исповедует прямо противоположный подход. Мастер периодически опрашивает при помощи NMT ведомые устройства на предмет их текущего режима. Нет ответа — нет узла.

Emergency


Если Вы терпеливо дочитали всё до этого места, то я практически уверен, что Вы не страдаете модным нынче недугом «Дефицитом внимания», с чем я Вас искренне поздравляю. Но это так, в качестве лирического отступления.
Возвращаясь к сути, обращу Ваше лишенное дефицита внимание вот на что: в распределенной сети могут происходит всякие неприятности вроде аварий, отказов, превышение уставок и т.д. и т.п. Для того, что бы экстренно об этом оповестить в CANOpen есть специальные сообщения Emergency, которые формируются вот так:

ID = 0x80 + Node Id, Len = 4..8, data = [Error Code(2 байта)] [Error Register(2 байта)] [Manufacturer Specific Error Field(0..4 байт)]

Error Code — это код ошибки, это коды частично стандартизованы протоколом, Error Register — об этом не в этот раз, Manufacturer Specific Error — код ошибки, который разработчики могут определять по своему усмотрению.

CANFestival


CANFesivalЕсть такой замечательный человек — Эдуард Тиссерант (Edouard Tisserant). Он вместе с единомышленниками в свое время реализовал стек, который реализует всё вышеописанное и даже больше. Имя этому стеку CANFestival. Кстати говоря это далеко не единственное достижение Эдуарда, с его именем тесно связан проект Beremiz, с которым познакомил наше сообщество коллега Антон Мидюковantohami .

Ну так вот, в официальном репозитарии CANFestival помимо портов под Linux можно найти порты и под микроконтроллеры AVR. Подробно о том что в этом репозитарии, как портировать и создавать объектные словари и обособленные проекты расскажу во второй части, ибо силы писать эту статью уже кончились.

Заключение


Несмотря на то, что статья чуть менее чем полностью была посвящена описанию протокола CANOpen, этого мало. В качестве исчерпывающего источника по протоколу могу порекомендовать замечательную книгу Embedded Networking with CAN and CANopen. В ней есть всё и даже больше. До портирования на STM32, как видите я не добрался, поэтому об этом в следующий раз, если он будет. На этом и закончу. Спасибо всем прочитавшим!

Комментарии (16)

RSS свернуть / развернуть
Для CANOpen используется 11-битный идентификатор. Почему не используется 29-битный идентификатор?
+1
Точно не знаю. Могу лишь предположить: 11 битный идентификатор ограничивает CANOpen диапазоном NodeId от 1 до 127, вероятно разработчики протокола сочли, что этого более чем достаточно.
0
Предположим, что нужно записать 4-ёх байтное число 0x12345678 в объектный словарь некоторого узла с Node Id равным 1 по индексу 0x2000 субиндексу 2.
А в пакете адрес 0x2001. Алсо, чуть ниже «например, 0х2002», а в пакете опять 0х2001.
txPDO0: ID = 0x180 + Node ID Len = 8 Data = [8 байт данных]
txPDO0: ID = 0x280 + Node ID Len = 8 Data = [8 байт данных]
txPDO0: ID = 0x380 + Node ID Len = 8 Data = [8 байт данных]
txPDO0: ID = 0x480 + Node ID Len = 8 Data = [8 байт данных]
А не
txPDO0: ID = 0x180 + Node ID Len = 8 Data = [8 байт данных]
txPDO1: ID = 0x280 + Node ID Len = 8 Data = [8 байт данных]
txPDO2: ID = 0x380 + Node ID Len = 8 Data = [8 байт данных]
txPDO3: ID = 0x480 + Node ID Len = 8 Data = [8 байт данных]
?
+1
  • avatar
  • Vga
  • 23 сентября 2017, 19:21
Спасибо! Исправил. Только не совсем понял по поводу txPDO0, что там поправить то?
0
Я так понимаю, 32 байта данных передаются не четырьмя запросами txPDO0, а последовательностью txPDO0, txPDO1, txPDO2, txPDO3. Аналогично с приемом.
+1
  • avatar
  • Vga
  • 23 сентября 2017, 20:56
Я похоже слишком невнятно описал работу PDO, поэтому и недоумение. Попробую более подробно переписать эту часть
0
Погуглил. Да, у тебя ошибка, ID 0x280 — это txPDO1, а не txPDO0. Аналогично с остальными.
Алсо, я не совсем понимаю, как ты ухитрился получить 36 байт из четырех пакетов по 8 байт (8*4=32).
+1
  • avatar
  • Vga
  • 24 сентября 2017, 21:30
Точно! Блин, в шары долблюсь — ошибку не вижу. Поправил — спасибо!
0
Вы предложили стек CANFestival для которого я не нашёл порт под STM32. Однако, в CANopenNode порт под STM32 есть.
+1
Да, CANOpenNode это альтернатива. Тут уж дело вкусов, кому что ближе.
0
Почему именно CAN и чем он лучше, например, 485-го?
в самом начале уже путаница и с этим надо что то делать :(
RS-485 это чисто электрический интерфейс на основе витой пары с большими скоростями до 30 мегабит… У Сименса 12 мегабит на Профибус как пример или Модбус у других. Приёмопередатчики электрические дешёвые. Протокол можно организовать любой или стандартизованный.
А вот CAN это и электрический интерфейс и программный протокол под одним названием… и неокрепшие умы чего то не допоняв начинают выдумывать вымыслы :(
Рекомендую читать нормальную литературу, а не черпать сомнительные данные от неопытных в теме блохеров.
-1
А вот CAN это и электрический интерфейс и программный протокол под одним названием…

Ничего подобного. Тогда CANOpen что такое?
0
Ещё раз повторю — RS-485 это чисто электрический интерфейс с подключением через приёмопередатчики=преобразователей уровния типа ADM485, преобразующие электрический симметричный сигнал на двух проводах с амплитудой до -/+12 вольт витой пары в в обычные уровни 3...5 вольт.
Логический сигнал может подаваться с обычного последовательного порта USART микропроцессора или просто с цифровой схемы… например для передачи тактового сигнала на большие расстояния в условиях помех (режим электрического интерфейса RS422).
Программный протокол может быть стандартный типа Профибус или Модбус или DMX512 или самопальный.

CAN — это и электрический аппаратный интерфейс канала связи в виде приёмопередатчиков=преобразователей уровня и программный интерфейс протокола обработки данных.
Аппаратные приёмопередатчики преобразуют электрические сигналы проводной системы CAN в обычные уровни 3...5 вольт,
которые обрабатываются процессорами со специализированными блоками CAN программно
0
С этим я не спорю. Меня смутило вот это
А вот CAN это и электрический интерфейс и программный протокол
. Протокольно зависимая часть CAN реализуется специализированным контроллером это да, но разработчик прикладного ПО, если так можно выразится, к этому отношения не имеет.

А путаницы я никакой не вижу: я не отрицал, что RS-485 это чисто электрический интерфейс, это вы уже сами придумали. В статье возможно надо уточнить, что мол чем CAN лучше RS-485+UART, и на этом вопрос был бы исчерпан.
0
Почему именно CAN и чем он лучше, например, 485-го?
в самом начале уже путаница и с этим надо что то делать
Я вижу ты уже поправил в отношении логики
Почему именно CAN и чем он лучше, например, UART+RS485?
теперь требуется описать и аппаратные различия собственно сетей RS485 и CAN…
и дальше собственно плавно перейти к краткому сравнению программных протоколов CAN и например Модбус… который в некоторых USARTах от STM частично поддерживается на аппаратном уровне.
Хотя лично мне Модбус не нравится из-за самопальства в разделе пользовательских функций… то ли дело Профибус от Сименса :)

Непубличный протокол Профибус от Сименса как то решает задачи связи промышленной автоматики уже лет 30.
Сейчас в промышленности все массово переходят на Эзернет, который вытесняет другие типы сетей… из преимуществ = скорости от 100 мбит до гигабитов и решения коллизий через топологии типа Звезда с резервированными каналами связи и с защищёнными шифрованием протоколами передачи данных…
а CAN только ленивый не ломает через систему подогрева и положения зеркал :)
0
Господа, а вам не кажется, что CAN логичнее сравнивать с I2C? Они куда ближе друг к другу по принципу построения и назначению.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.