十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CAN自定义协议设计指南:从帧ID到波特率的完整实践

CAN自定义协议设计指南:从帧ID到波特率的完整实践 1. 协议设计先想清楚从裸报文到“业务语言”前阵子有朋友问我他手里的CAN节点已经能正常收发报文了波特率、终端电阻、收发器都没问题可一旦把两个以上设备挂在总线上数据就乱套。A发出去的电压值B读出来是错的偶尔想给某个节点单独下发配置其他节点也一起响应。他问我是不是滤波器设置不对我说你先别急着调滤波器你缺的是一个真正属于自己的自定义协议。这个问题太典型了。很多人把CAN通信理解成“能发能收就算通”但实际上CAN只是给你提供了一条最底层的、带仲裁能力的报文搬运管道。它能保证的是你塞进去的8个字节只要通过了CRC校验对端就能原样拿到。但它完全不负责解释这8个字节是什么意思、该发给谁、对方没收到怎么办、收到的是不是最新数据。这些全是协议层的事。所以“CAN自定义协议如何设计”本质上是回答这么一个问题如何在CAN这8字节小包上建立一套适合你自己业务的语言规则。这套规则要覆盖数据格式、节点寻址、错误发现、时序约定甚至要考虑到总线仲裁和时钟误差这些物理层约束。这篇文章会把自定义协议从0到1完整拆一遍包括帧ID怎么规划、8字节怎么布局、校验和重传怎么做、位时序误差怎么算、工程调试中有哪些坑。适合正在做车载、工业控制、机器人、仪器仪表里CAN通信开发的工程师也适合刚入门想搞懂CAN协议本质的嵌入式学习者。2. 逐字段打磨CAN自定义协议的具体设计要点2.1 帧ID规划优先级、过滤和路由本来就是一件事很多人设计协议时第一个纠结的就是CAN ID怎么分配。这个纠结非常值得因为ID在CAN协议里身兼数职它决定报文优先级、决定接收方如何过滤、在工程上还承担路由表的作用。先说优先级。标准帧的ID是11位扩展帧是29位。ID数值越小仲裁时优先级越高。数据类报文一般要给一个比较低的ID控制类报文优先级中等故障类和安全类报文必须给最高优先级。比如我做电机控制器的时候转速、电流这类周期性数据帧ID我习惯定义在0x200~0x2FF段控制指令帧放到0x100~0x1FF段急停和故障帧直接压到0x001~0x00F。这样哪怕总线上数据帧疯狂抢占急停帧也能在几个微秒内抢到总线。再说过滤和路由。CAN控制器的硬件过滤器按ID段匹配所以你的ID规划必须考虑“能否用掩码把一组相关报文框进来”。我习惯把ID拆成两段高几位当“报文类型/源地址”低几位当“目标地址”。比如用标准帧11位可以这样分bit10~bit8表示优先级和类型bit7~bit3表示源节点地址bit2~bit0表示目标节点地址或服务类型。这样接收节点只要配置一个掩码就能把所有发给自己的帧收进来其他帧直接硬件过滤掉节约CPU时间。还有一个经常被忽略的细节同一条总线上尽量让周期性报文的ID不要重叠更要保证任何两个帧的ID不冲突。因为CAN仲裁不是“谁先发谁赢”而是“谁ID小谁赢”。两帧同时发送时ID小的会毫发无损地继续传输ID大的那帧直接被总线“干掉”下次重发。如果两帧ID完全相同那就会发生位错误直接报错帧。设计列表时建议列一张完整的ID分配表宁可留空段不要强行填满。2.2 用满8字节数据字段布局与字节序CAN数据场的长度是0到8字节CAN FD最长64字节经典CAN下绝大多数协议都是采用8字节定长的标准格式。为什么要定长因为8字节定长让接收方处理逻辑最简单——一个周期信号到了直接按偏移取数不需要判断“这帧里面放几个字段”。字段布局的第一原则是把最核心、最需要实时性的数据放在前面。CAN的数据传输是从第0字节到第7字节按顺序发的虽然在一帧内部延迟差只有几十微秒但对于同步要求极高的多轴协调控制前排字段的相位差就是要更小一点。第二原则是一个物理量尽量不要跨字节边界存储时发生混乱。比如一个16位电压值占用两个字节你必须在协议文档里明确规定是大端还是小端。这里我强烈建议车载和工业场景统一用大端高字节在前。原因是CAN报文在总线上的字节顺序是从第0字节开始逐字节传输而且很多CAN分析工具展示报文时习惯把第0字节放在最左边。你打开CANalyzer或者周立功的上位机看一段十六进制报文左起第一字节就是byte0用大端解析时uint16就是把byte0左移8位或上byte1。这样人工排查报文时非常直观。第三原则预留字段一定要有明确填充值。那些暂时没定义的位发送方必须填0接收方不要对它们做任何业务判断只做保留。这一点看似简单实际上很多协议后期扩展出问题都是因为预留位被当成“空闲位”随意填了垃圾数据导致新旧版本设备不兼容。第四原则是DLC。如果只有两个字节的有效数据DLC到底是写2还是8我建议除非总线负载率极其紧张一律写8。因为CAN帧无论数据字段是1字节还是8字节帧头、CRC、ACK这些开销是固定的。在经典CAN里数据长度为8的帧也就比长度为1的帧多了大约13个位的传输时间。相比这点时间开销固定DLC带来的解析简化完全值得。你不需要在接收端写“若DLC2则取字节0~1若DLC5则取字节0~4”这样的分支逻辑直接把8字节全部接收未定义的字节当保留字节处理即可。2.3 填充、计数与校验不显眼的字段决定稳定性在协议里留出“帧计数器”和“校验字段”很多人觉得没必要。我一开始做项目时也觉得CAN帧本身有CRC15校验硬件都帮你查了错误软件层再搞校验不是多余吗踩过坑之后才明白这完全是两码事。CAN物理层的CRC15校验解决的是“传输过程中位被干扰”的问题。它不解决“发送的是旧数据”和“字节顺序对但业务逻辑错”的问题。比如传感器节点每隔100ms发一次温度数据第5帧发的是50℃这个值主控已经处理了接着第6帧因为某种原因没发出来第7帧才把最新的52℃发出来从CAN底层看这两帧都是合法的、CRC都通过主控无法区分这到底是不是最新数据。这时候帧计数器就有用了。约定每发一帧计数器加1接收方发现计数值跳变就能知道“中间丢了一帧”从而触发重发请求或同步刷新策略。校验字段方面推荐使用累加和、CRC8或CRC16。经典CAN一帧最多8字节留给校验的空间不大一般用一个字节做校验。我常用的做法是字节0放帧头和命令字节1放帧计数字节2~6放业务数据字节7放前面7个字节的异或和或CRC8。用异或和很简单代码量少适合MCU资源紧张的项目CRC8抗突发错误能力更强适合对数据可靠性要求高的场合。如果项目里数据长度超过8字节那就是多帧传输问题要引入序列号、总帧数、帧序号、包类型首包/续包复杂度上一个台阶后面会专门讲。校验字段还有一个容易被忽略的作用它帮你区分“这帧数据到底是新发的还是上一帧的重复”。在总线上一模一样的两帧数据如果连校验值都一样接收方很难识别这是新数据。加入帧计数后这个问题迎刃而解。2.4 超时与重复策略把“不通信”也算进协议协议设计到最后很多人会忽略一条最关键的原则协议的最终目的不是“让所有报文都通”而是“让系统在异常情况下可预期地降级”。车身控制器和发动机控制器之间有100个信号其中98个是周期性发送的2个是事件触发的。协议文档写得很清楚每个信号的意义但你没写“如果收不到这个周期帧该怎么办”。结果就是发动机转速这个核心信号一丢失整车控制器还在傻乎乎地等下一帧期间输出转矩指令毫无变化最后驾驶体验直接拉垮。设计协议时每个周期类报文都要定义一个“超时阈值”和“超时动作”。比如某个转速帧约定每10ms发一次超时阈值设为50ms连续丢5帧判超时超时后主控要进入“转速信号无效”逻辑转速值标记为无效同时把默认的转矩指令降到安全值。这个超时时间不能太短因为CAN总线在高负载时会偶尔丢帧连续丢一两帧就触发降级误报率太高也不能太长否则系统反应太迟钝。我一般取发送周期的5到10倍。事件类报文比如故障报警、按钮按下这类报文要约定“重复发送次数和间隔”。如果故障节点只发一次故障帧接收方万一没收到故障就无声无息地消失了。正确的做法是事件触发后按20ms间隔连发3次接收方收到第一帧就响应后两帧用于确认和冗余。接收方还要在一个固定时间窗口内没有收到任何跟某个节点相关的帧时判定该节点离线向应用层上报“节点失联”。3. 时间参数与仲裁把物理层限制变成设计约束3.1 位时序和采样点为什么波特率越高越要抠时钟误差自定义协议不只是“数据怎么组织”你定义完字段后要面对的总线物理层问题一点不比数据层少。最典型的就是波特率和时钟误差的博弈。每个CAN节点里都有一个晶振或时钟源用来产生CAN控制器的位时序。比如你配置500kbps波特率总线速度为每比特2微秒控制器会把2微秒拆成若干个时间段TQ分别用于同步段、传播段、相位缓冲段1和相位缓冲段2。采样点就落在这几个段的交界处。理想状态下所有节点的采样点应该在同一个位置但实际晶振都有误差普通晶振误差一般是±50ppm到±100ppm便宜的陶瓷谐振器甚至到±0.3%。当总线上有100个节点每个节点自己的位时间都有一点偏差经过一段时间累积采样点就会慢慢漂移。如果某个节点采样点正好落在位边沿附近它就可能采到错误电平然后报CRC错误或填充错误甚至进入总线关闭状态。这也是为什么“波特率越高越要关注时钟误差”。500kbps时一个位只有2μs相位缓冲段总共也就1μs左右到了1Mbps一个位只有1μs采样点的容差空间直接减半。在这种高速率下如果总线两端节点的时钟误差一个正一个负最高支持的总线长度会急剧缩短。设计自定义协议时必须根据实际晶振精度来设定采样点位置。业内经典的推荐是采样点位于位时间的75%~87.5%之间常规配置选80%可靠性优先的场合选87.5%。具体计算方式假设总线波特率500kbpsAPB1外设时钟36MHz那么一个位时间需要的TQ数就是72采样点80%意味着采样点位于第58个TQ处。如果采样点设得太靠前比如55%总线上信号反射和跳变毛刺还没稳定就容易误采如果太靠后留给相位缓冲段2的时间不够重同步能力会变弱。3.2 重同步机制用同步段换稳定性你可能会问既然晶振都有误差CAN又是怎么做到100个节点还能稳定通信的这就得说到CAN的硬件重同步机制。CAN每个节点在接收总线数据时会不停地把本地的位时间与接收到的位边沿做比较。如果发现本地位时间慢了总线的边沿比自己预期的来得早就把相位缓冲段1拉长一点如果发现本地位时间快了就把相位缓冲段2拉长一点。这样每一帧传输过程中每个节点的时钟都在被源源不断地“校准”。这个机制的关键在于同步跳转宽度SJW它规定了单次重同步最多能调整多少个TQ。SJW设得越大节点能容忍的时钟偏差越大但也意味着抗干扰能力下降因为在噪声环境下它会把毛刺误当成有效边沿去重同步反而加剧位错误。一般SJW设为1到4个TQ常用2。理解这个机制对协议设计的启示是你在自定义协议里约定波特率和采样点后必须把SJW等参数同步下发给所有节点不能只配波特率而忽略SJW。我在调试中见过太多次“明明波特率一致但两个不同厂家的设备死活通不上”的情况最后排查发现是一个SJW配了1另一个配了4两者在总线边沿抖动时行为差异太大。协议驱动开发时建议把位的各项参数TQ总数、采样点位置、SJW写进移植文档里别只写波特率。3.3 仲裁机制优先级不是“想设几就设几”CAN的自定义协议还有一个经常被低估的部分就是仲裁机制对消息实时性的影响。CAN的仲裁遵循“显性位优先”原则就是说逻辑0显性能覆盖逻辑1隐性。这也是为什么ID越小优先级越高。但很多人不知道的是数据帧和远程帧也有仲裁优先级区别而且数据帧优先于远程帧。另外标准帧和扩展帧混用时标准帧的ID在仲裁时占优。设计协议时帧优先级分配的功力体现在“高优先级报文的ID和低优先级报文的ID要有足够间隔”。如果一个急停帧的ID是0x010而一个普通数据帧的ID是0x011两者虽然差了1但在总线上同时竞争时急停帧只赢一个bit。实际传输中这两位ID的差异意味着仲裁窗口极短对接收方才说几乎无法在ID段就看出差别需要等到整个仲裁段结束后才能识别高优先级帧。但如果两者的ID差得足够大比如0x001和0x200从第一个bit开始就能立即区分。另外CAN的仲裁还直接影响协议里的“应答”和“重传”设计。两个节点同时发数据低优先级帧会在仲裁段输掉后自动重发这是硬件行为协议层不需要管。但你要知道一个设计不当的自定义协议可能导致高负载下的“优先级反转”——一个中等优先级的周期帧频繁重发把高优先级帧的时延拉长。解决思路是尽量让周期帧和时间关键帧的ID段错开同时控制总线上周期帧的数量这一点在下一节详说。3.4 帧间隔与负载率协议吞吐量的隐性天花板CAN总线的吞吐量上限不只是“波特率除以一帧总比特数”这么简单。每个节点在发送下一帧之前必须等待帧间隔和应答/错误处理。这些开销虽然不是协议里的显式字段但它们决定了一条总线上实际能跑多少帧。以经典CAN标准帧为例一帧完整的数据帧数据长度8字节大约要占110个位包括SOF、仲裁段、控制段、数据段、CRC段、ACK段、EOF、IFS。500kbps下理论最快帧率约4500帧/秒。但这是理论极限实际设计协议时总线负载率最好控制在30%~50%以下。负载率超过70%以后仲裁碰撞急剧增加高优先级帧的平均时延会变得很不稳定低优先级帧可能一连几个周期都发不出去。比如你要设计一个底盘域控制器的协议8个传感器每个周期10ms发一帧每帧128位总线上负载率大致是8×128位/10ms÷500kbps≈20.5%看起来不高。但如果你再加两个100ms一次的诊断大包每包4帧共32字节、一个事件型故障帧偶发但一次连发3帧负载率会涨到30%左右这还在安全范围内。如果继续加节点或者缩短周期就要重新核算了。还有一个常常被忽略的“时延抖动”指标。协议设计时不仅要考虑平均时延还要考虑最坏时延。比如一个高优先级帧在最坏情况下要等当前低优先级帧传完最多约130位、再等紧接着的另一个低优先级帧开始传输所以最坏等待时间是两个低优先级帧的传输时间总和。如果低优先级帧还带重传机制最坏时延会进一步拉长。这个数字要作为协议设计里的“实时性预算”写进需求文档。4. 帧格式与多帧传输一个可复用的实际模板4.1 单帧协议的经典布局示例这里给出一个我在多个项目里用过的经典8字节单帧模板你可以直接参考甚至拿来改字节位置 | 字段名 | 长度 | 说明 0 | Frame Type | 4bit | 0x1普通数据帧0x2控制帧0x3故障帧0x4配置帧 0 | Reserved | 4bit | 预留填0 1 | Message ID | 8bit | 应用层消息ID用于区分这个帧承载的是电压/电流/温度等 2 | Sequence/Frame Counter | 8bit | 帧计数每发一次累加1 3~6 | Data Field | 32bit | 业务数据区按需要在里面拆出多个子字段 7 | Checksum | 8bit | 字节0~6的异或和或CRC8这里把Message ID和应用层指令放到了数据场里而不是用CAN ID去映射每一种信号。这样设计的好处是CAN ID只承担分配和过滤功能同一帧可以承载不同业务数据协议扩展时不需要重新规划ID段。坏处是多占了一个字节且接收方要软件过滤一次Message ID会增加几微秒CPU开销。如果你的MCU处理能力很强总线类型多这种软件路由的方式灵活得多。对于更简单的场景可以直接把8字节全留给业务数据校验单独放在CAN控制器的CRC里帧计数也不做此时协议确实更轻量但代价是丢帧检测、乱序恢复能力都很弱。我一般只在这种场景下使用总线固定为点对点一个主机一个从机数据周期固定并且主机对从机有极强的实时状态监控。4.2 数据字段怎么拆位域还是字节对齐前面提到数据区是32bit那里面怎么放多个物理量两个方案位域紧凑打包和字节对齐。位域打包就是把一个12位的电压值、一个10位的电流值、一个8位的温度值紧挨着放总32位121082预留。优点是节省字节缺点是可读性差、多平台移植时要小心位域的内存布局跟编译器相关大端小端、分配顺序都可能不一样。字节对齐就是不管物理量大小每个量都从字节边界开始放。比如uint16电压占2字节、uint16电流占2字节、uint8温度占1字节、uint8状态占1字节总共6字节还有2字节预留。启示就是解析代码很好写不容易出错代价是数据密度低。我个人强烈推荐在MCU资源充足的情况下优先用字节对齐。省下的那几个位对CAN总线来说几乎不构成吞吐量压力一帧最多省最多几个字节但带来的代码简洁性和跨端一致性价值远高于那点带宽。如果实在要做位域建议用uint8_t数组位移宏来手动拼位不用C语言内置的bit-field语法避免编译器差异。4.3 多帧传输分包、重组与超时处理当单次业务数据超过8字节就要用到CAN多帧传输。最常见的一个场景是OTA固件升级一包固件可能有几KB甚至几MB。多帧传输方案有很多种这里介绍最简单实用的MTU总长度方案。发送方先把整个数据块拆成若干8字节单元最后一包不足8字节的做填充。协议格式建议用固定10字节首包前两字节是总长度表示整个数据块有多少字节后面6字节存放第一个数据分片从第二包开始全部是纯数据分片每个分片8字节最后一包用DLC指明实际字节数。接收方要做三件事解析总长度、申请缓冲或直接写入存储区、校验“分片序号是否连续”。连续序号可以用CAN ID的低字节放序号也可以用数据首字节放序号我习惯用后者因为这样CAN ID完全保留给过滤和路由。多帧传输里最麻烦的是“中途丢包”和“接收缓冲区溢出”。处理策略接收方如果发现序号跳变放弃整包并回发一个“请求重传”的控制帧发送方收到后重新发全包。这个策略很粗暴但绝对有效缺点是浪费带宽但考虑到多帧传输本来就用于非实时场景完全能接受。超时方面约定“整个多帧传输过程最长300ms每两个分片间隔最长50ms”。如果超时接收方丢弃已收数据恢复接收空闲状态。这个超时时间要根据总线上其他流量来定不能太短否则重传频繁也不能太长否则接收缓冲区一直占着影响其他业务。5. 总线时序与波形诊断从设计文档到真机验证5.1 波形测量眼见为实协议设计得再完备不在示波器上看到真实波形就等于纸上谈兵。我每次调CAN自定义协议第一步不是跑协议代码而是拿示波器或带波形分析功能的CAN分析仪看总线上的实际电平。CAN总线有两条线CANH和CANL差分电压在隐性时约为0VCANH和CANL都在2.5V附近显性时CANH抬到3.5V、CANL降到1.5V差约2V。用示波器同时抓CANH和CANL能直接看到每一位的电平变化和边沿位置。把波形放大到单bit级别你能清楚地看到采样点是否落在正确位置以及是否有信号反射导致电平振铃。如果波形上出现不稳定的毛刺或者边沿抖动明显你要意识到这不是“协议层”能解决的问题而是物理层问题。常见原因包括终端电阻接的不对应该总线两端各一个120Ω、分支线路过长、地电位差过大、线缆类型不对等。在自定义协议设计阶段就先把物理层调干净后面跑逻辑会省一半时间。5.2 用分析仪验证协议字段工程上我习惯用一个支持“自定义协议解析”的CAN分析仪CANalyzer、周立功CANTest、野火的分析仪、开源的BUSMASTER都行把我设计的DBC文件或者自定义解析脚本加载进去然后把真实的报文按字段解析成物理量。这一步能同时验证两件事一是发送端的字节序和位域填充是否符合设计二是接收端按同样规则解析后能否还原出发送端原始值。我见过不少协议翻车案例都是“发送端把高低字节写反了但测试时只看接收缓冲区的hex值没做物理量比对”结果两个人都觉得没问题。用解析后的物理量比对当场就能发现大小端、缩放系数、偏移量的问题。5.3 一致性测试不只测功能还要测异常自定义协议上线前除了测正常收发我强烈建议跑一轮侧重异常场景的一致性测试。测试用例不用很复杂但覆盖面要够第一个用例是“错误帧注入”。在总线上故意制造一个位错误很多分析仪支持主动发送错误帧观察协议栈能否正确识别并恢复。正确表现是错误帧不导致整个节点失去同步节点在几个帧周期内能恢复正常通信。第二个用例是“冷启动时序”。把总线上所有节点同时上电或者随机错开上电观察协议是否还能按设计的优先级和时序正常运行。CAN本身支持在总线上热插拔添加节点但很多自定义协议里的初始化握手流程在“先上电的节点等后上电节点”时会卡住。设计时一定要约定任何节点在单独上电时不依赖其他节点回应也能进入待机状态而不是死等初始化握手完成。第三个用例是“干扰环境下的鲁棒性”。在实验室里用CAN干扰仪或用示波器注入毛刺信号观察协议的错误恢复机制是否有效。这一项对车载、工业现场尤其重要因为现场电磁环境远比实验室恶劣。5.4 精选问题排查两个常见但隐蔽的坑我在多个CAN项目里遇到过同一个坑所有节点的波特率配置看起来一样分析仪也能正常通信但两台设备直连时就是会周期性报错。最后排查发现这两个节点的采样点一个设置在65%一个设置在85%波特率一致但采样点差异太大导致在边沿附近抖动时两者对位电平的判断不一致于是频繁产生位错误。解决办法是统一约束所有节点的采样点配置最好在同一份配置向导里下发。另一个坑是“终端电阻和级联影响”。有些设备内置了120Ω终端电阻你又在总线上外接了两个120Ω结果等效阻抗变成40Ω收发器驱动能力跟不上总线电平振幅变小远端节点采样点误判。做自定义协议时一定要在硬件设计手册里明确总线上的节点哪些默认打开内置终端电阻、哪些需要外接、哪些是中间节点。这些看似不是协议问题但它直接影响协议能否稳定运行我宁可在这里啰嗦也要讲一遍。6. 工具、参考协议和移植落地建议做CAN自定义协议设计时手头趁手的工具能极大提升效率。最基础的是CAN分析仪推荐至少有一台带记录功能的分析仪能把整车或整条产线的报文录下来回放这在分析偶发错误帧时等于有了一台“黑匣子”。其次是示波器模拟示波器就够了带宽50MHz以上能抓到位级波形。软件工具方面周立功的CANTest和ZCANPRO、Vector的CANalyzer贵但功能全、开源的BUSMASTER都可以备着。自定义协议并不是从零发明。行业里已经沉淀了几套成熟协议比如CANopen基于对象字典和PDO/SDO侧重于设备描述和网络管理、J1939侧重于重型车辆的动力总成和部件通信、UDS/ISO 14229侧重于诊断。如果你的项目能直接套这些协议就别自己设计省时省力。但如果项目需求比较特殊比如大量私有传感器数据、简单的主从控制、小批量产品自定义协议反而更灵活。我的经验是协议栈的扩展性和可读性完全靠文档和代码注释跟选不选自定义协议没有必然关系。哪怕选CANopen你也要针对PDO映射、对象字典条目做大量自定义工作量并不小。移植自定义协议时建议把协议解析逻辑做成平台无关的C文件不直接依赖具体MCU的CAN寄存器。所有对外接口精简为初始化、发送帧、接收帧、超时处理、错误回调。这样以后换MCU、换CAN控制器只需要把底层接口重新实现一遍上层业务逻辑完全不动。顺便提一句很多CAN控制器硬件自带多个邮箱和过滤器你要根据同一时间可能接收多少种不同类型的帧来合理分配邮箱防止“邮箱被占满导致新帧丢失”。这在多帧传输时尤其明显——建议至少预留一个高优先级邮箱给故障帧这是我在项目里踩过的一次教训。最后再说一个容易被忽视的细节协议文档里要包含一条“版本号”。在协议首包的某个固定位置放协议版本字节比如V1.0填0x10。这样当新旧版本设备混装在同一总线上时双方能通过版本号识别兼容性而不是等到通信失败才去抓瞎。我在模块化项目中吃过亏升级了A模块的协议忘了同步B模块的解析代码结果整条产线全部报错排查了一个下午最后发现就是协议版本不匹配。CAN自定义协议设计最重要的不是代码怎么写而是你在写代码之前有没有把数据布局、ID规划、时序约束、异常处理全部想清楚。只要你把上面这些点都考虑到位了剩下的工作其实就是在草稿纸上画一张表格然后照着填空编码。希望这篇文章能帮你把协议设计的第一版做扎实。
返回列表