介绍
Libcanard 是一个uavcan/can协议的c语言实现,一般适用于高可靠性的实时嵌入式系统。它具有以下特点:
- 最少需要32k ROM和4.8k RAM。
- 满足MISRA C规则,100%覆盖测试
- 兼容8-64位的处理器
- 主要包含
canard.c,canard.h,canard_dsdl.c,canard_dsdl.h
四个文件。前面两个主要是协议的实现,后面两个用于DSDL对象的序列号和反序列化。 - uavcan需要确定性的固定时间的有界碎片的动态内存分配;推荐使用O1Hheap
- 针对多个平台的底层驱动:
https://github.com/UAVCAN/platform_specific_components - MISRA C 编译报告
https://forum.uavcan.org. - 这个库不是线程安全的,如果使用并行处理器,注意保证程序的同步性。
- 支持29bit包头的拓展can包,不支持PRT包、11bit的传统包
架构
UAVCAN协议栈主要包含两层,传输层(TRANSPORT)和表示层(PRESENTATION)。传输层用于实现不同物理层传输的统一调度形式,libcanard只支持can协议的物理层。表示层用于管理DSDL语言描述的数据包。
- 传输层: 主要在
canard.c,canard.h
中。是这个库的核心。 - 表示层:可选的DSDL支持拓展库。主要在
canard_dsdl.c,canard_dsdl.h
中。
这里提供了Nunavut (https://github.com/UAVCAN/nunavut)工具,用于将DSDL描述的语言转换位C语言。
传输层
传输层主要包含TX通道和RX通道,着两个通道是互相独立的,但是依赖同一个内存管理工具。TX通道存储准备向外输出的CAN帧(有优先级序列),RX通道存储收到的数据包和重组状态机(这个我还没弄懂)。传输层的移植注意保证分配的内存空间足够存放数据。
TX通道主要通过3个API来管理。当需要启动传输的时候,使用canardTxPush()
.这个函数会把要传输的数据分解成CAN帧,并按优先级存储到发送队列。接着,在一个专门的发射任务中,canardTxPeek()
会被用来逐个地获取CAN帧,并调用物理发送函数进行数据包发送。最后,canardTxPop()
被用来弹出已经发送的CAN帧,注意需要释放弹出CAN帧的空间。
类似的,RX通道也主要通过3个API来管理,canardRxAccept()
用于接受一个收到的CAN帧,并把它组合到合适的传输状态机序列。canardRxSubscribe()
和canardRxUnsubscribe()
用于管理接受哪些uavcan数据包。(默认不接受任何包)注意在订阅时,需要准备充足的内存空间,如果接受的数据超过设定的最大size,会引起OOM错误。
宏定义
// 错误类型,注意实际需要取反,是负数,
#define CANARD_ERROR_INVALID_ARGUMENT 2
#define CANARD_ERROR_OUT_OF_MEMORY 3
// 目前的can主要支持8和64字节两种模式
#define CANARD_MTU_CAN_CLASSIC 8U
#define CANARD_MTU_CAN_FD 64U
// 头部的参数范围
#define CANARD_SUBJECT_ID_MAX 8191U
#define CANARD_SERVICE_ID_MAX 511U
#define CANARD_NODE_ID_MAX 127U
#define CANARD_PRIORITY_MAX 7U
#define CANARD_TRANSFER_ID_BIT_LENGTH 5U
#define CANARD_TRANSFER_ID_MAX ((1U << CANARD_TRANSFER_ID_BIT_LENGTH) - 1U)
// 广播ID
#define CANARD_NODE_ID_UNSET 255U
数据类型
// 优先级
typedef enum
{
CanardPriorityExceptional = 0,
CanardPriorityImmediate = 1,
CanardPriorityFast = 2,
CanardPriorityHigh = 3,
CanardPriorityNominal = 4, ///< Nominal priority level should be the default.
CanardPriorityLow = 5,
CanardPrioritySlow = 6,
CanardPriorityOptional = 7,
} CanardPriority;
// 消息类型,第一个是发布-订阅者,第23是服务请求模式
typedef enum
{
CanardTransferKindMessage = 0, ///< Multicast, from publisher to all subscribers.
CanardTransferKindResponse = 1, ///< Point-to-point, from server to client.
CanardTransferKindRequest = 2, ///< Point-to-point, from client to server.
} CanardTransferKind;
// uavcan的数据包帧
typedef struct
{
/// For RX frames: reception timestamp.
/// For TX frames: transmission deadline.
/// The time system may be arbitrary as long as the clock is monotonic (steady).
CanardMicrosecond timestamp_usec;
/// 29-bit extended ID. The bits above 29-th shall be zero.
uint32_t extended_can_id;
/// The useful data in the frame. The length value is not to be confused with DLC!
/// If the payload is empty (payload_size = 0), the payload pointer may be NULL.
/// For RX frames: the library does not expect the lifetime of the pointee to extend beyond the point of return
/// from the API function. That is, the pointee can be invalidated immediately after the frame has been processed.
/// For TX frames: the frame and the payload are allocated within the same dynamic memory fragment, so their
/// lifetimes are identical; when the frame is freed, the payload is invalidated.
/// A more detailed overview of the dataflow and related resource management issues is provided in the API docs.
size_t payload_size;
const void* payload;
} CanardFrame;
// uavcan的操作句柄
struct CanardInstance
{
/// User pointer that can link this instance with other objects.
/// This field can be changed arbitrarily, the library does not access it after initialization.
/// The default value is NULL.
void* user_reference;
/// The transport-layer maximum transmission unit (MTU). The value can be changed arbitrarily at any time.
/// This setting defines the maximum number of bytes per CAN data frame in all outgoing transfers.
/// Regardless of this setting, CAN frames with any MTU can always be accepted.
///
/// Only the standard values should be used as recommended by the specification;
/// otherwise, networking interoperability issues may arise. See recommended values CANARD_MTU_*.
///
/// Valid values are any valid CAN frame data length value not smaller than 8.
/// Invalid values are treated as the nearest valid value. The default is the maximum valid value.
size_t mtu_bytes;
/// The node-ID of the local node.
/// Per the UAVCAN Specification, the node-ID should not be assigned more than once.
/// Invalid values are treated as CANARD_NODE_ID_UNSET. The default value is CANARD_NODE_ID_UNSET.
CanardNodeID node_id;
/// Dynamic memory management callbacks. See their type documentation for details.
/// They SHALL be valid function pointers at all times.
/// The time complexity models given in the API documentation are made on the assumption that the memory management
/// functions have constant complexity O(1).
///
/// The following API functions may allocate memory: canardRxAccept(), canardTxPush().
/// The following API functions may deallocate memory: canardRxAccept(), canardRxSubscribe(), canardRxUnsubscribe().
/// The exact memory requirement and usage model is specified for each function in its documentation.
CanardMemoryAllocate memory_allocate;
CanardMemoryFree memory_free;
/// Read-only DO NOT MODIFY THIS
CanardRxSubscription* rx_subscriptions[CANARD_NUM_TRANSFER_KINDS];
/// This field is for internal use only. Do not access from the application.
struct CanardInternalTxQueueItem* _tx_queue;
};
函数介绍
- 初始化,会返回操作句柄
CanardInstance canardInit(const CanardMemoryAllocate memory_allocate, const CanardMemoryFree memory_free);
- 发送函数,把DSDL数据包帧序列化成CAN帧,并按优先级存储到缓冲区。返回值位,序列化后的CAN帧的数量,失败为负数。
int32_t canardTxPush(CanardInstance* const ins, const CanardTransfer* const transfer);
- 获取发送帧函数,获取发送缓冲区当前优先级最高的CAN帧,当缓冲区为空时返回NULL,否者返回一个CAN帧
const CanardFrame* canardTxPeek(const CanardInstance* const ins);
- 弹出发送帧函数,发送完的帧需要从发送队列弹出。注意,这个函数不会自动清理弹出的帧,需要手动清理。
void canardTxPop(CanardInstance* const ins);
- 接受函数,注意原来的接受函数即将被抛弃。
int8_t canardRxAccept2(CanardInstance* const ins,
const CanardFrame* const frame,
const uint8_t redundant_transport_index,
CanardTransfer* const out_transfer,
CanardRxSubscription** const out_subscription);
- 订阅函数,返回1代表成功,0代表订阅已存在,负数代表参数错误。
int8_t canardRxSubscribe(CanardInstance* const ins,
const CanardTransferKind transfer_kind,
const CanardPortID port_id,
const size_t extent,
const CanardMicrosecond transfer_id_timeout_usec,
CanardRxSubscription* const out_subscription);
- 取消订阅函数,返回1代表订阅存在,0代表订阅不存在。负数代表错屋。
int8_t canardRxUnsubscribe(CanardInstance* const ins,
const CanardTransferKind transfer_kind,
const CanardPortID port_id);
表示层
待完成