本站为个人非经营性网站,仅用于技术学习与分享,不提供任何商品、服务、报价或收费咨询。 合规信息

多板卡系统的通信分层:
什么时候用以太网,什么时候用 RS485

同一套多板卡控制系统里,人机面板和主控之间走以太网,主控和上位机之间走以太网长连接, 主控和现场执行机构之间走 RS485,采集设备的整机数据还要经无线上云。 这不是“技术栈不统一”,而是每一层要解决的约束不同。这篇笔记把选型的判据摊开成一张五维表, 并说明我在两个项目里为什么做出了相反的传输层选择。

通信分层 以太网 RS485 UDP / TCP 已发布

一、一个系统里为什么会有三种总线

我做过的一套多板卡控制系统,物理上大概是这样:一块人机输入面板,一块控制主控板, 一台上位机/显控机,下面挂着一串现场执行机构——几台驱动板、几路断路器、一组光源。 这套东西里同时存在三种通信方式:面板与主控之间走以太网(小包、高频、双向), 主控与上位机之间走以太网长连接(整包状态上报 + 命令下行), 主控与执行机构之间走 RS485 上的 Modbus RTU(一主多从、轮询)。 另一个采集类系统里也是类似的分层:主控向上走以太网并外接蜂窝模块上云, 向下用两路 RS485 轮询采集单元与执行单元。

三种总线不是设计出来的,是被约束逼出来的。把每一层的需求摊开看就很清楚:

这一层数据特征拓扑真正的约束
人机输入 → 主控 每包几十字节,周期很短,丢一帧下一帧就补上 点对点 延迟要低,不能因为一次丢包把后面的新数据堵住
主控 → 上位机 整包状态,字段多,缺一个就不完整 点对点 要完整、要有顺序,还要能判断链路是不是还活着
主控 → 现场执行机构 每台几个寄存器,一问一答 一主多从,一根线挂一串 节点多、单主机、距离远、必须确定性;执行机构本身只支持这种接口
采集设备 → 云端 周期上传一组状态量,字段会随需求增加 点对点,无自建线路 现场没有有线通道;字段要能前后兼容地增删

这张表里最值得记住的是最后一列。每一种总线的存在都应该有它唯一要解决的那个约束; 一旦发现某种总线“只是为了统一而存在”,那它多半是多余的,而且会长期贡献维护成本。 反过来,如果有人问“为什么不全部用以太网”,正确的回答不是“RS485 更便宜”, 而是“现场那些执行机构只有 RS485 接口,而这恰好也是那一段最合适的拓扑”。 关于这两套系统的具体形态,我在 多板卡以太网控制实验记录 与 电池管理单元实验记录 里写过更多细节。

自上而下四层:上位机、以太网、主控、RS485 现场设备,右侧另挂一条无线上云支路,每条链路标注协议类型
图 1 · 一个系统的分层通信拓扑图

二、五维决策表:带宽 / 主机数 / 距离 / 成本 / 实时性

选总线的时候,我习惯按五个维度逐个过一遍。这张表不是“标准答案”, 而是把“什么样的需求该往哪条路走”写清楚,免得每次都凭感觉:

维度什么需求该走以太网什么需求该留 RS485最容易搞错的地方
带宽 单次交互要传几十到上千字节(整包状态、日志、固件),或者要传文件 每次只读写十几个到几十个寄存器 按平均值算带宽。应该按最坏一轮算:节点数 × 单次字节数 ÷ 轮询周期
主机数 / 拓扑 多主机、点对点双向并发,节点之间还要互相通信 单主机轮询多从站,从站之间不通信 RS485 是半双工共享介质,物理上不支持多主机;“两个主站”只能靠软件严格分时
距离 机柜内、机房内,几米到几十米,可以加交换设备 跨设备、跨桥架,几十米到上千米 只算“线有多长”。真正的约束是共模干扰有多强、两端能不能共地
成本 节点需要较高带宽时才划算 每加一个节点只要一个收发器,一根双绞线挂一串 只算物料成本,不算配置与维护成本(地址规划、交换端口、网管、IP 台账)
实时性 平均延迟低,适合“快但可以偶尔慢一点”的交互 单次慢,但一问一答、时序可算,确定性好 把“低延迟”和“确定性”当成一回事。控制类系统要的往往是后者

