分享一种好用的通信协议:UAVCAN(Cyphal)

什么是 UAVCAN(Cyphal)

UAVCAN 是一种高效的、轻量级的开源通信协议,专门设计用于嵌入式系统,特别是在无人机(UAV)及相关领域。主要有以下特性:

  • UAVCAN 最初是为无人机设计的,但它也可以用于任何需要高可靠性和实时通信的嵌入式系统。它被广泛用于飞行控制、传感器、执行器等组件之间的数据传输。现已改名为 Cyphal。
  • UAVCAN 是一种基于 CAN(Controller Area Network)总线的通信协议。CAN 总线是一种用于车辆和工业控制系统的标准通信协议。UAVCAN 将 CAN 总线的功能扩展到更多复杂的应用。
  • UAVCAN 允许设备以一种高效且可靠的方式交换数据。它支持多主机和多从机模式,可以处理不同设备之间的通信需求,包括点对点和广播模式。
  • UAVCAN 设计时考虑到了高可靠性,具有容错机制,可以在恶劣环境下工作,确保通信的稳定性。
    • 冗余机制:UAVCAN 支持多个通信通道的冗余配置。在多通道配置下,如果一个通道出现故障,系统可以自动切换到其他通道,从而保证通信的连续性。
    • 错误检测和纠正:UAVCAN 使用了帧校验和数据完整性检查方法,如 CRC(循环冗余检查),以检测和修正数据传输中的错误。这能有效地防止数据损坏和传输错误。
    • 消息确认:采用了消息确认机制,以确保数据的可靠传输。接收方会发送确认应答,发送方会根据应答确认消息是否成功送达。
    • 数据包分片:如果消息数据量较大,UAVCAN 支持将消息分片并分别传输。每个分片都有序列号和重传机制,确保整个消息能够完整地传输和重组。
    • 重传机制:在数据丢失或未确认的情况下,UAVCAN 能够自动重传消息,以确保数据最终能正确到达目标。
    • 消息优先级:UAVCAN 支持消息优先级配置,允许系统根据不同消息的紧急程度调整传输优先级。这有助于确保关键消息在高负载条件下优先处理。
    • 时间同步:UAVCAN 支持高精度的时间同步,以确保在多节点系统中数据的时间一致性。这对实时系统尤为重要,确保各节点的数据交换在一致的时间框架下进行。
    • 动态节点管理:协议设计中包含动态节点管理功能,使得系统能够适应节点的添加或移除而不会影响整体通信链路的稳定性。

总的来说,UAVCAN 提供了一个灵活、可靠的通信框架,适用于现代嵌入式系统中的各种应用。

官方参考资料

基本概述

Cyphal 是由三个解耦部分组成的分层组合:应用层、表示层和传输层(特意省略了物理层)。应用层定义了一些通用的高级功能和概念,这些功能和概念有望在不同的应用领域有用,如诊断、配置、基本物理量等。表示层由 DSDL 建模,即数据结构描述语言,它描述数据格式以及如何序列化和解释该数据。传输层决定如何通过网络传输序列化的数据结构。Cyphal 是一种点对点(民主)协议,这意味着不存在“主”或任何其他类型的集中式智能——所有节点都是平等的。

数据结构种类

  • 消息:节点可以使用特定的数字(主题标识符)发布消息。使用该主题标识符,另一个或多个节点可以订阅特定消息。这是标准的发布-订阅模式。
  • 服务:一个节点可以向另一个节点发送由特定数值(服务标识符)的请求。另一个节点将接收请求,然后可能发回响应。这是标准的客户端-服务器模型。

在一般地讨论主题或服务标识符时,我们称它们为端口标识符。

端口标识符

Cyphal 协议规范里约定了端口标识符取值。

Subjects 主题

说明
06143不受监管的标识符
61447167非标准规范标识符(命名空间 reg)
71688191标准规范标识符(命名空间 uavcan)

Service 服务

说明
0255不受监管的标识符
256383非标准规范标识符(命名空间reg)
384511标准规范标识符(命名空间uavcan)

隐式零扩展,隐式截断

cyphal 有两个相同类型的版本(v0, v1),它们看起来非常不同,但在语义上都是兼容的。一个节点可以发布 my_project/ mymessagettype .1.1,另一个节点可以使用 my_project/ mymessagettype .1.2 订阅并反序列化消息,由于隐式的零扩展规则,它们可以很好地通信。该规则指出,如果反序列化程序预期的数据比实际数据多,它将假设所有数据都是0,因此键看起来是空的,额外的字段将是零。如果节点反转了它们的角色,隐式截断规则就会出现,即如果数据比节点预期的多,那么它应该假装多余的数据根本不存在。在 DSDL 级别,一个相关的概念是结构多态性或结构子类型。

