Libcanard — это компактная реализация стека протоколов Cyphal/CAN в C99/C11 для высокоинтегральных встроенных систем реального времени.
Cyphal — это открытый облегченный стандарт шины данных, предназначенный для надежной внутриавтомобильной связи в аэрокосмических и робототехнических приложениях через шину CAN, Ethernet и другие надежные транспортные средства.
Прочтите документацию в libcanard/canard.h
.
Найдите примеры, стартеры и руководства на форуме Cyphal.
Если вы хотите внести свой вклад, прочтите CONTRIBUTING.md
.
Библиотека предназначена для использования сразу после установки с любой обычной 8/16/32/64-битной платформой, включая глубоко встраиваемые «голые» платформы, при условии, что доступен совместимый со стандартами компилятор. Предполагается, что уровень ввода-вывода мультимедиа (драйвер) для конкретной платформы предоставляется приложением:
+---------------------------------+
| Application |
+-------+-----------------+-------+
| |
+-------+-------+ +-------+-------+
| Libcanard | | Media layer |
+---------------+ +-------+-------+
|
+-------+-------+
| Hardware |
+---------------+
Команда разработчиков OpenCyphal хранит коллекцию различных компонентов, специфичных для конкретной платформы, в отдельном репозитории по адресу https://github.com/OpenCyphal/platform_специфические_компоненты. Пользователям рекомендуется искать в этом репозитории драйверы, примеры и другие части, которые можно повторно использовать в целевом приложении, чтобы ускорить разработку уровня ввода-вывода мультимедиа (драйвера) для приложения.
Этот пример дополняет документацию, но не заменяет ее.
Библиотеке требуется детерминированный динамический распределитель памяти постоянной сложности. Мы могли бы использовать стандартную кучу C, но большинство реализаций не имеют постоянной сложности, поэтому предположим, что вместо этого мы используем O1Heap. Нам понадобятся базовые обертки:
static void * memAllocate ( CanardInstance * const canard , const size_t amount )
{
( void ) canard ;
return o1heapAllocate ( my_allocator , amount );
}
static void memFree ( CanardInstance * const canard , void * const pointer )
{
( void ) canard ;
o1heapFree ( my_allocator , pointer );
}
Инициализируйте экземпляр библиотеки:
CanardInstance canard = canardInit ( & memAllocate , & memFree );
canard . node_id = 42 ; // Defaults to anonymous; can be set up later at any point.
Чтобы иметь возможность отправлять передачи по сети, нам понадобится одна очередь передачи на каждый резервный CAN-интерфейс:
CanardTxQueue queue = canardTxInit ( 100 , // Limit the size of the queue at 100 frames.
CANARD_MTU_CAN_FD ); // Set MTU = 64 bytes. There is also CANARD_MTU_CAN_CLASSIC.
Опубликовать сообщение (сериализация сообщения не показана):
static uint8_t my_message_transfer_id ; // Must be static or heap-allocated to retain state between calls.
const CanardTransferMetadata transfer_metadata = {
. priority = CanardPriorityNominal ,
. transfer_kind = CanardTransferKindMessage ,
. port_id = 1234 , // This is the subject-ID.
. remote_node_id = CANARD_NODE_ID_UNSET , // Messages cannot be unicast, so use UNSET.
. transfer_id = my_message_transfer_id ,
};
++ my_message_transfer_id ; // The transfer-ID shall be incremented after every transmission on this subject.
int32_t result = canardTxPush ( & queue , // Call this once per redundant CAN interface (queue).
& canard ,
tx_deadline_usec , // Zero if transmission deadline is not limited.
& transfer_metadata ,
47 , // Size of the message payload (see Nunavut transpiler).
"x2Dx00" "Sancho, it strikes me thou art in great fear." );
if ( result < 0 )
{
// An error has occurred: either an argument is invalid, the TX queue is full, or we've run out of memory.
// It is possible to statically prove that an out-of-memory will never occur for a given application if the
// heap is sized correctly; for background, refer to the Robson's Proof and the documentation for O1Heap.
}
Используйте Nunavut для автоматического создания кода сериализации (де) на основе определений DSDL.
Кадры CAN, сгенерированные в результате передачи сообщений, теперь сохраняются в queue
. Нам нужно выбрать их одного за другим и передать. Обычно следующий фрагмент должен вызываться периодически для выгрузки кадров CAN из приоритетной очереди передачи (или нескольких, если используются резервные сетевые интерфейсы) в драйвер CAN:
for ( const CanardTxQueueItem * ti = NULL ; ( ti = canardTxPeek ( & queue )) != NULL ;) // Peek at the top of the queue.
{
if (( 0U == ti -> tx_deadline_usec ) || ( ti -> tx_deadline_usec > getCurrentMicroseconds ())) // Check the deadline.
{
if (! pleaseTransmit ( ti )) // Send the frame over this redundant CAN iface.
{
break ; // If the driver is busy, break and retry later.
}
}
// After the frame is transmitted or if it has timed out while waiting, pop it from the queue and deallocate:
canard . memory_free ( & canard , canardTxPop ( & queue , ti ));
}
Прием передачи осуществляется путем подачи кадров в конечный автомат сборки передачи из любого из резервных интерфейсов. Но сначала нам нужно подписаться:
CanardRxSubscription heartbeat_subscription ;
( void ) canardRxSubscribe ( & canard , // Subscribe to messages uavcan.node.Heartbeat.
CanardTransferKindMessage ,
7509 , // The fixed Subject-ID of the Heartbeat message type (see DSDL definition).
16 , // The extent (the maximum possible payload size) provided by Nunavut.
CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC ,
& heartbeat_subscription );
CanardRxSubscription my_service_subscription ;
( void ) canardRxSubscribe ( & canard , // Subscribe to an arbitrary service response.
CanardTransferKindResponse , // Specify that we want service responses, not requests.
123 , // The Service-ID whose responses we will receive.
1024 , // The extent (see above).
CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC ,
& my_service_subscription );
«Объем» относится к минимальному объему памяти, необходимому для хранения любого сериализованного представления любой совместимой версии типа данных; или, другими словами, это максимально возможный размер получаемых объектов. Этот параметр определяется автором типа данных во время определения типа данных. Обычно он превышает максимальный размер объекта, чтобы позволить автору типа данных ввести больше полей в будущих версиях типа; например, MyMessage.1.0
может иметь максимальный размер 100 байт и размер 200 байт; исправленная версия MyMessage.1.1
может иметь максимальный размер от 0 до 200 байт. Значения экстента предоставляются для каждого типа данных транскомпиляторами DSDL, такими как Nunavut.
В Libcanard мы для простоты используем термин «подписка» не только для тем (сообщений), но и для сервисов.
Мы можем подписываться и отписываться во время выполнения столько раз, сколько захотим. Однако обычно встроенное приложение подписывается один раз и продолжает работать с ним. Хорошо, вот как мы получаем переводы:
CanardRxTransfer transfer ;
const int8_t result = canardRxAccept ( & canard ,
rx_timestamp_usec , // When the frame was received, in microseconds.
& received_frame , // The CAN frame received from the bus.
redundant_interface_index , // If the transport is not redundant, use 0.
& transfer ,
NULL );
if ( result < 0 )
{
// An error has occurred: either an argument is invalid or we've ran out of memory.
// It is possible to statically prove that an out-of-memory will never occur for a given application if
// the heap is sized correctly; for background, refer to the Robson's Proof and the documentation for O1Heap.
// Reception of an invalid frame is NOT an error.
}
else if ( result == 1 )
{
processReceivedTransfer ( redundant_interface_index , & transfer ); // A transfer has been received, process it.
canard . memory_free ( & canard , transfer . payload ); // Deallocate the dynamic memory afterwards.
}
else
{
// Nothing to do.
// The received frame is either invalid or it's a non-last frame of a multi-frame transfer.
// Reception of an invalid frame is NOT reported as an error because it is not an error.
}
Также предоставляется простой API для создания конфигураций приемочного фильтра оборудования CAN. Фильтры приема генерируются по расширенной схеме 29-битный идентификатор + маска и могут использоваться для минимизации количества нерелевантных передач, обрабатываемых программным обеспечением.
// Generate an acceptance filter to receive only uavcan.node.Heartbeat.1.0 messages (fixed port-ID 7509):
CanardFilter heartbeat_config = canardMakeFilterForSubject ( 7509 );
// And to receive only uavcan.register.Access.1.0 service transfers (fixed port-ID 384):
CanardFilter register_access_config = canardMakeFilterForService ( 384 , ins . node_id );
// You can also combine the two filter configurations into one (may also accept irrelevant messages).
// This allows consolidating a large set of configurations to fit the number of hardware filters.
// For more information on the optimal subset of configurations to consolidate to minimize wasted CPU,
// see the Cyphal specification.
CanardFilter combined_config =
canardConsolidateFilters ( & heartbeat_config , & register_access_config );
configureHardwareFilters ( combined_config . extended_can_id , combined_config . extended_mask );
Полная спецификация API доступна в документации. Если примеры кажутся вам непонятными или неверными, пожалуйста, откройте тикет.
canardRxGetSubscription
.Обновите фирменный стиль, поскольку UAVCAN v1 переименован в Cyphal.
Улучшите соответствие MISRA, исключив использование гибкого массива: (#192).
Исправьте проблемы с зависимостями в наборе инструментов Docker.
В этом выпуске нет никаких изменений API, кроме ребрендинга/переименования: CANARD_UAVCAN_SPECIFICATION_VERSION_MAJOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MAJOR
CANARD_UAVCAN_SPECIFICATION_VERSION_MINOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MINOR
_canard_cavl.h
(#196). Выделенные очереди передачи для каждого резервного интерфейса CAN с ограничениями по глубине. Теперь ожидается, что приложение создаст экземпляр CanardTxQueue
(или несколько в случае избыточного транспорта) вручную.
Замените связанные списки O(n) быстрыми деревьями AVL O(log n) (библиотека Cavl распространяется вместе с libcanard). Обход списка подписок RX теперь требует рекурсивного обхода дерева.
Удалены помощники ручной сериализации DSDL; вместо этого используйте Нунавут.
Замените побитовое вычисление CRC на гораздо более быструю статическую таблицу по умолчанию (#185). Это можно отключить, установив CANARD_CRC_TABLE=0
, что, как ожидается, сэкономит ок. 500 байт ПЗУ.
Исправлены проблемы с константной корректностью в API (#175).
canardRxAccept2()
переименован в canardRxAccept()
.
Поддержка заголовков конфигурации сборки через CANARD_CONFIG_HEADER
.
Добавьте API для создания конфигураций приемочного фильтра оборудования CAN (#169).
canardRxAccept2()
, объявить устаревшей canardRxAccept()
.CanardRxSubscription
.Первоначальный выпуск.