这五维里,“实时性”是最容易被说错的一维。以太网的平均延迟通常远低于 RS485 (两者的速率差着数量级),但它的最坏延迟受交换、协议栈、上层流量共同影响, 很难给出一个可证明的上界;RS485 单次交互慢,可只要轮询表定下来, 最坏周期是可以手算出来的。所以“哪个更快”这个问题本身没有意义, 真正要问的是“最坏情况下晚多久,你能不能接受”。 这也是我在 《Modbus 主机的轮询调度与离线降级》 里专门用符号把最坏周期算一遍的原因。

还有一条经验:这五个维度不是平权的。实际项目里通常有一到两个维度是硬约束—— 比如“现场设备只支持 RS485”或者“这段距离必须走差分”,剩下的维度只是在硬约束划定的范围里做微调。 先找到硬约束,再谈优化,比一开始就把五个维度摆在一起权衡要快得多。

五个轴:带宽、可挂节点数、传输距离、成本、实时性,把以太网、RS485、无线三种方式各画一条线,直观体现各自优势区间
图 2 · 五维选型雷达图

三、板间用 UDP 还是 TCP

这一节我想说一件看起来自相矛盾的事:我在两个项目里做出了相反的选择。

一个项目里,板间通信用的是 UDP 承载的小包(把 Modbus 的帧结构套在 UDP 上, 两端都是我自己写的),同时“整机状态上报给上位机”用的是 TCP 长连接。 另一个项目里,我把 UDP 整个关掉了——在协议栈配置里直接不编译 UDP,只保留 TCP, 上位机通过 TCP 读写变量表。

这不是前后矛盾,而是在衡量不同的东西:可靠优先,还是延迟优先。

UDP 那一侧的理由:板间那一层是“高频小包 + 状态量”,丢一帧的后果只是这一轮的值没更新, 很短的时间之后下一轮就带着新值来了。UDP 没有连接、没有重传, 也就不会出现“一次丢包导致后面的数据排队等重传”的队头阻塞。 反过来,如果这里用 TCP,一旦网络抖动触发重传,所有新数据都要排在旧数据后面, 而这一层要的语义恰恰是“我只要最新值,旧值没到也无所谓”。

TCP 那一侧的理由:那个系统里这一层的交互是“上位机读写变量表”, 每一次读写的语义都是“我要一个确定的答案”,不能丢、不能乱序; 而且网络环境不完全可控(要过网关、可能跨网段), UDP 在这种环境下的可达性问题会变成一个很难复现的现场问题。 TCP 的连接状态还有一个额外作用:它本身就是一个“对端还在不在”的信号。

问自己倾向 UDP倾向 TCP
丢一帧能不能接受?能,下一帧就带新值来了不能,缺一个就错
能不能接受队头阻塞?不能,我要的永远是最新值能,顺序与完整性更重要
需要“连接”本身当在线信号吗?不需要,我自己有心跳机制需要,连接断了就是离线
网络环境可控吗?可控:直连或同一交换网,两端都是自己写的不完全可控:过网关、跨网段、现场接线情况未知
节点数量与拓扑?节点较多,甚至需要一对多发送少数点对点连接
/* 两种传输的调用形状对比:地址与端口一律来自配置参数,不写死在代码里 */
/* --- 无连接:绑定本地地址,每次收发都要带上对端 --- */
int ufd = socket(AF_INET, SOCK_DGRAM, 0);
bind(ufd, 本地地址);
recvfrom(ufd, buf, sizeof buf, 0, (struct sockaddr *)&peer, &peerlen);
sendto(ufd, buf, len, 0, (struct sockaddr *)&peer, peerlen);

/* --- 有连接:建立之后就像读写文件一样收发 --- */
int tfd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(tfd, SOL_SOCKET, SO_REUSEADDR, ...);  /* 便于异常退出后立刻重绑 */
bind(tfd, 本地地址);
connect(tfd, 对端地址);
n = recv(tfd, buf, sizeof buf, 0);  /* 返回 0 表示对端已正常关闭 */

