AN945: стек CANopen для PIC18 ECAN Печать
Добавил(а) microsin   

CANopen это протокол, основанный на шине Controller Area Network (CAN). Как подсказывает имя протокола, это открытый сетевой стандарт, принятый во всем мире. Хотя CANopen было создан для индустриальной автоматизации, он нашел широкое применение и для других, не индустриальных приложений.

Имеется множество вариантов применения CANopen. Эта статья фокусируется на описании спецификации DS-301 [2, 6], разработанной организацией CAN in Automation (CiA). Фактически большая часть дискуссии ограничена определенными областями спецификации, с акцентом на понимании, как пользователи могут разработать приложения на основе стека CANopen. Чтобы помочь иллюстрировать эти вопросы, было разработано простое приложение примера на основе спецификации CiA DS-401 - обычный модуль ввода вывода (Generic I/O Module). Дополнительный код предоставлен исключительно для демонстрации; таким образом, здесь нет детального обсуждения демонстрационного кода. Однако здесь будут часто использоваться примеры кода с комментариями из этого демо-приложения (здесь приведен перевод апноута AN945 [1], грубые смысловые опечатки по возможности исправлены). Незнакомые аббревиатуры и термины см. в разделе "Словарик" статьи [5].

Весь код, предоставленный в этом апноуте, был разработан для семейств микроконтроллеров PIC18F8680 и PIC18F4680, которые имеют на борту аппаратный интерфейс по технологии ECAN. Код написан для компилятора Microchip C18 v2.30 (или более свежей версии). Хотя код рассчитан на эти 2 семейства микроконтроллеров, его можно адаптировать и на другие семейства PIC18, на борту которых есть CAN.

Ожидается, что читатель уже имеет некоторые знания протокола CANopen, или имеет доступ к последним стандартам CANopen (перечисленным в разделе Ссылки), чтобы прояснить для себя теорию и/или критическую терминологию. Информация, представленная в этом апноуте, склоняется к пониманию реализации демо-приложения и разработке на её основе, вместо того, чтобы сосредотачиваться на многих деталях CANopen.

[Обзор стека]

Стек CANopen предоставляет нижние слои протокола. Этот дизайн включает в себя:

• Встроенную машину состояний для обработки всех коммуникаций между всеми узлами и объектами.
• Сервер сервисного объекта по умолчанию (Default Service Data Object, SDO).
• До 4 передающих и 4 принимающих объекта обработки данных (Process Data Objects), сокращенно TPDO и RPDO.
• Поддержка явного обмена сообщений (Explicit Messaging) и сегментированного (Segmented Messaging).
• Поддержка статического отображения PDO к переменным приложения (PDO mapping).
• Структурированный словарь для объектов PDO и SDO.
• Поддержка протоколов Node Guard / Life Guard.
• Потребитель сообщений SYNC.
• Генератор "сердцебиения" (Heartbeat Producer).
• Поддержка драйвера ECAN.

Как можно увидеть из этого списка, стек CANopen, обсуждаемый здесь, разработан для приложений, которые ведут себя в сети как подчиненные (slave) устройства. Этот дизайн статичен по своей природе, что дает возможность получить более эффективное использование памяти кода.

Действительный код CANopen, написанный на языке C, дополнительно разбит на отдельные файлы исходного кода и заголовков. Это позволит пользователям выбрать для себя нужные службы, которые могут понадобиться для приложения, и селективно собрать для себя проект, удовлетворяющий определенным требованиям. Полный список файлов исходного кода представлен в таблице 1.

Конечно, реальное приложение и некоторые аспекты коммуникаций все еще должны быть разработаны пользователем. Предоставленный стек CANopen Stack дает базу, на которой можно построить свое приложение.

Таблица 1.

Имя файла Описание
CO_CANDRV.c Модуль драйвера ECAN. Эти файлы могут быть заменены другой реализацией драйвера, если это необходимо.
CO_CANDRV.h
CO_COMM.c Службы поддержки коммуникаций (Communication Management). Требуется для всех приложений.
CO_COMM.h
CO_DEV.c Файлы, специфичные для устройства (узла сети CANopen). Пользователь должен отредактировать эти файлы, чтобы учесть специфику устройства.
CO_DEV.h
CO_DICT.c Словарь объектов (Object Dictionary, OD). Требуется для всех приложений.
CO_DICT.h
CO_DICT.def
CO_MAIN.c Основные службы CANopen. Требуется для всех приложений.
CO_MAIN.h
CO_MEMIO.c Функции копирования памяти, используемые OD. Требуется для всех приложений.
CO_MEMIO.h
CO_NMT.c Конечная точка системы управления сетью CANopen.
CO_NMT.h
CO_NMTE.c Конечная точка Node Guard, Heartbeat и Boot-up.
CO_NMTE.h
CO_PDO.c Основные службы PDO.
CO_PDO.h
CO_PDO1.c Конечные точки обработки объекта PDO. Предоставлены в формате шаблона, который требует разработки пользователя, чтобы учесть специфику приложения. Должны использоваться вместе с основными файлами служб PDO.
CO_PDO1.h
CO_PDO2.c
CO_PDO2.h
CO_PDO3.c
CO_PDO3.h
CO_PDO4.c
CO_PDO4.h
CO_SDO1.c Конечная точка коммуникации по умолчанию сервера SDO.
CO_SDO1.h
CO_SYNC.c Конечная точка коммуникации потребителя синхронизации.
CO_SYNC.h
CO_TOOLS.c Инструментарий для преобразования форматов идентификаторов между стандартом Microchip и стандартом CANopen. Чтобы улучшить быстродействие процесса, все идентификаторы COB-ID сохраняются внутренне в формате Microchip. Когда COB-ID представляется по запросу, он преобразовывается в формат CANopen.
CO_TOOLS.h
CO_ABERR.h Общие определения для ошибок. Требуется всеми приложениями.

[Модель кода firmware CANopen]

Программное обеспечение микроконтроллера (firmware) разработано в 3 уровнях, как это показано на рис. 1. Самый низкий уровень это драйвер ECAN, предоставляющий абстрагированную поддержку аппаратуры CAN. Уровень коммуникаций это главный интерфейс между драйвером и обработкой каждой индивидуальной конечной точки.

Помимо приложения, имеется также словарь объектов CANopen (OD). В сущности он находится за пределами объекта коммуникации, и непосредственно подключен к конечной точке SDO.

AN945 Basic firmware model CANopen stack fig01

Рис. 1. Базовая модель firmware стека CANopen.

Драйвер. На нижнем уровне стека находится драйвер ECAN который обслуживается как абстрагированный аппаратный интерфейс. Он реализован в исходных файлах CO_CANDRV.c и CO_CANDRV.h.

Этот драйвер обрабатывает всю связанную с аппаратурой ECAN функциональность, и удобно абстрагируется от более сложной фильтрации, которая является частью протокола CAN. Дальше этот момент обсуждается более подробно.

Управление коммуникациями. Менеджер коммуникаций является частью общего объекта коммуникаций. Он предоставляет захват любых событий из драйвера и верхних уровней приложения, и диспетчеризирует их на соответствующую обработку коммуникационными sub-объектами и функциями. В сущности открытие, закрытие, передача в конечную точку и прием из неё - все это направляется менеджером коммуникаций, который реализован в файлах CO_COMM.c и CO_COMM.h.

Менеджер знает состояние как каждой конечной точки, так и глобальное состояние устройства. Таким образом, он может блокировать сообщения к конечным точкам, если это необходимо на основании локального или глобального состояния.

Другая функция менеджера - использование однобайтного метода "handle", поддерживаемого драйвером, чтобы декодировать события сообщения. Этот handle имеет определенную структуру, разработанную для повышения производительности; она значительно ускоряет декодирование 11-битного или 29-битного идентификатора CAN, чтобы определить, какая функция обработки нужна для определенного сообщения.

Конечные точки. Спецификация CANopen определяет несколько возможных конечных точек. Ниже перечислены 5 объектов конечной точки, которые реализованы в этом примере; в будущем могут быть доступны и другие объекты.

• Сервер SDO по умолчанию (Default Server SDO)
• До 4 статических PDO
• Потребитель сообщений синхронизации
• Подчиненное устройство NMT
• Node Guard или Heartbeat

Предоставлен сервер SDO по умолчанию. Термин "сервер" в данном контексте означает, что код сервера выполняет какие-то запросы клиента SDO (обычно это мастер сети CANopen). Путь коммуникаций SDO прямо привязан к OD; сообщения SDO содержат информацию, которая связывает SDO с определенным объектом. Данные каждого сообщения декодируются, проверяются на достоверность и (если они достоверны) выполняются как событие.

В сущности здесь работают 2 основные операции: чтение (read) и запись (write). Каждая полная передача SDO (которая может состоять из нескольких сообщений) будет либо читать, либо записывать один определенный объект OD. Объект SDO по умолчанию находится в исходных файлах CO_SDO1.c и CO_SDO1.h.

Путь коммуникаций PDO (Process Data Object) непосредственно отображен на объект (или объекты) приложения. Таким образом, путь подразумевается устройством, и в коммуникации не содержится никакой информации о пути. В сущности данные PDO внутренне отображены на один или большее количество объектов (по-русски можно сказать так: в программе есть некоторые переменные, и данные этих переменных присуствуют в PDO). Данные отображаются либо статически (отображение определяется один раз во время компиляции проекта), либо динамически (может быть установлена во время выполнения приложения с помощью обработки команд, поступающих серверу SDO). Одно сообщение может содержать в себе данные более чем одного объекта приложения.

Код firmware, предоставляемый с этим апноутом, поддерживает 4 объекта PDO по умолчанию. Все службы PDO предоставлены в исходных файлах CO_PDO.c и CO_PDO.h. Дополнительные файлы CO_PDOn.c и CO_PDOn.h (здесь n может иметь значение от 1 до 4) используются для реализации отдельных объектов PDO. Они предоставлены в форме шаблона, и должны быть разработаны для удовлетворения требований приложения.

Предоставлено подчиненное устройство, принимающее команды управления сетью (Network Management, сокращенно NMT), как этого требует стандарт CANopen. Объект NMT принимает команды для изменения состояния устройства, или сброса приложения устройства и/или его коммуникаций. На рис. 2 показана машина состояний CANopen, а также команды, которые вызывают срабатывание переходов между состояниями.

AN945 State Machine CANopen device fig02

Рис. 2. Алгоритмическая машина состояний устройства CANopen.

Примечание: не снабженные метками переходы (которые показаны более черными линиями) являются автоматическими и не требуют внешнего события.

Обработка NMT предоставлена в исходных файлах CO_NMT.c и CO_NMT.h.

Спецификациями CANopen требуется, чтобы была одна конечная точка протокола Node Guard или Heartbeat (это 2 разных протокола, и требуется наличие поддержки одного из них). Имеется код обоих этих протоколов; однако в любой момент времени разрешен только один из этих методов поддержки целостности узлов сети (это также определено спецификациями).

Функционал конечной точки Node Guard и Heartbeat предоставлен в исходных файлах CO_NMTE.c и CO_NMTE.h.

Предоставлен один потребитель сообщений синхронизации (synchronization consumer, SYNC). Сообщение SYNC просто предоставляет приложению событие, по которому приложение генерирует любые синхронные сообщения PDO.

Исходные файлы CO_SYNC.c и CO_SYNC.h содержат в себе объект SYNC.

[Словарь объектов CANopen (OD)]

