文章目录
- [一、RPC 是什么:诞生背景与核心抽象](#一、RPC 是什么:诞生背景与核心抽象)
-
- [1.1 为什么会出现 RPC](#1.1 为什么会出现 RPC)
- [1.2 RPC 与普通消息协议的差异](#1.2 RPC 与普通消息协议的差异)
- [二、RPC 的调用流程:从本地调用到远端执行](#二、RPC 的调用流程:从本地调用到远端执行)
- [三、RPC 解决什么问题](#三、RPC 解决什么问题)
-
- [3.1 解决接口抽象问题](#3.1 解决接口抽象问题)
- [3.2 解决序列化和跨语言问题](#3.2 解决序列化和跨语言问题)
- [3.3 解决工具化和自动化测试问题](#3.3 解决工具化和自动化测试问题)
- [3.4 解决服务边界管理问题](#3.4 解决服务边界管理问题)
- [四、RPC 不解决什么问题](#四、RPC 不解决什么问题)
- [五、为什么 RPC 适合点对点、低频、结构化调用](#五、为什么 RPC 适合点对点、低频、结构化调用)
-
- [5.1 点对点使请求/响应关系清楚](#5.1 点对点使请求/响应关系清楚)
- [5.2 低频调用可以接受额外封装成本](#5.2 低频调用可以接受额外封装成本)
- [5.3 结构化参数能发挥 IDL 价值](#5.3 结构化参数能发挥 IDL 价值)
- [5.4 调用方可以处理超时和错误](#5.4 调用方可以处理超时和错误)
- [六、为什么 RPC 不适合高频实时 PDO、广播和多主多从总线仲裁](#六、为什么 RPC 不适合高频实时 PDO、广播和多主多从总线仲裁)
-
- [6.1 高频实时 PDO 的目标与 RPC 相反](#6.1 高频实时 PDO 的目标与 RPC 相反)
- [6.2 广播不是普通 RPC 主路径](#6.2 广播不是普通 RPC 主路径)
- [6.3 多主多从仲裁属于链路层/MAC 层](#6.3 多主多从仲裁属于链路层/MAC 层)
- [6.4 Classic CAN 上 RPC 还会遇到分片问题](#6.4 Classic CAN 上 RPC 还会遇到分片问题)
- 七、工业/嵌入式通信的三层分工
-
- [7.1 实时数据面](#7.1 实时数据面)
- [7.2 诊断配置面](#7.2 诊断配置面)
- [7.3 工具调试面](#7.3 工具调试面)
- [7.4 不同链路/总线下 RPC 的适配边界](#7.4 不同链路/总线下 RPC 的适配边界)
-
- [7.4.1 以太网 / TCP/IP](#7.4.1 以太网 / TCP/IP)
- [7.4.2 UART / RS485](#7.4.2 UART / RS485)
- [7.4.3 SPI / I2C](#7.4.3 SPI / I2C)
- [7.4.4 多核通信内部](#7.4.4 多核通信内部)
- [7.4.5 分布式节点之间](#7.4.5 分布式节点之间)
- [八、Modbus、CANopen、EtherCAT、UDS、原生 CAN 与 RPC 的边界对比](#八、Modbus、CANopen、EtherCAT、UDS、原生 CAN 与 RPC 的边界对比)
-
- [8.1 原生 CAN](#8.1 原生 CAN)
- [8.2 Modbus](#8.2 Modbus)
- [8.3 CANopen](#8.3 CANopen)
- [8.4 EtherCAT](#8.4 EtherCAT)
- [8.5 UDS / ISO-TP](#8.5 UDS / ISO-TP)
- [8.6 RPC / eRPC / gRPC](#8.6 RPC / eRPC / gRPC)
- 九、工程落地建议与选型检查表
-
- [9.1 不要用一个协议覆盖所有通信](#9.1 不要用一个协议覆盖所有通信)
- [9.2 RPC 接口设计建议](#9.2 RPC 接口设计建议)
- [9.3 多主多从场景的判断](#9.3 多主多从场景的判断)
- 十、结语
- 参考资料
- [附录 A:术语速查](#附录 A:术语速查)

RPC 是什么:原理、边界与工业/嵌入式通信中的适用场景
摘要: RPC(Remote Procedure
Call,远程过程调用)把跨节点通信抽象成"调用远端函数",解决的是接口抽象、参数序列化、请求/响应匹配、跨语言代码生成和工具化问题。它不解决物理层、链路层、总线仲裁、实时调度和广播多回应聚合问题。因此,RPC
更适合点对点、低频、结构化的控制/配置/调试接口;不适合高频实时
PDO、广播控制、硬实时闭环和多主多从共享总线仲裁。工业和嵌入式系统中,更稳妥的做法是把通信拆成实时数据面、诊断配置面和工具调试面:实时数据面使用原生
CAN/PDO;诊断配置面使用 UDS/ISO-TP、CANopen SDO 或
Modbus;工具调试面再考虑 eRPC/gRPC 这类 RPC。
一、RPC 是什么:诞生背景与核心抽象
RPC
的直译是"远程过程调用"。它试图解决一个长期存在的软件工程问题:当一个程序需要使用另一台机器、另一个进程、另一个
CPU 核或另一个 MCU 上的能力时,能否像调用本地函数一样调用远端能力?
在没有 RPC
抽象时,调用方通常要直接处理连接、帧格式、字节序、参数编码、超时、错误码、重试、响应解析等细节。代码会逐渐变成"通信协议拼包/拆包代码"与"业务逻辑"混在一起。RPC
的目标不是消灭网络和总线的复杂性,而是把"远端服务"变成可声明、可生成、可复用、可测试的接口。
Birrell 和 Nelson 在 1984 年的经典论文《Implementing Remote Procedure
Calls》中把 RPC
描述为一种在高层语言程序之间通过网络通信调用远端过程的机制。ONC RPC
后续通过 RFC 1057、RFC 1831、RFC 5531 等文档形成了更清晰的协议描述。现代
gRPC、Thrift、eRPC、JSON-RPC
等虽然实现差异很大,但共同核心仍是:定义服务、序列化参数、发送请求、远端执行、返回结果。
在嵌入式领域,NXP eRPC 把这种思想缩小到多芯片、多核 SoC、MCU
与主控之间的轻量 RPC 场景;gRPC
则更常见于服务器、云服务、桌面工具和移动端服务。二者规模不同,但抽象类似:服务接口先被
IDL 描述,再生成客户端 stub 和服务端 shim。
业务实现 分发代理 传输通道 编解码层 调用代理 应用代码 业务实现 分发代理 传输通道 编解码层 调用代理 应用代码 #mermaid-svg-JLzuYDhTpERIE1qi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JLzuYDhTpERIE1qi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JLzuYDhTpERIE1qi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JLzuYDhTpERIE1qi .error-icon{fill:#552222;}#mermaid-svg-JLzuYDhTpERIE1qi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JLzuYDhTpERIE1qi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JLzuYDhTpERIE1qi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JLzuYDhTpERIE1qi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JLzuYDhTpERIE1qi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JLzuYDhTpERIE1qi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JLzuYDhTpERIE1qi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JLzuYDhTpERIE1qi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JLzuYDhTpERIE1qi .marker.cross{stroke:#333333;}#mermaid-svg-JLzuYDhTpERIE1qi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JLzuYDhTpERIE1qi p{margin:0;}#mermaid-svg-JLzuYDhTpERIE1qi .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JLzuYDhTpERIE1qi text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-JLzuYDhTpERIE1qi .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-JLzuYDhTpERIE1qi .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-JLzuYDhTpERIE1qi .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-JLzuYDhTpERIE1qi .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-JLzuYDhTpERIE1qi #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-JLzuYDhTpERIE1qi .sequenceNumber{fill:white;}#mermaid-svg-JLzuYDhTpERIE1qi #sequencenumber{fill:#333;}#mermaid-svg-JLzuYDhTpERIE1qi #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-JLzuYDhTpERIE1qi .messageText{fill:#333;stroke:none;}#mermaid-svg-JLzuYDhTpERIE1qi .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JLzuYDhTpERIE1qi .labelText,#mermaid-svg-JLzuYDhTpERIE1qi .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-JLzuYDhTpERIE1qi .loopText,#mermaid-svg-JLzuYDhTpERIE1qi .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-JLzuYDhTpERIE1qi .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-JLzuYDhTpERIE1qi .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-JLzuYDhTpERIE1qi .noteText,#mermaid-svg-JLzuYDhTpERIE1qi .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-JLzuYDhTpERIE1qi .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JLzuYDhTpERIE1qi .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JLzuYDhTpERIE1qi .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JLzuYDhTpERIE1qi .actorPopupMenu{position:absolute;}#mermaid-svg-JLzuYDhTpERIE1qi .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-JLzuYDhTpERIE1qi .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JLzuYDhTpERIE1qi .actor-man circle,#mermaid-svg-JLzuYDhTpERIE1qi line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-JLzuYDhTpERIE1qi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用远端函数 1 打包参数和请求编号 2 发送请求消息 3 接收完整请求 4 调用对应服务函数 5 返回结果或错误 6 发送响应消息 7 接收完整响应 8 解包返回值 9 返回结果或超时错误 10
图 1:RPC 调用路径与职责边界
| 核心观点 RPC 的本质是"服务接口抽象",不是"现场总线协议"。它可以跑在 UART、TCP、RPMsg、USB、CAN/ISO-TP 等 transport 之上,但它本身不定义物理层,不处理总线冲突,也不替代实时数据帧。 |
|---|
1.1 为什么会出现 RPC
-
分布式程序需要调用远端能力,但不希望每个业务函数都手写通信细节。
-
多语言系统需要统一接口定义,例如 C/C++ 设备端和 Python/Go/Java
上位机。
-
参数和返回值越来越复杂,手工拼字节容易出错。
-
协议演进需要保持接口兼容,不能每增加一个字段就重写大量解析逻辑。
-
测试、产测、调试工具需要直接调用设备能力,而不是维护多套私有脚本。
1.2 RPC 与普通消息协议的差异
| 维度 | RPC 思路 | 普通消息/寄存器协议思路 |
|---|---|---|
| 抽象对象 | 远端函数、服务、接口 | 帧、寄存器、命令码、数据区 |
| 接口描述 | IDL/proto/erpc 文件描述参数和返回值 | 协议文档描述字节偏移、功能码、长度、CRC |
| 调用方式 | 像本地函数一样调用,调用方等待返回或处理超时 | 发送帧后根据协议状态机解析返回 |
| 类型约束 | 通常较强,代码生成器生成结构体和访问函数 | 取决于人工实现和协议文档 |
| 适合场景 | 低频控制、配置、诊断、工具、服务化接口 | 实时数据、固定帧、寄存器、标准现场总线 |
二、RPC 的调用流程:从本地调用到远端执行
一个典型 RPC 调用流程可以拆成 10 步。不同 RPC
框架的名称可能不同,但基本结构相似。
-
接口定义:用 IDL、.proto 或 erpc
文件描述服务、函数、参数、返回值和数据结构。
-
代码生成:工具生成客户端 stub、服务端 shim、序列化/反序列化代码。
-
本地调用:应用代码调用本地 stub 函数,例如 readParam(index)。
-
参数打包:stub 把函数 ID、参数、sequence 等信息编码成消息。
-
transport 发送:通过 UART/TCP/RPMsg/CAN transport
等发送字节流或消息。
-
服务端接收:服务端 transport 收到完整消息,交给 RPC runtime。
-
分发执行:服务端 shim 根据 service ID / method ID
调用真正的业务函数。
-
结果编码:返回值或错误码被序列化为 reply。
-
响应匹配:客户端按 sequence 或调用上下文匹配 reply。
-
返回应用:stub
把结果还原为本地返回值,或者抛出/返回超时、连接错误等状态。
注意:RPC
"看起来像本地调用",但不能真的当成本地调用。远端调用可能失败、超时、重复执行、部分执行、返回晚到,底层
transport 也可能丢包、断连或阻塞。因此,在嵌入式系统中,RPC
接口应显式定义超时、幂等性、错误码和状态恢复策略。
| 工程提示 不要把 RPC 接口设计成"隐式实时接口"。例如 setTargetCurrent() 如果每 1 ms 调一次并参与闭环控制,就不应该是 RPC;如果只是低频配置电流限幅,可以是 RPC 或诊断服务。 |
|---|
三、RPC 解决什么问题
RPC 的价值不是"比所有协议都高级",而是它能很好地解决一类软件接口问题。
3.1 解决接口抽象问题
RPC 把"命令码 + 参数偏移 + 返回帧解析"转化为"服务 + 方法 +
类型"。这对复杂命令集非常有价值。对于 PC
端工具和嵌入式设备端同时维护的项目,IDL 能让接口成为单一事实来源。
3.2 解决序列化和跨语言问题
现代 RPC 框架通常使用 IDL 描述数据结构,再为多种语言生成代码。gRPC 使用
Protocol Buffers 作为常见 IDL 和消息交换格式;eRPC 使用 erpcgen 和自己的
IDL 生成嵌入式 C/C++/Python
等代码。这样可以减少手写结构体偏移、字节序转换和人工解析错误。
3.3 解决工具化和自动化测试问题
很多嵌入式项目最终都会需要产测工具、参数配置工具、自动化测试脚本和现场调试工具。RPC
能让 PC 脚本直接调用设备接口,例如
getVersion()、readParam()、runSelfTest()、dumpStats(),而不是复制一套帧格式解析器。
3.4 解决服务边界管理问题
当一个系统从单 MCU 扩展到多 MCU、多核 SoC、主控 + 协处理器时,RPC
可以给每个节点定义清晰的服务边界。例如传感器服务、参数服务、日志服务、升级控制服务等。
四、RPC 不解决什么问题
RPC 的误用,常常来自把"远程函数调用"理解成"万能通信协议"。实际上,RPC
只位于应用层或接近应用层的位置。
| RPC 不解决的问题 | 原因 | 应由谁解决 |
|---|---|---|
| 物理层电气问题 | RPC 不知道差分电平、收发方向、终端电阻、波特率容差 | PHY、驱动、BSP、硬件设计 |
| 总线仲裁 | RPC 不决定多个节点同时发时谁赢 | CAN 控制器、MAC 层、token、主站轮询 |
| 实时调度 | RPC 调用有排队、超时、分片和 dispatch 开销 | RTOS 调度、PDO 周期表、优先级设计 |
| 广播多回应聚合 | 普通 RPC 是一次调用对应一个返回;广播多返回是特殊模型 | 发现协议、轮询、事件收集器、专用广播机制 |
| 高吞吐流传输 | RPC 消息通常要求完整序列化/反序列化 | 流协议、文件传输、ISO-TP/UDS TransferData、TCP stream |
| 安全策略本身 | RPC 可承载认证字段,但不自动提供可信安全模型 | 安全架构、认证、授权、加密、密钥管理 |
| 关于 Broadcast RPC RFC 1057 确实描述了 broadcast RPC:客户端向网络广播调用并等待多个回复,并明确要求使用 UDP 这类 packet-based transport。这说明"广播多回应"并非普通点对点 RPC 主路径,而是需要额外规则的特殊机制。把这一机制直接搬到 CAN/RS485 多节点网络时,仍然需要处理节点过滤、回应窗口、总线负载、超时聚合和冲突恢复。 |
|---|
五、为什么 RPC 适合点对点、低频、结构化调用
5.1 点对点使请求/响应关系清楚
RPC 最自然的模型是 client 调用一个明确 server
上的一个明确方法。点对点链路或者"一主多从逐个寻址"的链路可以很容易把一次调用映射为一次请求和一次响应。
5.2 低频调用可以接受额外封装成本
RPC 需要 message header、函数
ID、参数序列化、sequence、dispatch、返回值解析。这些开销对"读一次版本号""写一个参数""执行一次自检"影响很小,但对
1 ms 周期控制帧影响明显。
5.3 结构化参数能发挥 IDL 价值
如果接口参数是结构体、数组、字符串、枚举、版本信息、错误信息,RPC
的类型系统和代码生成会明显降低维护成本。反过来,如果每次只传 2
字节速度或 4 字节位置,固定 CAN 帧更简单。
5.4 调用方可以处理超时和错误
RPC 适合那些允许调用方等待结果的操作。例如 readParam() 可以等待 20 ms 或
100 ms;runSelfTest() 可以返回 busy;saveConfig()
可以返回错误码。这类接口和"函数调用 + 状态返回"匹配。
六、为什么 RPC 不适合高频实时 PDO、广播和多主多从总线仲裁
#mermaid-svg-x4dXa4hXaLxldstN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-x4dXa4hXaLxldstN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-x4dXa4hXaLxldstN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-x4dXa4hXaLxldstN .error-icon{fill:#552222;}#mermaid-svg-x4dXa4hXaLxldstN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-x4dXa4hXaLxldstN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-x4dXa4hXaLxldstN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-x4dXa4hXaLxldstN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-x4dXa4hXaLxldstN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-x4dXa4hXaLxldstN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-x4dXa4hXaLxldstN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-x4dXa4hXaLxldstN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-x4dXa4hXaLxldstN .marker.cross{stroke:#333333;}#mermaid-svg-x4dXa4hXaLxldstN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-x4dXa4hXaLxldstN p{margin:0;}#mermaid-svg-x4dXa4hXaLxldstN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-x4dXa4hXaLxldstN .cluster-label text{fill:#333;}#mermaid-svg-x4dXa4hXaLxldstN .cluster-label span{color:#333;}#mermaid-svg-x4dXa4hXaLxldstN .cluster-label span p{background-color:transparent;}#mermaid-svg-x4dXa4hXaLxldstN .label text,#mermaid-svg-x4dXa4hXaLxldstN span{fill:#333;color:#333;}#mermaid-svg-x4dXa4hXaLxldstN .node rect,#mermaid-svg-x4dXa4hXaLxldstN .node circle,#mermaid-svg-x4dXa4hXaLxldstN .node ellipse,#mermaid-svg-x4dXa4hXaLxldstN .node polygon,#mermaid-svg-x4dXa4hXaLxldstN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-x4dXa4hXaLxldstN .rough-node .label text,#mermaid-svg-x4dXa4hXaLxldstN .node .label text,#mermaid-svg-x4dXa4hXaLxldstN .image-shape .label,#mermaid-svg-x4dXa4hXaLxldstN .icon-shape .label{text-anchor:middle;}#mermaid-svg-x4dXa4hXaLxldstN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-x4dXa4hXaLxldstN .rough-node .label,#mermaid-svg-x4dXa4hXaLxldstN .node .label,#mermaid-svg-x4dXa4hXaLxldstN .image-shape .label,#mermaid-svg-x4dXa4hXaLxldstN .icon-shape .label{text-align:center;}#mermaid-svg-x4dXa4hXaLxldstN .node.clickable{cursor:pointer;}#mermaid-svg-x4dXa4hXaLxldstN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-x4dXa4hXaLxldstN .arrowheadPath{fill:#333333;}#mermaid-svg-x4dXa4hXaLxldstN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-x4dXa4hXaLxldstN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-x4dXa4hXaLxldstN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-x4dXa4hXaLxldstN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-x4dXa4hXaLxldstN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-x4dXa4hXaLxldstN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-x4dXa4hXaLxldstN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-x4dXa4hXaLxldstN .cluster text{fill:#333;}#mermaid-svg-x4dXa4hXaLxldstN .cluster span{color:#333;}#mermaid-svg-x4dXa4hXaLxldstN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-x4dXa4hXaLxldstN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-x4dXa4hXaLxldstN rect.text{fill:none;stroke-width:0;}#mermaid-svg-x4dXa4hXaLxldstN .icon-shape,#mermaid-svg-x4dXa4hXaLxldstN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-x4dXa4hXaLxldstN .icon-shape p,#mermaid-svg-x4dXa4hXaLxldstN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-x4dXa4hXaLxldstN .icon-shape .label rect,#mermaid-svg-x4dXa4hXaLxldstN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-x4dXa4hXaLxldstN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-x4dXa4hXaLxldstN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-x4dXa4hXaLxldstN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 节点甲
想发送请求
共享总线
节点乙
也想发送请求
节点丙
可能返回响应
链路层或总线协议
决定谁可以发送
RPC 只处理
请求、响应、序列化和分发
没有底层仲裁时
RPC 不能判断谁先发
也不能恢复物理冲突
图 2:RPC 不解决多主多从共享总线的发送权问题
6.1 高频实时 PDO 的目标与 RPC 相反
PDO 的典型目标是短、快、固定、可预测。CANopen PDO、原生 CAN
周期帧和许多电机控制帧都依赖固定 CAN ID 和固定 payload
布局来降低开销。RPC 的目标是类型化、服务化和可维护,两者优化方向不同。
| 维度 | 实时 PDO / 原生 CAN | RPC / eRPC |
|---|---|---|
| 发送模式 | 周期或事件触发,通常不等待响应 | 请求/响应或 oneway 调用 |
| 数据布局 | 固定 ID + 固定 DLC + 固定 bit/signal | 服务 ID + 方法 ID + 编码后的参数 |
| 实时性 | 强,可设计优先级和周期表 | 弱于固定帧,存在分片、排队、dispatch |
| 带宽效率 | 高,Classic CAN 8 字节可全部用于信号 | 低,尤其在 Classic CAN 上需要分片 |
| 维护方式 | DBC/对象字典/协议表 | IDL/代码生成/stub |
| 典型用途 | 位置、速度、电流、IO、心跳 | 参数、诊断、调试、配置 |
6.2 广播不是普通 RPC 主路径
广播控制帧可以很适合
CAN,例如同步帧、心跳、全局使能、紧急停止类信号。但广播 RPC
会引出"一次请求多个回复"的问题:每个节点何时回复?按什么顺序回复?谁没回复算失败?总线如何避免回复风暴?这些不是普通
RPC 调用模型默认解决的问题。
6.3 多主多从仲裁属于链路层/MAC 层
多主多从共享总线的核心问题是"谁有资格发送"。CAN 在数据链路层通过 ID
优先级进行非破坏性仲裁;而 RS485
只是物理层,通常需要额外的主站轮询、token、时隙或冲突退避。RPC
运行在这些机制之上,不能替代这些机制。
6.4 Classic CAN 上 RPC 还会遇到分片问题
Classic CAN 单帧 payload 最多 8 字节,CAN FD 可到 64 字节。RPC
消息往往超过这个大小,因此必须引入分片、重组、超时和流控。ISO-TP 正是为
CAN 上承载更长诊断消息而定义的 transport/network layer。
七、工业/嵌入式通信的三层分工
#mermaid-svg-8p1JljSp82cioYZD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8p1JljSp82cioYZD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8p1JljSp82cioYZD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8p1JljSp82cioYZD .error-icon{fill:#552222;}#mermaid-svg-8p1JljSp82cioYZD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8p1JljSp82cioYZD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8p1JljSp82cioYZD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8p1JljSp82cioYZD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8p1JljSp82cioYZD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8p1JljSp82cioYZD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8p1JljSp82cioYZD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8p1JljSp82cioYZD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8p1JljSp82cioYZD .marker.cross{stroke:#333333;}#mermaid-svg-8p1JljSp82cioYZD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8p1JljSp82cioYZD p{margin:0;}#mermaid-svg-8p1JljSp82cioYZD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8p1JljSp82cioYZD .cluster-label text{fill:#333;}#mermaid-svg-8p1JljSp82cioYZD .cluster-label span{color:#333;}#mermaid-svg-8p1JljSp82cioYZD .cluster-label span p{background-color:transparent;}#mermaid-svg-8p1JljSp82cioYZD .label text,#mermaid-svg-8p1JljSp82cioYZD span{fill:#333;color:#333;}#mermaid-svg-8p1JljSp82cioYZD .node rect,#mermaid-svg-8p1JljSp82cioYZD .node circle,#mermaid-svg-8p1JljSp82cioYZD .node ellipse,#mermaid-svg-8p1JljSp82cioYZD .node polygon,#mermaid-svg-8p1JljSp82cioYZD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8p1JljSp82cioYZD .rough-node .label text,#mermaid-svg-8p1JljSp82cioYZD .node .label text,#mermaid-svg-8p1JljSp82cioYZD .image-shape .label,#mermaid-svg-8p1JljSp82cioYZD .icon-shape .label{text-anchor:middle;}#mermaid-svg-8p1JljSp82cioYZD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8p1JljSp82cioYZD .rough-node .label,#mermaid-svg-8p1JljSp82cioYZD .node .label,#mermaid-svg-8p1JljSp82cioYZD .image-shape .label,#mermaid-svg-8p1JljSp82cioYZD .icon-shape .label{text-align:center;}#mermaid-svg-8p1JljSp82cioYZD .node.clickable{cursor:pointer;}#mermaid-svg-8p1JljSp82cioYZD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8p1JljSp82cioYZD .arrowheadPath{fill:#333333;}#mermaid-svg-8p1JljSp82cioYZD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8p1JljSp82cioYZD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8p1JljSp82cioYZD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8p1JljSp82cioYZD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8p1JljSp82cioYZD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8p1JljSp82cioYZD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8p1JljSp82cioYZD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8p1JljSp82cioYZD .cluster text{fill:#333;}#mermaid-svg-8p1JljSp82cioYZD .cluster span{color:#333;}#mermaid-svg-8p1JljSp82cioYZD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8p1JljSp82cioYZD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8p1JljSp82cioYZD rect.text{fill:none;stroke-width:0;}#mermaid-svg-8p1JljSp82cioYZD .icon-shape,#mermaid-svg-8p1JljSp82cioYZD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8p1JljSp82cioYZD .icon-shape p,#mermaid-svg-8p1JljSp82cioYZD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8p1JljSp82cioYZD .icon-shape .label rect,#mermaid-svg-8p1JljSp82cioYZD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8p1JljSp82cioYZD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8p1JljSp82cioYZD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8p1JljSp82cioYZD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 实时数据面
周期控制、状态上报、心跳、同步
优先:原生 CAN、CANopen PDO、EtherCAT 过程数据
诊断配置面
参数读写、故障、例程、刷写
优先:UDS/ISO-TP、CANopen SDO、Modbus
工具调试面
产测、调试、跨语言工具
可选:eRPC、gRPC、JSON-RPC
图 3:实时数据面、诊断配置面、工具调试面的分层
7.1 实时数据面
实时数据面承载高频、周期、事件触发且对延迟敏感的数据。它的接口通常不是"函数调用",而是"最新状态"和"控制目标"。典型例子包括电机目标速度、实际位置、电流、状态字、IO
状态、心跳和同步帧。
-
优先选择:原生 CAN、CANopen PDO、自定义固定 CAN ID 帧。
-
目标:短帧、低开销、固定优先级、确定性调度。
-
不建议:把每个周期信号包装成 RPC 调用。
7.2 诊断配置面
诊断配置面承载低频但需要可靠语义的操作,如读写参数、读取故障、清除故障、进入会话、执行例程、刷写固件等。UDS/ISO-TP、CANopen
SDO 和 Modbus 都属于这一层常见方案。
-
优先选择:UDS/ISO-TP、CANopen SDO、Modbus 寄存器/功能码。
-
目标:标准化、可诊断、可记录、可恢复。
-
RPC 可用于这一层,但在已有 UDS/CANopen/Modbus 生态时,需避免协议重复。
7.3 工具调试面
工具调试面关注开发效率和跨语言工具链。eRPC/gRPC 在这一层价值明显:PC
Python 工具可以调用设备的
getBuildInfo()、readDebugVar()、dumpStats()、runTestRoutine() 等接口。
-
优先选择:eRPC/gRPC/JSON-RPC 等 RPC 框架,或基于 UDS 的工具服务。
-
目标:可读、可生成、可测试、跨语言。
-
注意:工具面不应影响实时数据面,调试接口应限流、限权、明确超时。
7.4 不同链路/总线下 RPC 的适配边界
前面的分层结论不能只套在 CAN 网络上。不同链路的物理模型、寻址模型、带宽、时延和主从关系不同,RPC 的适用边界也不同。比较稳妥的判断方法是:先判断底层是否已经提供寻址、流控、仲裁和可靠性,再判断上层是否真的需要"远程函数调用"这种抽象。
7.4.1 以太网 / TCP/IP
以太网和 TCP/IP 已经提供了比较完整的寻址、连接、分片重组、拥塞控制和错误恢复能力。RPC 在这类网络上最自然,典型形态包括 gRPC、HTTP RPC、JSON-RPC 或自定义二进制 RPC。它适合设备管理、网关配置、远程调试、上位机工具、边缘控制器与服务端之间的结构化接口。
需要注意的是,以太网并不天然等于硬实时。普通 TCP/IP RPC 适合配置面和工具面;如果要求确定性周期控制,应优先考虑实时以太网方案、现场总线周期调度或专门的实时数据通道。
7.4.2 UART / RS485
UART 点对点链路没有共享总线冲突,RPC 可以比较直接地运行在其上。RS485 虽然常用于多节点,但它主要提供差分物理层,并不自动提供多主仲裁。若要在 RS485 上运行 RPC,应先在 RPC 下面补齐地址、主站轮询或 token、收发方向控制、超时重试和冲突恢复。否则,RPC 只能描述"要调用哪个函数",不能决定"谁现在可以发"。
因此,UART/RS485 上的 RPC 更适合一主一从、主站轮询多从、低频配置和调试,不适合裸多主抢占式通信。
7.4.3 SPI / I2C
SPI 和 I2C 通常都有明确主从关系。SPI 多数情况下由主机选择片选并发起传输;I2C 虽支持多主规范,但工程上常见设计仍是一主多从。RPC 可以作为主机访问从设备服务的抽象层,但不应忽视底层事务边界。
对 SPI,RPC transport 通常需要定义片选粒度、帧长度、半双工/全双工时序和从机准备好机制。对 I2C,RPC transport 需要处理地址、最大传输长度、时钟拉伸、仲裁丢失和超时恢复。若每次只是读写几个寄存器,直接寄存器协议可能比 RPC 更简单。
7.4.4 多核通信内部
多核 SoC 或双核 MCU 内部常见共享内存、mailbox、RPMsg、virtio、IPC 中断等机制。这类场景适合轻量 RPC:一侧提供传感器服务、日志服务或控制服务,另一侧通过生成的 stub 调用。RPC 能减少跨核接口的手写消息分发代码。
但实时核和应用核之间仍要分清数据面和控制面。高频采样、控制闭环或大块共享数据更适合共享内存环形缓冲区;RPC 更适合启动、停止、配置、状态查询和低频命令。
7.4.5 分布式节点之间
分布式节点之间是否适合 RPC,取决于底层网络是否已经解决节点寻址、访问冲突和错误恢复。以太网或 CAN FD 上可以设计 RPC 通道;裸 RS485 或资源很紧的 I2C/SPI 网络则需要先明确链路层协议。对于大量节点的周期状态同步,仍应优先使用固定帧、对象字典、PDO、过程数据映射或发布订阅模型。
#mermaid-svg-IEOiP13S588UCTxa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IEOiP13S588UCTxa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IEOiP13S588UCTxa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IEOiP13S588UCTxa .error-icon{fill:#552222;}#mermaid-svg-IEOiP13S588UCTxa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IEOiP13S588UCTxa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IEOiP13S588UCTxa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IEOiP13S588UCTxa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IEOiP13S588UCTxa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IEOiP13S588UCTxa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IEOiP13S588UCTxa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IEOiP13S588UCTxa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IEOiP13S588UCTxa .marker.cross{stroke:#333333;}#mermaid-svg-IEOiP13S588UCTxa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IEOiP13S588UCTxa p{margin:0;}#mermaid-svg-IEOiP13S588UCTxa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IEOiP13S588UCTxa .cluster-label text{fill:#333;}#mermaid-svg-IEOiP13S588UCTxa .cluster-label span{color:#333;}#mermaid-svg-IEOiP13S588UCTxa .cluster-label span p{background-color:transparent;}#mermaid-svg-IEOiP13S588UCTxa .label text,#mermaid-svg-IEOiP13S588UCTxa span{fill:#333;color:#333;}#mermaid-svg-IEOiP13S588UCTxa .node rect,#mermaid-svg-IEOiP13S588UCTxa .node circle,#mermaid-svg-IEOiP13S588UCTxa .node ellipse,#mermaid-svg-IEOiP13S588UCTxa .node polygon,#mermaid-svg-IEOiP13S588UCTxa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IEOiP13S588UCTxa .rough-node .label text,#mermaid-svg-IEOiP13S588UCTxa .node .label text,#mermaid-svg-IEOiP13S588UCTxa .image-shape .label,#mermaid-svg-IEOiP13S588UCTxa .icon-shape .label{text-anchor:middle;}#mermaid-svg-IEOiP13S588UCTxa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IEOiP13S588UCTxa .rough-node .label,#mermaid-svg-IEOiP13S588UCTxa .node .label,#mermaid-svg-IEOiP13S588UCTxa .image-shape .label,#mermaid-svg-IEOiP13S588UCTxa .icon-shape .label{text-align:center;}#mermaid-svg-IEOiP13S588UCTxa .node.clickable{cursor:pointer;}#mermaid-svg-IEOiP13S588UCTxa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IEOiP13S588UCTxa .arrowheadPath{fill:#333333;}#mermaid-svg-IEOiP13S588UCTxa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IEOiP13S588UCTxa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IEOiP13S588UCTxa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IEOiP13S588UCTxa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IEOiP13S588UCTxa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IEOiP13S588UCTxa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IEOiP13S588UCTxa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IEOiP13S588UCTxa .cluster text{fill:#333;}#mermaid-svg-IEOiP13S588UCTxa .cluster span{color:#333;}#mermaid-svg-IEOiP13S588UCTxa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IEOiP13S588UCTxa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IEOiP13S588UCTxa rect.text{fill:none;stroke-width:0;}#mermaid-svg-IEOiP13S588UCTxa .icon-shape,#mermaid-svg-IEOiP13S588UCTxa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IEOiP13S588UCTxa .icon-shape p,#mermaid-svg-IEOiP13S588UCTxa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IEOiP13S588UCTxa .icon-shape .label rect,#mermaid-svg-IEOiP13S588UCTxa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IEOiP13S588UCTxa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IEOiP13S588UCTxa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IEOiP13S588UCTxa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
这条通信是否高频、实时、周期性?
优先使用原生 CAN、PDO、EtherCAT 过程数据或固定帧
是否用于诊断、配置、参数或刷写?
优先使用 UDS/ISO-TP、CANopen SDO 或 Modbus
是否需要跨语言、工具化、结构化远程调用?
可以考虑 RPC、eRPC 或 gRPC
保留简单命令帧、寄存器模型或专用协议
图 4:协议选型决策树
八、Modbus、CANopen、EtherCAT、UDS、原生 CAN 与 RPC 的边界对比
8.1 原生 CAN
原生 CAN 提供的是多主消息广播总线和 ID 仲裁能力。CAN ID
同时承担消息标识和优先级角色。它非常适合短帧实时数据,但不提供应用层对象字典、诊断会话、参数模型或
RPC 服务接口。
8.2 Modbus
Modbus 是典型的主从/客户端-服务器寄存器模型。Modbus Application Protocol
以功能码描述读线圈、读离散输入、读保持寄存器、写寄存器等操作。它简单、稳定、易调试,但接口表达力主要是寄存器/线圈/功能码,不像
RPC 那样以"函数签名"描述服务。
8.3 CANopen
CANopen 在 CAN
之上定义对象字典、PDO、SDO、NMT、心跳等机制。对象字典是协议栈与应用之间的关键接口,保存通信和应用参数。PDO
适合实时过程数据,SDO
适合访问对象字典中的参数。对于多节点工业设备网络,CANopen
的分层比直接使用 RPC 更贴近 CAN。
8.4 EtherCAT
EtherCAT 是面向实时工业以太网的现场总线技术。它的核心价值不是"远程函数调用",而是通过主站调度和从站过程数据映射实现高效、确定性的周期数据交换。EtherCAT 更适合实时数据面,例如多轴运动控制、分布式 IO、伺服驱动和高速采样同步。
从分层角度看,EtherCAT 的过程数据类似"实时数据面",对象字典、邮箱通信、CoE 等机制更接近"配置和诊断面"。RPC 可以作为上位机或工具侧的管理接口,但不应替代 EtherCAT 的周期过程数据通道。
8.5 UDS / ISO-TP
UDS 是车载电子控制单元诊断服务的应用层标准,定义诊断仪客户端控制 ECU
服务端诊断功能的通用服务。ISO-TP/DoCAN 则提供 CAN 上承载更长诊断消息的
transport/network
layer。它们适合诊断、DID/RID、会话、安全访问、刷写和大数据传输控制。
8.6 RPC / eRPC / gRPC
RPC 适合把能力暴露成 typed service。gRPC 更适合网络服务、跨语言后端和 PC
工具;eRPC 更适合嵌入式多芯片、多核或 MCU 与主控之间的轻量调用。RPC
可以作为工具调试面或部分控制配置面,但不应替代 CANopen PDO、UDS
诊断体系或原生 CAN 实时数据。
| 协议/机制 | 核心抽象 | 强项 | 弱项/边界 | 典型适用场景 |
|---|---|---|---|---|
| 原生 CAN | CAN ID + DLC + data | 短帧、硬件仲裁、广播、实时优先级 | 无应用层对象模型;Classic CAN payload 小 | PDO、状态上报、控制目标、心跳、同步 |
| Modbus | 功能码 + 寄存器/线圈模型 | 简单、通用、主从清晰、调试容易 | 表达复杂服务不方便;实时性和并发能力有限 | PLC/传感器/仪表参数读写 |
| CANopen | 对象字典 + PDO/SDO/NMT | CAN 工业网络成熟分层;PDO/SDO 边界清楚 | 对象字典设计和协议栈成本较高 | 多节点运动控制、IO、驱动器、工业设备 |
| EtherCAT | 主站调度 + 过程数据映射 | 实时工业以太网、周期数据、分布式时钟、运动控制 | 不适合用 RPC 替代周期过程数据;协议栈和主站设计成本较高 | 多轴控制、伺服、分布式 IO、实时采样同步 |
| UDS/ISO-TP | 诊断服务 + CAN 长消息传输 | 诊断、DID/RID、刷写、会话和安全访问 | 不适合高频实时数据;服务设计偏诊断 | ECU 诊断、故障、例程、刷写 |
| RPC/eRPC/gRPC | 远程服务/函数调用 | IDL、代码生成、跨语言、工具化、结构化参数 | 不解决总线仲裁;不适合高频 PDO/广播多回应 | 配置、调试、产测、低频控制、跨传输服务 |
九、工程落地建议与选型检查表
9.1 不要用一个协议覆盖所有通信
成熟的嵌入式通信系统通常不是"一个协议打天下"。实时状态、诊断配置、工具调试的目标不同,应使用不同层次的机制。
| 问题 | 如果答案是"是" | 优先方案 |
|---|---|---|
| 是否每 1 ms / 5 ms / 10 ms 周期发送? | 是 | 原生 CAN / PDO |
| 是否固定 8 字节或 64 字节即可表达? | 是 | 原生 CAN / CAN FD 固定帧 |
| 是否需要读写参数、故障、例程或刷写? | 是 | UDS/ISO-TP、CANopen SDO、Modbus |
| 是否希望 PC Python 直接调用 typed API? | 是 | eRPC/gRPC 工具通道 |
| 是否需要广播后多个节点回应? | 是 | 专用发现/轮询/事件聚合机制,不要当普通 RPC |
| 是否需要多主共享 RS485? | 是 | 先设计 MAC/token/主站轮询,RPC 只能在其上层运行 |
| 是否已经有 UDS/CANopen/Modbus 生态? | 是 | 优先统一现有诊断配置面,避免再引入重复 RPC 面 |
9.2 RPC 接口设计建议
-
接口应低频、清晰、幂等性可分析;避免把实时控制环做成 RPC。
-
每个 RPC 方法必须定义超时、错误码、busy 语义和版本兼容策略。
-
参数结构要稳定,IDL 文件应版本化管理。
-
transport 层必须明确最大消息长度、分片策略、接收缓存和重组超时。
-
如果运行在 CAN 上,优先复用 ISO-TP 或设计独立轻量分片层;不要把 RPC
直接塞进单个 Classic CAN 帧。
-
如果已有 UDS/ISO-TP,eRPC
更适合作为调试/产测/开发工具接口,而不是诊断主协议。
9.3 多主多从场景的判断
多主多从本身不是 RPC
能否表达的问题,而是底层网络是否已经提供发送权仲裁、节点寻址和错误恢复。
| 底层网络 | 多主多从可行性 | RPC 角色 |
|---|---|---|
| CAN / CAN FD | 链路层有 ID 仲裁,较适合多主消息网络 | 可作为低频服务层,但仍需分片、节点路由和超时 |
| RS485 裸总线 | 物理层不提供仲裁,直接多主风险高 | 不能解决冲突;必须先有主站轮询/token/时隙等机制 |
| TCP/IP | 已有寻址、连接、拥塞和可靠传输机制 | 非常适合 gRPC/RPC |
| UART 点对点 | 无共享冲突 | 适合 eRPC 等轻量 RPC |
| SPI 主从 | 通常主机调度,适合一主一从或主控多从片选 | 可用 RPC,但实时高频仍需评估 |
十、结语
RPC
是一种非常有价值的软件工程抽象,但它的价值边界必须放在正确层次上。它擅长把远端能力变成
typed
service,减少手写通信代码,提高工具化和跨语言开发效率。它不擅长做实时数据帧、广播控制、总线仲裁和物理链路恢复。
对工业/嵌入式系统,推荐采用"三层分工":实时数据面使用原生
CAN/PDO;诊断配置面使用 UDS/ISO-TP、CANopen SDO 或
Modbus;工具调试面再引入
eRPC/gRPC。这样既保留现场总线的确定性和成熟生态,也能利用 RPC
的开发效率。
参考资料
1 Andrew D. Birrell, Bruce Jay Nelson, "Implementing Remote
Procedure Calls," ACM Transactions on Computer Systems, 1984. DOI:
10.1145/2080.357392.
2 RFC 5531, "RPC: Remote Procedure Call Protocol Specification
Version 2," RFC Editor / IETF, 2009.
3 RFC 1057, "RPC: Remote Procedure Call Protocol specification:
Version 2," IETF, 1988. Section 7.4.2 describes broadcast RPC.
4 NXP MCUXpresso SDK documentation, "mcuxsdk-middleware-erpc";
EmbeddedRPC API Reference.
5 gRPC documentation, "Introduction to gRPC"; Protocol Buffers
documentation, "Overview."
6 Modbus Organization, "MODBUS Application Protocol Specification
V1.1b3," 2012.
7 CAN in Automation (CiA), "CANopen CC - The standardized embedded
network."
8 Texas Instruments, "Introduction to the Controller Area Network
(CAN)," SLOA101B.
9 ISO 14229-1:2020, "Road vehicles - Unified diagnostic services
(UDS) - Part 1: Application layer."
10 ISO 15765-2:2024 / Linux Kernel documentation for ISO-TP,
"Diagnostic communication over Controller Area Network (DoCAN) /
ISO-TP."
-
EtherCAT Technology Group, "EtherCAT Technology."
-
Beckhoff, "EtherCAT System Documentation."
附录 A:术语速查
| 术语 | 含义 |
|---|---|
| RPC | Remote Procedure Call,远程过程调用,把远端服务封装为本地函数调用形式。 |
| IDL | Interface Definition Language,接口定义语言,用于描述服务、方法和数据结构。 |
| Stub / Shim | 客户端代理和服务端分发胶水代码,通常由工具生成。 |
| Marshalling | 把参数序列化为可传输消息的过程。 |
| PDO | Process Data Object,CANopen 中用于实时过程数据的对象。 |
| SDO | Service Data Object,CANopen 中用于访问对象字典的服务。 |
| UDS | Unified Diagnostic Services,统一诊断服务。 |
| ISO-TP | ISO 15765-2 定义的 CAN 传输/网络层协议,用于承载超过单帧的诊断消息。 |
| CAN ID 仲裁 | CAN 控制器依据 ID 优先级在链路层完成非破坏性仲裁。 |