DSDL 数据类型指令

  • 根据 DSDL 语法规范(第3.2节),指令可能有,也可能没有相关的表达式。如果违反了相关指令表达式的期望或缺少期望,则包含的 DSDL 定义是畸形的。
  • 指令的效果受限于包含它的数据类型定义。这意味着服务类型,除非特别说明,放置在请求(响应)类型中的指令只影响请求(响应)类型。在 dsdl 描述文件里通过三个 - (— )分隔请求与服务类型。

Extent and sealing

  • 如 DSDL 语法规范 3.8 节所述,数据类型可能会随着时间的推移而演变,以适应新的设计需求功能,以纠正问题等。为了允许将已部署的系统逐步迁移到修改后的数据类型,我们希望确保它们可以以一种不会导致新定义不兼容的方式进行修改他们的早期版本。在这方面有两个相关的概念:
    • Extent——应分配用于存储类型的序列化表示的最小内存量(以位为单位)。任何类型的范围总是大于或等于其位长集的最大值。它总是8的倍数。
    • Sealing——密封的类型是不可演化的,其范围等于其位长设置的最大值,未密封的类型也称为分隔的类型。
  • extent 是类型在演化过程中最大比特长度的增长极限。Extent 应该至少与类型的任何兼容版本的最长序列化表示一样大,这确保了由于规避,利用特定版本的代理可以正确处理任何其他兼容版本序列化表示的意外截断。
  • 可演化定义的序列化表示可以携带额外的元数据,引入特定的开销。的序列化表示的情况下,这在某些情况下可能是不需要的定义应该是高度紧凑的,从而使开销相对更大。在这种情况下,设计人员可以通过声明定义为密封的方式选择不使用可扩展性。序列化密封定义的表示不会产生上述开销。相关的机制在官方 DSDL 文档 3.7.5.3 节中描述。

由于 Cyphal 致力于决定论,内存缓冲区分配可能成为一个问题。当使用带有隐式截断规则的平面复合类型(其中每个字段都是基本类型),就是这样如果分配的缓冲区太小,不能容纳整个序列化表示,则明确最后定义的字段将被截断。如果存在复合类型字段,则不能执行此行为不再被依赖。解释这一点的技术细节在 3.7.5.3 节给出。传统协议通过将内存需求识别推迟到运行时来实现这一点,这对 Cyphal 来说是不可接受的。Cyphal 的解决方案是允许数据类型作者要求实现来保留比数据类型定义所需的更多的内存,除非它是 @sealed (或除非实现确实使用动态内存分配)。

Union

  • 任何数据类型定义都可以用一个特殊的指令(union)来提供,该指令指示它形成一个带标记的联盟(作用同 c 的 union)。带标记的联合类型应至少包含两个字段属性。带标记的联合不应包含填充字段属性。带标记的联合对象的值是字段属性的函数,该属性当前持有该值字段属性本身的值。为了避免歧义,非标记联合的数据类型被称为结构。
  • 在数据类型定义中出现此指令表明包含此指令的数据类型定义属于带标签的联合类别3.4.5.3(部分)。
  • 此指令的使用受以下约束:
    • 该指令在每个数据类型定义中不得使用多次。
    • 该指令应放在当前定义中的第一个复合类型属性定义之前。
uint8[<64] name # Request is not a union
---
@union # Response is a union
uint64 natural
#@union # Would fail - @union is not allowed after the first attribute definition
float64 real

Offset attribute

  • offset 属性由它的标识符“offset”引用。其值的类型为 set。
  • 在下面的文本中,术语引用语句指的是包含引用offset属性的表达式的语句。术语位长集在3.1.5节中定义。
  • 属性的值是引用语句前面的字段属性声明和包含定义的类别的函数。
  • 如果当前定义属于带标记的联合类别,引用语句应位于最后一个字段属性定义之后。引用语句后面的字段属性定义会使当前定义无效。对于带标记的联合,offset属性的值被定义为联合字段的累积位长,其中集合的每个元素都由隐式联合标记字段的位长递增。
  • 如果当前数据定义不属于带标记的联合类别,则引用语句可以位于当前定义中的任何位置。offset 属性的值被定义为引用语句之前的语句中定义的字段的累积位长(参见 DSDL 语法规范 3.2 节关于语句顺序)。
@union
uint8 a
#@print _offset_ # Would fail: it's a tagged union, _offset_ is undefined until after the last field
uint16 b
@assert _offset_ == {8 + 8, 8 + 16}
---
@assert _offset_ == {0}
float16 a
@assert _offset_ == {16}
void4
@assert _offset_ == {20}
int4 b
@assert _offset_ == {24}
uint8[<4] c
@assert _offset_ == 8 + {24, 32, 40, 48}
@assert _offset_ % 8 == {0}
# One of the main usages for _offset_ is statically proving that the following field is byte-aligned
# for all possible valid serialized representations of the preceding fields. It is done by computing
# a remainder as shown above. If the field is aligned, the remainder set will equal {0}. If the
# remainder set contains other elements, the field may be misaligned under some circumstances.
# If the remainder set does not contain zero, the field is never aligned.
uint8 well_aligned # Proven to be byte-aligned.