Функции OD предоставляют центральную информационную базу данных для устройства. Каждый объект в пределах устройства представлен в OD по индексу, sub-индексу, вместе с некоторой информацией о доступе. Объектом может быть как простым байтом, так и более сложной структурой данных. Таблица 2 показывает базовые области OD, которые определены по индексу, в соответствии со спецификацией CANopen.

Таблица 2. Диапазоны индексов областей внутри OD.

Индекс Объект
0001-001F Статический тип данных.
0020-003F Комплексные типы данных.
0040-005F Типы данных, специфичные для производителя.
0060-007F Статические типы данных профиля устройства.
0080-009F Комплексные типы данных профиля устройства.
00A0-0FFF Зарезервировано.
1000-1FFF Область профиля коммуникации.
2000-5FFF Область профиля, специфичного для производителя.
6000-9FFF Область стандартизованного профиля.
A000-FFFF Зарезервировано.

Разработка и определение объектов словаря более подробно обсуждается в разделе "Объекты и OD".

С использованием индекса может быть получен доступ к определенному объекту. С точки зрения сети на устройство, доступ к объекту предоставляется через конечную точку SDO или PDO, как это показано на рис. 1. Функционал словаря CANopen реализован в следующих файлах:

• CO_DICT.c
• CO_DICT.h
• CO_DICT.def
• CO_STD.def
• CO_MFTR.def
• CO_PDO.def

Объекты SDO. Спецификацией требуются так называемые "стандартные объекты устройства" (Standard Device Objects, SDO), хотя они не показаны на рис. 1. Эти объекты SDO включают в себе такие данные, как статус, имя устройства, серийный номер, информация о версии.

SDO предоставлены в исходных файлах CO_DEV.c и CO_DEV.h.

[Объекты приложения]

На верхнем уровне стека находится объект приложения, который должен быть определен с учетом поддержки требований приложения, и включен в OD. Реальные объекты определяются и пишутся пользователями для специфики их приложения.

Другой дополнительный код Firmware. Имеются некоторые другие файлы, предоставленные для определения стандартных типов данных, определения ошибок, поддержки функций копирования памяти и предоставления инструментария для преобразования COB-ID. Вот они:• CO_TOOLS.c

• CO_TOOLS.h
• CO_MEMIO.c
• CO_MEMIO.h
• CO_ABERR.h
• CO_TYPES.h

[Настройки времени компиляции]

Всего имеется 40 опций компиляции для конфигурирования исходного кода, учитывающих требования определенного приложения. Большинство этих опций используются для конфигурирования факторов, которые управляют скоростью бит CAN (параметры времени сегмента фазы, ширина скачка синхронизации, прескалер скорости, и т. д.). Все эти опции перечислены в таблице 3.

Таблица 3. Основные опции времени компиляции.

Имя Описание
CAN_BITRATE0_BRGCON1 Настройка скорости по умолчанию для приложения. Значения BRGCON соответствуют конфигурациям для регистров BRGCON, и определяют все требуемые параметры для скорости шины CAN. Пользователю следует обратиться к соответствующему даташиту для получения подробной информации по конфигурированию этих регистров.
CAN_BITRATE0_BRGCON2
CAN_BITRATE0_BRGCON3
CAN_BITRATEn_BRGCON1 Настройка скорости n, где допустимый диапазон для n составляет от 1 до 8. Эти опциональные настройки могут использоваться вместо настроек скорости по умолчанию. Так же, как и с настройками скорости по умолчанию, здесь значения BRGCON соответствуют настройкам для регистра BRGCON.
CAN_BITRATEn_BRGCON2
CAN_BITRATEn_BRGCON3
CAN_BITRATEn Разрешает использовать настройку скорости n.
CAN_MAX_RCV_ENDP Устанавливает максимальное разрешенное количество конечных точек приема в драйвере. Рекомендуется использовать значение 8, чтобы поддерживать все конечные точки приема стека CANopen. Для этого параметра можно установить максимальное значение 16.
CO_NUM_OF_PDO Устанавливает количество поддерживаемых PDO. Допустимое значение от 1 до 4.
CO_SPEED_UP_CODE Разрешает использовать некоторые участки кода в виде встраиваемого ассемблера. С помощью установки этой опции повышается скорость выполнения кода.
CO_SDO1_MAX_RX_BUF Устанавливает максимальное пространство буфера, используемого объектом SDO по умолчанию. Хорошее значение для этого параметра задается по размеру самого большого записываемого объекта.
CO_SDO1_MAX_SEG_TIME Устанавливает максимальное время для сторожевого таймера SDO. Используется для ожидания завершенного сегмента перед сбросом машины состояния SDO.

Установка информации об устройстве. Спецификация CANopen идентифицирует некоторое количество объектов, которые идентифицируют конкретное устройство. Информация, специфическая для устройства, предоставляется через простой набор данных, который находится в OD. Эта информация должна быть включена в приложение. Таблица 4 перечисляет эти объекты.

Таблица 4. Стандартные объекты устройства (Standard Device Objects, SDO).

Имя объекта Описание
rom unsigned long rCO_DevType Тип устройства.
rom unsigned char rCO_DevName[] Имя устройства.
rom unsigned char rCO_DevHardwareVer[] Версия аппаратуры.
rom unsigned char rCO_DevSoftwareVer[] Версия программного обеспечения.
rom unsigned char rCO_DevIdentityIndx Индекс идентификации устройства.
rom unsigned long rCO_DevVendorID Идентификатор производителя.
rom unsigned long rCO_DevProductCode Код изделия.
rom unsigned long rCO_DevRevNo Номер ревизии.
rom unsigned long rCO_DevSerialNo Серийный номер устройства.
unsigned char uCO_DevErrReg Регистр ошибки устройства.
unsigned long uCO_DevManufacturerStatReg Регистр статуса, специфичный для производителя.

[Написание приложения]

При разработке приложения должна быть проделана значительная работа, касающаяся коммуникаций в соответствии со спецификациями CANopen. Предоставленный код упрощает этот труд, предоставляя обработку некоторых низкоуровневых коммуникаций. Кроме работы, необходимой для разработки самого приложения, должны быть также разработаны следующие элементы кода:

• Определение объектов приложения в OD
• Разработка поддержки для сложных объектов
• Разработка функций поддержки необходимых коммуникационных событий CANopen
• Разработка PDO

Эта секция описывает набор инструментария, предоставленный в предлагаемом firmware. Описаны все функции событий и службы, которые могут потребоваться для любого приложения.

Основные службы. Протокол CANopen запускается вызовом функции mCO_InitAll(). Она генерирует сброс драйвера CAN, и приводит к отправке сообщения загрузки (boot-up message). Однако перед запуском протокола CANopen должны быть установлены в надлежащее состояние параметры по умолчанию, относящиеся к специфике коммуникаций. Например, значения node_id (идентификатор узла) и baudrate (скорость по шине CAN) критичны для правильного обмена сообщениями. К другим настройкам относятся опции Node Guard / Heartbeat, объект ошибок устройства, а также специфичный для производителя статус.

После запуска протокола вся обработка происходит через функции mCO_ProcessAllEvents() и mCO_ProcessAllTimeEvents(). Первая обрабатывает все основные коммуникации, связанные с передачей ил приемом сообщений CAN для каждой конечной точки. Вторая функция обрабатывает коммуникацию конечных точек, которая имеет требования по времени, такие как NMTE (Heartbeat/Node Guard) и любая конечная точка PDO. Функция mCO_ProcessAllEvents() должна быть вызвана достаточно часто, чтобы было можно захватить все события сообщения от драйвера. Функция mCO_ProcessAllTimeEvents() должна вызываться с интервалом 1 мс.

В описанном стеке используется свой формат идентификаторов CAN, привязанный к архитектуре CAN микроконтроллеров PIC, это так называемый формат MCHP (или формат Microchip). Соответствие между идентификатором в формате CANopen и идентификатором в формате MCHP показано ниже.

Байт:          B3        B2        B1        B0
               --------  --------  --------  --------
Формат CANopen 28 .. 24  23 .. 16  15 ..  8  7  ..  0
               nmtxxxxx  xxxxxxxx  xxxxxxxx  xxxxxxxx
MCHP format    7  ..  0  15 ..  8  20 .. 16  28 .. 21
               xxxxxxxx  xxxxxxxx  xxxntmxx  xxxxxxxx
 
Формат CANopen                     10 ..  8  7  ..  0
               nmt-----  --------  -----xxx  xxxxxxxx
MCHP format                        3  ..  0  10 ..  3
               --------  --------  xxxntm--  xxxxxxxx
 
n = бит опций 1
m = бит опций 2
t = тип идентификатора (standard = 0, extended = 1)

void mCO_ProcessAllEvents(void)

Это основная подпрограмма, из которой обрабатываются все события. Отсюда обрабатываются все события передачи и приема в Менеджере Коммуникаций. Эта функция должна вызываться так часто, как это только возможно, чтобы можно было обработать любые события коммуникации. Насколько часто это нужно - сильно зависит от драйвера и необходимости ответить на все события драйвера до наступления переполнения.

Пример:

void main(void)
{
   // Выполнение любой специфичной для приложения инициализации:
   TimerInit();   // инициализация таймера
   mSYNC_SetCOBID(0x12);      // Установка SYNC COB-ID (в формате MCHP)
   mCO_SetNodeID(0x01);       // Установка идентификатора узла
   mCO_SetBaud(0x00);         // Установка скорости
   mNMTE_SetHeartBeat(0x00);  // Установка начального heartbeat
   mNMTE_SetGuardTime(0x00);  // Установка начального guard time
   mNMTE_SetLifeFactor(0x00); // Установка начального life time
   mCO_InitAll();             // Инициализация CANopen для запуска
   
   while(1)
   {
      // Обработка событий CANopen:
      mCO_ProcessAllEvents();
      // Обработка специфичных для приложения функций
      ...
      if (TimerIsOverflowEvent())
      {
         // Событие таймера: сюда попадаем каждую 1 мс.
         // Обработка связанных с таймером событий:
         mCO_ProcessAllTimeEvents();
         // Выполнение других функций, привязанных к отсчету реального времени:
         ...
      }
   }
}

void mCO_ProcessAllTimeEvents(void)

Это основная подпрограмма, из которой обрабатываются все события, связанные с реальным временем низкого разрешения. Эта функция должна быть вызвана с интервалом 1 мс. События высокого разрешения (обычно в области микросекунд) должны обрабатываться в приложении. Внутренне эта функция гарантирует, что все объекты стека, которые требуют управления по времени, получили свое событие тика.

Пример см. в предыдущей врезке, где приведено описание подпрограммы mCO_ProcessAllEvents.

void mCO_InitAll(void)

Эта функция должна вызываться после установки всех начальных параметров объектов. Она выдает сброс драйверу CAN, и запускает открытие требуемых коммуникаций. После этого вызова узел появится в сети, и будет отправлено сообщение boot-up.

Функция mCO_InitAll в настоящее время является макросом-оберткой над функцией _CO_COMMResetEventManager. Она должна быть вызвана перед входом в главный цикл main приложения. Пример:

void main(void)
{
   // В этом месте выполняются инициализации, специфичные
   // (настройка выводов GPIO, аппаратуры и т. п.):
   ...
   TimerInit();               // Инициализация таймера
   mSYNC_SetCOBID(0x1000);    // Установка SYNC COB-ID (в формате MCHP)
   mCO_SetNodeID(0x02);       // Установка Node ID
   mCO_SetBaud(0x00);         // Установка скорости CAN
   mNMTE_SetHeartBeat(20000); // Установка начального heartbeat
   mNMTE_SetGuardTime(0000);  // Установка начального guard time
   mNMTE_SetLifeFactor(0x00); // Установка начального life time
   DemoInit();                // Инициализация демонстрационного объекта AN945
   mCO_InitAll();             // Инициализация стека CANopen для запуска, будет
                              // отправлено сообщение boot-up
   while(1)
   {
      // Обработка событий CANopen:
      mCO_ProcessAllEvents();
      // Обработка событий, специфичных для приложения:
      DemoProcessEvents();
      // События таймера происходят с периодом 1 мс:
      if (TimerIsOverflowEvent())
      {
         // Обработка событий, связанных с таймером
         mCO_ProcessAllTimeEvents();
      }
   }
}

void mCO_SetNodeID(unsigned char node_id)

Вызовите эту функцию для установки идентификатора узла node_id. Значение node_id должно быть типа unsigned char, где самый старший бит сброшен (его использование зарезервировано). Дополнительно спецификация CANopen резервирует значение 00h для NodeID. Таким образом, допустимое значение для идентификатора узла node_id находится в диапазоне 01h .. 7Fh включительно. Эта функция должна быть вызвана перед mCO_InitAll().

Параметр:

unsigned char node_id: идентификатор этого узла, допустим диапазон от 01h до 7Fh.

Пример см. во врезке, где приведено описание подпрограммы mCO_ProcessAllEvents.

unsigned char node_id mCO_GetNodeID(void)

Вызовите эту функцию, чтобы получить значение текущего ID, используемого стеком. Значение будет возвращено как unsigned char, допустимый диапазон 01h..7Fh.

void mCO_SetBaud(unsigned char bitrate)

Вызовите эту функцию, чтобы установить скорость по шине для узла. Значение bitrate должно быть в диапазоне 0..8 включительно. Любое другое значение приведет к использованию установки по умолчанию 0. Действительная скорость определяется константами в драйвере CAN. Эта функция должна быть вызвана перед mCO_InitAll().

Параметр:

unsigned char bitrate: значение для выбора скорости в диапазоне 0..8.

Пример см. во врезке, где приведено описание подпрограммы mCO_ProcessAllEvents.

unsigned char mCO_SetBaud(void)

Вызовите эту функцию, чтобы получить текущую настройку скорости, используемую этим узлом. Настройка будет возвращена как значение unsigned char. Точные значения скоростей, соответствующие настройке, определяются в драйвере CAN (см. раздел "Драйвер ECAN™").

Пример см. во врезке, где приведено описание подпрограммы mCO_ProcessAllEvents.

[События и службы PDO]

В этой секции описаны функции, используемые для поддержки PDO. Все они нужны для поддержки таких низкоуровневых коммуникаций, как открытие, закрытие и обмен данными со специфическими конечными точками PDO.

Разработка PDO. Критической задачей при разработке приложения CANopen является разработка объектов PDO. Должны быть приняты некоторые решения по поводу того, какие функции нужно поддерживать: выбор между динамическим и статическим отображением PDO (PDO mapping), выбор режима синхронизации передачи, и нужно ли поддерживать время запрета (inhibit time). Исходный код этого стека CANopen предоставляет базовый набор инструментария для поддержки коммуникации PDO для такого вида возможностей, с которыми может быть собран проект.

Критическими моментами для поддержки PDO является разработка кода обработки следующих элементов:

• События коммуникаций PDO
• Отображение PDO (PDO Mapping)
• Синхронизация PDO
• Событие PDO (PDO Event) и время запрета (Inhibit time)

События коммуникаций PDO. У каждого разрешенного PDO будут в наличии некоторые события коммуникаций, для поддержки установки типовых аспектов PDO. События в действительности это функции обратного вызова (callback), указанные в OD для обработки определенных параметров коммуникаций PDO. Например, мастер сети отправляет запрос через SDO к подчиненному устройству, чтобы поменять тип PDO (см. спецификации стандарта для получения информации по типам коммуникации). Запрос передается вверх через стек к словарю, и в конечном счете попадает к функции, которая обрабатывает доступ к типу.

Пример 1 и Пример 2 демонстрируют связь между словарем OD и действительной функцией CO_COMM_TPDO1_TypeAccessEvent(). Пример 1 показывает элемент словаря. Пример 2 показывает действительную callback-функцию. В этом случае пример демонстрирует поддержку только для типов 0..240, 254 и 255 (типы передач PDO показаны в таблице 5). Обратите внимание, что ни одно из событий не обсуждается подробно, потому что они создаются разработчиком приложения и, таким образом, обрабатываются в коде разработчика.

Пример 1, элемент PDO словаря:

{0x1800,0x00,CONST,1,{(rom unsigned char *)&uDemoTPDO1Len}},\
{0x1800,0x01,RW | FUNC,4,{(rom unsigned char *)&CO_COMM_TPDO1_COBIDAccessEvent}},\
{0x1800,0x02,RW | FUNC,1,{(rom unsigned char *)&CO_COMM_TPDO1_TypeAccessEvent}}

Пример 2, обработчик события:

void CO_COMM_TPDO1_TypeAccessEvent(void)
{
   unsigned char tempType;
   switch (mCO_DictGetCmd())
   {
   //case DICT_OBJ_INFO:// Получение информации об объекте.
   //                   // Приложение должно использовать эту ветку
   //                   // для загрузки структуры длиной, доступом
   //                   // и информацией отображения.
   //   break;
   case DICT_OBJ_READ:  // Чтение объекта.
                        // Запись типа в буфер
      *(uDict.obj->pReqBuf) = uDemoSyncSet;
      break;
   case DICT_OBJ_WRITE: // Запись объекта.
      tempType = *(uDict.obj->pReqBuf);
      if ((tempType >= 0) && (tempType <= 240))
      {
         // Установка нового типа и повторная синхронизация:
         uDemoSyncCount = uDemoSyncSet = tempType;
      }
      else if ((tempType == 254) || (tempType == 255))
      {
         uDemoSyncSet = tempType;
      }
      else
      {
         mCO_DictSetRet(E_PARAM_RANGE);   //ошибка
      }
      break;
   }
}

Таблица 5. Типы передач PDO.

Тип передачи Символ синхронизации передачи PDO
Циклический Не циклический Синхронный Асинхронный RTR
0   x x    
1..240 x   x    
241..251 Зарезервировано
252     x   x
253       x x
254       x  
255       x  

Отображение PDO (PDO Mapping). Как уже упоминалось, отображение PDO на данные приложения может быть статическое или динамическое. Для поддержки каждого из этих вариантов не предоставлено никакого кода. Однако никакой код в реальности не нужен для предоставления статического отображения. Таким образом, статический код значительно проще, и требует меньше обработки для своей поддержки. Динамическое отображение PDO более сложное для реализации, потому что требует ссылок на словарь один или большее количество раз на один объект PDO. В этом варианте стека CANopen демонстрируется только статическое отображение.

Пример 3 показывает элемент в словаре. Действительное отображение это только данные ROM, как это показано в примере 4. Любые запросы к SDO по умолчанию для данных отображения в словаре будут приводить к чтению статических данных непосредственно из ROM. Подразумевается, что статические данные, сохраненные в ROM, имеют формат отображения, показанный на рис. 3, в соответствии со спецификацией CANopen.

AN945 mapping format ROM data fig03

Рис. 3. Формат отображения для данных ROM.

Пример 3, элемент отображения PDO:

#define DICTIONARY_PDO1_RX_MAP                                 \
   {0x1600,0x00,CONST,1,{(rom unsigned char *)&rMaxIndex2}},   \
   {0x1600,0x01,CONST,4,{(rom unsigned char *)&uRPDO1Map}},    \
   {0x1600,0x02,CONST,4,{(rom unsigned char *)&uPDO1Dummy}},   \
   {0x1600,0x03,CONST,4,{(rom unsigned char *)&uPDO1Dummy}},   \
   {0x1600,0x04,CONST,4,{(rom unsigned char *)&uPDO1Dummy}},   \
   {0x1600,0x05,CONST,4,{(rom unsigned char *)&uPDO1Dummy}},   \
   {0x1600,0x06,CONST,4,{(rom unsigned char *)&uPDO1Dummy}},   \
   {0x1600,0x07,CONST,4,{(rom unsigned char *)&uPDO1Dummy}},   \
   {0x1600,0x08,CONST,4,{(rom unsigned char *)&uPDO1Dummy}}

Пример 4, структура словаря:

rom unsigned long uTPDO1Map = 0x60000108;
rom unsigned long uRPDO1Map = 0x62000108;
rom unsigned long uPDO1Dummy = 0x00000008;

Синхронизация PDO. Объекты PDO могут быть синхронизированы путем привязки их функций к объекту SYNC. Синхронизация зависит от типа передачи. Типы передач перечислены в таблице 5 (см. врезку "События коммуникаций PDO").

Синхронизация заключается просто в использовании функции CO_COMMSyncEvent() для обработки конечной точки PDO. Это более подробно обсуждается в секции событий синхронизации, см. раздел "События и службы SYNC".

Таймеры. Поддерживается таймер событий (event timer), в то время как таймер запрета (inhibit timer) оставлен на предоставление дизайнеру приложения. Причина в том, что требуется более точная разрешающая способность по времени (100 мкс). Если приложению требуется таймер событий, можно обработать CO_PDO1LSTimerEvent(), чтобы получить события тика 1 мс.

void mRPDOOpen(const unsigned char PDOnum)

Открывает конечную точку RPDO по её номеру. Здесь доступно только 4 объекта PDO. Обычно эта функция была бы вызвана в коммуникационном событии записи объекта RPDO. В сущности событие коммуникации записи объекта PDO генерируется, когда узел сети запрашивают на запуск коммуникаций PDO.

Параметр:

const unsigned char PDOnum: допустимый диапазон от 1 до 4. Это должно быть реальное число, не макрос.

Пример:

// Обработка события доступа к COB-ID
void CO_COMM_RPDO1_COBIDAccessEvent(void)
{
   switch (mCO_DictGetCmd())
   {
   case DICT_OBJ_READ:  // Чтение объекта.
                        // Трансляция формата MCHP COB в формат CANopen COB:
      mTOOLS_MCHP2CO(mRPDOGetCOB(1));
      // Возврат COB-ID
      *(unsigned long *)(uDict.obj->pReqBuf) = mTOOLS_GetCOBID();
      break;
   case DICT_OBJ_WRITE: // Запись объекта.
                        // Трансляция COB в формат MCHP:
      mTOOLS_CO2MCHP(*(unsigned long *)(uDict.obj->pReqBuf));
      // Если это запрос остановки PDO:
      if ((*(UNSIGNED32 *)(&mTOOLS_GetCOBID())).PDO_DIS)
      {
         // И если принятый COB соответствует сохраненному COB и типу, то закрыть
         if (!((mTOOLS_GetCOBID() ^ mRPDOGetCOB(1)) & 0xFFFFEFFF))
         {
            // Но закрыть только если конечная точка PDO была открыта
            if (mRPDOIsOpen(1))
            {
               mRPDOClose(1);
            }
            // Показать локальному объекту, что этот PDO запрещен
            (*(UNSIGNED32 *)(&mRPDOGetCOB(1))).PDO_DIS = 1;
         }
         else
         {
            mCO_DictSetRet(E_PARAM_RANGE);   //ошибка
         }
      }
      // Иначе если RPDO не открыт, то запустить RPDO
      else
      {
         // И если принятый COB соответствует сохраненному COB и типу, то откроем
         if (!((mTOOLS_GetCOBID() ^ mRPDOGetCOB(1)) & 0xFFFFEFFF))
         {
            // но откроем только елси конечная точка PDO была закрыта
            if (!mRPDOIsOpen(1))
            {
               mRPDOOpen(1);
            }
            // Показать локальному объекту, что этот PDO разрешен
            (*(UNSIGNED32 *)(&mRPDOGetCOB(1))).PDO_DIS = 0;
         }
         else
         {
            mCO_DictSetRet(E_PARAM_RANGE);   //ошибка
         }
      }
      break;
   }
}