顺带说一句“把 UDP 整个关掉”这件事有多彻底:它不是“不发 UDP 报文”, 而是在协议栈的配置里把这个传输层整个排除掉。

/* 只保留 TCP:在协议栈的配置头文件里关掉不需要的传输层(LwIP 公开配置项名) */
#define LWIP_UDP   0
#define LWIP_TCP   1

LwIP 是第三方开源协议栈(BSD 许可),上面两行只是它公开的配置项名称, 用来说明“连传输层都可以按需裁掉”这件事。这么做的直接好处是省下了 UDP 那部分代码空间与内存, 间接好处是把“这一层只用 TCP”变成了一个编译期事实—— 不会有人后来顺手加一个 UDP 接口上去,让系统悄悄多出一种需要维护的通信路径。

TCP 那一侧也不是没有代价,有两个坑我踩过:

  1. 对端异常断开时连接可能不会立刻被察觉。拔网线、对端掉电, 都可能留下一个“本地以为还在连、其实对端已经没了”的半开连接,连接资源不释放, 现象是“重新接入连不上”。两种处理方式:给接收设一个超时,超时就主动把连接踢掉重新等待接入; 或者在主动关闭时用强制复位的方式关闭,避免连接长时间停在半关闭状态。
  2. 重连必须有间隔。一断开就立刻重连,会在对端还没恢复的时候 把连接请求打成一片,反而更难恢复。我在几个项目里用的都是秒级的固定间隔—— 工业设备里“宁可慢一点,也别把链路打满”是个常见的取舍。

这两个坑和“在线 / 离线”状态管理是同一件事的两面。我在 《LwIP 上的 UDP 通信笔记》 里写过板间小包交互的实现要点,在那篇里也提过:无连接不等于不用管状态, 只是状态由应用层自己维护而已。

四、现场总线为什么还是 RS485

经常有人问:既然以太网更快更通用,为什么下面的执行机构还在用 RS485? 我的理由按重要性排是这五条:

  1. 一根线挂一串。现场每台设备一个地址,主机按地址轮询。 增加一台设备就是“多接一段线、多分配一个地址”,不需要交换端口、不需要网线、不需要地址规划台账。 这在现场施工上的差别非常大——尤其是设备分散安装、线槽已经封好的时候。
  2. 距离与抗干扰。差分传输对共模干扰的抑制能力,配合屏蔽双绞线, 可以覆盖跨设备、跨桥架的距离;用同样的成本让以太网覆盖这段距离,施工量和成本都要上去。
  3. 成本。每个节点只需要一个收发器加一对双绞线,从站侧通常连协议栈都不需要 (很多执行机构就是“寄存器 + 串口”的极简实现)。
  4. 生态。Modbus RTU 几乎是所有执行机构的“最小公约数”。 我这几个项目里的驱动板、断路器、光源、采集单元,来自不同厂家,但接口是同一种。 这一点决定了“能不能买到”和“坏了能不能换”,是选型里最实际的一条。
  5. 确定性。一主多从、一问一答,时序可以算。 对控制类系统来说,这一点比“平均更快”重要得多。

但代价也必须写清楚。这些代价恰好解释了为什么“以太网做上行、RS485 做下行” 会成为最常见的那种分层:

RS485 的代价表现缓解办法
半双工,需要方向控制 收发切换的时序不对,会削掉第一个字节的起始位,表现为“偶发乱码、CRC 错很多” 方向切换加必要的延时,并把这段时序当成硬件时序来验收,而不是当成“保险起见”
节点越多,轮询周期越长 周期大致等于节点数乘以单次耗时;设备增加后整机刷新变慢 算清最坏周期再决定节点数;必要时拆第二条总线或把非实时设备挪走
地址与波特率必须全总线一致 新设备接上去忘了改地址,会撞地址或不应答,而且看起来像“设备坏了” 把地址与波特率做成可读写的参数,上电时校验并上报
没有即插即用与拓扑发现 线接错了只能一段一段断开排除,费时且依赖现场经验 做诊断模式或诊断板,能读到每台的在线状态与超时计数
一段总线上的单点故障会影响整段 某一段短路或某个节点故障,可能把整条链路拖垮 分支尽量短、加隔离、终端电阻按规范接,避免长分支