Extent

  • Extent 是保存数据类型的任何兼容版本的任何序列化表示所需的最小内存量;或者,换句话说,它是接收到的该类型对象的最大可能大小。大小是用字节(而不是位)指定的,因为根据定义,extent 是字节长度的整数。在为这种数据类型分配反序列化(RX)缓冲区时,它至少应该是extent 字节大小。
  • 在分配序列化(TX)缓冲区时,使用最大的序列化表示的大小而不是区段的大小是安全的,因为它提供了更严格的对象大小限制;它是安全的,因为在序列化期间具体类型总是已知的(不像反序列化)。如果不确定,可以到处使用范围。
  • 这个指令声明了当前的数据类型定义要分隔(非密封)并指定其范围(第3.4.5.5节)。获取 extent 值通过对提供的表达式求值。表达式必须存在,并产生一个非负整数“理性”类型的价值(第3.4.3节)的评估。该指令的使用受以下约束(否则,定义是畸形的):
    • 该指令在每个数据类型定义中不得使用多次。
    • 指令应该放在当前数据类型的最后一个属性定义之后。
    • 该值应满足 3.4.5.5 节给出的约束条件。总是8 bit的倍数。
    • 数据类型不应密封(sealed)。
uint64 foo
@extent 256 * 8 # Make the extent 256 bytes large
#@sealed # Would fail -- mutually exclusive directives
uint64[<=64] bar
@extent _offset_.max * 2
#float32 baz # Would fail (protects against incorrectly computing
# the extent when it is a function of _offset_)

Sealing marker

  • 密封标记指令的标识符为“sealed”。该指令将当前数据类型标记为密封(部分3.4.5.5)。
  • 该指令的使用受以下约束(否则,定义是畸形的):
    • 该指令在每个数据类型定义中不得使用多次。
    • 在此数据类型定义中不能使用 extent 指令。
uint64 foo
@sealed # The request type is sealed.
#@extent 128 # Would fail -- cannot specify extent for sealed type
---
float64 bar # The response type is not sealed.
@extent 4000 * 8

Deprecation marker

  • 弃用标记指令的标识符为" deprecated "。在数据类型中存在此指令定义指示数据类型定义的当前版本已接近其生命周期的末尾可能很快就会被移除。第3.8节解释了数据类型版本控制原则。代码生成工具应该使用该指令来反映生成代码中即将删除的当前数据类型版本。
  • 此指令的使用受以下约束:
    • 该指令在每个数据类型定义中不得使用多次。
    • 该指令应放在定义中的第一个复合类型属性定义之前。
    • 在服务类型的情况下,该指令只能放在请求类型中,它也影响响应类型。
@deprecated # Applies to the entire definition
uint8 FOO = 123
#@deprecated # Would fail - shall be placed before the first attribute definition
---
#@deprecated # Would fail - shall be placed in the request type

Assertion check

  • 断言检查指令的标识符是“assert”。断言检查指令需要一个表达式,在求值时将产生一个类型为“bool”的值(第3.4.3节)。
  • 如果表达式的结果为真值,则断言检查指令将不起作用。如果表达式产生假值,即类型不是“bool”的值,或者无法求值,则包含DSDL定义是畸形的。
float64 real
@assert _offset_ == {32} # Would fail: {64} != {32}

Print

  • 打印指令的标识符是“print”。打印指令可以提供也可以不提供相关联的表达式。
  • 如果没有提供表达式,则行为是由实现定义的。
  • 如果提供了表达式,则对其求值,并由DSDL处理工具以人类可读的实现定义的形式显示其结果。实现应该努力产生文本表示它们本身形成有效的DSDL表达式,因此如果用一个函数求值,它们将产生相同的值DSDL处理工具。
  • 如果提供了表达式但无法求值,则包含的DSDL定义是畸形的。
float64 real
@print _offset_ / 6 # Possible output: {32/3}
@print uavcan.node.Heartbeat.1.0 # Possible output: uavcan.node.Heartbeat.1.0
@print bool[<4] # Possible output: saturated bool[<=3]
@print float64 # Possible output: saturated float64
@print {123 == 123, false} # Possible output: {true, false}
@print 'we all float64 down here\n' # Possible output: 'we all float64 down here\n'

扩展

应用思考

  • 对于广播类消息,发送节点在发送消息时指定接收节点为广播节点(255),而接收节点则需要事先订阅相应的主题标识符。适合用于单方面上报的情况,如心跳,传感器数据上报等。
  • 对于服务请求,请求时节点在发送消息时指定具体接收节点(也可用广播节点),通常也需要事先订阅相应的服务标识符以接收响应。而服务发送方节点则需要订阅相应的服务标识符,以便能收到正确的请求进行响应。适合于节点间的交互,如上位机节点既需要配置下位机节点模块,又需要请求模块上报数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值