Libcanard 是 C99/C11 中 Cyphal/CAN 协议栈的紧凑实现,适用于高完整性实时嵌入式系统。
Cyphal 是一种开放式轻量级数据总线标准,旨在通过 CAN 总线、以太网和其他可靠的传输方式在航空航天和机器人应用中实现可靠的车内通信。
阅读libcanard/canard.h
中的文档。
在 Cyphal 论坛上查找示例、入门教程和教程。
如果您想贡献,请阅读CONTRIBUTING.md
。
该库设计为可在任何传统 8/16/32/64 位平台上开箱即用,包括深度嵌入式裸机平台,只要有可用的符合标准的编译器即可。平台特定的媒体 IO 层(驱动程序)应该由应用程序提供:
+---------------------------------+
| Application |
+-------+-----------------+-------+
| |
+-------+-------+ +-------+-------+
| Libcanard | | Media layer |
+---------------+ +-------+-------+
|
+-------+-------+
| Hardware |
+---------------+
OpenCyphal 开发团队在 https://github.com/OpenCyphal/platform_specific_components 的单独存储库中维护了各种特定于平台的组件的集合。我们鼓励用户在该存储库中搜索驱动程序、示例以及可在目标应用程序中重用的其他部分,以加快应用程序的媒体 IO 层(驱动程序)的设计。
该示例补充了文档,但并没有取代它。
该库需要一个恒定复杂度的确定性动态内存分配器。我们可以使用标准 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.
}
还提供了一个用于生成 CAN 硬件接受过滤器配置的简单 API。接受过滤器以扩展的 29 位 ID + 掩码方案生成,可用于最大限度地减少软件中处理的不相关传输的数量。
// 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) 链表替换为快速 O(log n) AVL 树(Cavl 库随 libcanard 一起分发)。现在,遍历 RX 订阅列表需要递归遍历树。
手动 DSDL 序列化助手已删除;请改用努纳武特地区。
默认情况下用更快的静态表替换按位 CRC 计算 (#185)。可以通过设置CANARD_CRC_TABLE=0
来禁用此功能,预计可以节省大约 10% 的时间。 500 字节 ROM。
修复了 API 中 const 正确性的问题 (#175)。
canardRxAccept2()
重命名为canardRxAccept()
。
通过CANARD_CONFIG_HEADER
支持构建配置标头。
添加用于生成 CAN 硬件接受过滤器配置的 API (#169)。
canardRxAccept2()
,弃用canardRxAccept()
。CanardRxSubscription
中提供用户参考。最初的发布。