机器人上层业务真正需要的不是某一个驱动,而是一组稳定、可测试、可替换的通信语义。gocan 的目标,是让同一套业务代码可以运行在 Linux SocketCAN、Windows PCANBasic,以及基于 CANable 2.0 的 SLCAN-FD 设备上。
问题不是“能发帧”,而是“如何保持一致”
不同平台的 CAN API 在通道命名、帧结构、超时、错误码、时间戳与并发模型上都有差异。直接在业务层调用驱动,短期看最快,长期却会让设备协议、操作系统和硬件接口紧密耦合。
驱动层负责解释平台,上层只负责理解 CAN 帧和业务协议。
因此,核心抽象只保留两件事:总线和帧。平台相关配置通过后端配置对象进入,而不是泄漏到所有调用位置。
从最小 Bus / Frame API 开始
type Frame struct {
ID uint32
Data []byte
Extended bool
FD bool
BRS bool
}
type Bus interface {
Open(ctx context.Context) error
Send(ctx context.Context, frame Frame) error
Receive(ctx context.Context) (Frame, error)
Close() error
}
这个接口刻意不暴露 PCAN handle、Linux socket 文件描述符或串口对象。业务协议只操作 Frame,从而可以用同一套逻辑实现设备发现、命令发送和响应匹配。
帧模型必须明确表达 CAN FD
仅靠 len(Data) > 8 推断 CAN FD 不够可靠。FD 帧还可能包含 BRS,扩展帧 ID 也必须独立表达。显式字段能避免后端做猜测,也便于日志和测试准确还原通信过程。
后端驱动如何隔离
| 后端 | 主要环境 | 关键差异 |
|---|---|---|
| SocketCAN | Linux | 网络接口模型,支持经典 CAN 与 CAN FD |
| PCANBasic | Windows | DLL 与通道常量,初始化参数由驱动定义 |
| SLCAN-FD | Windows / Linux | 串口协议,需要处理文本命令、波特率与帧编码 |
每个后端都实现相同接口,并在内部完成帧转换。构造函数可以根据 URI 或配置类型选择后端,例如 socketcan://can0、pcan://PCAN_USBBUS1 或 slcanfd:///dev/ttyACM0。
并发读取与请求响应匹配
机器人设备经常同时存在周期状态帧、命令响应和错误上报。若每个请求都直接调用底层 Receive,不同 goroutine 会竞争同一数据流,导致响应被错误消费。
更稳定的结构是只保留一个读取循环:
- 后端读取循环持续收帧;
- 帧进入统一分发器;
- 订阅者按 ID、命令字或自定义谓词匹配;
- 未匹配帧进入状态流或日志流。
sub := bus.Subscribe(func(f Frame) bool {
return f.ID == responseID && len(f.Data) > 0 && f.Data[0] == command
})
defer sub.Close()
if err := bus.Send(ctx, request); err != nil {
return Frame{}, err
}
select {
case frame := <-sub.C():
return frame, nil
case <-ctx.Done():
return Frame{}, fmt.Errorf("wait response: %w", ctx.Err())
}
错误必须能指导下一步动作
send failed 对现场调试几乎没有帮助。错误信息应携带后端、通道、操作、帧 ID 和原始错误,并区分超时、总线关闭、设备断开、参数不支持等类别。
测试策略
硬件库不能只依赖真实设备测试。建议分三层:
- 纯单元测试:帧编码、DLC 映射、错误转换、过滤器。
- 虚拟总线测试:Linux 使用 vcan 验证收发、并发与超时。
- 硬件矩阵测试:PCAN、CANable、实际机器人设备验证驱动差异。
最终得到的不是“封装”,而是可迁移能力
统一通信层让机器人协议实现、设备管理和控制台日志不再关心具体驱动。新增后端时,只需要实现总线接口并通过一致性测试;新增设备型号时,则可以复用稳定的收发、超时与诊断机制。
了解这套通信层如何进入多型号机器人控制台。
Maestro 统一控制台 →