深入探讨通信协议的设计思路、原则与实现流程
引言
通信协议是计算机和嵌入式系统领域中让不同设备可靠交流的“语言”和规则集合。无论是两个微控制器之间的简单串口通信,还是数百台设备共享一条总线的大型网络,都需要精心设计的协议来规范数据格式和传输流程,确保信息能够准确、高效地从发送端抵达接收端,并让各节点“听懂”彼此的数据。本文旨在深入探讨通信协议的设计原则和实现流程,并通过案例分析各主流协议的结构特点和设计权衡。
通信协议的通用设计原则
通信协议是在通信系统中预先约定的一套规则,用于控制信息在各节点之间的传输与解释。无论协议的具体实现如何,其设计通常需要遵循若干通用原则,以确保系统的可靠性、有效性和兼容性。本节将从几个方面介绍通信协议设计的基本原则,包括分层结构、帧格式、仲裁机制、错误处理以及兼容扩展等。
分层与模块化设计
分层设计是通信协议设计中最核心的思想之一。将复杂的通信过程划分为多个层次,每一层完成相对独立的功能,通过明确的接口与上下层交互,可以大大降低系统复杂度并提高灵活性。这种抽象分层的方法最初在计算机网络(如 OSI 七层模型和 TCP/IP 协议栈)中取得了巨大成功,如今已成为各类通信协议设计的基础。
一般来说,协议栈会划分出从底层信号传输到高层应用数据的不同层级。例如,常见的分层包括:物理层、数据链路层、网络层、传输层、应用层等。在嵌入式设备的通信协议中,通常重点关注物理层和数据链路层,有时再加上简单的应用层协议定义。例如 CAN 总线协议根据 ISO/OSI 模型被划分为数据链路层和物理层两层,各自定义不同层次的规范,以提高设计的透明度和实现的灵活性。
采用分层和模块化的设计有多方面好处:
- 功能解耦:每一层关注解决一类特定问题,相互之间通过接口通信,便于独立开发和测试。例如,物理层关注信号如何在介质上传输,数据链路层则关注如何组帧、检测错误并可靠交付数据。这种纵向分离让协议的某一层实现可以替换或升级,而不影响其他层的工作(只要接口契约不变)。
- 灵活组合:不同层次的协议可以像乐高积木一样组合形成“协议栈”。系统并非只使用单一协议完成所有通信,而是经常采用协议套件来分层合作。例如嵌入式设备接入互联网时,可能在数据链路层用以太网协议,在传输层用TCP协议,在应用层用HTTP协议,各司其职。
- 易于扩展和维护:当通信需求增加或修改时,只需在相应层进行调整。例如需要引入新的错误检测手段,可以局部修改数据链路层协议而无需重构整个通信系统。
总之,分层设计赋予通信协议良好的结构化特性,使其具备清晰的边界和内聚的功能模块。这种设计原则使得协议既简单明了又方便扩展。在实际设计中,哪怕针对的是简单的嵌入式串行通信协议,也可以借鉴分层思维,例如将“字节级别的传输”和“消息级别的解释”划分为不同模块处理,以降低耦合度。
通过上述分层方法,一个复杂通信系统被拆解为多个易于理解和实现的部分,各层之间接口清晰,既减少了设计和实现的难度,又提升了整个系统的鲁棒性和灵活性。这也是为什么“分层”被认为是协议设计的基石之一。
图 1:典型通信协议的分层模型示意图。底层物理层负责比特信号的传输,中间数据链路层将比特组帧并可靠传输给对端,高层应用层则解释数据含义并服务于具体应用。分层设计使各层功能独立又紧密衔接。
帧结构与数据格式
帧是数据链路层传输的基本单位,它将原始比特流组织成有意义的字段组合,通常包括标识信息、有效载荷和校验信息等部分。合理的帧结构设计对于通信协议至关重要——它决定了协议如何封装数据、检测边界、识别通信对象,并直接影响系统的效率和可靠性。
一个典型的数据帧往往划分为帧头(Header)和帧载荷(Payload)两大部分。帧头包含协议运作所需的控制字段,例如地址、类型、长度、序号等,而载荷则承载实际要传输的用户数据。在帧头和载荷之后,很多协议会附加校验码(如 CRC 校验)用于错误检测。此外,一些协议还定义帧开始标志、帧结束标志,或在帧之间插入特定间隔,以便接收方正确识别帧边界。
设计帧结构时的通用准则包括:
- 字段简明、含义明确:通信协议应当有清晰的规范,帧中每个字段的作用和格式都要明确规定,避免歧义。例如,在自定义一个简单串行协议时,可以规定“帧头2字节(固定值作为同步标记)、长度1字节、命令类型1字节、数据N字节、校验1字节、帧尾1字节”等。这种固定且清晰的字段划分便于发送和接收双方解析。过于复杂或隐晦的帧格式会增加实现难度和出错风险。
- 长度适宜:帧长度既不能太短也不宜过长,需要在协议复杂度与性能之间权衡。帧太小则每次只能传输很少的数据,协议开销相对于有效载荷过高;帧太大则不利于解析和缓存处理,并且一旦出错需要重传的成本也更高。一般而言,帧应能容纳一个原子性的数据单元——足以完成基本交互,但又不会因为体积庞大而难以处理。
- 包含完整的标识和控制信息:优秀的协议帧通常是自描述的,即光靠帧内部信息即可知道它属于什么类型、要发给谁以及有多长等,不需要依赖前一帧的上下文才能解析。例如帧中包含目标地址和源地址字段,使多个节点共享介质时可以判断“这帧数据是谁发给谁”;再如包含帧类型或命令码字段,以便接收方知道如何处理载荷。每个帧尽可能独立,有助于降低由于丢帧或乱序导致的串扰。
- 校验与完整性:帧结构应当预留校验机制来保证数据完整。例如几乎所有底层协议都会使用循环冗余校验(CRC)或校验和(Checksum)字段,将帧内容计算出一个校验码附在帧尾,供接收方重新计算比对,以检测传输中的比特翻转错误。这一点属于错误检测机制的一部分,下面章节详细讨论。
- 考虑扩展:在帧结构设计时,最好为将来协议扩展预留空间或标志位。例如可以在帧头留出版本号或保留位,将来升级协议时旧设备可以据此识别新帧格式并尽量向后兼容。又如CAN 总线协议的帧格式中增加了显性/隐性位用于区分标准帧和扩展帧,从而在升级到扩展帧格式(29位标识符)时仍可与旧11位标识符的帧共同存在于总线而不冲突。
良好的帧结构让协议通信变得井井有条。例如,UART并没有固定帧格式(它仅定义起始位/数据位/停止位),因此常需要由更高层协议(如Modbus或自定义协议)定义帧边界和内容;而像CAN、以太网这些协议则在规范中严格定义了帧的各字段及顺序,所有实现者都必须遵循。设计自己的协议时,往往借鉴这些成熟协议的思路,例如帧起始标志+长度+类型+数据+校验+结束标志这样一个通用模板。实践证明,这种结构能满足大多数通信需求并易于实现和调试。
仲裁机制与媒体访问控制
当多个设备共享同一通信媒介时,就会出现竞争使用信道的问题。仲裁机制(Arbitration)或媒体访问控制(MAC)指的就是协议用于解决总线访问冲突的规则。设计一个良好的仲裁策略,可以确保多个节点公平或按优先级有序地使用通信介质,在避免碰撞的同时最大化总线利用率。
常见的仲裁/介质访问控制机制包括:
- 主从控制:最简单的办法是指定一方为“主站”(Master)控制通信,其他为“从站”(Slave)被动响应。这样总线不会发生竞争——只有主站说话或询问,其他从站只有被点名时才回应。例如I²C和SPI都采用主从模式:在I²C中通常只有主设备主动发起时序,从设备在被寻址时才驱动总线;SPI更是固定由主设备提供时钟来主导通信。从站无法自行发送数据,因此避免了冲突。但缺点是单点故障(只有主设备能调度通信)以及从设备无法主动报告事件(除非轮询)。主从模式适用于结构简单、小规模的系统。
- 多主竞争:当允许多个节点都有主动发送的需求时,就需要一套公平争用机制。典型如CAN和I²C总线,它们支持多主模式,即多个节点都可以在总线空闲时尝试发送数据。此时仲裁机制决定谁能最终占用总线。CAN/I²C采用有线与(wired-AND)方式实现按位仲裁:发送过程中每位都监视总线电平,如果某节点发送的是“1”但检测到总线是“0”,就表示被其他节点抢占(因为0为显性电平),于是该节点立即停止发送,等待下一次机会。这样可以确保高优先级(以消息ID或地址数值大小决定)消息不被低优先级延误,而且不会因为冲突而损失总线带宽(输掉仲裁的帧只是未发送出去,不会产生无效数据)。这种确定性的仲裁非常高效,CAN 总线正是凭借逐位仲裁实现了总线利用率和实时性的兼顾。相比之下,以太网传统的CSMA/CD机制则是随机竞争——发送前“监听”信道空闲即可发送,若撞车则各自等待一个随机时间重发。这种方式简单但在总线繁忙时效率较低且延时不可预测。因此在现代实时系统中更倾向于CAN这类确定性仲裁策略。
- 令牌传递:这是工业网络中曾经流行的机制,如令牌环、Token Bus协议等。网络上循环保有一个特殊的“令牌”帧,节点只有拿到令牌时才能发送数据,发送完再把令牌传给下一个节点。这样通过令牌的秩序传递实现无冲突的访问控制,并可预估最长等待时间(因为令牌按顺序轮转)。令牌机制保证公平但实现复杂且在节点较多或出现丢令牌时管理麻烦,所以现今Ethernet取代令牌环成为主流后,此类机制少见于新设计。
- 时分多址:如果通信有严格的时间同步,也可以采用时隙分配的方式,即每个节点只能在各自被分配的时间段发送(如TDMA机制)。这种机制也能避免冲突且保证实时性,但要求全网时钟同步,常用于一些硬实时总线(例如航空电子的ARINC总线)。在一般嵌入式总线协议中不常自行实现,而是在更高层调度中可能用到。
对于我们在嵌入式系统常用的总线(如UART总线点对点无需仲裁,I²C/CAN多点需要仲裁,SPI主从式已固化仲裁由主控时序),设计仲裁机制的重点在于:如何最大程度避免冲突和无效占用,同时满足优先级和实时性需求。以 CAN 为例,其采用的优先级仲裁极具代表性:每个消息帧都有标识符ID作为优先级标志,数值越小优先级越高。在总线空闲时多个节点同时发送,逐位比较ID,发送显性位(0)的节点会覆盖发送隐性位(1)的节点,使后者检测到失配而退出发送。最终ID最小(优先级最高)的节点占据总线完成帧发送,其余节点则自动退避,等待下一次总线空闲再重试。这个过程中没有任何时间浪费,低优先节点在竞争时发送的位在高优先节点的帧中其实成为仲裁的一部分,因而实现了零冲突开销。如下图示例:
图 2:基于显性/隐性电平的逐位仲裁示意(以CAN总线为例)。节点A和B同时发送,比较帧ID的每一位。当某节点发送隐性位“1”而检测到总线为显性位“0”时,即可认定自身优先级较低而退出。最终优先级高(ID数值小)的节点获取总线发送权。
从上面的例子可以看出,CAN 的仲裁机制保证了不丢消息也不浪费时间地裁决出了胜者。这种机制在I²C总线中也有类似应用(多主I²C也采用显性低电平支配总线的方法仲裁SDA线)。相比之下,传统以太网CSMA/CD在冲突时会废弃整个帧并随机重传,效率要低得多。
对于设计新协议,我们需要根据应用场景选择合适的仲裁方案:如果节点很少且有明确主次关系,可选主从模式减少复杂度;如果节点可能同时发送且有实时性要求,应当设计确定性的仲裁(如优先级仲裁或时隙);而若不太关注实时但追求简单,也可以采用随机重传的方式。然而总体而言,避免冲突、充分利用带宽应是仲裁机制追求的目标。良好的仲裁策略能让总线“秩序井然”,不会出现多个设备互相打断或干扰通信的混乱局面。
错误检测与错误处理
任何通信系统都不可避免地要面对信号误码或设备故障带来的异常情况,因此通信协议必须具备完备的错误检测和错误处理机制,以保证整体系统的健壮性。这里的错误包含传输中的数据比特错误,以及协议级别的帧丢失、冲突、超时等。一个健壮的协议应当在错误发生时检测出来,并采取措施加以应对,从而把影响降到最低。
1. 错误检测 (Error Detection): 常用的错误检测手段包括:
- 帧校验:前文已提及,通过在帧中添加冗余校验字段(如CRC-16/32,奇偶校验位等)来检测数据篡改。发送端根据帧内容计算校验码,接收端重算比对,若不一致则认定帧出错。这种方法对随机噪声干扰导致的比特翻转非常有效。以 CAN 为例,其帧包含15位CRC和1位奇偶校验组成的CRC序列,可检测绝大多数双比特及以下错误。
- 格式校验:协议可以规定帧在格式上的合法模式,接收端若检测到违背格式的比特序列,则判定出现错误。例如CAN协议要求帧中某些固定位必须是显性或隐性,若接收端在那些位置检测到相反电平,就知道帧不合规。又如CAN有位填充规则(连续5个相同位后必须插入相反位),若检测到6个连续相同位,则这是一个填充错误的信号。这种基于协议规则的自我校验也能发现许多传输错误。
- 监视反馈:发送节点可以同时监听总线上的实际信号以验证发送成功与否(即监视自己的发送)。如果发送出去的信号在总线回读时不一致,就表明发生冲突或错误。例如CAN发送节点发送每个位的同时都在读取总线电平,如果发现有位不一致,要么是仲裁失败要么是出现错误,此时会立即终止发送并触发错误处理。这一机制可快速发现问题发生的位位置。
- 响应超时:对于需要对方应答的协议,如果在规定时间内未收到预期的应答(ACK、回复帧等),可以判定出现了丢帧或对方未收到。超时检测需要双方对时间有大致一致的认识(如波特率固定、链路延迟已知范围)。典型例子是UART上的一些高级协议会规定等待ACK的时间窗口,超时则重发。
通过上述手段,协议应当能够在传输错误发生时第一时间发现。然而光能检测还不够,更重要的是如何应对。
2. 错误处理 (Error Handling): 当检测到错误后,协议需规定后续动作,比如是否丢弃帧、重传帧、通知上层、隔离故障节点等。错误处理的设计直接关系到系统的鲁棒性和稳定性。例如:
- 丢弃错误帧并重传:最基本的策略是,对检测到校验错误或不完整的帧,一律丢弃不予采信,然后由发送方重传。这在几乎所有链路层协议中都存在。以太网帧如果CRC不符直接被舍弃,上层TCP会察觉丢包后重发数据。在CAN总线中更是自动重传:只要发送方没收到ACK(表示没人正确收到),就会在总线空闲后自动重发该帧。这样的机制确保短暂干扰不会造成数据永久丢失。当然,重传次数通常有限制以防止无限制占用带宽。
- 错误信号广播:有的协议在发现错误时会主动在总线上广播一个错误标志,通知所有节点此次传输作废。例如CAN规定,一旦任何节点检测到帧错误(校验错误、格式错误等),就立即发送一个错误帧(由6个显性位的错误标志加若干分隔位构成)。这会破坏当前正在发送的帧,使所有节点都意识到错误并丢弃该帧,从而保证网络中节点的数据一致性:要么大家都收到正确帧,要么都丢弃错误帧。然后由发送节点稍后重传。这个过程类似拉响警报通知全网“刚才那帧有问题”,避免了某些节点收到错误数据而不自知。
- 故障节点隔离:更高级的协议会实现对经常出错的节点进行“降级”处理,以保护网络其他部分不受其害。例如CAN总线具有故障隔离机制:每个节点维持发送和接收错误计数器(TEC/REC),每当发送或接收错误发生时计数递增,成功发送则递减。当错误计数超过一定阈值(如TEC或REC ≥ 128)时,节点进入“错误被动(Error Passive)”状态,意味着它后续只发送被动错误标志,且需要在总线空闲时额外等待一段时间,优先权降低。若错误计数进一步超过上限(TEC ≥ 256),节点将进入“总线关闭(Bus Off)”状态,被强制断开总线不再参与通信。这样设计的目的在于将持续故障的节点隔离,防止其占用总线影响整个网络。只有当Bus Off节点故障排除并被复位后,才允许其重新加入网络。这种机制极大提高了网络容错能力:即便某节点硬件损坏疯狂发送错误帧,协议也会自动将其“禁言”,保证其他节点还能正常交流。在我们设计自己的协议时,如果网络拓扑较复杂且对可靠性要求高,也可以借鉴这种“基于错误计数的惩罚机制”,以避免单点故障拖垮全局。
- 确认与反馈:许多协议会要求接收方对成功收到的数据进行确认(ACK)。例如CAN帧末尾有一个ACK槽,任何正确收到帧的节点都会在该槽发送显性ACK位,让发送者检测到至少有一个接收者收到。如果该位保持为隐性,表示无人收到,发送者就会重传。再比如面向连接的协议(如TCP)更是在传输层构建了序号和ACK机制,每收到一定数据就确认,否则发送端超时重发。这种积极确认能及时让发送者知道传输是否成功,配合重传策略可有效提高可靠交付率。
综合来说,错误处理机制的目标是:在出现错误时,协议应能迅速感知,并纠正或隔离错误影响,使通信继续进行,避免整个系统陷入紊乱。以CAN为例,正是由于其内建的严谨错误处理(错误帧、重传、错误计数与隔离),才使得CAN总线被誉为高可靠性总线,在汽车等安全领域广受青睐。事实上,CAN 的鲁棒性很大程度上得益于“发生瞬态错误时销毁错误帧并重发、持续错误则将节点下线”这一套策略——短暂干扰不会丢数据,持续故障不会瘫痪总线。
在我们设计新协议时,应根据应用需求选择恰当的错误处理方案:对于简单场景,也许只需CRC校验+重发即可;对于复杂网络,则需要考虑错误通知(让所有节点知晓错误)、错误分级处理(例如隔离故障节点)等高级机制。健全的错误处理能显著提升协议可靠性,使系统在恶劣环境或部分设备失效时依然稳健运行。
兼容性与扩展性
技术不断演进,通信协议也常需升级更新。因此,在设计协议时务必考虑兼容性(Compatibility)和扩展性(Extensibility),以便将来的改进能够平滑融入而不破坏现有系统。一个具有良好扩展性的协议设计,能延长其生命力,并方便地被应用到更广阔的场景中。
兼容性可以分为向后兼容(新版本设备能识别旧版本通信)和向前兼容(旧设备在一定限度上忽略未知的新特性,仍能与新设备协作)。实现这些兼容的原则包括:
- 预留扩展位/字段:在初始设计协议的数据帧格式时,就为未来可能增加的功能预留一些比特或字节。例如预留几个未用标志位,或允许帧有可变长度并在帧头包含长度字段。这样以后如果增加新的标志或选项,可以利用原先保留的位而不扰乱既有字段。许多协议的报文格式中都有"Reserved"字段,就是出于这个考虑。只有当协议规划充分,新版本才能做到修改这些保留位实现新功能,同时老设备因为保留位当初被规定为忽略处理,所以不会造成误解。
- 版本号机制:在帧头增加协议版本号字段,或在握手阶段交换各自支持的版本。当新版本设备与旧版本对话时,可以降级使用旧版本格式通信。版本号清晰也有助于调试时判断不兼容问题。比如BLE蓝牙协议广播包中包含版本信息,不同版本设备可据此采取兼容模式。
- 提供混用模式:如果升级涉及重大改动,允许新旧两种格式一段时间内同时存在也是一种策略。例如CAN2.0协议推出了扩展帧格式(29位ID)来替代原来的标准帧(11位ID),那么如何让混杂的新旧节点和平共处?CAN协议的做法是利用帧中一个IDE标志位:标准帧IDE为显性0,扩展帧IDE为隐性1。在总线仲裁时,若两种帧竞争且前11位ID相同,标准帧的显性IDE位会覆盖扩展帧的隐性位,使得标准帧赢得仲裁。结果就是标准帧天然优先级更高,而扩展帧在有冲突时会退让等待。这样扩展格式可以逐步加入网络而不干扰老格式帧,只是要承担遇到冲突时可能被延后的代价。另外大多数CAN控制器也都同时支持标准和扩展帧的收发,只需配置滤波器即可。这个案例体现了协议设计者的考虑:新旧协议要能够互操作一段时期,否则一次硬切换往往不现实。
- 兼容旧设备行为:新的协议实现通常会针对旧设备的局限做特殊处理。例如以太网从10Mbps发展到100Mbps再到更高,链路层通过自协商来达成双方最高共同速率。如果新设备连上老交换机,它会自动降速以兼容老标准,从而保证基本通信可用。又如I²C总线的高速模式(3.4Mbps)在启动高速传输前,会发送一个特殊格式的预备码,让老设备因为地址不匹配而不参与通信,同时新器件识别预备码后切换到高速模式。这也是一种聪明的前向兼容手段:利用协议机制确保旧设备在听不懂新通信时干脆不干预。
- 文档和规范明确:最后但很重要的一点是,在协议规范中清楚地标明哪些行为是“未来可能改变或扩展”的,以及设备在遇到未知字段时应如何处理(通常是忽略未知选项)。这在互联网协议中非常常见——比如HTTP报文允许有不认识的首部字段,要求实现跳过未知首部;很多文件格式也允许包含自定义扩展块而旧程序会跳过它们。这样的规定使得协议具有开放性,可以逐步演进而不至于旧实现“炸裂”。
需要注意的是,过度追求兼容性也可能带来开销和复杂性。因此设计时应在简洁和扩展之间取得平衡。对于嵌入式点对点的简单协议,可能完全没必要引入版本号或复杂的协商机制;但对于预期寿命长、应用广泛的协议(如USB、以太网、CAN等),在初版设计中前瞻性地考虑扩展空间会大大减少未来升级的麻烦。
综上,兼容性与扩展性原则提醒我们:协议的生命往往长于单个产品,优秀的协议设计应具有自适应变化的能力。正如有业内名言所说:“最成功的协议往往不是最先进的,而是最能适应变化的。”在设计通信协议时,为未来留有余地,才能使其经久不衰、历久弥新。
主流通信协议剖析
掌握了上述通用原理后,我们来看看实际应用中几种主流通信协议的结构与特点。不同协议由于诞生背景和应用需求的差异,在设计上各有侧重和权衡。以下将介绍 CAN、SPI、UART、I²C、以太网这五种常见协议,分析它们的工作原理、适用场景和优缺点。透过这些实例,可以更深刻地体会前面讨论的设计原则是如何在真实协议中落地实践的。
CAN 总线协议
CAN(Controller Area Network,控制器局域网)是一种面向分布式实时控制系统的串行通信总线,由德国Bosch公司在20世纪80年代为汽车电子应用而开发,现已成为汽车电子和工业控制领域的事实标准。CAN 总线最大的特点是多主架构和内容寻址:总线上所有节点地位平等、可自主发送消息,消息通过标识符进行优先级仲裁和过滤,而非点对点寻址。
基本原理:CAN总线采用双绞线差分信号作为物理介质(经典CAN为ISO11898-2双线,逻辑“显性”电平为差分电压高表示二进制0,“隐性”电平为电压低表示二进制1)。多个节点以有线与的方式连接在总线上:只要有任何节点驱动总线为显性0,则总线电平即为0,这为仲裁和错误通告提供了基础。CAN 的通信速率典型为 125Kb/s、250Kb/s、500Kb/s 或 1Mb/s(标准CAN2.0),在长度较短时可达1Mbps,随着距离增加速率需降低(例如40米可1Mbps,500米则建议不超过125Kb/s)。
CAN 工作在OSI模型的物理层和数据链路层。它采用非破坏性仲裁机制(逐位仲裁前面已详述),使得冲突不会浪费带宽且实现消息的自然优先级调度。所有节点以广播方式发送消息帧,无指定目的地址——而是由各节点根据帧中的标识符(ID)自行决定是否接收(即所谓内容过滤或称内容寻址,一个节点可对感兴趣的ID配置过滤器,只处理相应消息)。这一模式非常适合于分布式控制系统中的数据共享:传感器节点发送测量值,所有需要该数据的执行器或控制器节点都能同时收到,实现数据一致性。
帧结构:CAN2.0规范定义了四种帧类型:数据帧、远程帧、错误帧和过载帧。其中最主要的是数据帧,用于传输实际数据。一个标准的数据帧由以下字段构成:
- 起始位:标志帧开始(隐性到显性的边沿用于位同步)。
- 仲裁场:包含标识符ID(标准帧11位,扩展帧为29位,其中含额外的控制位如SRR和IDE)和一个RTR位(远程请求标志,数据帧中固定为显性0)。ID决定了消息优先级和内容类别。仲裁场用于总线仲裁:两个节点若同时发送,谁的ID在某个位上为显性0而另一方为隐性1,则前者胜出。
- 控制场:包括6位,内容有数据长度代码(DLC,用4位表示数据字节数0~8),以及保留位等。DLC指明随后数据段的字节数。
- 数据场:0~8字节的数据(CAN2.0中最大8字节;在更新的CAN FD协议中扩展为最多64字节)。可以承载传感器读数、控制指令等任意内容,但受字节数限制,一次只能发送较小的数据包。
- CRC场:包含15位CRC校验序列和1位CRC界定符。发送方对仲裁场、控制场和数据场计算CRC,填入此字段,接收方据此验证帧内容正确性。CRC是CAN错误检测的重要环节,可发现绝大部分传输位错误。
- 应答场:由1位ACK槽和1位ACK界定符组成。在ACK槽期间,任何正确接收此帧的节点都会发送一个显性位作为应答,从而使总线上的该位成为显性。如果发送方检测到ACK槽为隐性(表示无人应答),则会自动重发帧。需要注意的是,ACK位的存在并不意味着“特定节点收到了数据”,只表明“至少一个节点正确收到了”。
- 结束场:包括一个帧结束标志序列(7个隐性位)以及帧间隔等,用于标识帧结束并让总线复位为空闲状态。
标准帧使用11位ID,可以表示2^11=2048种不同标识。在1990年代扩展出的CAN 2.0B规格中,引入了扩展帧,使用29位ID(其中前11位与标准ID共用,新增18位扩展段),通过IDE位区分帧格式。扩展帧大大增加了可用ID范围(约5.36亿种),以适应更大型的网络和更细的消息分类,并且与标准帧兼容共存,只是仲裁时标准帧优先。这一设计保证了CAN协议向后兼容的平滑升级。
报文发送与过滤:CAN总线是广播总线,所有节点都能听到总线上传输的每一个帧。然而,节点会本地配置硬件过滤,只接收自己关心的ID消息。这使得CAN在逻辑上实现了一种发布/订阅模型:发送者不需要指定收件人,只“发布”带某ID的数据,任何“订阅”了该ID的节点都会处理该数据,其余节点则自动忽略。这种机制带来极大的系统灵活性:可以在不影响其他节点的情况下增加/移除某节点,或修改节点对消息的兴趣组合。新增节点只要监听已有某ID即可获取相关信息,无需修改发送端配置。这也是CAN在工业和汽车中受到青睐的原因——组网自由度高、扩展方便。
**优势与设计权衡:**CAN总线针对实时控制进行了诸多优化,其主要优点包括:
- **实时性好:**位级仲裁确保高优先级报文能以最小延迟发送,而且因为无碰撞重传,无需随机退避等待。在总线负载高的情况下,关键报文依然可以按时传输。这对汽车中的安全控制(如制动、转向)至关重要。
- **鲁棒性高:**CAN定义了完善的错误检测和处理机制,包括五种错误类型检测(位错误、填充错误、CRC错误、ACK错误、格式错误)和自动错误帧通告、重传与故障隔离。单个节点故障不会拖垮网络,而短暂干扰只会影响当前帧且立刻被纠正。这使CAN非常适用于嘈杂的电气环境和对可靠性要求高的场合。
- 多主通信灵活:任何节点都可在空闲时发帧,多节点同时发送由仲裁自动解决,无需中心仲裁者。加之广播和内容过滤机制,网络拓扑灵活,增加节点无需重新定义地址,与生俱来的组播/广播能力简化了数据分发。
- **成熟的生态:**作为老牌协议,CAN硬件接口(控制器、收发器)在微控制器中广泛集成且成本低廉。还有诸如CANopen、DeviceNet等高层协议可直接基于CAN硬件实现分布式应用。调试工具(示波器、总线分析仪)和软件栈也非常丰富。
然而,CAN也有局限,主要在于:
- **带宽相对低:**经典CAN最高1Mbps,而且单帧最多8字节数据,对于大数据量传输并不高效。因此不适合用来传输比如视频流或大块文件。在需要高吞吐的应用中(如车辆娱乐系统、ADAS摄像头数据),传统CAN就不够用了。为此,Bosch在2012年发布了CAN FD(Flexible Data Rate)协议,通过更高位率(最高可达8Mbps)和最多64字节数据字段扩展了CAN的容量,部分缓解了带宽问题。CAN FD保持了经典CAN的大部分机制,但老旧节点无法直接识别,需要网管上加以隔离或桥接,是向更高带宽演进的一种解决方案。
- **帧开销较大:**一帧CAN数据最多8字节数据,却有至少47位的协议开销(不含填充位),再加上填充和间隙,实际有效负载比例偏低。这种低效率在发送零散小数据时可以接受,但对于长消息需要拆分成多帧发送时,效率会明显下降(解决办法是上层协议可以定义消息分段和重新组装,如ISO-TP用于在CAN上发送长诊断帧)。
- **距离有限:**虽然差分总线抗噪性能好,但1Mbps下标准CAN理论距离约40米。若需要几百米以上通信,需降低速率甚至需要中继设备。相比之下,一些工业总线(如RS-485上的Modbus)在低速率下可达上千米,而以太网借助光纤也可延伸更远。
- **无传输层功能:**CAN本身不区分源/目的地址,不保证数据交付顺序,也没有流控等机制。这些需要由更高层协议或应用逻辑去实现。例如CANopen、J1939就在应用层对消息ID进行规范以表示不同设备和消息类型,并增加心跳、ACK之类机制适应应用需求。也就是说,CAN更偏底层,提供了可靠的数据链路,但应用协议仍需自行定义。
总的来说,CAN 总线在设计上充分体现了实时可靠通信的理念,以牺牲一定带宽换取确定性和鲁棒性,非常适合车辆、工业控制等领域的小数据高可靠通信场景。在这些场合,控制信号往往简短但对及时性要求极高,CAN 的优先级仲裁和错误自动恢复机制能很好地满足需求。同时,CAN 的“发布/订阅”数据模型让系统扩展更容易,不同ECU模块之间可以松耦合集成。在现代汽车中,一辆车往往有多个CAN子网(动力总成CAN、车身CAN等)连接数十上百个ECU节点,实时地交换发动机参数、车速、转向角、灯光状态等信息。可以说,没有CAN总线,就没有今日电子化程度如此高且安全可靠的汽车。
SPI 串行外设接口
SPI(Serial Peripheral Interface)是一种广泛用于板级高速数据传输的同步串行通信协议。它由Motorola公司提出,现已成为嵌入式系统中微控制器与各种外设(传感器、存储芯片、ADC/DAC、屏幕驱动等)连接的事实标准之一。SPI的设计目标非常明确:以最小的软件/协议开销,实现主设备与少数从设备之间的高速数据交换。
基本原理:SPI总线采用简单的主从架构,由一个主设备(Master)控制一个或多个从设备(Slave)。它通常使用4根信号线:时钟线 SCLK(由主产生), 主输出/从输入线 MOSI, 主输入/从输出线 MISO, 以及从设备选择线 SS(每个从设备通常需要一根独立的片选线)。通信时,主设备通过拉低某条SS线选中对应的从设备,然后产生时钟脉冲,同时在MOS I线上发送位数据,Slave就在时钟引导下在MISO线上回传数据。由于MOSI和MISO可同时传输,这使SPI实现了真正的全双工通信。一条SPI链路上可以串接多个从设备,但每个从设备需要主控提供独立的SS片选,这在硬件上呈现出星形拓扑。下图直观展示了一个典型SPI连接架构:
图 3:SPI 总线的典型拓扑结构。一主多从,主设备以多根片选线(SS)控制不同从设备的选通,SCLK/MOSI/MISO三线则在所选从设备与主设备之间传输数据。其他未被片选的从设备其MISO输出通常三态悬空,以避免总线冲突。
SPI通信有几项关键特性:
- 同步时钟驱动:主设备提供连续的时钟脉冲,同步数据传输。这意味着发送和接收无需像UART那样依赖波特率精确匹配,时钟的存在使时序严格受控。主设备决定时钟频率,常见从几百kHz到几十MHz。许多微控制器的SPI模块可高达数十MHz,FPGA实现甚至可超百MHz。SPI协议本身并未规定最高频率限制,理论上性能受限于物理信号完整性和器件能力。高速时钟带来的好处是数据吞吐量高——相比I²C原始标准100kHz(或快速模式400kHz),SPI轻松达到几MHz乃至几十MHz,在传输大量数据时优势明显。
- **全双工数据流:**SPI的MOSI和MISO分开传输,允许主在发送新数据的同时,上一周期从设备的数据就通过MISO返回。这意味着在一个时钟周期内,主和从各发送1 bit数据,实现同时收发。这在一些应用中非常高效,例如主发送读命令字节同时从设备回送前一次采样数据。不过很多场景下主发送数据的同时从发出的数据可能无意义(比如发指令时从还在返回伪字节)。即使如此,全双工的硬件能力仍然提升了总吞吐(相当于每周期传2比特总信息)。相比之下I²C共享单线SDA,只能半双工,UART虽然有独立TX/RX线实现全双工,但通常应用层协议还是请求-应答模式,无法像SPI这样连续高速流式交换。
- 无地址/帧开销:SPI本质上是一种流式接口,没有固定的数据帧格式,也没有地址字段。因为只有被选中的那个从设备会响应,所以不需要在数据中附加地址。这种“无包头”设计极大降低了通信开销。SPI传输时除数据本身外几乎无附加比特(仅每个字节内部可能有占位)。正因为没有额外协议开销,SPI可以接近硬件极限地利用带宽。举例来说,在8位单片机上,I²C每发送1字节需要附加地址和ACK等握手机制,而SPI连续发送多个字节时就是时钟咔咔地连发出去,中间不插其他信号。正如一篇对SPI的描述所言:“由于无需地址帧等额外开销,SPI总线能够维持非常高的数据传输率”。这点对于需要快速传输大量数据(如显示屏像素数据、ADC采样流)非常关键。
- 模式灵活:SPI虽然简单,但提供了一些灵活选项,比如可配置时钟极性(CPOL)和相位(CPHA)组合出4种模式,以适应不同外围设备采样时序需求;可选择每字传输的bit数(常见8位,也可16/32位等);可选择先传高位或低位。这些并非协议标准统一规定,而是由具体设备数据手册指定,主设备软件需匹配设置。灵活性提高了SPI的适应性,但也带来非标准化的问题:不同厂商设备在时序细节上可能不一致,因此工程上必须仔细配置,否则就会导致数据错位。总的来说SPI并没有官方国际标准文件,各厂商实现略有差异,这也是SPI的一点不足——它更像是一种约定俗成的接口而非严格规范。不过基本原则各家一致,所以兼容性问题通常能通过设置解决。
- 拓扑多样:除了一主多从的星型结构,SPI还支持一些变形拓扑。例如菊链连接:将多个设备的MISO连到下一个设备的MOSI,串联成环,所有设备共用一个片选。这时主设备连续输出串联链长度的数据,总线上的时钟周期要足够多才能把指令和数据“推”到链末设备。菊链优点是节省片选GPIO,但限制在某些支持级联的器件(如串行移位寄存器、LED驱动)中使用,而且总延时增长。另一个拓扑变化是三线SPI:把MOSI和MISO合并为一根双向数据线(俗称单工SPI或Microwire),这样只需 SCLK、DIO、SS 三线。它节省引脚但失去全双工能力,一次只能单方向传输。还有的存储器件支持**双SPI/四SPI(QSPI)**接口,通过2或4根并行数据线同时传输位,实现更高吞吐。这些都是在SPI基本概念上演变出的更高速接口,仍遵循时钟驱动、多线并行的思想。
应用场景与优缺点:SPI广泛用于短距离高速通信场合,例如:
- 存储芯片(Flash、EEPROM):读写速度快、延迟低。大量外部Flash用SPI接口(如SPI Nor Flash)来获得高吞吐,且硬件接口简单。
- 传感器/转换器:许多高速ADC、DAC,温度传感器、触摸控制器等采用SPI,将数据快速送给MCU处理。SPI的高时钟使它能满足高速采样的数据读取。
- 显示屏和用户界面:如LCD屏幕驱动芯片,SPI可以高效传输显示缓冲数据;还有一些RF模块、音频编解码器等通过SPI配置和传输数据。
- 工业控制模块:数字电位器、马达控制器、IO扩展移位寄存器等,因为SPI低延时的特点,适合实时要求高的控制信号发送。
SPI的主要优势可以总结为**“快而简单”**:
- 高速性能:无额外开销,利用差分时钟实现极高数据率,实际吞吐可达到几十Mbps量级,远超典型I²C。对于大数据传输SPI几乎是不二选择。
- 实现简易:硬件上只需移位寄存和时钟,无复杂握手逻辑。许多MCU的SPI模块支持DMA等,使批量数据传输占用极低CPU资源。
- 全双工通信:在需要边发边收的场景效率极佳,比如读传感器数据时可以同时发送下一个指令。
- 灵活扩展:可根据需要添加从设备(受限于片选引脚数量),还可通过二、四线模式进一步提速。硬件允许的情况下,甚至能用一组主机SPI接口通过外部多路开关控制更多设备。
当然,SPI也存在一些局限和权衡:
- 连线多且不支持总线共享: SPI每新增一个从设备就需要一根片选线。如果设备很多,会耗费主控大量GPIO,引线和PCB走线也复杂。而且SPI没有地址概念,无法挂很多设备共享同一总线,扩展性在设备数量上不如I²C。针对此,可用外部译码器拓展片选,但也增加硬件复杂度。
- 单主模型: SPI规定只有主设备发起通信,从设备无法未经请求主动发送数据。这意味着如果从设备有紧急数据,也只能等待主设备轮询询问,不适合多主需求。因此SPI多用于一个主控带多个外围,不适合多处理器对等通信。
- 距离有限: SPI属于板内通信,信号高速且无差分驱动,通常只适用于几厘米到几十厘米范围,长距离容易信号失真。若要传更远需要降低时钟频率或采用差分驱动器。所以SPI一般用于同一PCB或相邻板卡,不用于跨设备长距离连线。
- 无规范标准: SPI并非正式标准,导致厂商实现在时序细节上可能不同。比如有的从设备采用CPOL=1模式,有的CPOL=0;有的需要先发MSB,有的LSB。这就需要开发者仔细配置匹配,否则很容易通讯异常。在多从场景下,如果不同SPI设备要求的模式/位序不同,每次切换片选前也要重设SPI模式,增加了软件负担。
- 无流控和应答机制: SPI底层没有类似ACK或NAK的设计,也不自带CRC等错误检查(这些可由上层协议添加在数据帧内)。因此,如果对方未收到数据或数据错误,SPI硬件层面也不知情,一般通过高层协议解决。例如一些SPI协议会在数据包里附加校验或使用特定字节握手。相比I²C有ACK、CAN有错误帧,SPI的可靠性机制纯粹由应用层主导。
- 占用主控资源: 多个SPI外设意味着多IO,在微控制器引脚资源紧张时是一大挑战。另外SPI没有总线仲裁概念,所以不能有两个主控挂在一条SPI线上,不适合多主机场合(除非用专门仲裁电路或切换主机角色,极少见)。
即便如此,SPI在嵌入式系统中依然大放异彩,因为它简单直接、效率极高。在板级通信中,若需要高速传数据(如显示、存储、传感器阵列),SPI几乎总是首选。而对于那些只有少量数据的慢速外设,I²C可能更节省引脚;需要远距离通信时,则可能改用UART/RS-485等。但总体而言,SPI以其卓越的速度性能和实现简便而成为连接微控制器与各种外围设备的核心纽带之一。在实际应用中,工程师需针对具体需求权衡SPI和其他方案:例如,同样是连接多个传感器,若传感器数据量很小且对速度不敏感,用I²C共享两线更方便;但如果传输陀螺仪/摄像头的海量数据,就必须用SPI甚至QSPI才能满足带宽要求。了解SPI的特性,才能在设计中做出明智选择。
UART 通用异步收发传输
UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是一种最古老也最常见的串行通信方式,广泛用于点对点的低速数据传输和调试通信。UART本身不是一个特定协议,而是一种硬件电路实现,用于在两个设备之间进行异步串行数据收发。然而由于其简单可靠,UART接口上的通信协议(如RS-232、RS-485、Modbus等)在嵌入式和工业领域无处不在。几乎每一款微控制器都会内置UART模块,这也使UART成为初学者接触的第一个通信接口。
基本原理:UART通信通常使用两条信号线:一条发送线TX(由发送方输出,连接到接收方RX),一条接收线RX(反之),再加上公共地线。数据以串行的方式在TX线上逐位发送,在RX端解析恢复为并行数据。UART是异步的,即通信双方不共享时钟信号,而是约定好一个波特率(baud rate, 每秒符号数)各自按该速率进行发送和采样。为了让接收方能够按位同步,UART在每帧数据前发送一个起始位(通常为逻辑0)以标志帧开始,随后发送固定数量的数据位(一般5~8位),可选一个奇偶校验位用于简单错误校验,最后以1或2个停止位(逻辑1)标志帧结束。整个帧结构典型样例如:“起始位(0) + 8位数据 + 校验位(无/奇/偶) + 停止位(1)”。
由于没有时钟,发送和接收双方必须在硬件上设定相同的波特率,否则就无法正确取样判别位。一旦双方波特率一致且初始起始位对齐,接收方可以在起始位中点后按照波特率周期依次采样数据位。停止位用于让线路恢复空闲高电平一段时间,以分隔帧。常见UART波特率有9600, 115200, 1Mbps等。
UART的特点:
- **简单的点对点通信:**UART只支持一对一,全双工(因为有独立的TX/RX线,可以同时发送和接收)。没有总线选择或仲裁机制。如果要多设备互连,需要引入总线收发器(如RS-485)和高层协议来区分地址,UART硬件本身不管这些。这种一对一模式适合于简单连接两个设备,如MCU与PC、两个模块之间的数据线。
- 异步传输:无需时钟线,减少了连线数量,也使得UART可以很方便地通过电平转换芯片扩展到远距离传输。如经典的RS-232串口可以使用±12V电平可靠传输数十米,RS-485差分收发器使多节点半双工网络可达数百米甚至上千米。在长距离情况下,不用时钟可以避免高频信号衰减,并且接收方只需根据本地时钟采样即可。这一点使得UART非常适合跨设备、跨网络的通信,比如工厂里PLC通过RS-485网络控制远端设备。
- 低速低开销:UART典型波特率相对低,比如常用115200bps,大约每秒发送1万个字符。虽然有些MCU UART也支持几Mbps,但由于异步方式和串口收发器限制,实际可靠速度很难比SPI/I²C更高。不过UART胜在实现极简:发送方硬件就是把数据装入移位寄存器按波特率移出线,接收方按照波特率采样判决即可。没有复杂握手,所以在软件上使用时,只需配置波特率和帧格式就能工作,易于调试。因此UART经常被用作调试接口(单片机的串口打印日志)或是系统管理控制接口。
- 协议灵活:UART本身只定义了帧的起始和停止格式,并不规定应用层协议。因此可以在UART上承载各种不同协议。例如用于电脑和调制解调器通信的AT命令就是通过串口发送ASCII文本;工业控制常用的Modbus RTU协议在UART帧上构建地址、功能码和CRC校验;还有许多自定义的简易通信协议都以UART为载体。UART相当于是提供了一条可靠字节流管道,具体数据意义由通信双方自行决定。这给予了开发者极大的灵活性,但也意味着UART并非开箱即用的高层协议——需要我们自己定义数据帧结构、错误处理、地址识别等(前文设计原则关于帧格式的一节其实多半就是为这种情况服务的)。
- 广泛兼容性:由于历史悠久,UART接口已经标准化(如RS-232标准)并存在于大量设备中。从PC机上的串口(现在常通过USB转UART)到各种微控制器、蓝牙/WiFi模块,都支持UART通信。因此用UART可以实现跨平台连接。例如单片机的UART可以直接接GPS模块的串口、蓝牙串口模块,实现互操作。并且许多工程师熟悉串口调试,这也降低了使用门槛。
UART的优点概括来说:
- 实现简单,使用方便: 硬件和协议都很简单,无需复杂配置。调试时可以用串口工具直接查看数据,非常直观。
- 点对点全双工: 两线实现双向通信,支持同时收发,延时小。在典型请求-响应模式下,一个来回交互的延迟可低至毫秒级,这对许多人机交互或慢速控制已经足够。
- 距离和可靠性: 通过差分驱动芯片(如RS-485)可使通信距离达几百米,且因为UART速率低,信号频谱窄,不易受高频干扰。使用适当的收发器也可提供一定抗共模干扰能力。所以UART常用于长距离低速通信。
- 软硬件成本低: MCU内部通常都有UART模块,不需要额外IP核授权。接口电路简单(MAX232、RS485芯片都很便宜),线缆要求也不高(屏蔽双绞线足矣)。因此在成本敏感的工业设备中广泛存在UART/RS-485网络。
UART的局限性主要在:
- 不适合多节点复杂网络: UART缺乏总线仲裁和地址机制,硬要多点通信需要借助RS-485这样的总线收发器并规定软件地址。这时虽然能挂多机,但通信是半双工的,需要主站轮询或者Token方式避免冲突,实际等同于一个简易总线协议。它远没有CAN/I²C那样方便自然。通常超过2个节点的场合,会考虑使用更高级协议而不是生拉硬拽UART做多播。
- 速度较低: UART波特率高受限于时钟精度,两端时钟偏差不能过大,否则长帧会同步失效。这使得UART很难达到极高速度(一般上限几Mbps级别,在可靠传输要求下常用低于1Mbps)。因此大数据传输用UART会很慢。另外UART每帧至少10位(含起始停止),又无压缩,效率也就80%。相比SPI等差远。当然,对于人阅读的文本或者少量控制指令,这不成问题。
- 错误处理弱: UART硬件提供的错误信息有限(帧错误、校验错、溢出之类)。没有自动重传机制。所以在需要高可靠时,必须靠上层协议处理超时和重发。像Modbus RTU这种在高噪环境用的协议,就明确要求CRC校验,否则UART单字节奇偶校验很弱。和CAN那种硬件级保障比,UART更多依赖协议层约束和人工管理网络健康。
- 同步开销: 异步通信的起始/停止位是必要开销。如果连续传输大量数据,这两个位的存在导致约20%的比特流不属于有效数据。此外由于异步机制,UART无法像I²C那样由接收方拉低时钟暂停发送——也就是说,一旦发送开始,双方必须按固定节奏进行,否则漏过位就破坏帧。这需要接收方有足够快的处理速度或硬件FIFO,否则在高波特率下容易溢出丢数据。I²C有SCL线可被从机拉低进行“时钟延伸”暂停主机,这点UART办不到。
尽管如此,UART仍然不可替代在许多应用中的地位。它简直是嵌入式系统的“万金油”接口:从打印调试日志、监控控制台,到连接GPS模块、蓝牙模块,再到工业设备配置、传感器读数输出,UART随处可见。现代MCU即便有I²C/SPI/UART等多种接口,也总会至少提供一个UART给用户使用。对于初学者而言,UART串口是理解通信协议的良好起点,因为不需要复杂的总线管理,就能直接实验数据收发和简单的协议设计。
可以这样理解:当通信只涉及两点、距离较远或对性能要求不高时,UART及其演变(RS-232/485)是简单有效的方案。举例来说,一个远程水泵控制系统,可以用RS-485串联几个泵,每个泵有一个ID,主控通过UART发送Modbus命令轮询各泵状态。这实现很简单且可靠。而如果换成CAN,虽然功能更强,但增加了学习和开发成本,可能并不划算。因此,根据应用需求选择合适的协议非常重要,我们将在下一章对此展开讨论。
在结束UART部分前需要指出的是,由于UART传输常用ASCII文本(如调试日志、AT指令等),所以在很多调试场合,它还兼任人机接口的角色。这也是初学者喜欢用串口输出printf()调试信息的原因:数据在人可读和机可读之间切换方便。这一点虽不涉及协议设计本身,但的确提高了UART在开发过程中的价值。
I²C 集成电路总线
I²C(Inter-Integrated Circuit,也常写作 I2C,“集成电路间通信”)是一种由飞利浦公司在1980年代发明的双线串行总线,主要用于在一块电路板上的芯片之间低速通信。I²C以其硬件开销低、协议简单却支持多主多从而闻名,在传感器、存储器等外围设备与微控制器的连接中非常普及。可以说,在嵌入式板级通信领域,I²C 和 SPI 并称两大主力,各有所长。
基本原理:I²C总线使用两根双向信号线:数据线 SDA(串行数据)和时钟线 SCL(串行时钟)。总线上可以挂接多个器件,包括一个或多个主设备(Master)和从设备(Slave)。所有器件通过开漏/开路输出(或集电极开路)连接到总线,并由上拉电阻将总线保持在高电平。所以I²C总线上的信号逻辑为“线与”: 任一器件拉低线(输出0)则总线电平为0,而只有当所有器件都不拉低时总线才能被上拉电阻拉高为1。这种设计与CAN类似,实现了有线与仲裁能力。
I²C通信由主设备控制时钟SCL,并在SDA线上发送字节序列。一次完整的通信被定义为一个消息(Message),包括:一个起始条件(Start),然后是一个7位(或10位扩展)从地址 + 1位读/写方向位 + ACK位,接着若为写操作则主设备发送数据字节,若为读操作则从设备发送数据字节,每个字节后面由接收方回ACK,最后由主设备发送停止条件(Stop)结束传输。其中 Start 和 Stop 条件是I²C时序中特定的信号组合(当SCL高电平时SDA由高变低表示Start,由低变高表示Stop),所有节点都能检测到这些事件以同步总线状态。
I²C的总线仲裁:当总线空闲时,任何主设备都可发送Start发起通信。如果两个主设备几乎同时开始,则会发生仲裁。仲裁过程是在地址和数据发送过程中按位仲裁的:每个发送方在发送比特同时监视SDA实际电平,如果某主发送1而检测到0,则表示另一主发送了0且赢得仲裁,它必须立即停止发送。这与CAN逐位仲裁如出一辙,因为I²C的线与特性使得“0”主导总线。这样能够确保不会因为冲突导致消息损坏:赢得仲裁的主设备的信息仍完整地被其他从设备接收,只是失败者停止而已。I²C仲裁具有确定性,且只在仲裁过程中稍微浪费失败方已经输出的位时间,效率影响很小。I²C也规定当一个主丢失仲裁后,它如果正处于发送模式则需切换为接收模式以监听总线,防止干扰其他通信。
特点和设计权衡:
- **简单的总线拓扑:**I²C只用2根总线即可连接多个器件,这在硬件上非常简洁。对于一个系统板,挂上几个I²C温度传感器、EEPROM存储器,布线和引脚都很节约。这与SPI每个设备多一根CS线形成鲜明对比。因此在需要连接许多低速外围的情况下,I²C具有明显优势。
- **地址机制:**I²C采用7位地址(可扩展到10位,但很少用),理论上可寻址128个设备(排除保留地址则实际略少)。每个从设备有唯一地址,可被主设备寻址通信。当主发出地址后,只有匹配地址的从设备应答ACK。这使得总线上可以有多个不同功能的芯片而互不冲突。当然需注意某些设备地址固定,系统设计时要避免地址冲突。
- 支持多主:如上所述,I²C总线协议容许多个主设备存在,并通过仲裁和总线忙信号(Start发生后,其他主检测到总线忙就不会干预)来协调。这对于需要两个处理器共享外设或容错冗余场景有用。不过现实中多数I²C总线只有一个主(MCU),多主设计相对复杂,必须处理仲裁丢失后的恢复等细节。许多微控制器的I²C控制器并不支持充当从设备或多主模式,所以虽然协议允许,但多主I²C不常见。
- 速率与可靠性:I²C标准模式速率为100 kbps,飞利浦后来定义了400 kbps(Fast Mode),1 Mbps(Fast Mode+),3.4 Mbps(High Speed Mode)等。还有更高的5 Mbps超高速模式用于特殊应用。需要注意的是,高速模式下为保证同步,I²C引入一些特殊处理,如前面提到的High Speed模式需要先发送一个特定“主码”让总线进入高速状态。总体上,I²C速率不及SPI,但在合理范围内可以权衡速度和兼容性。例如很多旧外设支持到400k或1MHz,新器件支持更高,但高低速设备混挂时,总线实际速率往往只能用低速那档以保证兼容。I²C信号完整性因开漏和上拉电阻的缘故,也限制了速率进一步提高(上拉电阻和总线电容决定SDA/SCL的上升时间,不宜过短)。因此I²C更适合中低速应用。
- 同步与流控:I²C虽然由主产生SCL时钟,但从设备在处理不过来时可以拉低SCL保持低电平,迫使主进入等待状态——这叫时钟拉伸(Clock Stretching)。这等于提供了一种简易流控机制,使得像慢速ADC转换这种情况,从设备可以暂时暂停通信直到准备好数据。这是I²C的一个优秀特性,让异构速度设备能共存。不过滥用拉伸会影响总线实时性,而且如果某设备死掉拉住SCL不放,会挂死整个总线通信。
- **ACK/NACK机制:**每个字节传输后由接收方产生ACK(低电平)或NACK(保持高电平)。ACK表示成功接收并希望继续,NACK则表示停止(例如主读数据时,在读完所需的最后一个字节后发送NACK通知从停止发送)。ACK机制也可用于简单错误检测(未应答可能表示设备不存在或忙)和结束读取。
- **错误恢复:**I²C对错误的处理不像CAN那么复杂,但有一些基本原则。例如如果仲裁丢失,失败方停止通信(其已发送的数据会被胜者的帧覆盖丢弃)。对于接收方CRC错误(I²C本身无CRC,但一些应用层会有校验),可以通过发送NACK来让发送方停传并重发消息。严重错误或总线卡死时,一般通过发多个时钟脉冲尝试使从设备退出堵塞(因为从在等时钟),然后发送一个Stop条件复位总线。这些都需要主设备软件来实现。
应用方面:I²C被广泛运用于板上周边器件的连接,例如:
- 传感器模块:温湿度、光照、加速度计、陀螺仪、压力计等,I²C接口非常常见。因为这些传感器数据量小,I²C两线即可方便连接多个传感器,且许多传感器IC默认就提供I²C接口。
- 存储器:如EEPROM、RAM(如FRAM)、以及实时时钟RTC等,以I²C接口与MCU相连,可在板上挂多个扩展存储或其他辅助芯片。
- 模拟器件:不少ADC、DAC、数字温度计、数字电位器等采用I²C,配置和读取都简单。
- 用户接口:I/O扩展、LCD字符屏驱动、键盘扫描芯片,有不少通过I²C来减少MCU引脚占用。
- 多板通信:在简单多板系统中,有时也用I²C在板间通信(距离很短的情况下)。不过超过一两米I²C就不适宜了,需转用差分或其他协议。
I²C的优势总结为:
- 引脚占用少: 两根线连接众多设备,在引脚稀缺的小型MCU上尤为宝贵。加之支持菊花链式挂接,扩展方便。
- 协议简单: 帧结构固定(地址+数据+ACK),没有复杂的帧头解析。硬件通常有状态机自动完成从设备寻址和ACK/NACK产生,软件只需读写数据寄存器即可。
- 多主与组网能力: 相比SPI只能单主,I²C允许多主一定程度提高了总线灵活性(尽管很少用)。广播寻址通过保留地址也可实现。其组网能力虽不如CAN但比起UART强不少。
- 成熟度和设备生态: I²C设备种类繁多,厂商喜欢在传感器/存储上用I²C,因为用户方便使用。现成的软件库和工具也很多。可以说,要找一个低速外设方案,十有八九有I²C接口可选。
- 成本低廉: I²C不需要复杂收发器,仅靠上拉电阻就可工作(短距离板内通信)。因此系统硬件成本极低。
劣势和局限则有:
- 速度有限: 最大3.4Mbps且实际很少用这么高,最常用400kHz。对于大量数据传输(比如显示屏像素数据)就显得吃力。I²C更适合少量状态或配置数据,而非持续的数据流。
- 总线负载和容量限制: 由于线是线与,每个器件的并联电容加总会影响波形,上拉电阻也限制了上升沿陡度。因此总线上器件数量和总电容有上限(典型400pF)。器件过多或走线太长会导致信号变差,需要降低速率甚至加总线缓冲器。
- 地址受限: 7位地址最多127个,这对一般板子够了,但对于系统庞大或地址冲突(不同厂商设备地址相同)会有问题。虽然可用10位扩展地址,但支持的设备很少。
- 调试不如UART直观: I²C是二进制协议,需要逻辑分析仪才能方便地解码数据。不像UART可以直接用终端看ASCII。所以调试上比UART繁琐些,不过逻辑仪等工具现在也很普及。
- 从设备实现复杂: 对MCU而言,实现一个I²C从设备要处理起停条件、仲裁、时序等,比实现UART收发要难很多(因为UART硬件收发器几乎全包办)。很多低端MCU甚至不支持当I²C从,这也反映了这一点。不过绝大多数情况下MCU都是I²C主,这不算大问题。
综合来说,I²C在设计上体现了一种**“用较低硬件代价换取中等性能”**的哲学。它填补了UART和SPI之间的空档:当需要连接多个设备又不要求极高速度时,I²C是最佳选择。它的发展也证明了这一点——从家用电子设备(飞利浦最初在电视机内部用I²C连接CPU和周边芯片)到现在各种传感网络,都大量采用I²C。对比CAN,I²C不具备那么强的实时和鲁棒机制,但简单易用;对比SPI,I²C速度慢一些,但胜在设备多还省线。这些都是典型的工程折中。
最后也提一下,在汽车等领域,还有一种单线串行总线LIN(Local Interconnect Network),其实可以看作UART的变种,用于低速低成本设备网络。LIN主从结构,UART帧格式,但通过在字节前插校验等实现同步和简单校验,速率典型19.2kbps。之所以提LIN,是因为它经常和CAN一起出现:CAN用在重要快速总线上,LIN用在非关键的支线上,比如车门、座椅的小马达控制。这也再一次说明,不存在“一种协议通吃一切”,实际设计中根据场景选协议非常关键,我们下一章正要讨论这个问题。
以太网 (Ethernet) 协议
以太网是一种应用极其广泛的通信技术,最早用于计算机局域网,如今也渗透到工业和汽车领域。严格来说,以太网是一套涵盖物理层和链路层的协议规范,其核心是IEEE 802.3标准。由于以太网主要用于高带宽、广覆盖网络,与嵌入式板间总线有所不同,但在更大的系统架构下经常需要考虑以太网的使用。因此我们在此介绍以太网的结构原理、适用场景和设计取舍,以便对比前述串行总线协议。
**基本原理:**Ethernet原型诞生于1970年代Xerox帕洛阿尔托研究中心,最初运行在同轴电缆上,以竞争性CSMA/CD方式共享介质,速率10 Mbps。后来以太网转用双绞线(10BASE-T)结合集线器或交换机,由共享介质改为星型拓扑。现代以太网几乎都使用交换式全双工模式,不再有冲突问题。常见以太网标准有100 Mbps(百兆以太网,Fast Ethernet)、1 Gbps(千兆以太网),以及10G、40G、100G等高速版本。物理介质包括双绞线(100m左右距离),光纤(可达数公里)等。
以太网链路层采用**MAC(媒体访问控制)**地址进行设备寻址,每个网络接口都有全球唯一的48位MAC地址。以太网帧的格式通常为:前导码(7字节0x55序列)+帧开始标志(1字节0xD5)+目的MAC地址(6字节)+源MAC地址(6字节)+类型/长度(2字节)+数据(46~1500字节,可变长度)+CRC校验32位(4字节)+帧间间隔(12字节的信号空闲时间)。每帧最短64字节(含头尾,不含前导码),以避免冲突检测机制中帧过短问题。最长1518字节(不含前导码),这是传统以太网MTU(最大传输单元)。现代网络有时启用“巨型帧”,允许更大MTU以提高效率,但需要双方支持。
以太网最初的冲突仲裁机制是CSMA/CD(载波监听多路访问/冲突检测)。节点发送前先监听总线,若忙则等待空闲后发送;发送时同时监听介质电压,若检测到冲突(信号振幅异常)则立即发送干扰信号(Jam)并停止,然后等待一个随机退避时间再重试。这样随着冲突次数增加退避时间指数级增长,可降低碰撞概率。这种机制简单但不确定:在高负载下碰撞重试会导致吞吐下降和延迟飙升。不适合严苛实时场合。不过,自从以太网进入交换式全双工时代,CSMA/CD几乎退出历史舞台:因为全双工点到点链路上不会有冲突,每段链路只有两端设备对传,交换机负责转发帧,相当于提供每个节点独享的通信信道。因此现在以太网链路层更多体现为IEEE 802.3帧格式和MAC地址寻址的逻辑,与CSMA/CD的传统联系不大(除非你用集线器之类半双工共享介质,已少见)。
以太网的特点可以从几个方面看:
- 高带宽高吞吐:哪怕是百兆以太网100 Mbps,也远高于前述CAN/I²C/UART等。千兆和更高速以太网已是常态,满足大量数据传输需求。并且以太网帧可承载1500字节数据,协议开销相对低,每字节有效负载比串行总线高得多。如果有大量数据(如视频流、文件)需要传,以太网几乎是唯一现实选择。举例来说,一路1080p视频压缩后几Mbps,CAN这种1Mbps以下总线根本无力胜任,而千兆以太网绰绰有余。
- 灵活的组网范围:以太网可通过交换机级联扩展网络,理论上一个广播域可以容纳2^48 MAC地址(约2.8亿亿个)设备,范围从几十米局域网到全球广域(尽管跨网要通过IP协议路由)。它支持的网络规模和距离非板级总线可比。对于大型系统,以太网提供了无与伦比的可扩展性。这也是工业以太网取代许多现场总线的原因之一。
- **标准化和互操作性:**以太网作为国际标准(IEEE 802系列),各种厂商设备均遵守统一规范,可即插即用互连。不像很多嵌入式总线(例如CANopen vs DeviceNet等)还有不同子规范。以太网保证了不同设备在链路层的兼容,这为系统集成提供了便利。
- 协议栈丰富:以太网之上有完善的TCP/IP协议族,可以实现从传输控制、组网路由到应用层各种协议(HTTP/FTP/MQTT等),构建分层完备的通信体系。这比起I²C、CAN等需要自己定义高层协议来说,要强大得多。因此当嵌入式设备需要与计算机、服务器或云端通信时,通常都借助以太网或其衍生(如Wi-Fi)来承载IP协议。可以说以太网是通往互联网的桥梁。
- 实时性和确定性:传统以太网不是为实时控制设计的,CSMA/CD时期更是非确定延迟。交换式以太网消除了碰撞,但由于交换机缓存和转发表查找等,也会引入难以精确估计的延迟抖动。此外,在高层,TCP等传输协议也引入重传机制,延迟大且不确定。所以以太网一直被质疑难以用于实时控制。不过,随着网络技术发展,也出现以太网实时化方案。例如很多工业以太网协议(EtherCAT、PROFINET IRT、TSN - 时间敏感网络)在链路层或MAC调度上做特殊处理,使以太网满足毫秒甚至微秒级实时要求。比如EtherCAT让帧在设备之间无停留地过站处理,TSN则在交换机上基于时间窗调度,确保关键流量准时。这些方案让以太网延伸进工厂车间或汽车车载网络,逐步侵蚀传统现场总线领域。当然,它们的实现复杂度较高,需要特制硬件/软件支持。
以太网在嵌入式/工业中的应用场景:当系统对数据量和异构互联要求高时,以太网是理想选择。例如:
- 工业自动化:以前PLC和传感器/执行器用CAN、现场总线(Profibus等)连接,如今越来越多转为工业以太网,统一在以太网上跑工业协议(如OPC UA、Modbus TCP)。以太网带宽高,可以同时承载控制信息和大量状态数据。尤其生产监控、机器视觉等需要高数据量的,都只能靠以太网。典型如工厂产线用PROFINET,用TCP/IP承载PLC与SCADA系统通讯等。
- 智能建筑/安防:摄像头、门禁控制等设备大量部署,需要将视频、音频和控制都联网,以太网是首选。有的摄像头支持PoE(以太网供电),一根网线搞定数据和供电,很方便。
- 车辆内部网络(车载以太网):为满足自动驾驶对传感器高带宽连接的需求,汽车引入100BASE-T1/1000BASE-T1单对以太网,用来连接高清摄像头、雷达、高性能ECU之间的数据通信。这逐渐成为车内骨干网络,与CAN/CAN-FD一起协同:CAN负责传统控制消息,以太网负责高带宽数据和后台互联。
- 分布式系统通信:当你的嵌入式设备需要与PC、服务器通信(比如上传数据到服务器,或远程控制),往往使用以太网接口,因为它能轻松融入现有网络。微控制器可以通过以太网控制器和协议栈,实现LAN或Internet连接。这让嵌入式设备具备接入云和大数据平台的能力,这是现代IoT的重要方面。
以太网的主要优势归纳:
- 超高吞吐和广域覆盖:独一无二的横跨局域和广域的通用网络,性能不断提高,能承载大数据量。
- 成熟的标准生态:全球统一标准,不同设备轻松互联;丰富的现成硬件(PHY、MAC)和软件(协议栈)支持。
- 灵活多样的应用:可适配各种拓扑(星型、树型、环冗余等),可承载任意上层协议(网际协议使其无所不能)。
- 成本随普及降低:过去以太网复杂昂贵,但如今千兆交换芯片几美元,微控制器带MAC也常见。整体造价已不是障碍,这也是工业现场总线被以太网替代的经济动因之一。
以太网的不足/考虑:
- 实现复杂、功耗高:相对于串行总线,以太网控制器复杂许多,需要处理MAC帧、缓存、DMA等。同时PHY收发器也较耗电(特别是高速以太网)。对于低功耗小型设备,直接跑以太网不划算(这类场景可能选低功耗无线或I²C、CAN等)。另外软件协议栈(TCP/IP)较重,内存和性能开销大,在小MCU上跑可能吃力。
- 非实时性及改善成本:如前所述,原生以全局随机仲裁的以太网实时性差。在工业应用中,要达实时就得用Time Trigger或类似技术,需要特殊硬件/软件支持,增加系统复杂度和成本。有些场合为了兼顾,用于监控的数据可以走以太网,但闭环控制仍保留CAN/Profibus等以确保确定性。
- 维护和调试难度:以太网网络一旦庞大,调试需要抓包分析,一般串口/总线调试更直观。而且涉及网络配置(IP地址等),对传统嵌入式工程师是新增门槛。不过这已逐渐成为必备技能随着IT与OT融合。
- 过度性能富余:有时系统其实用不着那么大带宽和复杂性,却为了标准化用了以太网,结果资源利用率低反而增加不必要的复杂度。例如只需1kB/s数据,却要跑一个TCP/IP,显得小题大做。这种情况下可能选用简单协议更优。
总之,以太网代表着高性能通信的方向,在越来越多嵌入式环境中扮演重要角色。选择以太网往往不只是为了带宽,也是为了对接更广阔的网络体系(如工业4.0中设备直接连企业以太网)。随着工业以太网和汽车以太网技术的发展,我们会看到越来越多以前CAN/现场总线承担的任务被以太网接管。但这并不意味着传统总线消亡——恰恰相反,它们可能与以太网形成分层:以太网作为上层高速网络,下面仍然挂接低速可靠总线提供末端控制**(参考下一章的场景讨论)**。
经过以上各节分析,我们可以看到,每种协议都有其特定的设计思路和适用范围。CAN注重实时可靠多主控,SPI追求高速低延迟点对点,UART胜在简易通用,I²C强调节省资源多从设备,而以太网则提供无可比拟的带宽和网络规模。没有一种协议绝对优于其他,需要根据具体需求选择。
不同应用场景下的协议选择
在实际工程项目中,选择合适的通信协议常常是架构设计中的关键一步。因为协议不仅决定了硬件连接和软件栈,也影响系统性能、可靠性和开发成本。下面我们结合几种典型应用场景,讨论该如何根据需求来挑选通信协议。
板级控制通信
**场景特点:**板级通信指在同一电路板或紧邻板卡之间的短距离数据传输。例如一个微控制器与多个传感器/存储芯片在同一PCB上的通信,或者通过板对板连接器连接的模块间通信。特点是距离短(厘米级),环境干扰相对可控,更关注引脚数量、数据速率和实现简易程度。
**需求分析:**板上通常有一颗主控MCU,需要与若干外围器件交换数据。外围数量可能较多,但每个数据量通常不大(如传感器读数、配置寄存器等)。因此关注点在于如何用尽可能少的连线连结多个器件,并满足所需的速度。例如,一个惯性测量单元(IMU)同时带三个加速度计、陀螺仪等,需要高速读取六轴数据;又比如一块板上有温度、湿度、光照等多个传感器,每秒读几次即可,不要求高频。
协议选择考虑:
- 若设备数量较多且每个数据量较小(典型如多传感器,多小芯片),I²C 会是首选。因为I²C两线可挂很多设备。比如板上有5个传感器,总共只占用MCU两根I/O,非常经济。I²C 400kHz足以满足大多数传感器读写频率,且硬件和驱动支持广泛。很多传感器芯片自己就只有I²C接口,这时别无选择只能用I²C。需要注意检查地址是否冲突,但一般不同类型传感器地址不同,或可通过引脚配置解决冲突。
- 若数据速率要求较高(比如需要高速采集ADC数据、驱动高刷新率显示屏),SPI更适合。SPI的优势是高带宽和低延迟。板级高速ADC通常提供SPI接口以实现几MHz的采样读取,这时MCU用SPI接口就能跟上。又如驱动TFT屏幕发送帧缓存数据,也常用SPI(或并行接口),否则用I²C 400kHz远远不够刷新。一般来说,当所连接的某个外设标称速率超过几百Kb/s时,就应考虑SPI了。
- 一对一简单连接:如果板上只有一个外设,需要点对点通信且速率要求不高,用UART也完全可以胜任。例如一个板上MCU与另一块板上的另一MCU通信,双方可能用UART通过板对板连接器互连。这种情况下UART实现最简单,而且UART稳定性好,可用较低波特率提高抗干扰。又比如GPS模块输出NMEA数据通过串口,MCU只需UART RX接收解析即可。这些场景下,引入I²C/SPI反而复杂不必要。
- 可靠性和实时性:板内环境一般较好,无需CAN那样复杂的错误机制。但若你的板级网络涉及多微控制器对等通信且对实时一致性有要求,可以考虑用CAN总线代替UART/I²C。比如在一个机器人控制板上,有多个MCU分别控制电机、传感器,要协同运作,则CAN总线连接它们会是很健壮的方案。CAN保证了任一节点故障不会瘫痪全网。不过大部分普通板子不会上多颗MCU互联,所以CAN在板内不常用,更多用在板与板(或设备与设备)连接上。
- 引脚资源考量:有些微控制器管脚不多,如果SPI片选线太占IO,可以倾向I²C或UART方案。举例来说,一个8位MCU要连接3个传感器,若用SPI则需要3根CS,加SCLK/MOSI/MISO共5根线,而I²C只要2根,差别明显。如果性能允许,少管脚场合应优先I²C或一条UART多收发(通过RS-485等总线收发器实现)。
- 功耗:I²C和UART在空闲时总线静止,没有切换;SPI若时钟不停会持续翻转消耗功耗。所以在低功耗应用中,I²C比较有利。此外I²C开漏结构还可以方便地通过上拉电阻配合MOSFET关断实现总线电平控制,以在休眠时彻底不给外设供电。
简而言之,对于板级通信,可按如下经验选择:“针脚少/设备多用I²C,速度高/数据流大用SPI,两设备简单通信用UART,多个主控协作考虑CAN”。实际上,板级设计经常是I²C+SPI+UART组合使用:I²C挂低速器件,SPI连高速器件,UART用于调试或特定模块。例如Arduino主板上典型就是I²C用于扩展模块通信,SPI用于SD卡/显示屏,UART供下载和打印日志。一块STM32开发板可能连接了多个传感器(走I²C)和一个屏幕(走SPI),同时预留UART接口调试,如此搭配取长补短。
下面用一个决策流程图总结板级协议选择思路:
图 4:板级通信协议选择示意图
车载网络通信
场景特点:现代汽车内部往往有由多个电子控制单元(ECU)组成的网络,例如发动机控制器、变速箱控制器、ABS制动控制器、车灯模块、车窗模块等等。这些ECU分散各处,需要通过车内网络通信协作。同时车上的传感器和执行器也大量接入网络,如转速传感器、雷达、摄像头、电机驱动等。车载网络强调实时性和可靠性,因为很多通信直接涉及行车安全(如转向、制动)。另外,汽车工作环境电磁噪声强(火花塞、高压线等)且温度范围大,因此抗干扰和健壮性也很关键。
需求分析:车载网络通常有分层设计:底层网络负责底盘/动力等关键控制信号,需要实时和高可靠;中间有车身舒适网络,传输如空调、座椅等非关键控制;高层还有娱乐信息网络,传输影音数据,带宽要求高但实时性相对次要。此外,还有诊断和编程等功能需要通过网络实现(如OBD车载诊断)。因此,同一车辆中会并存多种通信协议,各司其职。例如传统汽车中:动力底盘用CAN,总线1 Mbps;舒适功能用一个低速CAN或LIN,19 kbps;娱乐导航用MOST光纤网络或现在的以太网。
协议选择考虑:
- CAN 总线几乎是标配:大多数车载控制网络都使用CAN或其升级版CAN-FD。CAN的仲裁和错误处理适合车内这种多节点实时控制环境。典型一辆车里可能有2-5条CAN网,总线把各ECU连接起来。比如发动机和变速箱ECU通过高速CAN交换转速扭矩信息;ABS、ESP模块也接入同一总线分享车速和制动状态。CAN满足他们毫秒级通信周期且极低错误率的要求,同时一条总线上可挂几十个节点,非常经济。另外,CAN线为双绞线差分,抗噪声和适应长线缆能力强,这是汽车布线需要的。从上世纪90年代起,CAN就成为汽车主干网络,至今在传统汽车中仍不可或缺。未来即便以太网进车,CAN依然会保留用于各类控制器局域网的通信(例如门模块、座椅、电池管理等)。
- **LIN 总线用于低成本节点:**LIN是一个单线的低速串行网络,用于连接诸如车门、车灯等不太重要的执行机构。LIN成本极低(节点只需UART + LIN收发器IC),速率典型19.2 kbps足够玻璃升降、座椅调节等控制。一个车门里的模块可能有好几个LIN从节点(窗户马达、镜子马达、锁止传感器等)由一个门控制单元通过LIN控制,然后门控制单元再通过CAN与车身中央网关通信。LIN作为CAN的支线,完善了车内网络分层架构:CAN主干 + LIN支线。选择LIN是出于成本和实际需求考虑,因为用CAN的话每个节点都太贵且不需要那么高性能。因此,在设计汽车网络时,会把对实时要求不高但需要廉价连接的部件放LIN上。LIN协议本身简单,可视为主/从调度的UART网络,易于实现。
- 汽车以太网兴起:随着高级驾驶辅助(ADAS)和信息娱乐(Infotainment)需求猛增,传统CAN带宽明显不够(例如多个高清摄像头的数据就上百Mbps)。为此汽车网络开始引入以太网技术(100 Mbps及以上),如以太网AVB/TSN保证一定实时性,用于传输传感器原始数据、高清影像等。一些新车型已用以太网连接车载摄像头到域控制器,或中央网关通过千兆以太网与后 seat娱乐系统通信。设计上,以太网通常不会直接替代CAN,而是两者并存、各担其责:以太网做高带宽数据骨干,CAN继续承担各ECU实时控制协调。未来全自动驾驶,可能有更多ECU之间需要高速同步(如雷达点云数据,激光雷达数据),那时车内网将更多依赖以太网。不过,考虑到以太网节点费用仍高于CAN节点,目前业界趋势是在关键ECU(域控制器)间使用千兆以太网,末端执行器传感器仍通过CAN/LIN由域控制器转接。
- **FlexRay 等其他协议:**FlexRay是为满足更高实时和冗余要求而开发的汽车网络,速率可达10 Mbps,采用时分复用+两通道冗余设计。在一些高级车型(尤其早期高端车)用于底盘或X-by-wire系统。然而FlexRay成本高、复杂,未大规模普及。随着CAN FD和汽车以太网出现,FlexRay的定位有些尴尬,目前新平台用的少了。此外,MOST(媒体定向系统传输)曾用于车载娱乐,但现在被以太网取代。综合看来,CAN+LIN+Ethernet是车载网络的主流组合。
- 网关和协议转换:通常一辆车会有一个中央网关ECU,连接不同总线并转发数据(例如把动力CAN上的信息转到仪表CAN等)。这意味着设计车载网络时,不一定要求所有节点都直接互通,而是通过网关灵活隔离。例如动力ECU的数据网关筛选后才发给娱乐系统,保证安全。这种架构上,不同子网可以选择最适合自己的协议,由网关衔接。比如在电动汽车里,电池管理系统(BMS)内部可能用CAN FD 2 Mbps跑大量电池数据,而整车主CAN还是经典CAN 500 kbps,网关ECU连接二者并进行数据汇总与发布。
- **诊断需求:**OBD-II诊断口一般走CAN(新车法规要求),所以整车一定有CAN用于诊断通信。设计上会让车内某条CAN直接通往OBD接口,让维修电脑可以读故障码刷新程序等。这也巩固了CAN在车里的必要地位。
概括选择经验:“实时控制首选CAN,低速从属设备用LIN,高数据吞吐选Ethernet,其它如UART仅用于点对点辅助链接”。例如一个车窗控制器与主车身控制通信可直接用LIN(一对一UART变体即可);但发动机和变速箱必须CAN以保障同步性和抗干扰;而中控屏的视频输入则走以太网才够带宽。
车载网络协议选择需要在性能和成本上折中。CAN节点成本低可靠性高,所以依然承担大量任务;以太网性能极佳但成本和开发复杂度高,只在必要处用。LIN是性价比极高的末端网络。如果毫无约束地全车都上最高速协议,那会造成资源浪费和成本飙升。因此汽车工程师非常重视根据不同功能域来分层设计网络,用最合适的协议做最合适的事。
工业总线通信
场景特点: 工业自动化现场有大量的传感器、执行器和控制器,它们需要联网构成控制系统,例如PLC控制若干传感器和马达,以完成生产线某工序。工业环境要求通信可靠抗干扰、实时确定,并往往需要连接较长距离(工厂车间可能上百米)。传统上工业现场使用专门的现场总线(Profibus, Modbus, DeviceNet, CANopen, CC-Link等等),近年正逐步被工业以太网方案取代(Profinet, EtherCAT, Ethernet/IP, Modbus TCP等)。因此工业通信协议呈现出百花齐放且持续演进的特点,没有绝对统一标准,每种都有一定装机基础。
需求分析:工业控制系统按功能层级分:现场设备层(传感器/执行器)、控制层(PLC/DCS等控制器)、车间监控层(SCADA/HMI)再到上位管理层(MES/云平台)。底层设备层通信传统靠现场总线,追求实时;高层监控和管理以太网和TCP/IP已经占据主导,关注吞吐和集成。现在趋势是统一网络,底层实时流量通过以太网上的新技术(TSN等)保证实时,上层和下层都跑以太网使集成变简单。但短期内,很多旧设备和控制器仍然依赖现场总线协议(一些运行了几十年的生产线不会轻易全部换新)。所以工业通信场景是新旧并存,非常复杂的局面,需要兼顾兼容和升级。
协议选择考虑:
- **CAN/现场总线在设备层:**许多工业设备内部或设备到控制器的连接使用CAN及其高层协议。例如DeviceNet和CANopen就是基于CAN的工业总线标准,在工厂里应用广泛。它们优点是节点成本低、可靠性高,非常适合连接传感器、简单执行器。比如一条流水线上若干智能传感器通过DeviceNet连到一个PLC,每个传感器8字节报文就够传信息,这种应用CAN完全胜任。另外一些老的485总线协议如Modbus RTU、Profibus DP等也广泛存在。RS-485网络成本低长距离,所以在一些工厂设备间仍在用,典型如Modbus RTU用于远程I/O模块。
- 工业以太网在控制层:新的产线一般倾向使用工业以太网。像西门子的PROFINET、倍福的EtherCAT、Rockwell的EtherNet/IP等都是基于以太网物理层但针对实时做优化的协议。它们带宽高,能传输大量诊断和工艺数据,还能与IT系统打通(因为上层也是IP协议)。选择哪种工业以太网往往取决于企业的设备生态:如果已经是某大厂PLC,则用其主推的协议兼容最好。这些协议区别不少,但都满足毫秒级循环刷新I/O数据的需求。EtherCAT特点是超低延迟,适合高性能运动控制;PROFINET强在灵活组网和与老Profibus兼容;EtherNet/IP则方便与标准以太网互通等。尽管不同,但它们都有统一点:采用以太网物理层,能利用现成交换机和布线并传IP流量。所以若是新设计大型工厂网络,多半会选择某种工业以太网为主干,而不是重新上老总线标准。
- 混合使用与网关:现实工业系统里,经常需要协议网关来连接不同网络。例如老设备只有串口Modbus,PLC上只有以太口,那就加一个Modbus网关转换为Modbus TCP;又如仓库移动机器人,本体局域用CANopen控制各电机,但要与中央控制通信,需要网关把CANopen数据映射到上位的以太网协议。设计选择协议时要考虑兼容已有设备。如果现场已有很多4-20mA模拟传感器,那么新的控制系统也许还得保留这些接口;如果已有Profibus仪表,不会一下子全换Ethernet。因此经常一个新系统会集成多种网络。作为设计者,需要根据性能要求和成本,对各子系统分别选择合适协议,再通过主控制器或网关进行衔接。
- 确定性需求:对于一些实时性极高的场合,如高速流水线上位置同步,机器人多轴协调运动,协议必须能提供微秒级同步和确定消息延迟。传统CAN等达不到(微秒级更适合用EtherCAT或时间触发总线)。所以如果系统有这样的需求,应果断选用专门实时协议,不该为了兼容性迁就低性能方案。不然会影响控制品质甚至造成危险。换言之,在工业控制中,要确保关键控制信号走最可靠快速的通道,非关键信息走普通通道。通常做法是:高优先I/O数据使用如EtherCAT或PROFINET IRT(实时等级),次要数据走TCP/IP普通信道。而一些完全非实时的监控数据,可能还通过无线网络或普通以太网远程传输。通过QoS或协议分层,确保不同需求的流不互相干扰。
- 环境和距离:工厂车间常有强电磁干扰(大电机、焊接设备等),因此差分总线和屏蔽线很重要。RS-485、CAN这些都是差分,抗噪强,更适合长线部署。以太网双绞线也能良好抗噪,但如果距离>100米需光纤或中继。设计选协议时要看分布范围:如果设备分散上百米甚至跨厂区,则必须走以太网(可通过光纤模块延伸)或工业无线(如LoRa/工业Wi-Fi),传统有线总线一般100米内。若是小型产线设备集中在10米内,则现场总线如CAN完全没问题。有些室外场合(矿场、管道)则需要长距离,用工业以太网光纤或4G/5G物联网等,这又属于更高层通信了。
简而言之,工业通信协议选择强调可靠和实时,其次才是速度。老的现场总线虽然慢(几百Kb/s)但胜在确定性和超低误码率,新一代工业以太网则努力兼顾带宽与实时。设计时,可以遵循一个思路:“控制闭环内信号用现场总线/实时以太网,监控管理数据用标准以太网/TCPIP,现有系统则尽量兼容集成”。举例来说,一条包装流水线改造:保留原有的Modbus RTU传感器,新增部分用PROFINET连到新PLC,PLC再通过OPC UA将数据上传工厂MES系统。这样的混搭在实际非常普遍,各种协议通过分层网关协同运作,以满足不同层级的需求。
图 5:工业系统中不同协议通过网关和控制器互连的示例。现场的Modbus设备和CANopen设备分别接入PLC或网关,再由PLC/网关连接到上层工业以太网,与其它控制器和计算机通信。实际工程常见多种通信共存的情况。
上述图示显示,工业现场往往需要兼顾新旧,两者通过控制器或网关衔接。这提醒我们,在选择协议时不能孤立考虑某一段链路的最优,还要看它如何融入整个系统。如果一种标准能在系统上下通吃,当然最好,但现实是需要桥梁和过渡。当下趋势是各种工业协议向以太网靠拢,以太网俨然成了“万能胶水”。所以如果没有特殊原因,新设计设备最好具备以太网接口,与现有IT/OT基础设施对接。这也是工业物联网(IIoT)的要求:底层设备直接上网,实现信息透明流动。
总结来说,在不同场景下选择通信协议,要抓住主要矛盾:板级重点在引脚和速率,车载注重实时可靠,工业则平衡实时和集成。灵活运用多种协议组合,才能既满足本地控制需求,又兼顾全局数据融合。
从零设计一个类似 CAN 的通信协议
经过前文对原理和案例的探讨,最后我们来进行一个“综合实践”——设想如何从零开始设计一个类似CAN总线的通信协议。为什么选CAN为蓝本?因为CAN在通信协议设计上非常经典:它覆盖了物理层、电气规范,定义了高效的帧结构,创新了仲裁和错误机制,被视作可靠实时网络的标杆。设计一个类似CAN的协议,有助于我们把前面讨论的分层、帧格式、仲裁、错误处理等原则融会贯通。这个新协议我们姑且命名为“MyCAN”(仅表示借鉴CAN思想的自定义协议)。
设计目标假定 :MyCAN协议用于多节点共享总线的嵌入式控制网络,有如下需求:
- 支持多个设备(比如最多有 32 个节点)连接在一条总线上,任意节点都可发送数据(多主)。
- 提供优先级调度能力,使高优先数据获得低延迟传输。
- 实现可靠传输,要求误码率极低,即使有暂时干扰也能自动恢复,不丢帧。
- 数据帧长度适中,每帧有效载荷几十字节以内即可(控制信号不用太长)。
- 网络速率适中,比如 500 kbps~1 Mbps 级别;总线长度可达几十米。
- 拥有故障隔离机制,单个节点硬件故障不会瘫痪全网。
- 易于扩展,比如将来需要更多节点或更大数据量时可升级而老节点仍可共存。
这些需求其实就是CAN总线被设计时考虑的要点。那么如何一一实现呢?我们将按分层逐步展开,从物理层到链路层。
物理层设计:信号与介质
1. 总线拓扑与信号编码:选择一种支持多点共连的物理介质是首要任务。像UART那样点对点模式不行,我们需要总线式拓扑。CAN的成功经验是用差分对传输(高电平-低电平表示“1/0”),这样抗干扰强且允许多个节点线与连接。MyCAN也采用双线差分总线。总线两条信号线Bus+和Bus-,节点通过收发器芯片连接。当无节点驱动时,两线电压通过偏置处于同电位(表示逻辑“1”,即空闲)。任一节点驱动时,输出一个显性电平,使Bus+电压比Bus-高一定幅值(如+2V对+0V),表示逻辑“0”。差分设计好处是若外部噪声同时加在两线上会相互抵消,提高可靠性。
为实现总线仲裁,需定义显性位和隐性位的逻辑:沿用CAN理念,“0”定义为显性(dominant),“1”定义为隐性(recessive)。这意味着如果一个节点发送显性0,而另一节点发送隐性1,那么总线结果将是显性(即0),相当于0“覆盖”1。这可通过开漏/开路驱动实现:节点发显性时主动拉低差分,使Bus+低于Bus-(对于接收比较器而言输出0);发隐性时则不主动驱动(让上拉电路将总线保持默认高态)。这样多个节点同时发时,只要有任意一个发0,总线就是0。这种有线与行为正是逐位仲裁所需。
驱动电平上,可参考CAN规范:显性时Bus+≈3.5V, Bus-≈1.5V(差分约2V);隐性时两线都约2.5V(差分≈0)。但具体电平不是关键,只要所有节点驱动特性一致即可。可以让MyCAN沿用CAN收发器硬件,这样设计上更简便(直接利用成熟器件)。
2. 比特定时与同步:采用异步串行发送,每个节点需在比特级同步。CAN通过非归零码(NRZ)加位填充来实现连续同步。MyCAN也采用NRZ编码(0/1直接对应显性/隐性电平,不做曼彻斯特之类的复杂编码)。NRZ效率高但长时间不跳变可能使接收方时钟漂移,因此要引入位填充规则:数据中若出现5个连续相同位,则自动插入一个相反填充位,接收方在收到后移除。这样保证最长不跳变序列为5+1=6位,足以维持同步。MyCAN选择与CAN相同的5位填充规则,因为实践证明这很好地平衡了同步需要和额外开销。
时钟速度方面,可设MyCAN标称位速率如500 kbps。每节点独立使用晶振+可编程位定时寄存器产生这个速率,并利用总线跳变不断校准相位,使长期同步。CAN控制器通常采用一个稍高频基准(比如8倍bit速率)对位采样并做相位调节。MyCAN类似处理,不详细展开硬件原理,只需知道软件上将配置“500k波特率”而控制器自动同步就好。
**3. 物理接口连接:**采用双绞线作为传输线缆(120欧姆特性阻抗),在总线两端加终端电阻匹配(例如120欧并联在Bus+和Bus-)防止反射。这与CAN物理层完全一致,没什么改进空间,就是成熟可靠的做法。每个节点用一颗MyCAN收发芯片(或者直接用CAN收发器芯片)。这样一来,MyCAN总线物理层几乎与CAN等同——毕竟CAN物理层已经相当优化,复用它让我们省去很多验证工作。
**4. 总线长度与节点驱动能力:**假设MyCAN目标支持40米以内长度(典型车辆或机器设备范围),1 Mbps下40米问题不大。如果需要更远,可降速或使用中继器。节点数规划32个以内,每个收发器负载不算高,业界CAN收发器可以驱动110以上节点(因为输入阻抗高)。所以32只是逻辑限制而非电气限制,可在软件协议层用地址/标识限定。物理上无硬性节点数限制,只要总线电容不超规格即可。这里我们暂不深究信号完整性,认为跟随CAN经验500m总线125kbps,100m可500kbps,40m可1Mbps,都在可接受范围。
综上,MyCAN物理层采用双线差分有线与架构,NRZ编码+位填充确保同步。这个设计能满足多节点竞争需要,也具备较强抗干扰性,非常类似CAN。毕竟我们是在模拟CAN的精髓,没有必要另起炉灶设计物理层。物理层定好了,接下来重点是数据链路层协议设计。
帧格式设计:标识符、数据与校验
MyCAN的数据链路层需要定义帧格式,即一帧传输单元内部的比特组成和含义。这部分至关重要,决定了协议的信息组织和效率。
借鉴CAN的帧,我们也划分出数据帧(Data Frame)和控制帧等不同类型。但先聚焦设计数据帧格式,因为它是最主要的。
MyCAN 数据帧拟包含以下字段:
起始位 (Start of Frame):用一个显性“0”位表示帧开始。CAN中也是如此。该位到来时其他节点若空闲就开始接收帧。由于显性为0,能确保总线出现边沿以同步位时序。SOF之前应保证至少总线保持一定时间隐性1(称为帧间隙),才能区分帧界。具体帧间隔我们可以仿CAN规定最少3位时间的空闲(加上特殊情况Overload帧之类)。基本思路是帧与帧之间至少有3个隐性比特的间隔,这样所有节点都能认定上一帧结束、总线空闲3位后来的显性是新帧开始。这3位是最低要求,如果上一帧发送节点有剩余发送缓冲,也需等待间隔满足再发下帧。
仲裁字段 (Arbitration Field):包含标识符(ID)和远程帧请求位。我们为MyCAN设定ID长度为11位(与CAN标准帧相同),以标识消息类型和优先级。ID的数值越小优先级越高。与CAN不同之处:也许我们可以默认只用扩展帧格式,比如直接采用29位ID?这是个取舍。11位ID提供2048个消息类别,多数应用够用,而且帧更短仲裁冲突概率低。但是有些系统或许需要更多ID。CAN的解决方案是扩展帧,我们也可以设计两种帧格式:标准帧11位ID和扩展帧29位ID,通过一个标志加以区分。为了简化,这里暂先设计只有标准帧格式。扩展帧也可类似CAN,用一个IDE位指示,然后ID字段扩展。限于篇幅,我们不详细设计扩展模式,但会预留位以备后续升级。
ID之后紧跟1位RTR(Remote Transmission Request)位。该位若为显性0表示数据帧,若为隐性1表示远程帧请求(Remote Frame)。Remote帧是一种特殊控制帧,用于节点请求别的节点发送某ID的数据。例如节点A想要ID=100的传感数据,可以发一个ID=100、RTR=1的帧,拥有该ID数据的节点收到后应立即以ID=100、RTR=0发出数据帧回应。CAN协议支持这一概念以减少网络冗余传输。MyCAN保留RTR机制供需要时使用。不过现代CANopen等高层很少用远程帧,直接周期广播数据更常见。所以RTR存在意义不大,但为了CAN功能完整性我们还是设计进去。简单说,RTR=0表示这是个携带数据的帧,RTR=1表示这是个请求帧,不含数据。
仲裁字段总计ID(11)+RTR(1) = 12位。
控制字段 (Control Field):控制字段的作用是说明数据长度和保留协议信息等。我们至少需要数据长度码DLC,用于指示帧携带的字节数。CAN用4位DLC支持08字节数据。MyCAN可以考虑支持更多数据,比如016字节,需4位不够,可扩展到5位DLC(031字节)或继续4位但定义部分值>8表示16、32字节等。为简单起见,这里假设与CAN一致4位DLC支持08字节数据,后续有需求再另行扩展。控制场其余位可包括保留位或协议版本之类。CAN在标准帧控制场有2位保留位(以后用于表示CAN FD之类)。MyCAN也可以设2位保留,将来升级时用。暂定这2位一律显性0发送,接收方忽略其值。若以后我们想引入新特性(比如更长数据帧模式),可以通过占用其中一位通知新格式。
此外,CAN控制场还有在扩展帧情况下用的SRR和IDE位,但我们暂不设计扩展帧,这两位可省略或者兼用作保留。综上,Control Field大小暂定6位:4位DLC + 2位保留。全部发送显性0的保留位接收方不做处理。
**数据字段 (Data Field):**变长0~8字节,由DLC指示长度。数据字节按字节发送(每字节8位,无特别约定则MSB先发送,当然接收从第一位收到的是MSB)。如果DLC为0则无数据字节。考虑对齐,MyCAN可以固定将数据字段字节数 exactly 等于 DLC,不足0则没有数据。CAN就是这样定义的。需要说明的是,若之后想扩展单帧数据长度,比如CAN FD可达64字节,那DLC需要特殊映射方式或增大位数,这里不展开。MyCAN1.0版就支持8字节最长,与经典CAN相同容量。
CRC字段 (CRC Field):为了确保帧正确接收,需要CRC校验。CAN使用15位CRC+1位CRC界定符。我会为MyCAN采用16位CRC(多一位用于更强检错能力,也方便2字节对齐)。界定符其实就是紧随CRC后的固定显性位,用于区分数据和CRC结尾。CAN的那个CRC Delimiter位始终隐性1,因为在发送完CRC后总线需一个固定隐性来让接收方处理。MyCAN可以仿照——例如CRC多项式和长度我们定16位生成,多项式可选CRC-16 IBM (0x8005)或CRC-15 CAN用的(0x4599扩充位)。为了不纠结细节,就假设MyCAN CRC采用标准CRC-16 CCITT(0x1021)计算覆盖从SOF后的所有字段直到数据结束。发送端计算所得16位序列,然后发送。之后加一个CRC Delimiter位,一律隐性1,以标志CRC结束。这CRC场总长度=16+1=17位。
**应答字段 (ACK Field):**与CAN一样,MyCAN需要接收方对正确收到的帧发确认信号,以告知发送方不用重发。这通过ACK场实现。ACK场2位:第1位ACK槽,第2位ACK界定符。在ACK槽期间,发送方发送一个隐性1(表示“我期待ACK”),而任何接收方若正确校验帧无误,就会在此位驱动显性0。结果总线上就会出现显性0(有一个接收方ACK即可),发送方检测到ACK槽不是原先发出的隐性而被拉低,即知道“至少一个节点收到了”。若ACK槽保持隐性1不变,则表示无人收到/或都校验错误(比如此发送方孤立在网外或帧坏掉),发送方则会重传帧。紧随ACK槽的是ACK界定符,为一个隐性1,类似CRC界定符作用,用来将ACK槽与下一个字段分开。CAN规定ACK界定符必须隐性,由发送方发送,接收方不干预。MyCAN遵循这个:ACK界定符=1显性,由发送方发出。这样ACK字段2位搞定。
结束字段 (End of Frame):标志帧结束。CAN用7个隐性位作为EOF,要求所有节点在这7位内不发送显性,否则会当作错误标志。MyCAN可以简单使用7个隐性1位表示帧结束。同理帧结束后再空闲至少3位才能开始下一帧。
将上述字段串联,MyCAN数据帧格式如下:
| SOF (1位) | ID (11位) | RTR (1位) | 控制字段(6位) | 数据字段(0-8字节) | CRC序列(16位) | CRC定界符(1位) | ACK槽(1位) | ACK定界符(1位) | EOF(7位) |
可以算一下长度开销:假设8字节数据帧,则总位长 = 1 + 11+1 + 6 + 8*8 + 16+1 + 1+1 + 7 = 1+12 +6 +64 +17 +2 +7 = 109位。CAN对应配置下是108位(因为CAN CRC15位),我们略长一点,但影响不大。填充位根据比特流而定,每5连续相同插入1,所以最坏情况比如ID全零+后面很多零,那每5位多插1位,会增大长度。不过通常熵不这么低。以CAN经验,一帧平均填充位开销在几位左右。
远程帧 (Remote Frame):如果RTR=1,就是远程帧请求。这样的帧没有数据字段,因为请求不包含数据,只是ID。本质上远程帧和数据帧格式一样直到DLC,但DLC数值应等于请求的数据长度。举例某节点想请求ID=0x100的8字节数据,则发送ID=0x100,RTR=1,DLC=8的帧。接收方识别RTR=1不会有数据段,仅CRC和ACK流程。远程帧发送过程节点自身不会对ACK槽拉低,因为它期待的是其他持有该ID数据的节点来响应ACK并随后发数据帧。所以我们需要定义优先级:当远程帧与对应数据帧同时出现仲裁谁胜?CAN中规定数据帧优先于远程帧。MyCAN可以沿用:如果两个节点同时发同ID,一个RTR=0,一个RTR=1,则RTR位送仲裁时显性0胜过隐性1,意味着数据帧赢。这样就避免了鸡肋的情况(请求和回应同时发冲突)。反过来说,请求方可能仲裁赢发出了远程帧,那么它期待随后别的节点回应一个数据帧。这涉及上层应用实现,协议层只需支持RTR标记即可。远程帧收到后持有该ID的数据节点应尽快发送数据帧(这通常应用层做,我们不深入)。
错误帧与过载帧:CAN链路层还有错误标志帧(6显性位)用于中止错误传播,还有过载帧用于从机告知主机自己繁忙需延迟发送。MyCAN也应该考虑错误处理帧,后面“错误处理机制”会详细讲。这里提醒一点,错误标志在物理上就是一串显性位违反填充规则,会被所有节点识别为错误条件。MyCAN可规定:任一节点检测到错误立即发送错误标志,定义为6个显性位连续。这个和CAN一样。发送错误标志的节点后面应跟错误定界符一段隐性。那么对于帧格式而言,一旦中途中出错,原帧被破坏,各节点丢弃之,等错误状态恢复后才能重新发送。我们帧结构中EOF 7位隐性,如果在EOF之前任意不该显性的地方出现显性,则当作错误标志的一部分。这些规则是实现上的,但要和帧格式配套。过载帧CAN用和错误帧一样的格式,MyCAN也可用,当一个节点需要额外延迟时发送过载标志。
但为了不混淆,这里帧格式主要讲数据帧。错误帧/过载帧不是正常帧,它们没有标准开头,只是总线特殊符号序列,放在错误章节说。
兼容性和扩展字段:我们在控制字段保留了2位,这可以用于未来扩充。例如将来想支持MyCAN FD(比如把DLC>8解释为更长数据帧),可以用保留位之一作为“协议切换指示”,老节点见此位未知也忽略,但是可能接收到长帧它也解析不了怎么办?这就需要一个版本管理机制。也许我们可以在帧头搞1位叫FDF (FD Format), 0=classic format, 1=new format。这跟CAN FD的思路一样。不过老节点不认识FDF =1的帧,它可能当作保留位处理继续解析结果肯定CRC不符,从而丢弃。这样老节点就算无法解析新帧,也不会乱动作,只是忽略新格式帧。因此扩展位和CRC、ACK的配合很重要。幸运的是CAN FD的方案已经验证可行,MyCAN只需仿效即可。但由于我们是第一版协议,不具体展开这个扩展点,只确保保留位留好了。比如当前全部发0,后续如有新版定义它们不同组合含义,老节点按要求忽略即可,不会误用。
这样MyCAN帧格式基本敲定。可以画一个简单时序例子说明:
例如:节点A发送一个ID=0x123、DLC=4的数据帧,假设数据为 [0xAA, 0x55, 0x00, 0xFF](4字节)。节点B正确收到并ACK。
- SOF:显性0
- 仲裁段:ID=0x123 = 二进制
0100100011(11位),这些位与其他节点争总线,假设A最低ID所以赢了仲裁。紧接RTR=0显性,因为是数据帧。 - 控制段:DLC=0100(表示4),保留位00。
- 数据段:按字节发。0xAA=10101010;0x55=01010101;0x00=00000000;0xFF=11111111。注意每当出现5个连续同位,需要塞填充位。观察:0xAA的二进制10101010没有5连;0x55=01010101也没有;0x00=00000000就有8个连续0,需要填充。在CAN规则,每5个0后插入1,所以0x00发送时序实际上是
00000 1 000(在第五个0后插入一个1,然后继续余下3个0);0xFF类似8个1,会发11111 0 11(插入一个0后再两个1)。需要注意这个填充位本身也参与后续填充计数。如果这样连续,我们后面CRC计算要基于插入前数据,但发送时包含这些填充位。 - CRC段:发送方根据仲裁、控制、数据(原始bit序列,不含填充位)算出16位CRC,比如假设算出来是0xABCD(二进制1010...)。按二进制发送,同时遵守填充规则(CRC序列若有长连同样插入填充)。然后发CRC定界符 = 1。
- ACK段:发送方在ACK槽送1(隐性),此时节点B如正确收帧则在此位驱动0(显性),总线表现为0;ACK定界符发送方送1。
- EOF:最后A发送7个1结束帧。期间如B在最后一字节处理不过来,也许可以发一个过载标志(6个0)打断EOF,不过希望不发生。
这样节点A收到ACK确定B收到了,于是认为传输成功。B已获得数据帧内容,传给应用层。
这个流程和CAN基本一样,MyCAN的帧格式与行为都借鉴其成熟经验。所以MyCAN和CAN数据帧应该是互通的(如果忽略我们改成16位CRC,老CAN设备当CRC错丢弃)。如果想要MyCAN能直接兼容CAN2.0,那么CRC得保持15位,ACK机制也一样。这是取舍。如果我们不是为了兼容,而想稍增强,那16位CRC未尝不可,多一位检测更强一点。但是CAN的15位CRC已经足够检测突发错误(Hamming距达6),几率非常低才漏检。我们选16位只是出于整字节考虑。
**小结:**MyCAN帧结构高度参考CAN:有起始、仲裁(ID+RTR)、控制(DLC等)、数据、CRC、ACK、结束这些部分,每一段的作用清晰,而且组合起来满足仲裁、多播、可靠性需求。这个帧结构之所以经典,因为它几乎达到了控制网络效率、实时、可靠的平衡状态。我们的MyCAN也继承了这些优点。
仲裁机制设计:总线访问控制
在前述物理层和仲裁字段设计中,其实仲裁机制已经蕴含其中:基于有线与的逐位竞争加ID优先原则。这里再系统说明MyCAN的总线仲裁方案,以及在协议实现时需要注意的事项。
1. 多主竞争规则:任何节点在检测到总线空闲(最近一次传输结束后的帧间隔过后)时,都可以开始发送帧。如果恰巧多个节点同时在空闲后立刻发送,那么仲裁机制启动。仲裁发生在帧的仲裁字段期间,即从SOF后一位到RTR位这段(CAN仲裁扩展帧还包括SRR,IDE,但咱暂不考虑)。过程如下:
- 各发送节点同时驱动总线发送自己的ID位序列和RTR位。但因为总线的电平由所有节点的驱动综合决定(显性会覆盖隐性),所以实际总线上每个位时刻表现为这些节点发送位的“与”。具体:如果一个节点发送0而其他都是1,则总线=0;如果所有发1,则总线=1。
- 每个节点一边发送一边监视总线:将自己发送的位与实时读取的总线位比较。如果某节点某位想发1却读到0,意味着别的节点发了0显性覆盖了它,说明在ID比较上它处于劣势(它发送的这一位1大于竞争者的0,因为ID数值小优先权高意味着更多前导0)。此时该节点判定仲裁失败,应立刻停止发送,切换为接收模式,直到当前帧结束。它退出争夺后,不再干扰总线。
- 仲裁过程一直持续到有且只有一个节点未退出为止。那个节点就是当前帧的发送者。由于ID位越小(高位越多0)越不易在仲裁中被判负,所以ID数值最小的节点赢。这确保了静态优先级调度:ID越小优先级越高。
- 值得注意的是,如果两个节点的ID完全相同且同时发送,那么在11位ID和RTR都一样的情况下,两者都会认为未检测到不一致,一直持续发送到仲裁字段结束。这在CAN会产生仲裁平局:然后进入数据字段,如果一个是真正的数据帧另一个只是请求帧,就会在RTR位区别出胜负(数据帧RTR=0显性,会覆盖远程帧RTR=1,使远程发起者输)。如果两个都是数据帧且ID相同——这其实不该发生,因为ID应当在全网唯一标识一个消息类型,两节点不应同时发送同一ID的数据,否则应用设计就冲突。假设有此情况,在仲裁字段结束时两发送者都没退出,它们发送接下来的控制段。因为二者发送内容一样,总线无差异,最终整个帧两个节点都发送了,这将导致总线上出现两个发送者驱动却完全相同的信号流,接收方没法分辨,但会导致ACK冲突(两个发送者都认为对方在ACK,所以自发ACK不会拉低? 复杂情况)。CAN标准规定同ID同时发送的数据帧不会导致仲裁失败,因此会出现错误,因为两个发送者都发送完发现ACK槽都是自己发的隐性无人拉低(因为彼此都在发不会去应答)。结果双方都认为“没人收到我发的帧”,触发重传。为了避免这个,应用上必须保证同一时刻不会两个节点发同一ID(可以通过协调或ID在系统中唯一指派给某节点的数据)。我们的MyCAN也需要这个假设成立,否则碰到这种情况就按CAN那样当错误处理:双方都没有ACK则都会重发,再冲突,如此循环。所以MyCAN要求每个数据ID在网络中应有单一数据源,远程帧请求者除外。
- 仲裁失败的节点停发后,变为接收状态,它会收到胜出节点发的剩余帧数据,然后因为CRC不匹配(帧被破坏,因为它没发完导致局部填充不同等)而丢弃,但这没关系,它知道自己丢仲裁输了,会在后面的帧间隔后重试发送自己的帧。当总线空下来就再发。一旦优先级高的帧发送完,下一个优先级的就可发送。因此实现了无饥饿的优先级队列调度。
- MyCAN仲裁机制跟CAN2.0完全一样。实现上,硬件需要支持在发送过程中监测总线并自动在失去仲裁时停止发送并作为监听节点继续帧。在我们的设计里,可以假设使用CAN控制器IP就有这功能,MyCAN仅需在逻辑上遵循规则即可。
2. 仲裁机制的特性:它保证总线利用率极高,因为冲突过程中实际上总线依然在传有效位。例如两个节点竞争,在某个位出现分歧,一方退出,但此时胜者的帧比特已经在总线进行了,并没有重传浪费(不同于Ethernet碰撞要丢整帧重发)。所以MyCAN网络几乎没有因仲裁造成的带宽浪费,只是延迟上低优先帧可能被高优先帧插队。这样很好地满足实时控制场合需要(关键消息不会因低优先饱和而延迟太久)。
3. 总线空闲判定:MyCAN节点在侦听总线7位EOF + 3位间隔全是隐性1后,认为总线进入空闲状态,可以发新帧。要防止“总线闲着但连续有人要发帧”的饿死现象,CAN要求如果在EOF结束后没有检测到下一个SOF,就进入Idle;一旦Idle了节点可以同时尝试发又回到仲裁上。所以Effectively没有固定主时隙等调度,是随机接入。MyCAN也是如此:空闲判断条件大致就是前一帧结束+间隔满足,所有想发的都在下一个bit尝试SOF。谁先拉低SDA=0看到别人也0继续仲裁,否则如果某节点动作慢了一点,在它想发时已经有别的SOF到了,它就只能等下一次了。这带来一个潜在问题:如果总线一直高负载(总有节点排队要发),那么空闲间隔很短甚至没有,节点要抓准机会。CAN对这一点没有强制公平机制,但由于消息优先级定死,高优先的多也不影响低优先的有机会吗? 的确,如果一直高优先消息轮番发,低优先消息可能长时间发不出。这就是优先级饿死问题。但在汽车控制这反而是希望(关键消息确保送达,次要可延后)。MyCAN inherits this属性。因此设计应用时就要保障即使低优先也能周期发送(避免完全被淹没)。一般用调度法或限制高优先消息频率等来预防。协议本身不解决此问题,除非引入仲裁等级动态变化或Token之类,那就完全不同范式了。我们这里坚持CAN模式,不额外修改。
4. 远程帧仲裁:前面帧格式部分说过,MyCAN规定数据帧比远程帧优先。实现上,就是RTR=显性0赢过隐性1。所以如果某节点要发数据帧ID=100,另一节点同时发远程帧ID=100请求数据,由于它们前11位ID都一样,直到第12位RTR才不同,此时数据帧节点发送0,远程节点发1,结果总线=0,远程节点检测失配退出。这样请求帧不会打断数据帧。同理,如果两个请求帧ID相同同时发,他们ID完全一致包括RTR=1,那就跟两个数据帧ID相同情形相似,导致仲裁无胜者同时发送完,所幸远程帧没有数据只有到CRC前都是同步的,这种情况两个远程帧发送者都会没检测到仲裁丢失而一起发完整帧——CRC计算上,由于二者发的bit一模一样,总线接收看到的是一帧,不会出错,Ack由谁给? 远程帧receivers是谁呢,Remote帧其实不需要Ack吧?但CAN协议中Remote帧仍需要ACK,只不过一般数据拥有者才会发ACK吗? 实际上CAN远程帧要求任何一个准备发对应数据的节点都会在远程帧的ACK槽ACK它。如果两个节点同时发相同远程帧,就相当于俩人同时请求自己这个数据,这不符合应用逻辑,因为一般不会有两个节点都请求一个消息。或许有,但那也无妨,因为大家收到了请求就收到了。为简化,我们不打算优化这种极端情形,由应用避免。
**5. 位仲裁实现注意:**由于采用位填充,仲裁字段也会填充。好在填充规则对所有同时发送者是等效的,因此不会破坏仲裁结果。但实现电路要在填充插入时也检查仲裁,这通常没问题,因为发送器硬件知道插入填充位的时候别的节点也会插入相同填充(规则全局统一),因此依然按显性优先原则看。除非某个节点出错填充错误位,那相当于协议违规就会造成格式错误,触发错误帧。
6. 总线占用效率:MyCAN仲裁不浪费带宽,但会引入发送延迟。举例:节点A高优占住总线发送100位耗时200us,那么低优节点B就得等200us才能发自己的。如果A频繁发,B可能一直等。这个就看应用对时延要求。CAN网络一般高负载不超过30-40%,以确保延迟不会无界膨胀。设计MyCAN系统也应遵守不让总线100%占满的原则,否则部分消息延迟无法保证。
总的来说,MyCAN仲裁机制=位监测+显性胜出,在我们设计中已经嵌入,无需额外复杂算法。关键是ID规划:要合理分配ID优先级。设计者需要给紧迫的重要消息低数值ID,高数值留给不重要消息。这样才能发挥仲裁的实时调度作用。如果ID赋值乱,可能该快的不快。比如心跳消息ID=1023而日志消息ID=1,那日志会压制心跳发,这是不理想的。因此协议设计也包含ID分配策略作为应用层约定一部分,但不属于协议规范范畴,这里点出即可。
错误处理机制设计:错误检测与恢复
任何通信都有可能出错,MyCAN必须像CAN一样具备强健的错误处理能力,以保障网络可靠运行和迅速从故障中恢复。我们将设计错误检测方法和错误处理/隔离策略。
**1. 错误类型与检测手段:**参考CAN定义,可能的链路层错误类型包括:
- 位错误:发送节点在发送某位时,监测到总线电平与自己发送的不符,且该位不在仲裁场或ACK槽。这意味着发生了意外干扰导致位翻转(因为仲裁期不算错误,ACK槽0也不算发送者错误)。例如发送方发1却读到0,这只能在非仲裁阶段发生的话说明有干扰别的节点误判成0。发送节点即可判定位错误。
- 填充错误:接收节点在收帧时发现违反位填充规则(出现了6个连续相同位而未插入反位)。说明发送方出错或总线干扰跳过了应有的反转。此时接收节点标记填充错误。
- CRC错误:当接收节点接收完数据和CRC后,自行计算CRC与接收到的CRC字段不符,就知道帧数据被破坏了。判定CRC校验错误。
- 帧格式错误:接收节点在固定格式字段检测到非法值。例如在CRC定界符或ACK定界符位置没有读到应有的隐性1,或者在EOF 7位中检测到显性位(除非已在错误标志期间)。这些都属于格式错误。CAN中还有比如ACK槽若没有节点应答,发送方也认为发生错误,但这更像一种响应错误。我们简单认为ACK缺失也归类格式错误处理,因为期望应该有ACK而没有,是一种异常情况。
- 超时丢失错误:CAN协议本身不定义帧超时,因为帧长度有限max < 130 bit,硬件能检测某帧进行太久或者间隔太长。不过CAN有监视概念:当总线闲置时间超一定阈值,有的系统当作故障。MyCAN可以不管超时检测,网络层或应用监控健康。我们着重链路错误。
MyCAN各节点应能识别上述错误。一旦检测到错误,应立刻中止当前帧传输并通知所有节点有错误。
2. 错误标志与帧销毁:CAN采取的做法是发送方或接收方一旦发现错误(CRC错、填充错等),马上在总线上插入一个错误标志序列(6个显性位)。这一串显性位会违背填充规则(因为正常通信中不会有那么长连续显性),因此所有节点都能识别“啊,有人发错误标志了”。结果是当前帧余下部分被破坏——显性位覆盖了后续所有字段,于是所有节点都判定此帧无效丢弃。MyCAN复制这机制:定义错误标志为连续6个显性“0”位。任何节点只要检测到错误,就在下一个比特时隙开始发送错误标志(如果别的节点同时也发送错误标志,不碍事,因为错误标志本来就是多个节点可以同时发)。6个显性后跟一个错误定界符(至少8个隐性位)表示错误结束,让总线重回空闲状态。
CAN错误标志其实分主动错误标志(Error Active节点发6个显性)和被动错误标志(Error Passive节点发6个隐性,不主动干扰总线)。这是和节点错误状态有关的,后面讨论。先假定所有节点初始处于“Error Active”,所以都会发主动错误标志。这样任何检测错误的节点就会用6显性告诉大家“此帧坏了”。
错误标志的效果是中断帧:不管帧在哪个字段发生错误,后面的数据都作废,发送方也立即停止发送,其余节点同步察觉不对称。然后在错误定界符隐性期间大家重置接收器,准备下一次通信。
3. 错误恢复与帧重传:一次错误后,总线经过错误定界符(和额外的帧间隔)后重新变空闲。此时原本发送帧的节点需要重发刚才的帧,因为它没被正确接收。在CAN中,发送方检测到未被ACK或者自己也检测到错误时,会自动尝试重传,通常立即在下一个允许时隙重发帧(当然要再次仲裁)。MyCAN发送方同理:只要发送中检测到错误或者没收到ACK,就将该帧重新排入待发送队列,再次仲裁发送。重传可能还会遇到更高优先帧插队,但最终会发出去或重试多次。
需要注意避免无限重传:如果某因素导致一直出错,比如某节点硬件坏了每次发帧都错,这会持续占用总线。CAN有错误计数来把出错节点“降级”避免无限重试。我们下面会讲这个机制。总之错误帧重传次数不能无限,可以有限尝试几次后把问题暴露给上层。
4. 错误计数与节点状态:CAN为保证网络健壮性,引入了错误计数器和节点错误状态概念。每节点维护两个计数器:发送错误计数TEC和接收错误计数REC。正常时<128则节点状态“错误活跃”(Error Active),可以发主动错误标志;一旦任一计数>=128,节点进入“错误被动”(Error Passive)状态,不再发主动错误标志而是被动错误标志(被动标志是6隐性位,不会破坏总线,只能通过别人发的错误标志或超时来感知错误)。如果TEC继续增长>=256,则节点进入“总线关闭”(Bus Off)状态,断开总线不参与通信。这些规则确保一个连续出错的节点(比如硬件故障导致乱发)不会永远占用总线——它很快Bus Off,就相当于被网络驱逐。
MyCAN应采用同样策略:
计数增减规则:参考CAN。例如,发送节点每发送帧成功ACK则TEC减1(最低减到0);发送错误被检测(自己检测位错、填充错、未被ACK等)则TEC加8(主动错误标志触发);接收节点如检测到错误且发出错误标志,则REC加1(或TEC加1? CAN里谁发现谁加? 实则CAN规则复杂,但简化来说:谁发现错误谁加对应计数)。另外如果接收正确则REC减1。这样持续发送错误会迅速把TEC推过128进被动,再过256进Bus Off。具体阈值我们沿用CAN128/256即可。
Error Active vs Passive行为:Active节点出错时发主动错误标志(6显性),Passive节点出错时发被动错误标志(6隐性)。这样当总线上只有被动节点检测到错误,就不会有显性标志,怎么通知其他? 答案是,一般不会出现只有被动节点检测错误的情况,因为如果发送方被动,它不会发显性错误标志,但接收方若Active就会发主动标志覆盖总线。所以至少6显性还是会出现。如果极端所有节点都被动,那就没有显性错误标志,错误帧就没法中断,这种情况网络质量已经很糟但也可发生——CAN规定如全被动错误则帧可能错误传播不到位。不过常见场景不会全被动。
Bus Off处理:当节点Bus Off时,应当断开总线驱动(相当于不发送不影响总线),并需要软件介入复位处理。CAN要求Bus Off后,节点必须等待一段时间(比如128个11位隐性,约0.128s@1Mbps)再尝试复位进入Error Active。MyCAN可采用类似策略:Bus Off节点记录Bus Off状态,只有上层指示或经过一定空闲后才重新加入网络。这样坏节点不会频繁扰乱总线。
状态指示:为了系统可以监控健康,节点应提供状态信息如当前错误计数和状态。这样ECU可以在日志中报“节点X Bus Off”之类,便于维护。MyCAN不是应用协议,但可约定一个管理消息ID或诊断服务来读取错误计数。当现场调试用。
为什么这么麻烦? 因为要隔离持续故障节点。例如一个节点短路总线持续发0,则自己TEC会爆表Bus Off,它停止驱动,那么总线其他节点才恢复通信。没有这机制,全网就瘫痪。所以MyCAN重中之重也要实现错误计数/隔离。
5. 特殊帧:CAN定义了过载帧(Overload Frame),用于接收方要求发送延迟一点发送下帧,比如接收方来不及处理,需要多点时间,就发过载标志(跟错误标志一样6显性)在帧间隔处插入一个过载帧(总长6显性+8隐性),迫使总线延迟帧开始。MyCAN可以支持这个,但现代实现少用过载,因为会扰乱实时调度。通常设计要求接收节点要能跟上,不允许任意过载。由于复杂度有限,我们也实现过载帧:当某节点收完一个帧但内部缓存满了,想要拖延下一个帧,可以在刚收到EOF后立即发送一个过载标志(6显性)+过载定界符(8隐性),告知大家给点时间。CAN最多允许连续两个过载帧插入。MyCAN照抄。这样代价是稍微增加延迟,但总比因处理不及导致错误强。过载帧使用与错误帧相同格式6显性,使其他节点也检测到如同错误对待,把本来短帧间隔延长为错误后的间隔。其实overload和error在物理上都体现为6显性,只不过过载不是检测错误而是主动发出的。
6. 帧终止及恢复: 有了错误帧/过载帧,就需定义帧后的错误定界符和过载定界符。CAN统一采用8位隐性作为定界符。MyCAN也这么做:无论错误还是过载,只要某节点发了6显性标志,接下来就有8隐性位定界符(发送方保证,别的也发隐性)。这些都完成后,总线重回空闲或等待下一帧。如果在错误定界符期间又有节点错误? CAN规定错误标志间要隔开,不然后错套错。我们可以规定在标志期间节点不再发新标志,除非又遇错那那就等这个定界符完后再发新的错误标志。通常不需要这样多重错误,因为一次错误帧销毁了当前帧,应该等恢复后再继续帧。引申:错误锁定情况 ——万一有节点持续错误(比如坏节点每次重发又错),CAN错误计数机制会把它Bus Off,从而解锁。MyCAN的设计透过Bus Off也解决这个。所以错误不会无限连发,一定通过错误计数停掉坏节点,恢复网络通讯。
实例走一遍错误处理:
假设节点A发送帧,节点B收到但因干扰导致CRC错。B在检查CRC发现错误后:
- B立即在CRC定界符之后(即ACK槽前)发送错误标志(6显性),破坏余下字段。这六显性覆盖了ACK槽也成显性,自然发送方A会看到ACK=0以为有人ACK了? 实则CAN规定发送方看到显性ACK便以为帧有人收,但实际上是错误标志破坏了CRC,ACK阶段已经进入错误过程,所以A也检测到了位错误(因为ACK定界符本应隐性却见显性)。A也发送错误标志。总线上一串显性(可能更长叠加),然后8位隐性定界符。
- A、B都递增错误计数(B因CRC错接收失败加1REC,A因检测位错加8TEC),然后A计划重发该帧。
- 定界符结束后,由于错误标志相当于一个特殊帧,所以需要帧间隔(CAN要求error后也得有帧间隔)。之后总线空闲,A重新仲裁发送帧。若干次尝试后成功收到ACK说明B收到了,下次B计算CRC对上,不再报错。
如果B持续不行,A TEC会涨可能Bus Off,但B本身REC也涨变被动,主动标志后面由A的标志覆盖…最终A挂Bus Off停止发?那这样重要帧没发送成功,整个网络可能工作不正常但至少别的还能通信,因为A不发了。至此需要人工检查A节点了。
**小结:**MyCAN的错误处理几乎照搬CAN:谁发现错谁发错误标志、中止帧,发送方重试,累计错误超阈值则隔离节点。这套机制在CAN上经受考验几十年,被证明可以将网络因局部故障的冲击降至最低。MyCAN借鉴了它,自然能达到类似鲁棒性。
节点状态机设计:错误隔离与容错
上一节我们引入了错误主动/被动/总线关闭状态转换,这其实就是为每个MyCAN节点设计了一个错误状态机。理解并实现这个状态机,对于保证协议健壮性很关键。下面正式描述MyCAN节点的错误状态机,以及各状态下行为。
**状态定义:**和CAN一致,节点有三种主要状态:
- Error Active(错误活动):正常状态,TEC<128且REC<128。节点能参与总线通信,发送主动错误标志。
- Error Passive(错误被动):当TEC>=128或REC>=128时进入。节点仍可通信,但在发生错误时只能发送被动错误标志(不会主动干扰总线),并且在发送帧时需要延迟一个额外的悬停时间(Suspend Transmission)8位后才能重新争总线。后者是CAN规范要求,为给其它节点优先权。(即错误被动节点发送完一帧后,必须等3位间隔+额外8位再发新帧,避免频繁争用)。
- Bus Off(总线关闭):当TEC超过255时进入。节点此时被逻辑上断开总线,不再参与通信。必须经过软件干预复位,或者等规定时间(如128个11位隐性时间)后自动恢复(CAN中需要硬件计时或者要求软件显式复位)。
还有一个隐含状态:Error Passive Recovery:当节点在Error Passive后,错误计数降低到128以下,可以返回Error Active。CAN规定必须同时TEC<128和REC<128才恢复Active。Bus Off恢复则需要等待一段时间+清零计数,再进入Active。
MyCAN沿用这些阈值和条件:
- TEC或REC >= 128 -> Error Passive
- TEC >= 256 -> Bus Off(注意CAN是>255,即256值触发)
- Bus Off->Error Active 恢复条件:节点不发送,监测到128个空闲帧(11个隐性位)后,可以将状态设为Error Active且TEC=REC=0。这个可能需要应用介入。
- Error Passive->Active 恢复条件:当TEC<128且REC<128时,回到Active。CAN里面,如果因为发错而被动,那么需要足够正确报文接收才能降计数。一般通过连续正确通信实现。
**计数调整规则:**CAN的具体规则较繁琐,我们设计MyCAN可以精简或略调整,但仍遵循“出错的节点受罚更多”原则:
- 每发送帧成功(有ACK且无自检错误):TEC减1,最低减到0。若节点Bus Off不发帧则不变。
- 发送帧失败:
- 若因仲裁输,则不算真正错误,不加计数。
- 若发送方检测位错误、CRC错(即没ACK)等:TEC += 8。在CAN中,不同错误加分不一样(比如ACK错加8,位错加8,其他加4),我们可以简单全按8处理,重点是使TEC快速涨到阈值以隔离故障发送者。
- 发送方若接收到其他节点错误标志,也会加计数吗?其实如果别的节点发错误标志说明发送方出问题,也会加。Simplify: 只要发送方没收到ACK或听到错误标志,都按1次发送错误对待,TEC+8。
- 接收帧:
- 如果接收方检测错误并且发出错误标志:REC += 1。CAN规则是一处错误被探测会导致所有探测者计数+1。发送者更严厉。
- 如果接收方未发现错误但帧最终因为别的节点错误标志终止,则一般说明发送者检测到了错误,接收者可能也没拿到数据,但如果接收者本身没检测错就不加,自然发送者会处理。所以REC主要在接收主动报告错误时才加。
- 正常接收帧则REC减1,最低0。
- 特殊:ACK错误(发送者没ACK)应该算发送者错误,所以TEC+8。接收方没应答不算错,可能它没收到或本就不该收。
- 错误被动节点发送错误标志时不是显性而是隐性,会不会被别的节点识别? 别的Active节点仍会发显性。被动节点不主动标志,使得总线上显性个数可能不足6? CAN规定如果Active节点没检测错,那Passive节点也不会检测错(或即便检测因为Active没发标志,Passive看到持续收也不应发标志)。有点复杂,但Simulate: Passive发送者如果出错不会发显性错误标志,如果没有其他节点Active检测到错,那么错误帧不会中止——但如果发送者Passive itself检测位错,它又不能发显性标志…这其实CAN设计认为当一个Passive节点发送,它检测到错(比如没ACK),它不能发显性,只能记内部。但这个帧怎么办? 其实其他接收Active节点也肯定检测出CRC错之类发标志,所以Passive发送者也能听到标志。不深究,总之CAN构思下,不会出现全Passive错而无标志的情况,因为如果发送者Passive,它至少还有Active接收者检测错,或者发送者Passive自己也通过Kai? 这里不细了,trust CAN, implement same state transitions.
状态机的意义:通过Error Passive降低故障节点优先权,通过Bus Off隔离持续故障节点。最终实现“局部故障不过分干扰全局”。举例:若某节点发送器坏掉一直发乱七八糟数据,每帧都错误。它每次TEC+8,很快(32次左右)TEC>255 Bus Off。Bus Off后它不再驱动总线,其他节点恢复正常通信。即便它听到数据想发Ack也不会发(Bus Off禁止Ack? CAN Bus Off禁止一切活动)。所以这坏节点被等同拔掉了,对总线无影响。维修时可以更换或复位它。这样整个网络健壮性提高很多。
**状态机实现:**MyCAN节点需要在内部固件中维护TEC/REC并根据规则调整。并对状态阈值跨越时改变自身行为:
- Active -> Passive:一旦进入Passive,本节点在之后发送帧结束后需要等待额外8位才尝试下一帧;发生错误时不主动发标志,而是静默地等待Active节点发。即Passive节点仍接收但减少参与。
- Passive -> Bus Off:立即停止发送和接受(驱动电路关断,逻辑上忽略总线帧直到复位)。可以通过software flag让上层知道Bus Off。
- Bus Off -> Active:当等待时间到了(e.g. 128 * 11 bits = 1408 bits, ~2.8ms at 500kbps, CAN用时间倒计数 recovery),或软件命令Reset bus, 则将计数归零状态Active。
Error Passive下仲裁: CAN Passive节点在仲裁时发送显性0时也会驱动bus? passive状态仅影响错误标志行为和稍延迟重发,不影响仲裁逻辑(仲裁时Passive一样发显性0/1,只是在自己Frame结束后多等8位再发新Frame以让Active节点有机会先发)。MyCAN仿效:Passive节点仲裁不变,只是发送帧之间多等8隐性位。
Ack行为: Passive节点能Ack别人的帧吗? CAN Passive节点仍可ACK,只要收到无错误。因为Ack只是拉低一下,对总线影响很小,标准允许以提高消息交付率。MyCAN Passive节点也应Ack接收的帧。
总之,MyCAN错误状态机一环扣一环,确保错的节点逐渐沉默而其余节点可以继续。这就是所谓**“容错”**能力:哪怕一个节点挂了,也不让全网瘫痪。CAN总线的优良性能很大程度来自此。MyCAN有此保障,也可应用在严苛环境如汽车/工业。
可以用一个简单状态图表示:
图 6:MyCAN节点错误状态转换图(数值阈值与CAN2.0一致)。节点发生过多错误会降级为错误被动,严重则关闭总线,待恢复后才重新进入活动状态。
状态机如上:Active -> Passive -> Bus Off是一条降级路线,Bus Off -> Active是复位路线。Passive -> Active则是修复路线,表示错误情况好转又回归正常。
实现要点:我们需在节点协议控制器实现对每次帧结果增减计数,检查阈值切换状态并对发送/错误标志行为进行条件判断。比如:
- 若状态Bus Off,则不允许开始发送帧,对接收帧仅监视不应ACK。Bus Off硬件最好完全不参与总线(CAN控制器通常会三态驱动)。
- 若状态Passive,则在发送帧完成后插入8位1的延迟,然后才允许判空闲发送下一帧;发生错误不主动发错误标志而仅记录递增计数;但如果其它节点发了错误标志自己依然接收得到,也不加自己计数? Passive不主动说明很多错自己不报,但别人报了也解决,所以Passive节点计数可能降下来(因为没报错REC没增,但听到了错误还是帧丢失TxAck不到TEC会增? Passive发送失败TEC增8, Passive接收失败没发标志也不增REC? 这样Passive节点更难恢复,CAN的规则是Passive节点即使检测到错误因为没发显性,它还是知道错误存在,会不会增REC? Good Q: Actually spec says "An error passive node that detects an error counts up the error counters and sends passive flags if needed".So yes it still增加计数,只是flag不dominant。MyCAN按逻辑,错误检测->计数增->若为被动flag隐性而已。
- Error counters不超过255,保持uint8即可。Bus Off标志可以单独flag防止溢出。
总结:MyCAN节点错误状态机借鉴CAN,使MyCAN网络具备自我修复、自我隔离功能。不同于某些简单协议遇到持续错误会陷入死循环,MyCAN可以自动让故障节点下线,维持大局通讯。这对于安全相关系统非常关键。
结语
我们几乎从头到尾重现了通信协议设计的全过程:从基础原理(分层、帧格式、仲裁、错误处理、兼容扩展),到典型协议实例(CAN、SPI、UART、I²C、Ethernet)的结构剖析,再到结合实际场景择优选型,最终尝试设计一个完整协议(MyCAN)。整个过程体现出通信协议设计的一个核心理念:在特定应用需求约束下,综合权衡复杂度、性能与可靠性,寻求最优的折衷方案。
我们可以看到,没有哪种协议是完美的万能方案。像CAN那样强实时高可靠,却牺牲了带宽;以太网带宽惊人,但早期不擅长实时;SPI飞快却只适合短距少节点;I²C简单易用但速度有限……每种协议都是当时场景需要和工程条件妥协的产物。通信协议工程师的任务,就是明确需求,然后善用前人经验和巧妙创新,设计出满足需求的协议。
幸运的是,我们不必总是从零开始。正如本篇中多次提及的,许多成熟协议的思想值得借鉴甚至直接复用。例如我们设计MyCAN时,就直接复用了CAN的物理电气规范和位仲裁、错误机制等——因为这些已经被证明有效且高效。这说明通信协议设计中,标准化和兼容性的重要性:遵循标准不仅减少设计工作量,还能提高互操作性和普适性。
在当今“万物互联”的时代,新场景新应用层出不穷,或许有时候会遇到必须定制通信协议的时候。最后,总结几点设计通信协议的指导原则,以供日后快速参考:
- 清晰简洁:协议结构要尽量简单明了,字段定义和含义明确。避免不必要的复杂度,否则实现和调试成本都会上涨。
- 分层解耦:将功能划分层次,各层职责单一。这样方便替换底层媒介或拓展高层功能,增强协议灵活性。
- 有效利用带宽:帧格式设计中,控制开销应尽可能小而必要信息齐全。同时采取适当编码、填充以利于时钟同步和误码检测。
- 仲裁与调度:多点协议必须考虑总线访问机制。根据应用需求选择固定主控(简易但单点瓶颈)或竞争仲裁(复杂但灵活)或时间片等方案,确保不会发生无法解决的冲突。
- 鲁棒性:提供充分的错误检测手段(CRC、监测、自校验等)。设计错误恢复策略,如重传、确认、故障隔离等。宁可传慢一点,也要传对传稳。
- 扩展规划:协议最好留有余量。预留保留位、版本号,或采用灵活长度字段,以便未来增加功能时老实现仍能忽略新元素正常工作。一个设计优秀的协议往往可持续演进很多年而无需推倒重来。
- 场景贴合:不同场景注重点不同,不存在“一招鲜”的协议。务必结合具体使用环境来取舍。例如高噪环境重可靠,节点多则重寻址和仲裁,资源受限则重简洁。没有最好,只有最合适。
- 充分测试:最后,设计只是开始,再好的协议也需要在各种边界条件下测试验证,包括不同负载、错误注入、兼容性等。通过仿真和实际实验,不断迭代完善协议细节,才能真正称得上一个健壮的协议。
通信协议是连接系统各部分的桥梁,也是保障信息畅通的基石。