BOOL mRPDOIsOpen(const unsigned char PDOnum)

Опрашивает, открыт ли RPDO. Обычно это должно быть вызвано в событии объекта коммуникаций PDO.

Параметр:

const unsigned char PDOnum: допустимый диапазон от 1 до 4. Это должно быть реальное число, не макрос.

Возвращаемые значения:

TRUE: RPDO открыт и принимает сообщения.
FALSE: RPDO закрыт и не принимает сообщения.

Пример см. во врезке с описанием функции mRPDOOpen.

void mRPDOClose(const unsigned char PDOnum)

Закрывает конечную точку RPDO. Обычно это должно быть вызвано в событии объекта коммуникаций PDO.

Параметр:

const unsigned char PDOnum: допустимый диапазон от 1 до 4. Это должно быть реальное число, не макрос.

Пример см. во врезке с описанием функции mRPDOOpen.

BOOL mRPDOnIsGetRdy(const unsigned char PDOnum)

Эта функция запрашивает у Менеджера Коммуникаций, был ли принят новый PDO по указанному номеру.

Параметр:

const unsigned char PDOnum: допустимый диапазон от 1 до 4. Это должно быть реальное число, не макрос.

Возвращаемые значения:

TRUE: данные были приняты и готовы к обработке.
FALSE: данные не были пока приняты.

Пример:

void DemoProcessEvents(void)
{
   unsigned char change;
   unsigned char rise;
   unsigned char fall;
   
   // Прочитать ножки входного порта
   (*(UNSIGNED8 *)uLocalXmtBuffer).bits.b0 = PORTBbits.RB5;
   (*(UNSIGNED8 *)uLocalXmtBuffer).bits.b1 = PORTBbits.RB4;
   // Определить, поменялось ли значение на них
   change = uIOinDigiInOld ^ uLocalXmtBuffer[0];
   // Определить, были ли события нарастания
   rise = (uIOinIntRise & change) & uLocalXmtBuffer[0];
   // Определить, были ли события спада
   fall = (uIOinIntFall & change) & ~uLocalXmtBuffer[0];
   // Определить, были ли события изменения
   change = (uIOinIntChange & change);
   // Присвоить текущее значение старому
   uIOinDigiInOld = uLocalXmtBuffer[0];
   // Если любое из этого верно, то показать условие прерывания
   if (uIOinIntEnable & (change | rise | fall))
      uDemoState.bits.b1 = 1;
   if (uDemoState.bits.b1)
   {
      switch (uDemoSyncSet)
      {
      case 0:     // Не циклическая синхронная передача.
         // Установка флага синхронной передачи
         uDemoState.bits.b2 = 1;
         break;
      case 254:   // Асинхронная передача
      case 255:
         // Установка флага асинхронной передачи
         uDemoState.bits.b0 = 1;
         break;
      }
   }
   // Если готовы к передаче
   if (mTPDOIsPutRdy(1) && uDemoState.bits.b0)
   {
      // Сказать стеку, что данные загружены для передачи:
      mTPDOWritten(1);
      // Сброс любых синхронных или асинхронных флагов:
      uDemoState.bits.b0 = 0;
      uDemoState.bits.b1 = 0;
   }
   // Если какие-либо данные были приняты
   if (mRPDOIsGetRdy(1))
   {
      // Записать первый байт буфера
      LATD = uLocalRcvBuffer[0];
      // Чтение PDO, освободить драйвер для приема других данных
      mRPDORead(1);
   }
}

void mRPDORead(const unsigned char PDOnum)

Эта функция вызывается, чтобы показать Менеджеру Коммуникаций, что последнее принятое сообщение было прочитано и надлежащим образом обработано. Это позволяет Менеджеру Коммуникаций принять другое сообщение PDO от драйвера. Приложение должно просто сделать копию данных или даже обработать данные на лету.

const unsigned char PDOnum: допустимый диапазон от 1 до 4. Это должно быть реальное число, не макрос.

Пример см. во врезке с описанием функции mRPDOIsGetRdy.

void mRPDOSetCOB(const unsigned char PDOnum, unsigned long rpdoCOB)

Эта функция устанавливает RPDO COB-ID (параметр rpdoCOB), PDO указывается по номеру (параметр PDOnum, допустимый диапазон от 1 до 4). Это должно быть установлено перед открытием PDO. COB-ID должен быть представлен в стандартном формате Microchip (формат MCHP).

Параметры:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.
unsigned long rpdoCOB: COB-ID принимаемый этим PDO.

Пример:

void DemoInit(void)
{
   // Порт D весь работает как выход
   LATD = 0;
   TRISD = 0;
   
   uDemoSyncSet = 255;
   
   uIOinFilter = 0;
   uIOinPolarity = 0;
   uIOinIntChange = 1;
   uIOinIntRise = 0;
   uIOinIntFall = 0;
   uIOinIntEnable = 1;
   
   uIOinDigiInOld = uLocalXmtBuffer[0] = 0;
   uLocalRcvBuffer[1] = uLocalXmtBuffer[1] = 0;
   uLocalRcvBuffer[2] = uLocalXmtBuffer[2] = 0;
   uLocalRcvBuffer[3] = uLocalXmtBuffer[3] = 0;
   uLocalRcvBuffer[4] = uLocalXmtBuffer[4] = 0;
   uLocalRcvBuffer[5] = uLocalXmtBuffer[5] = 0;
   uLocalRcvBuffer[6] = uLocalXmtBuffer[6] = 0;
   uLocalRcvBuffer[7] = uLocalXmtBuffer[7] = 0;
   // Преобразование в формат MCHP
   mTOOLS_CO2MCHP(mCOMM_GetNodeID().byte + 0xC0000180L);
   // Сохранение COB
   mTPDOSetCOB(1, mTOOLS_GetCOBID());
   // Преобразование в формат MCHP
   mTOOLS_CO2MCHP(mCOMM_GetNodeID().byte + 0xC0000200L);
   // Сохранение COB
   mRPDOSetCOB(1, mTOOLS_GetCOBID());
   // Установка указателя на буферы
   mTPDOSetTxPtr(1, (unsigned char *)(&uLocalXmtBuffer[0]));
   // Установка указателя на буферы
   mRPDOSetRxPtr(1, (unsigned char *)(&uLocalRcvBuffer[0]));
   // Установка длины
   mTPDOSetLen(1, 8);
}

unsigned long mRPDOGetCOB(const unsigned char PDOnum)

Эта функция возвращает COB-ID, используемый в настоящее время объектом RPDO.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

Пример см. во врезке с описанием функции mRPDOOpen.

unsigned char mRPDOGetLen(const unsigned char PDOnum)

Вовзращает длину последнего принятого PDO (длина сообщения, допустимый диапазон 0..8).

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

unsigned char * mRPDOGetRxPtr(const unsigned char PDOnum)

Эта функция возвращает указатель на локальный буфер приема. Указатель должен быть установлен перед открытием коммуникаций с конечной точкой. Когда коммуникации открыты, то все сообщения будут сохранены по месту, на которое ссылается этот указатель.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

void mRPDOSetRxPtr(const unsigned char PDOnum, unsigned char *pRXBUF)

Эта функция устанавливает указатель на локальный буфер приема. Этот указатель должен быть установлен перед открытием коммуникаций с конечной точкой. Когда коммуникации открыты, то все сообщения будут сохранены по месту, на которое ссылается этот указатель.

Параметры:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.
unsigned char *pRXBUF: буфер для сообщений (размер не менее 8 байт).

Пример см. во врезке с описанием функции mRPDOSetCOB.

void mTPDOnOpen(const unsigned char PDOnum)

Открывает конечную точку TPDO. Здесь доступно только 4 объекта PDO. Обычно эта функция должна быть вызвана с объектом коммуникации TPDO при событии записи объекта. В сущности событие объекта коммуникаций PDO генерируется, когда узел сети запрашивает запуск коммуникаций PDO.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

Пример:

// Обработка событий доступа к COB-ID
void CO_COMM_TPDO1_COBIDAccessEvent(void)
{
   switch (mCO_DictGetCmd())
   {
   case DICT_OBJ_READ:     // Чтение объекта
      // Трансляция COB формата MCHP в COB формата CANopen
      mTOOLS_MCHP2CO(mTPDOGetCOB(1));
      // Возврат COBID
      *(unsigned long *)(uDict.obj->pReqBuf) = mTOOLS_GetCOBID();
      break;
   case DICT_OBJ_WRITE:    // Запись объекта
      // Трансляция COB в формат MCHP
      mTOOLS_CO2MCHP(*(unsigned long *)(uDict.obj->pReqBuf));
      // Если был запрос остановить PDO
      if ((*(UNSIGNED32 *)(&mTOOLS_GetCOBID())).PDO_DIS)
      {
         // И если принятый COB совпадает с сохраненным COB и типом, то закрываем
         if (!((mTOOLS_GetCOBID() ^ mTPDOGetCOB(1)) & 0xFFFFEFFF))
         {
            // Но закрывает только если конечная точка PDO была открыта
            if (mTPDOIsOpen(1))
            {
               mTPDOClose(1);
            }
            // Показать локальному объекту, что этот PDO запрещен
            (*(UNSIGNED32 *)(&mTPDOGetCOB(1))).PDO_DIS = 1;
         }
         else
         {
            mCO_DictSetRet(E_PARAM_RANGE);   //Ошибка
         }
      }
      // Иначе если TPDO не открыт, то запускаем TPDO
      else
      {
         // И если принятый COB совпадает с сохраненным COB и типом, то открываем
         if (!((mTOOLS_GetCOBID() ^ mTPDOGetCOB(1)) & 0xFFFFEFFF))
         {
            // Но открываем только если конечная точка PDO была закрыта
            if (!mTPDOIsOpen(1))
            {
               mTPDOOpen(1);
            }
            // Показать локальному объекту, что этот PDO разрешен
            (*(UNSIGNED32 *)(&mTPDOGetCOB(1))).PDO_DIS = 0;
         }
         else
         {
            mCO_DictSetRet(E_PARAM_RANGE);   //Ошибка
         }
      }
      break;
   }
}

BOOL mTPDOIsOpen(const unsigned char PDOnum)

Опрашивает, открыт ли TPDO. Обычно эта функция должна вызываться в событии коммуникаций объекта PDO.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

Возвращаемые значения:

TRUE: Менеджер Коммуникаций готов принять новые данные.
FALSE: Менеджер Коммуникаций занят передачей предыдущего сообщения.

Пример см. во врезке с описанием функции mTPDOOpen().

void mTPDOClose(const unsigned char PDOnum)

Закрывает конечную точку TPDO. Обычно эта функция должна быть вызвана в событии объекта коммуникаций PDO.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

Пример см. во врезке с описанием функции mTPDOOpen().

BOOL mTPDOIsPutRdy(const unsigned char PDOnum)

Эта функция опрашивает Менеджер Коммуникаций, доступен ли слот для передачи PDO. Эта функция вернет true, если Менеджер готов принять сообщение для отправки по шине.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