关于“一帧到哪里算结束”这类现场总线上必须处理的问题(空闲判帧还是超时判帧、 半双工方向切换的时序),我在 《RS485 帧同步与超时判帧》 里单独写过;主站侧怎么把这些设备排进一个轮询状态机,则在 《Modbus 主机的轮询调度与离线降级》 里。

五、上云为什么用无线 + 文本协议

现场到云端没有自己铺设的线路,用蜂窝模块是最省事的一条路。这类模块对外通常只提供一个串口 加一套 AT 指令,所以主控侧的形态基本就被定死了:一个 AT 状态机 + 一个文本协议。

文本协议(JSON 这类)在嵌入式里的好处很实在:

  • 可读。用串口助手就能看懂上下行内容,不需要先写一个解析工具才能调试。
  • 字段可增删。云端要加一个字段,旧固件忽略它就行;要下线一个字段, 把它保留一段时间也不影响。前后兼容比二进制协议容易得多。
  • 云端解析成本低。几乎每种云平台都直接支持 JSON,不需要在云侧再写一套二进制解析。

代价同样实在:

  • 体积大。同样一组数据,文本形式的字节数要比紧凑二进制大好几倍, 在按流量计费的链路上是要算钱的。
  • 吃内存。解析要在小容量 MCU 上跑,必须先把堆的峰值算清楚。 我见过在只有几 KB RAM 的芯片上用 JSON 库,光解析缓冲就占掉了相当比例, 再叠加通信缓冲就很紧张。
  • 类型与精度要两端约定。浮点、整数、字符串之间的转换容易出现“看起来对、 对账时不对”的问题,尤其是小数位与时间戳。

还有一件事几乎一定会跟着上云一起来:对时。上云之后通常要把云端或模块 提供的时间同步到设备上,于是要在“文本时间字符串”和“设备内部的二进制时间”之间来回转换—— 时区、BCD 与 BIN 两种表示、闰年、以及“这个时间到底是哪一层给的”。 我在这上面犯过的错是把模块返回的时间当成 UTC 直接用了,结果是所有历史数据的时刻整体偏移, 而设备本地完全看不出来,只有在云端对账时才暴露。

所以上云和板间控制通常用完全不同的协议形态:控制用紧凑的二进制帧(要快、要可算), 上云用文本(要好读、要好加字段)。这不是技术栈不统一, 是两层的优化目标本来就不同。顺便说一句,如果采集设备还要靠低功耗休眠来省电, 那么上云这条链路的周期与唤醒策略会直接决定休眠能不能成立——这部分我在 《低功耗休眠、唤醒与喂狗的三件套》 里写过。

六、协议选型的代价:三种协议意味着三套调试手段

选型的时候很容易只看“能不能跑通”,不看“以后谁来维护”。我这套系统有三种协议, 意味着三套排查方法、三份文档,以及三种“看起来都像网络问题”的故障:

层次协议形态需要的调试手段现场至少要有的工具
板间 / 上位机 以太网 + TCP / UDP 抓包、连接状态、socket 收发与错误计数 一台能接进同一交换网络的笔记本
现场总线 RS485 + Modbus RTU 串口监听、报文解析、超时与错误计数、方向控制时序 USB 转 485 转换器、终端电阻、一段可断开的测试线
上云 蜂窝模块 + AT 指令 + 文本协议 模块日志、AT 交互回显、文本字段校验 一个独立的调试串口,能看到模块的原始回显

这三套里最麻烦的是故障现象会互相伪装。一个“数据不更新了”的现象,可能是:

  • 485 侧某台从站掉线,主机一直在它身上白等超时;
  • 板间 UDP 丢包,某一轮的值没到;
  • TCP 连接半开,对端已经没了但本地还以为在连;
  • 蜂窝模块掉网,上云那条路断了,而本地一切正常。

如果不做可观测性,这四种情况在现象上几乎一样。所以我的做法是: 每一层都维护自己的收发计数、错误计数、超时计数和最后错误码, 并把这些统计汇总到同一个能被远程读到的位置——对 Modbus 从站来说, 就是变量表里的几个统计寄存器。这样“数据不更新”的第一个排查动作就变成了 “读一次统计,看是哪一层的计数不对”,而不是从头开始猜。