Возвращаемые значения:

TRUE: Менеджер Коммуникаций готов принять новые данные.
FALSE: Менеджер Коммуникаций занят передачей предыдущего сообщения.

Пример см. во врезке с описанием функции mRPDOIsGetRdy.

void mTPDOWritten(const unsigned char PDOnum)

Показывает Менеджеру Коммуникаций, что сообщение для передачи было загружено. Это позволяет Менеджеру Коммуникаций поставить сообщение в очередь для передачи. Функция события CO_PDOTXFinEvent() вызывается, когда сообщение помещается на шину.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

Пример см. во врезке с описанием функции mRPDOIsGetRdy.

void mTPDOSetCOB(const unsigned char PDOnum, unsigned long tpdoCOB)

Эта функция устанавливает TPDO COB-ID. Она должна быть вызвана перед передачей TPDO. COB-ID должен быть в стандартном формате Microchip.

Параметры:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.
unsigned long tpdoCOB: COB-ID для передачи.

Пример см. во врезке с описанием функции mRPDOSetCOB.

unsigned long mTPDOnGetCOB(const unsigned char PDOnum)

Эта функция возвращает COB-ID, используемый в настоящее время объектом TPDO.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

Пример см. во врезке с описанием функции mRPDOSetCOB.

unsigned long mTPDOnSetLen(const unsigned char PDOnum, unsigned char length)

Эта функция устанавливает длину данных TPDO. Длина (length) должна быть в диапазоне от 0 до 8.

Параметры:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.
unsigned char length: длина PDO, должна быть в диапазоне 0 .. 8.

Пример см. во врезке с описанием функции mRPDOSetCOB.

unsigned char * mTPDOGetTxPtr(const unsigned char PDOnum)

Эта функция получает указатель на использующийся в настоящее время локальный буфер передачи. При передаче все сообщения будут переданы из места, на которое ссылается этот указатель.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

void mTPDOnSetTxPtr(const unsigned char PDOnum)

Эта функция устанавливает указатель на локальный буфер передачи. При передаче все сообщения будут переданы из места, на которое ссылается этот указатель.

Параметр:

const unsigned char PDOnum: допустимый диапазон 1 .. 4. Это должно быть реальное число, не макрос.

Пример см. во врезке с описанием функции mRPDOIsGetRdy.

void CO_PDOnLSTimerEvent(void)

Это функция обратного вызова (callback) события таймера. Она вызывается с интервалом 1 мс, если объект PDO разрешен. Обычно приложение должно использовать это функции для таймера события PDO, как это указано в стандарте CANopen.

void CO_PDOnTxFinEvent(void)

Это callback-функция события завершения передачи. Это событие генерируется, когда сообщение, которое было поставлено в очередь на передачу, было отправлено по шине CAN.

[События и службы SYNC]

Имеется только одно событие, которое наступает из объекта SYNC: CO_COMMSyncEvent(). Это событие генерируется только когда принято сообщение SYNC, и это используется для синхронизации обработки PDO. Это событие должно быть проанализировано в коде приложения, где обрабатывается сообщение PDO.

Есть только 2 службы, полезные для поддержки объекта SYNC. Самая важная часть для установки COB-ID для объекта SYNC перед инициализацией коммуникаций CANopen, поскольку конечная точка автоматически открывается при инициализации.

void CO_COMMSyncEvent(void)

Это единственное событие, которое генерирует объект SYNC. Это событие генерируется, когда принято сообщение SYNC, что используется для синхронизации обработки PDO.

Ниже показан простой пример функции обработки переменной синхронного типа PDO, которая по своей природе является циклической. Тип переменной задается в PDO Type (параметр коммуникаций TPDO по sub-индексу 2), который должен быть в диапазоне 1 .. 240 включительно.

void CO_COMMSyncEvent(void)
{
   // Обработка только если синхронный режим
   if ((uDemoSyncSet == 0) && (uDemoState.bits.b2))
   {
      // Сброс синхронной передачи и переход к асинхронной передаче
      uDemoState.bits.b2 = 0;
      uDemoState.bits.b0 = 1;
   }
   else if ((uDemoSyncSet >= 1) && (uDemoSyncSet <= 240))
   {
      // Подстройка счетчика синхронизации
      uDemoSyncCount--;
      // Если наступило время генерации синхронного сообщения:
      if (uDemoSyncCount == 0)
      {
         // Сброс счетчика синхронизации
         uDemoSyncCount = uDemoSyncSet;
         // Запуск передачи PDO
         uDemoState.bits.b0 = 1;
      }
   }
}

void mSYNC_SetCOBID(unsigned long SYNC_COB)

Эта функция используется для установки COB-ID объекта SYNC. Она должна быть вызвана как минимум 1 раз перед инициализацией, чтобы правильно установить COB-ID в firmware системы.

Параметр:

unsigned long SYNC_COB: значение COB-ID в формате Microchip.

Пример см. во врезке с описанием функции mCO_ProcessAllEvents.

unsigned long mSYNC_GetCOBID(void)

Эта функция используется, чтобы получить COB-ID используемый в настоящее время объектом SYNC.

Возвращаемое значение:

unsigned long SYNC_COB: значение COB-ID в формате Microchip (MCHP).

[События и службы NMT]

Представлено управление сетью (Network management) через объект NMT, который в сущности охватывает машину состояний узла (см. рис. 2).

Эта служба полезна для ввода узел сети в определенное состояние. Однако состояние будет меняться через обычные сетевые управляющие запросы от мастера NMT. Когда состояние поменялось по запросу мастера, генерируется событие. Ниже перечислены все события и службы NMT.

void mNMT_Start(void)

Вызовите эту функцию для запуска коммуникаций, которые были остановлены. Обычно функция автоматически вызывается подпрограммами обслуживания NMT в результате поступления запроса NMT от мастера, чтобы установить подходящее состояние узла.

void mNMT_Stop(void)

Вызовите эту функцию для остановки узла, который находился в состоянии Operational или Pre-operational. Обычно функция автоматически вызывается подпрограммами обслуживания NMT в результате поступления запроса NMT от мастера, чтобы установить подходящее состояние узла.

void mNMT_GotoPreopState(void)

Вызовите эту функцию, чтобы перевести узел сети в состояние Pre-operational. Обычно функция автоматически вызывается подпрограммами обслуживания NMT в результате поступления запроса NMT от мастера, чтобы установить подходящее состояние узла.

void mNMT_GotoOperState(void)

Вызовите эту функцию, чтобы перевести узел сети в состояние Operational. Обычно функция автоматически вызывается подпрограммами обслуживания NMT в результате поступления запроса NMT от мастера, чтобы установить подходящее состояние узла.

BOOL mNMT_StateIsStopped(void)

Позволяет определить, находится ли сейчас узел в остановленном состоянии.

Возвращаемые значения:

TRUE: если узел в состоянии STOPPED.
FALSE: если узел в состоянии PREOPERATIONAL или OPERATIONAL.

BOOL mNMT_StateIsOperational(void)

Позволяет определить, находится ли сейчас узел в рабочем (Operational) состоянии.

Возвращаемые значения:

TRUE: если узел в состоянии OPERATIONAL.
FALSE: если узел в состоянии STOPPED или PREOPERATIONAL.

BOOL mNMT_StateIsPreOperational(void)

Позволяет определить, находится ли сейчас узел в состоянии Pre-Operational.

Возвращаемые значения:

TRUE: если узел в состоянии PREOPERATIONAL.
FALSE: если узел в состоянии STOPPED или OPERATIONAL.

void CO_NMTStateChangeEvent(void)

Эта callback-функция вызывается, когда состояние системы меняется при поступлении запроса NMT.

void CO_NMTResetEvent(void)

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

void CO_NMTAppResetRequest(void)

Эта callback-функция вызывается, когда пришел запрос на сброс приложения. Как будет обработан этот запрос - зависит от дизайна приложения. После обработки этого события будет сгенерировано событие CO_COMMResetEvent(). Коммуникации автоматически сбрасываются после обработки события CO_COMMResetEvent().

[События и службы Node Guard/Heartbeat]

Предоставлен комбинированный объект Node Guard/Heartbeat, как этого требует стандарт. Для него имеется небольшое количество служб для инициализации и получение информации об объекте.

Для объекта Node Guard/Heartbeat имеется только одно возможное генерируемое событие, которое относится специально к половинке node guard объекта. Функция CO_NMTENodeGuardErrEvent() вызывается, когда превышено время жизни (lifetime) объекта. Время жизни определено в спецификации как результат перемножения lifetime factor и guard time.

void mNMTE_SetHeartBeat(unsigned long HeartBeat)

Вызовите эту функцию для установки Heartbeat. Параметр Heartbeat указывается как unsigned long в формате стандарта CANopen. Он устанавливается перед инициализацией коммуникаций.

unsigned long mNMTE_GetHeartBeat(void)

Вызовите эту функцию, чтобы получить текущее установленное значение Heartbeat (число unsigned long в формате стандарта CANopen).

void mNMTE_SetGuardTime(unsigned long GuardTime)

Вызовите эту функцию для установки guard time. Параметр GuardTime это число unsigned long в формате стандарта CANopen. Он устанавливается перед инициализацией коммуникаций.

unsigned long mNMTE_GetGuardTime(void)

Вызовите эту функцию, чтобы получить текущее установленное значение guard time (число unsigned long в формате стандарта CANopen).

void mNMTE_SetLifeFactor(unsigned char LifeFactor)

Используйте эту функцию, чтобы установить значение множителя времени жизни (параметр LifeFactor).

unsigned char mNMTE_GetLifeFactor(void)

Используйте эту функцию, чтобы получить значение множителя времени жизни.

void CO_NMTENodeGuardErrEvent(void)

Эта callback-функция вызывается, когда происходит событие защиты узла (node guard event). Это момент времени, когда не было принято событие node guard в течение установленного времени жизни lifetime (результат умножения life time factor и guard time). Как будет это событие обработано - зависит от приложения.

[Объекты и OD]

В этой разработке каждый элемент словаря объектов (Object Dictionary, OD) это структура в памяти программ. Внутри каждой такой структуры находится информация, необходимая для идентификации объекта и его места размещения. Идентификационные данные достаточно гибкие, чтобы можно было различать друг от друга простые типы данных, массивы и структуры, определенные в качестве объектов OD. Также в качестве объекта может быть определена функция, и это дает настоящую гибкость для сложных объектов.

Структура объекта. Объект, определенный в OD, хранится в памяти программ микроконтроллера; структура объекта показана в Примере 5. Эта структура содержит достаточно информации, чтобы описать любой объект:

• index: индекс объекта.
• subindex: sub-индекс объекта.
• ctl: байт управления. Он определяет тип объекта.
• len: длина объекта в байтах.
• *pROM: указатель на объект или функцию обработки объекта. Этот указатель всегда приводится к типу unsigned char *.

Пример 5, структура объекта словаря:

typedef struct _DICTIONARY_OBJECT_TEMPLATE
{
   unsigned int index;
   unsigned char subindex;
   unsigned char ctl;
   unsigned int len;
   rom unsigned char * pROM;
}DICT_OBJECT_TEMPLATE;

Группы объектов. OD разбит на группы для быстрого поиска по словарю. Таким образом, каждый элемент OD должен быть сохранен в подходящей для этого группе. В таблице 6 идентифицированы все группы. Любой элемент OD должен быть размещен в числовом порядке в пределах соответствующей группы.

Таблица 6. Группы объектов OD.

Имя группы объектов Индекс Описание
DICTIONARY_DATA_TYPES 0000h Типы данных, определенные в OD. Хотя типы данных определены в пределах OD, в стандарте указано, что поддержка этих типов со стороны словаря не требуется.
DICTIONARY_DEVICE_INFO 1000h Эта группа относится к секции коммуникаций CANopen, и она содержит информацию, специфичную для устройства (узла сети), включая идентификаторы COB-ID, определенные конечные точки и статус.
DICTIONARY_SDO 1200h Предоставлена одна группа для предоставления параметров SDO.
DICTIONARY_PDO1_RX_COMM 1400h Отдельные группы предоставлены для коммуникационных параметров 4 RPDO.
DICTIONARY_PDO2_RX_COMM 1401h
DICTIONARY_PDO3_RX_COMM 1402h
DICTIONARY_PDO4_RX_COMM 1403h
DICTIONARY_PDO1_RX_MAP 1600h Отдельные группы предоставлены для параметров отображения 4 RPDO.
DICTIONARY_PDO2_RX_MAP 1601h
DICTIONARY_PDO3_RX_MAP 1602h
DICTIONARY_PDO4_RX_MAP 1603h
DICTIONARY_PDO1_TX_COMM 1800h Отдельные группы предоставлены для коммуникационных параметров 4 TPDO.
DICTIONARY_PDO2_TX_COMM 1801h
DICTIONARY_PDO3_TX_COMM 1802h
DICTIONARY_PDO4_TX_COMM 1803h
DICTIONARY_PDO1_TX_MAP 1A00h Отдельные группы предоставлены для параметров отображения 4 TPDO.
DICTIONARY_PDO2_TX_MAP 1A01h
DICTIONARY_PDO3_TX_MAP 1A02h
DICTIONARY_PDO4_TX_MAP 1A03h
DICTIONARY_MANUFACTURER_SPECIFIC_1 2000h Эти группы предоставлены для объектов, специфичных для производителя.
DICTIONARY_MANUFACTURER_SPECIFIC_2 3000h
DICTIONARY_MANUFACTURER_SPECIFIC_3 4000h
DICTIONARY_MANUFACTURER_SPECIFIC_4 5000h
DICTIONARY_STANDARD_1 6000h Эти группы предоставлены для стандартных объектов CANopen.
DICTIONARY_STANDARD_2 7000h
DICTIONARY_STANDARD_3 8000h
DICTIONARY_STANDARD_4 9000h

Управляющие биты объекта. Как объект обрабатывается, зависит от его бит управления (object control bits). Объект может быть читаемый/записываемый, только для чтения, или даже функционально определен для приспособления к очень уникальным объектам. Таблица 7 определяет биты управляющего байта объекта (object control byte).

Таблица 7. Определения управляющих битов.

Бит Имя Описание
0 RD_BIT Этот бит определяет доступ на чтение к объекту. Если он установлен, то объект можно прочитать из другого узла сети.
1 WR_BIT Этот бит определяет доступ на запись к объекту. Если он установлен, то объект можно записать из другого узла сети.
2 ROM_BIT Этот бит определяет, что объект размещен в ПЗУ. Установка этого бита не подразумевает, что объект не может быть записан. Он только определяет место хранения объекта.
3 EE_BIT Этот бит определяет, что объект находится в EEPROM. Обратите внимание, что в настоящее время не предоставлена автоматическая поддержка доступа к EEPROM. Если бит EE_BIT установлен, то тогда бит FDEF_BIT также должен быть установлен, чтобы инструментарий доступа к OD знал, что разработчик приложения реализовал обработку доступа к EEDATA через свою специальную функцию.
4 FDEF_BIT Этот бит показывает, что объект определен функционально. Обычно объекты определяются через функцию, если для него существуют специальные правила доступа, которые не применимы к простому статическому типу. Например это объект, который срабатывает на событии, тогда он должен быть определен функционально. Или другой пример - у объекта могут меняться правила доступа чтение/запись в зависимости от состояния приложения, в таком случае объект тоже должен быть определен через функцию. Также имейте в виду, что если этот бит установлен, то все другие биты могут быть определены внутри функции обработки объекта, кроме бита FSUB_BIT.
5 MAP_BIT Этот бит определяет возможность отображения объекта. Таким образом, если этот бит установлен, то объект может быть отображен на PDO.
6 FSUB_BIT Этот бит определяет, определен ли функционально весь массив sub-индексов. Т. е. для определенного индекса в словаре будет только одна запись. И все запросы доступа к любому sub-индексу будут обработаны функцией доступа к объекту. Это полезно для объектов, у которых все sub-индексы имеют одинаковый функционал, но требуют разных значений параметров; таким образом, в OD для объекта понадобится только одна запись.
7 - В настоящий момент этот бит зарезервирован.

Чтобы упростить манипуляцию отдельными битами байта управления, предоставлена серия символических модификаторов битов. Таблица 8 предоставляет модификаторы логической операции И (AND) для управления объектом. Они могут быть скомбинированы вручную для формирования специфичного управления.

Таблица 8. Определения логического И для управляющих бит.

Бит Описание
RD Разрешено чтение.
N_RD Чтение не разрешено.
WR Разрешена запись.
N_WR Запись не разрешена.
ROM Объект базируется на ПЗУ.
N_ROM Объект не базируется на ПЗУ.
EE Объект базируется на EEPROM.
N_EE Объект не базируется на EEPROM.
FDEF Объект определен функционально.
N_FDEF Объект не определен функционально.
MAP Объект отображаемый.
N_MAP Объект отобразить нельзя.
FSUB Sub-индекс определен через функцию.
N_FSUB Sub-индекс не определен через функцию.

Например, следующий оператор задает, что объект можно читать, записывать, он определен как функция, и может быть отображен на переменную обработки (mappable):

RD & WR & N_ROM & N_EE & FDEF & MAP & N_FSUB

Подобным образом в таблице 9 предоставлены типичные модификаторы логической операции ИЛИ (OR) для управления объектом. Они могут быть также скомбинированы с именами бит, показанными в таблице 8.

Таблица 9. Определения логического ИЛИ для управляющих бит.

Бит Описание
CONST Объект только для чтения, базирующийся в ROM.
RW Объект можно читать и записывать.
RO Объект только для чтения.
WO Объект только для записи.
RW_EE Объект, размещенный в EEPROM, который можно читать и записывать.
RO_EE Объект, размещенный в EEPROM, который можно только читать.
WO_EE Объект, размещенный в EEPROM, который можно только записывать.
FUNC Функционально определенный объект.

Например, следующий оператор задает, что объект можно читать, записывать, он определен как функция, и может быть отображен на переменную обработки (то же самое, что и в предыдущем примере):

RW | FUNC | MAP_BIT

Некоторые примеры использования модификаторов бит показаны в Примере 6, элементы словаря 4, 8, 9 и 10.

Пример 6, записи OD:

#define DICTIONARY_DEVICE_INFO                                                            \
   {0x1000,0x00,CONST,4,{(rom unsigned char *)&rCO_DevType}},                             \
   {0x1001,0x00,RO,1,{(rom unsigned char *)&uCO_DevErrReg}},                              \
   {0x1002,0x00,RO,4,{(rom unsigned char *)&uCO_DevManufacturerStatReg}},                 \
   {0x1005,0x00,FUNC | RW,4,{(rom unsigned char *)&_CO_COMM_SYNC_COBIDAccessEvent}},      \
   {0x1008,0x00,CONST,24,{(rom unsigned char *)&rCO_DevName}},                            \
   {0x1009,0x00,CONST,4,{(rom unsigned char *)&rCO_DevHardwareVer}},                      \
   {0x100A,0x00,CONST,4,{(rom unsigned char *)&rCO_DevSoftwareVer}},                      \
   {0x100C,0x00,FUNC | RW,2,{(rom unsigned char *)&_CO_COMM_NMTE_GuardTimeAccessEvent}},  \
   {0x100D,0x00,FUNC | RW,1,{(rom unsigned char *)&_CO_COMM_NMTE_LifeFactorAccessEvent}}  \
   {0x1017,0x00,FUNC | RW,2,{(rom unsigned char *)&_CO_COMM_NMTE_HeartBeatAccessEvent}},  \
   {0x1018,0x00,CONST,1,{(rom unsigned char *)&rCO_DevIdentityIndx}},                     \
   {0x1018,0x01,CONST,4,{(rom unsigned char *)&rCO_DevVendorID}},                         \
   {0x1018,0x02,CONST,4,{(rom unsigned char *)&rCO_DevProductCode}},                      \
   {0x1018,0x03,CONST,4,{(rom unsigned char *)&rCO_DevRevNo}},                            \
   {0x1018,0x04,CONST,4,{(rom unsigned char *)&rCO_DevSerialNo}}

Простые объекты. OD предоставляет поддержку простых объектов. Простой объект в сущности это такой объект, который может быть представлен как обычный тип данных. Это включает любой тип данных, поддерживаемый компилятором, а также массивы.

Простой объект определяется в OD по ссылке внутри словаря. Это иллюстрировано по первому элементу словаря в Примере 7. Запрос на чтение для этого объекта вернет данные, сохраненные в uCO_DevManufacturerStatReg; запрос на запись возвратит ошибку, поскольку это объект только для чтения.

Пример 7, различные виды определения объекта:

//Определение простого объекта:
{0x1002,0x00,RO,4,{(rom unsigned char *)&uCO_DevManufacturerStatReg}}
 