所以我建议在做选型时把两笔成本一起算:实现成本 + 以后每一次故障排查的成本。 如果维护现场只有一块万用表和一台笔记本,“多一种协议”省下来的那点物料钱, 很快就会被多出来的出差与排查时间吃掉。反过来说,如果确实需要三种协议, 那么“每层都有统计、都能远程读”就不是可选项,而是这套架构能不能维护下去的前提。

七、几条我反复用到的经验

  1. 先问“最坏情况要多久”,再问“用哪个协议”。平均最快不等于最坏可接受, 控制类系统尤其如此。
  2. 能一根线解决的,不要引入第二套协议栈。每多一套协议栈, 就多一份内存占用、多一套初始化顺序、多一类现场故障。
  3. 分层的原则是“每层只解决一个约束”。板间要低延迟、上行要可靠、 现场要多从站与长距离、上云要好读好扩展——四种约束,四套形态。
  4. UDP 与 TCP 不是先进与落后,是延迟优先与可靠优先。 选之前先把那张判据表填一遍,比凭印象决定靠谱。
  5. 私有用法必须写明前提。在 UDP 上跑 Modbus 这件事我之所以敢用, 是因为两端都是我自己写的、网络完全可控;这个前提要写进文档,而不是留在脑子里。
  6. 参数一律外提。地址、速率、超时、间隔都要能远程读写, 并且要有默认值与合法性校验——否则每换一个现场就要重出一版固件。
  7. 可观测性和功能一起做。每层都有收发、错误、超时计数, 故障定位就变成“看哪个计数不对”。
  8. 选型时把“以后谁来维护”算进成本。三种协议意味着三套调试手段, 这个代价是长期的,而它往往在选型会上被完全忽略。

这三篇笔记其实是同一条链路上的三个视角:这一篇决定“用哪种链路”, 主机轮询调度那一篇决定“链路怎么用”, 低功耗那一篇决定“空闲时怎么安静下来”。 三者凑在一起,才是一套能长期在现场活下去的通信方案。 更多同类笔记可以回到 学习笔记列表 查看。

参考资料与说明

  • Modbus 应用协议规范(Modbus Application Protocol Specification)与 Modbus over Serial Line Specification and Implementation Guide 中关于 功能码、异常码、RTU 帧间隔与主从时序的部分,属于公开标准。
  • Modbus TCP 相关公开规范中关于“Modbus TCP 基于 TCP 承载”的说明, 用于解释本文提到的“在 UDP 上承载 Modbus 属于私有用法”。
  • LwIP 官方文档与其公开配置项说明(LwIP 为第三方开源协议栈,BSD 许可; 本文只引用公开配置项名称,不包含其源码)。
  • FreeRTOS 官方文档(第三方开源项目,本文只引用其公开文档中的概念)。
  • CAN 与 RS485 相关公开标准(ISO 11898 系列与 TIA/EIA-485 系列)中关于 差分传输、总线拓扑与终端匹配的部分,属于公开标准。
  • 公开的串口 / 以太网 / 蜂窝模块 AT 指令文档中关于物理层参数与模块交互方式的说明。
  • 文中涉及的芯片与模块型号仅用于说明技术方案,与相关厂商无隶属或授权关系; 文中的地址、速率、端口与节点数量一律不给出具体值,只讲选取方法。
  • 示例代码为按个人理解重写的最小片段,不含任何产品的实际参数, 不代表任何产品或交付代码;第三方组件的名称与许可归其各自所有者。
  • 出于对个人项目实现细节的保护,本文只公开方法与思路; 涉及核心实现的部分以步骤与字段说明代替代码,示例代码为按个人理解重写的通用片段。

相关阅读:LwIP 上的 UDP 通信 · RS485 帧同步与超时判帧 · Modbus 主机的轮询调度与离线降级 · 低功耗休眠、唤醒与喂狗的三件套 · 多板卡以太网控制实验 · 电池管理单元实验记录 · 返回学习笔记列表