//Функционально определенный объект:
{0x1005,0x00,FUNC | RW,4,{(rom unsigned char *)&_CO_COMM_SYNC_COBIDAccessEvent}

Функционально определенный объект. Объекты определяются функцией, когда у него есть некоторые свойства, которые не соответствуют стандартному типу данных или массиву, как они определены на языке C. Например, переменная unsigned char MyObj, у которой нет необычных условий, не нуждается в определении функцией; однако если в MyObj бит 7 разрешает запись в MyObj, то это потребует специальной обработки, и объект должен быть определен функцией, подобно тому, как это сделано для идентификаторов COB-ID.

Объект определен по функции, когда в управляющем байте установлен бит FDEF_BIT. Это продемонстрировано во втором элементе Примера 7, который определяет COB-ID для объекта SYNC. В этом случае функция _CO_COMM_SYNC_COBIDAccessEvent() вызывается, когда осуществляется запрос на доступ к объекту по индексу 1005h, sub-индексу 0.

Правила написания функции обработки объекта. К объекту обращаются через SDO, PDO или каким-либо доступом из приложения. Если объект определен по функции, то всякий раз, когда происходит обращение к объекту, будет вызвана функция, определенная в OD для этого объекта. Есть 3 возможных события, которые функция обработки объекта должна поддерживать, когда произошло обращение к объекту:

• Read control: чтение управляющих бит, определенных этой функцией. Это относится ко всем битам, кроме FSUB_BIT и FDEF_BIT; эти биты должны быть определены в OD для объекта.
• Read: чтение объекта, если он предназначен для чтения.
• Write: запись объекта, если он предназначен для записи.

Пример 8 демонстрирует, как выглядит типичная функция обработки объекта. Пример 9 показывает обработчик для объекта TPDO1 COB-ID.

Пример 8, общий вид (шаблон) обработки функционального объекта:

void MyObjectHandlingFunction(void)
{
   switch (mCO_DictGetCmd())
   {
   case DICT_OBJ_INFO:  // Получение информации об объекте.
      // Код в этом типе запроса должен модифицировать тип доступа. Например,
      // если объект может менять RO на RW на основании определенного
      // состояния приложения, то здесь должна быть обработана эта ситуация
      // изменения типа доступа. В большинстве ситуаций это может быть
      // опущено, поскольку информация об объекте статична; статичная
      // информация поддерживается непосредственно данными OD.
      break;
   case DICT_OBJ_READ:  // Чтение объекта.
      // Это запрос чтения объекта. Код в этом типе запроса должен обработать
      // любые перемещения данных и/или события, относящиеся к чтению.
      break;
   case DICT_OBJ_WRITE: // Запись объекта.
      // Это запрос записи объекта. Код в этом типе запроса должен обработать
      // любые перемещения данных и/или события, относящиеся к записи.
      break;
   }
}

Пример 9, реальная функциональная обработка объекта:

void CO_COMM_TPDO1_COBIDAccessEvent(void)
{
   switch (mCO_DictGetCmd())
   {
   case DICT_OBJ_READ:  // Чтение объекта.
      // Трансляция формата MCHP COB в формат CANopen COB
      mTOOLS_MCHP2CO(mTPDOGetCOB(1));
      // Возврат COB-ID
      *(unsigned long *)(uDict.obj->pReqBuf) = mTOOLS_GetCOBID();
      break;
   case DICT_OBJ_WRITE: // Запись объекта.
      // Трансляция COB в формат MCHP
      mTOOLS_CO2MCHP(*(unsigned long *)(uDict.obj->pReqBuf));
      // Если это запрос остановки PDO
      if ((*(UNSIGNED32 *)(&mTOOLS_GetCOBID())).PDO_DIS)
      {
         // И если принятый COB совпадает с сохраненным COB и типом, то закроем PDO
         if (!((mTOOLS_GetCOBID() ^ mTPDOGetCOB(1)) & 0xFFFFEFFF))
         {
            // но закроем только если конечная точка PDO была открыта
            if (mTPDOIsOpen(1)) {mTPDOClose(1);}
            // показать локальному объекту, что PDO запрещен
            (*(UNSIGNED32 *)(&mTPDOGetCOB(1))).PDO_DIS = 1;
         }
         else
         {
            mCO_DictSetRet(E_PARAM_RANGE);   //Ошибка
         }
      }
      // Иначе если TPDO не открыт, то запуск TPDO
      else
      {
         // И если принятый COB совпадает с сохраненным COB и типом, то откроем PDO
         if (!((mTOOLS_GetCOBID() ^ mTPDOGetCOB(1)) & 0xFFFFEFFF))
         {
            // но откроем только если конечная точка PDO была закрыта
            if (!mTPDOIsOpen(1))
            {
               mTPDOOpen(1);
            }
            // показать локальному объекту, что PDO разрешен
            (*(UNSIGNED32 *)(&mTPDOGetCOB(1))).PDO_DIS = 0;
         }
         else
         {
            mCO_DictSetRet(E_PARAM_RANGE);   //Ошибка
         }
      }
      break;
   }
}

Функция обработки объекта предоставляется с функциями и структурой для обработки запросов к объекту и от него. Это функции mCO_DictGetCmd() и mCO_DictSetRet(). Первая используется для получения команды, и вторая используется для возврата любых ошибок запрашивающему коду. Таблица 11 перечисляет ошибки, которые могут быть возвращены. В случае успешного запроса никакой ответ не требуется; OD подразумевает успех.

Запрашивающий код устанавливает указатель в словаре (uDict.obj) на свою локальную структуру DICT_OBJ. Эта структура содержит информацию как об объекте, так и о запрашивающем коде (структура определена в таблице 10). Пример 8 демонстрирует шаблон использования структуры в функции обработки объекта.

Таблица 10. Структура DICT_OBJ UDICT.

Элемент Тип Описание
pReqBuf unsigned char * Указатель на буфер запрашивающего кода. Этот указатель ссылается на данные в памяти, когда осуществляется запись объекта. Когда осуществляется чтение, этот указатель ссылается на место в памяти для буфера, которое предоставил запрашивающий код.
reqLen unsigned int Количество запрашиваемых байт. Это количество не должно превышать длины объекта.
reqOffst unsigned int Начальная точка запроса. Это предоставляется для поддержки порционных запросов, которые нужны по причине недостаточного места под буфер. Это наиболее полезно для запросов чтения; для запросов записи это вряд ли нужно, потому что скорее всего частичная запись объекта не будет желательна. Также этот параметр не нужно поддерживать, если количество байт в объекте PDO меньше или равно 8 (для объекта SDO это 4 байта).
index unsigned int Индекс CANopen.
subindex unsigned char Sub-индекс CANopen.
ctl enum DICT_CTL Тип доступа к памяти.
len unsigned int Размер объекта в байтах.
p union DICT_PTRS Указатели на объекты

Таблица 11. Определения ошибок.

Имя Описание
E_SUCCESS Успешное завершение операции, ошибок нет.
E_TOGGLE Бит toggle не поменял свое значение.
E_SDO_TIME Таймаут протокола SDO.
E_CS_CMD Спецификатор команды клиент/сервер недопустим или неизвестен.
E_MEMORY_OUT Недостаточно памяти.
E_UNSUPP_ACCESS Не поддерживаемый доступ к объекту.
E_CANNOT_READ Попытка чтения объекта, который предназначен только для записи.
E_CANNOT_WRITE Попытка записи объекта, который предназначен только для чтения.
E_OBJ_NOT_FOUND Объект не существует в OD.
E_OBJ_CANNOT_MAP Объект не может быть отображен на PDO.
E_OBJ_MAP_LEN Количество и длина объектов, которые должны быть отображены, превысили бы длину PDO.
E_GEN_PARAM_COMP Общая несовместимость параметра.
E_GEN_INTERNAL_COMP Общая внутренняя несовместимость в устройстве.
E_HARDWARE Сбой доступа из-за аппаратной ошибки.
E_LEN_SERVICE Тип данных не соответствует, длина параметра службы не соответствует.
E_LEN_SERVICE_HIGH Тип данных не соответствует, длина параметра службы слишком велика.
E_LEN_SERVICE_LOW Тип данных не соответствует, длина параметра службы слишком мала.
E_SUBINDEX_NOT_FOUND Sub-индекс не существует.
E_PARAM_RANGE Значение параметра вышло из диапазона (только для доступа на запись).
E_PARAM_HIGH Значение параметра слишком велико.
E_PARAM_LOW Значение параметра слишком мало.
E_MAX_LT_MIN Максимальное значение меньше, чем минимальное.
E_GENERAL Общая ошибка.
E_TRANSFER Данные не могут быть перемещены или сохранены приложением.
E_LOCAL_CONTROL Данные не могут быть перемещены или сохранены приложением из-за локального управления.
E_DEV_STATE Данные не могут быть перемещены или сохранены приложением из-за текущего состояния устройства.

Службы OD. Есть несколько служб обработки словаря для использования конечной точкой SDO. Если это необходимо, это также может использоваться для динамического отображения PDO (dynamic PDO mapping).

void mCO_DictObjectRead(DICT_OBJ myObj)

Эта функция читает объект, определенный через myObj. Чтобы это использовать, информация объекта должна быть сохранена локально как структура DICT_OBJ, когда она передается в функцию mCO_DictObjectRead(). Внутри используется только ссылка.

В структуре DICT_OBJ находится необходимая информация, которая должна быть получена из объекта. Некоторая часть этой информации должна быть предоставлена вызывающей функцией, и другая информация должна быть предоставлена из OD. Функция mCO_DictObjectDecode() должна быть вызвана перед вызовом mCO_DictObjectRead(), чтобы получить доступ и информацию ссылки, сохраненные в OD. Другая информация должна быть предоставлена пользователем. В таблице 12 описана структура и источник информации для каждого элемента.

Таблица 12. Структура DICT_OBJ.

Элемент Тип Кем предоставляется Описание
pReqBuf unsigned char * Пользователь Указатель на буфер запрашивающего кода.
reqLen unsigned int Пользователь Количество запрашиваемых байт.
reqOffst unsigned int Пользователь Начальная точка запроса.
index unsigned int Пользователь. Индекс CANopen.
subindex unsigned char Пользователь. Sub-индекс CANopen.
ctl enum DICT_CTL mCO_DictObjectDecode() Тип доступа к памяти.
len unsigned int mCO_DictObjectDecode() Размер объекта в байтах.
p union DICT_PTRS mCO_DictObjectDecode() Указатели на объекты

Параметры:

DICT_OBJ myObj: структура объекта, показанная в таблице 12, см. врезку с описанием функции mCO_DictObjectRead.

Возвращаемые значения отсутствуют. Используйте mCO_DictGetRet() для получения кода ошибки.

Пример:

void MyFunc(void)
{
   DICT_OBJ myLocalObj;
   unsigned char localArray[20];
   // Указание объекта
   myLocalObj.index = 0x1008L;
   myLocalObj.subindex = 0x00;
   // Получение информации, сохраненной в словаре OD
   mCO_DictObjectDecode(myLocalObj);
   // Указать локальное пространство, и что нужно читать данные
   myLocalObj.pReqBuf = localArray;
   myLocalObj.reqLen = 0x8;
   myLocalObj.reqOffst = 0x0;
   // Чтение объекта
   mCO_DictObjectRead(myLocalObj);
}

void mCO_DictObjectWrite(DICT_OBJ myObj)

Эта функция записывает объект, заданный через myObj. Чтобы это использовать, информация объекта должна быть сохранена локально как структура DICT_OBJ и затем передана в функцию mCO_DictObjectWrite(). Внутри используется только ссылка.

Параметр:

DICT_OBJ myObj: структура объекта, показанная в таблице 12, см. врезку с описанием функции mCO_DictObjectRead.

Возвращаемые значения отсутствуют. Используйте mCO_DictGetRet() для получения кода ошибки.

Базовое использование функции подобно тому, как показано в примере врезки описания функции mCO_DictObjectRead.

void mCO_DictObjectDecode(DICT_OBJ myObj)

Эта функция используется для заполнения любой статической информации для определенного объекта, который находится в OD. Объект задается через myObj, который должен быть определен локально и передан в эту функцию. Функция возьмет индекс и sub-индекс для поиска объекта в OD. Объект найден, то указатель, длина и некоторая управляющая информация будет загружена в структуру myObj (см. таблицу 12 во врезке с описанием функции mCO_DictObjectRead). Будет возвращена информация статуса, и она может быть получена вызовом функции mCO_DictGetRet().

Параметр:

DICT_OBJ myObj: структура объекта, как она определена в таблице 12 (см. врезку с описанием функции mCO_DictObjectRead).

Возвращаемое значение отсутствует. Используйте mCO_DictGetRet() для получения кода ошибки.

Базовое использование функции подобно тому, как показано в примере врезки описания функции mCO_DictObjectRead.

enum _DICT_OBJECT_REQUEST mCO_DictGetCmd(void)

Эта функция используется при получении команды для объекта. Имеется только 3 команды: DICT_OBJ_INFO, DICT_OBJ_READ и DICT_OBJ_WRITE.

Возвращаемые значения:

DICT_OBJ_INFO: чтение управляющей информации объекта.
DICT_OBJ_READ: чтение объекта.
DICT_OBJ_WRITE: запись объекта.

Как использовать функцию см. в Примере 9 секции "Функционально определенный объект".

unsigned char mCO_DictGetRet(void)

Эта функция используется для получения статуса возврата операции OD.

Возвращаемые значения: все они перечислены в таблице 11 (см. выше секцию "Функционально определенный объект").

void mCO_DictSetRet(unsigned char retVal)

Эта функция используется для установки статуса возврата операции OD. Это используется только внутри функции обработки объекта.

Параметр:

unsigned char retVal: статус возврата запроса объекта. Все возможные значения этого параметра перечислены в таблице 11 (см. выше секцию "Функционально определенный объект").

Как использовать функцию см. в Примере 9 секции "Функционально определенный объект".

[Драйвер ECAN™]

Функции в этой секции описывают функциональный интерфейс драйвера ECAN. Обратите внимание, что драйвер, предоставленный с этим стеком CANopen, специально разработан для устройств PIC18F, на борту которых имеется аппаратура технологии ECAN. Можно использовать также внешний контроллер CAN, с другим драйвером и другими вызовами функций. В этом событии пользователю требуется предоставить подходящий драйвер.

void mCANEventManager(void)

Это функция обработчика события. Все поставленные в очередь события обрабатываются внутри этой функции. Эта функция вызывается из стека CANopen, когда вызывается CO_ProcessAllEvents.

void mCANReset(unsigned char CANBitRate)

Эта функция сбрасывает коммуникации CAN, и устанавливает подходящую скорость по шине. Эта функция вызывается из стека CANopen, когда получен запрос сброса либо от приложения, либо от мастера NMT.

void mCANOpenComm(void)

Эта функция открывает коммуникации CAN, её следует рассматривать как запрос. В зависимости от состояния активности шины, коммуникации могут быть не открыты немедленно.

void mCANCloseComm(void)

Эта функция закрывает коммуникации CAN.

BOOL mCANIsCommOpen(void)

Эта функция может использоваться для опроса драйвера: были или нет открыты коммуникации.

Возвращаемые значения:

TRUE: коммуникации открыты.
FALSE: коммуникации закрыты.

void mCANErrIsOverFlow(void)

Эта функция используется, чтобы опросить драйвер: было ли событие переполнения буфера приема. Если найдено событие переполнения, то оно может быть удалено вызовом функции mCANErrClearOverFlow. Когда случилось условие переполнения, то одно или большее количество сообщений было потеряно. Как обработать эту ситуацию - зависит от приложения; спецификация CANopen не требует определенного метода обработки этого условия.

Возвращаемые значения:

TRUE: было переполнение буфера приема.
FALSE: не было переполнения буфера приема.

void mCANErrClearOverFlow(void)

Удаляет информацию об условии переполнения буфера приема.

void mCANSetBitRate(unsigned char CANBitRate)

Эта функция устанавливает текущее значение скорости бит (bitrate) шины CAN. Скорость не изменяется немедленно, это в действительности ставится в очередь драйвера, пока драйвер и аппаратура CAN не будут готовы принять изменение. Обычно эта функция вызывается только 1 раз в момент старта.

Параметр:

unsigned char CANBitRate: здесь может быть любое значение; однако считаются действительными только значения в диапазоне 0 .. 8. Все другие значения автоматически приведут к установке скорости по умолчанию, которая задается опцией 0. Все 9 опций скорости определены в файле CO_DEFS.DEF.

unsigned char mCANGetBitRate(void)

Эта функция вернет текущий битрейт, используемый драйвером.

Возвращаемые значения:

unsigned char: текущий битрейт. Достоверными будут только значения 0 .. 8; однако функция может вернуть другие значения, если в вызов mCANSetBitRate() было передано значение, отличающееся от диапазона 0 .. 8.

void mCANOpenMessage(unsigned char MsgTyp, unsigned long COBID, unsigned char hRet)

Эта функция сканирует доступные почтовые ящики в поиске открытого слота. Должен быть передан идентификатор CAN вместе с уникальным ненулевым дескриптором (handle) к этому идентификатору. Если найден слот, то все сообщения, содержащие предоставленный идентификатор CAN, будут приняты, и этот дескриптор будет использоваться для идентификации сообщения. В вызывающий код также будет возвращен дескриптор, если слот найден; иначе будет возвращено 0. Вызывающая функция должна хранить дескриптор, если конечная точка была освобождена позже без сброса.

Идентификатор CAN добавляется, но не активируется, пока шина и драйвер не будут готовы. В будущих модулях CAN эта функциональность очереди может быть удалена, в зависимости от доступной аппаратной поддержки.

Параметры:

unsigned char MsgTyp: уникальный дескриптор (handle) для идентификатора. Должен быть ненулевым.
unsigned long COBID: идентификатор CAN допустимого сообщения.

Возвращаемое значение:

unsigned char hRet: статус возврата. Здесь будет 0 либо дескриптор (handle).

void mCANCloseMessage(unsigned char hMsg)

Эта функция сканирует почтовые ящики в поиске дескриптора (handle). Если найден, то идентификатор CAN удаляется из списка приема.

Происходит только постановка в очередь запроса удаления идентификатора CAN из списка. Таким образом, сообщения все еще могут приниматься, пока драйвер не будет в состоянии полностью удалить идентификатор CAN из аппаратуры. В будущих модулях CAN эта функциональность очереди может быть удалена, в зависимости от доступной аппаратной поддержки.

Параметр:

unsigned char hMsg: дескриптор (handle) для сообщения.

void mCANIsGetRTR(void)

Эта функция опрашивает драйвер на условие RTR текущего сообщения. Перед этим запросом должна быть вызвана функция mCANIsGetReady для установки текущего сообщения.

void mCANIsGetReady(void)

Эта функция определяет событие приема. Если оно найдено, то связанный дескриптор (handle) помещается в буфер приема, во внутренний регистр, к которому можно получить доступ через mCANFetchRetStat. Иначе будет возвращен 0. Если ожидает допустимое сообщение, то оно должно быть обработано перед повторным вызовом этой функции.

Подразумевается доступ к буферу на последовательных вызовах приема, т. е. не требуется дескриптор для связанных функций чтения. Например, вызовы функций mCANGetDataLen() и mCANGetDataByten() подразумевают, что запрашиваются наиболее текущие принятые данные сообщения.

void mCANReadMessage(void)

Вызов этой функции показывает драйверу, что текущее сообщение было обработано, и драйвер теперь освобождает используемый буфер для нового сообщения. Функция mCANIsGetReady должна быть вызвана перед этим запросом, для установки текущего сообщения.

unsigned char * mCANGetPtrRxCOB(void)

Эта функция возвратит указатель на текущий идентификатор. Он также указывает на все сообщение, сохраненное в формате Microchip.

Возвращаемое значение:

unsigned char *: указатель на принятый идентификатор CAN.

unsigned char * mCANGetPtrRxData(void)

Эта функция вернет указатель на текущие данные.

unsigned char mCANGetDataLen(void)

Эта функция вернет длину текущего сообщения или запроса RTR.

unsigned char mCANGetDataByten(void)

Представляет 8 функций, где суффикс n может быть значениями от 0 до 7. Каждая из этих функций вернет соответствующий байт данных принятого сообщения.

void mCANIsPutReady(putHndl)

Эта функция сканирует в поиске доступного выходного буфера. В случае успеха дескриптор, переданный в функцию, будет такой же, что и возвращенный; иначе будет возвращен 0. Для получения возвращенного значения должна быть вызвана функция mCANFetchRetStat.

Параметр:

unsigned char putHndl: дескриптор (handle) сообщения.

void mCANIsPutFin(void)

Эта функция опрашивает драйвер на наличие любого сообщения, которое было помещено на шину, и возвращает дескриптор (handle) для сообщения, которое было отправлено. Для получения дескриптора этого сообщения должна быть вызвана функция mCANFetchRetStat.

Эта функция должна быть вызвана только один раз для индикации передачи. Вызов этой функции второй раз после получения индикации может не возвратить тот же самый дескриптор.

void mCANSendMessage(void)

Эта функция используется, чтобы показать драйверу, что данные, длина и идентификатор CAN были загружены и готовы к отправке.

unsigned char * mCANGetPtrTxCOB(void)

Эта функция вернет указатель на буфер идентификатора передачи CAN.

unsigned char * mCANGetPtrTxData(void)

Эта функция вернет указатель на буфер передачи данных.

void mCANPutDataLen(unsigned char CANlen)

Эта функция устанавливает длину данных или длину запроса RTR.

void mCANPutDataByten(unsigned char CANDat)

Это имя представляет 8 функций, где суффикс n может быть значением от 0 до 7. Каждая из этих функций может использоваться для установки соответствующего байта для отправки.

Параметр:

unsigned char CANDat: байт данных.

unsigned char mCANFetchRetStat(void)

Эта функция используется для получения статуса операции тех функций, которые такой статус возвращают. В описании функций указано, возвращают ли они статус.

Возвращаемое значение:

unsigned char: статус последней операции.

[Заключительные указания по созданию приложения]

Разумеется, требуется обработать некоторые специфичные для CAN детали. Вот некоторые моменты, о которых нужно помнить:

Объекты: определите и разработайте все объекты и функции их обработки, и привяжите их к OD. Объекты, которые определены через функцию, потребуют дополнительного кодирования из-за того, что требуется функция обработки; однако эти типы объектов очень гибки для использования.

OD: поместите все объекты в свое собственное правильное место внутри словаря. Правильно определите информацию управления, длины и ссылки для объектов.

Объекты PDO: они все еще должны быть определены и разработаны. Помните о том, что объекты PDO могут быть статическими или динамическими; статические всегда будут эффективнее по коду и обработке, но очевидно не такие гибкие, как динамические объекты PDO. Здесь есть также некоторое количество типов передачи PDO, которые зависят от специфики приложения. По этим причинам для разработчика предоставлен только базовый набор инструментария, так что разработчик может разработать более эффективный код для приложения.

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

Инициализация: разработайте правильный код инициализации. Многие объекты должны быть инициализированы из какого-нибудь статического источника данных, такого как ROM, EEPROM или даже перемычки, подключенные ко входным ножкам микроконтроллера.

Код в функции main: разработайте эффективный код, работающий по принципу кооперативности, чтобы правильно захватить и обработать все события.

События: имеется множество событий (events). Гарантируйте правильную обработку там, где это необходимо. Например, запросы сброса, поступающие через сеть, предоставлены как события для приложения. На усмотрение разработчика приложения оставлено, как нужно обработать запрос сброса (Reset request).

Настройки времени компиляции: установите соответствующие опции, чтобы достичь оптимального использования ресурсов и эффективности.

[Использование ресурсов]

Сколько будет затрачено места под стек CANopen, сильно зависит от опций времени компиляции, а также от установленных оптимизаций компилятора. Разработчик приложения должен ожидать, что код стека с включенной оптимизацией займет около 7000 .. 10000 байт памяти программ и 300 байт памяти данных.

Использование всех оптимизаций доступно в компиляторе MPLAB® C18 (v2.30.01), и демо-приложение, предоставленное с этим апноутом, потребовало 7434 байта памяти программ (ROM) и 314 байт памяти данных (RAM).

[Ссылки]

1. AN945: A CANopen Stack for PIC18 ECANTM Microcontrollers site:microchip.com.
2. DS-301 (v 4.02), "CANopen Communication Profile for Industrial Systems Based on CAL". Erlangen: CAN in Automation e.V., 2002.
3. M. Farsi and M. Barbosa, CAN Implementation: Applications to Industrial Networks. Baldock, Hertfordshire: Research Studies Press, 2000.
4. 170711CANopen-IAR-AT91SAM7X.zip - исходный код, документация.
5. Обзор протокола CANopen.
6. CiA301: слой приложения и профиль коммуникации CANopen.