当前位置: 首页 > news >正文

CAN(控制器局域网)工业协议教学文档(一)

文章目录

    • 1. 概述与环境背景
      • 1.1 什么是CAN?
      • 1.2 核心特性与优势
      • 1.3 典型网络环境与硬件
    • 2. 通讯结构与数据帧
      • 2.1 OSI模型中的CAN
      • 2.2 帧类型
      • 2.3 数据帧结构 (CAN 2.0A 标准帧)
    • 3. 数据传输过程
    • 4. Go语言Demo演练
      • 4.1 环境准备 (Linux)
      • 4.2 Go示例代码 (使用 `eliasnaur.com/can`)
      • 4.3 运行
    • 5. Rust语言Demo演练
      • 5.1 环境准备 (Linux)
      • 5.2 Rust示例代码
      • 5.3 运行
    • 6. 总结

1. 概述与环境背景

1.1 什么是CAN?

CAN(Controller Area Network)即控制器局域网,是一种高性能、高可靠性、高实时性的串行通信协议,最初由德国博世公司(BOSCH)于1980年代开发,主要用于解决汽车内部众多电子控制单元(ECU)之间的数据交换问题。

由于其卓越的特性,CAN总线如今已广泛应用于汽车电子、工业自动化(传感器、执行器、PLC)、船舶、医疗设备、航空航天等领域,是现场总线家族中至关重要的一员。

1.2 核心特性与优势

  • 多主结构:网络上任何节点都可以在总线空闲时主动向其他节点发送消息,无主从之分。
  • 基于优先级的总线仲裁:标识符(ID)决定了报文的优先级。ID值越小,优先级越高。当多个节点同时发送时,高优先级的报文会无损地继续传输,低优先级的则主动退出发送,大大提高了总线利用率。
  • 高可靠性
    • CRC校验:强大的循环冗余校验保证数据完整性。
    • 帧格式校验:自动检查报文格式错误。
    • 应答机制:发送节点会确认是否至少有一个节点正确接收了报文。
  • 通信灵活:支持广播(所有节点接收)和点对点(通过硬件过滤)两种模式。
  • 强抗干扰能力:采用差分信号(CAN_H和CAN_L)传输,能有效抑制共模干扰,适合恶劣的电磁环境。

1.3 典型网络环境与硬件

一个典型的CAN网络环境包括:

  1. CAN节点:带有CAN控制器(常集成在MCU中)的设备,如ECU、传感器、工控机等。
  2. CAN收发器:将CAN控制器的逻辑电平转换为CAN总线的差分信号(反之亦然),如TJA1050、MCP2551等。
  3. 物理介质:通常是双绞线(CAN_H和CAN_L)。
  4. 终端电阻:在总线两端(最远距离处)各接一个120Ω的电阻,用于阻抗匹配,消除信号反射。

连接方式:所有节点通过主干线(Trunk Line)支线(Drop Line) 以总线形式并联在一起。


2. 通讯结构与数据帧

2.1 OSI模型中的CAN

CAN协议主要定义了OSI模型中的物理层数据链路层(包括逻辑链路控制子层LLC和介质访问控制子层MAC)。

2.2 帧类型

CAN协议有四种帧类型:

  • 数据帧:用于发送节点向接收节点传输数据。
  • 远程帧:用于请求发送具有相同标识符的数据帧。
  • 错误帧:当节点检测到错误时,向总线发送错误帧,通知其他节点。
  • 过载帧:用于在数据帧或远程帧之间提供额外的延时。

其中最核心的是数据帧

2.3 数据帧结构 (CAN 2.0A 标准帧)

一个标准数据帧由以下7个字段组成,最多可传输8字节数据。

字段长度(bit)说明
SOF (Start Of Frame)1帧起始,显性位(0),用于同步。
Arbitration Field (仲裁场)12ID (11bit) + RTR (1bit)。ID决定优先级,RTR(远程传输请求)用于区分数据帧(0)和远程帧(1)。
Control Field (控制场)6IDE (1bit, 显性0表示标准帧) + r0 (保留位) + DLC (4bit, 数据长度码,0-8)。
Data Field (数据场)0-64 (0-8字节)实际要传输的数据。
CRC Field (CRC场)16CRC序列 (15bit) + CRC界定符 (1bit, 隐性1),用于接收方校验数据错误。
ACK Field (应答场)2ACK Slot (1bit, 发送方发隐性1,接收方用显性0覆盖以示应答) + ACK界定符 (1bit, 隐性1)。
EOF (End Of Frame)7帧结束,7个连续的隐性位(1)。

总线仲裁过程:在发送仲裁场(ID)时,发送节点同时监听总线电平。如果发送的是隐性(1)而监测到的是显性(0),说明有更高优先级的报文正在发送,该节点立即退出发送转为接收模式。这个过程不会损坏高优先级报文的任何位。


3. 数据传输过程

  1. 空闲检测:所有节点持续监听总线。当总线空闲(连续检测到11个隐性位)时,任何节点都可以开始发送。
  2. 仲裁:多个节点同时开始发送时,通过仲裁场(ID)进行“线与”仲裁。优先级高的报文赢得总线使用权。
  3. 数据传输与应答:赢得仲裁的节点继续完成数据帧的发送。所有接收节点进行CRC校验。校验正确的节点会在ACK Slot位期间发送一个显性位(0)来应答发送方。
  4. 错误处理:如果发送节点未收到应答(ACK错误),或CRC校验失败,或其他格式错误,该节点会发送一个错误帧,并自动重发报文(除非错误计数过高导致节点进入“Bus-Off”状态)。
  5. 帧结束:发送EOF字段,总线恢复空闲状态。

4. Go语言Demo演练

在Go语言中,我们可以使用第三方库(如github.com/golang/mock/gomock,但更常用的是如github.com/brutella/caneliasnaur.com/can)来操作SocketCAN(Linux下的CAN接口抽象)。

以下示例假设你使用的是Linux系统,并且已经配置好了虚拟或真实的CAN接口(vcan0)。

4.1 环境准备 (Linux)

# 加载vcan内核模块
sudo modprobe vcan# 创建虚拟CAN接口vcan0
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0# 检查接口
ip link show vcan0

4.2 Go示例代码 (使用 eliasnaur.com/can)

首先初始化Go模块并安装依赖:

go mod init can-demo
go get eliasnaur.com/can

代码:can_send_go.go

package mainimport ("context""fmt""log""time""eliasnaur.com/can"
)func main() {// 打开CAN接口 "vcan0"bus, err := can.OpenBus("vcan0")if err != nil {log.Fatalf("Failed to open CAN bus: %v", err)}defer bus.Close()// 创建一个要发送的帧// ID: 0x123, 数据: "HelloCAN!" (前8字节)frame := can.Frame{ID:     0x123,Length: 8,Data:   can.Data{'H', 'e', 'l', 'l', 'o', 'C', 'A', 'N'},}ticker := time.NewTicker(500 * time.Millisecond)defer ticker.Stop()ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()for {select {case <-ticker.C:// 发送帧err := bus.Send(frame)if err != nil {log.Printf("Failed to send frame: %v", err)} else {fmt.Printf("Sent frame: ID=%03X, Data=%s\n", frame.ID, string(frame.Data[:frame.Length]))}case <-ctx.Done():fmt.Println("Sender stopped.")return}}
}

代码:can_recv_go.go

package mainimport ("fmt""log""eliasnaur.com/can"
)func main() {// 打开CAN接口 "vcan0"bus, err := can.OpenBus("vcan0")if err != nil {log.Fatalf("Failed to open CAN bus: %v", err)}defer bus.Close()fmt.Println("Listening for CAN frames on vcan0...")// 持续读取总线上的帧for {var frame can.Frameerr := bus.Recv(&frame)if err != nil {log.Fatalf("Failed to receive frame: %v", err)}// 打印接收到的帧信息fmt.Printf("Received: ID=%03X, DLC=%d, Data=%X | ASCII: %s\n",frame.ID,frame.Length,frame.Data[:frame.Length],string(frame.Data[:frame.Length]),)}
}

4.3 运行

打开两个终端窗口。

终端1(接收):

go run can_recv_go.go

终端2(发送):

go run can_send_go.go

你将在接收终端看到每秒2条来自发送终端的数据。


5. Rust语言Demo演练

在Rust中,常用的CAN库是socketcan

5.1 环境准备 (Linux)

同Go demo,确保vcan0接口已设置并启动。

5.2 Rust示例代码

首先创建新的Rust项目并添加依赖:

cargo new can-demo-rs
cd can-demo-rs
# 在 Cargo.toml 的 [dependencies] 下添加
# socketcan = "2.2.0"
# tokio = { version = "1", features = ["full"] } # 用于异步示例

代码:src/main.rs (同步版本)

use socketcan::{CanFrame, CanSocket, Socket};
use std::time::Duration;fn main() -> Result<(), Box<dyn std::error::Error>> {// 打开CAN接口 "vcan0"let mut sock = CanSocket::open("vcan0")?;// 创建一个要发送的帧// ID: 0x123, 数据: "HelloRST!" (前8字节)let data = [b'H', b'e', b'l', b'l', b'o', b'R', b'S', b'T'];let frame = CanFrame::new(0x123, &data, false, false)?; // 标准帧,非远程帧loop {// 发送帧sock.write_frame(&frame)?;println!("Sent frame: ID={:03X}, Data={:?}", frame.id(), std::str::from_utf8(&data)?);// 等待1秒std::thread::sleep(Duration::from_secs(1));}
}

代码:接收端 src/bin/can_recv.rs

use socketcan::{CanSocket, Socket};fn main() -> Result<(), Box<dyn std::error::Error>> {// 打开CAN接口 "vcan0"let sock = CanSocket::open("vcan0")?;println!("Listening for CAN frames on vcan0...");loop {// 阻塞读取一帧let frame = sock.read_frame()?;println!("Received: ID={:03X}, DLC={}, Data={:02X?} | ASCII: {}",frame.id(),frame.dlc(),frame.data(),String::from_utf8_lossy(frame.data()));}
}

需要在Cargo.toml中配置为多二进制项目:

[[bin]]
name = "can_recv"
path = "src/bin/can_recv.rs"[[bin]]
name = "can_send"
path = "src/main.rs" # 假设发送代码在main.rs

5.3 运行

编译并运行两个程序。

终端1(接收):

cargo run --bin can_recv

终端2(发送):

cargo run --bin can_send

你将在接收终端看到每秒1条来自发送终端的数据。


6. 总结

本教程系统地介绍了CAN协议的核心概念:

  1. 背景与环境:理解了CAN诞生的原因、其卓越特性及典型应用场景和硬件组成。
  2. 通讯结构:深入学习了数据帧的构成,理解了标识符(ID)决定优先级这一核心机制。
  3. 传输过程:掌握了从仲裁、传输、应答到错误处理的完整数据流。
  4. 实战演练:通过Go和Rust两种现代语言的Demo,学会了如何使用代码在Linux系统上通过SocketCAN接口进行最基本的CAN报文收发。

下一步

  • 探索扩展帧(29位ID)
  • 学习CAN高层协议,如CANopen或J1939,它们定义了设备对象字典、通信参数等,是实际工业应用的基础。
  • 使用CAN分析仪(如PCAN, USB-CAN) 连接真实硬件,捕获和分析总线数据。
  • 深入研究错误状态和故障 confinement机制
http://www.dtcms.com/a/389035.html

相关文章:

  • PHP基础-变量与常量(第八天)
  • SQ01,SQ02,SQ03,SE93事务码配置
  • AI提示词Excel 表格提取数据准确度处理
  • DeviceNet 转 EtherNet/IP 实现罗克韦尔 PLC 与库卡机器人在汽车白车身焊接的微秒级数据同步协作案例
  • GPT-5 vs Gemini 2.5 Pro:两大AI旗舰模型深度技术对比
  • 31、GPT核心引擎完整手工构建:从算法原理到工程优化(Generative Pre-trained Transformer)
  • MySQL MHA 完整配置与故障后原主库还原指南
  • 栈-946.验证栈序列-力扣(LeetCode)
  • spring boot3.0整合rabbitmq3.13
  • Scrapy爬虫利器:CrawlSpider详解
  • 从零开始学Flink:数据源
  • GRPO算法复现
  • AI+Flask博客项目实战提示词笔记 20250918
  • 无人设备遥控器之时间戳技术篇
  • 模块四 展望微服务
  • RN 添加 <NavigationContainer>组件报错
  • 深入理解 AVL 树
  • 软考中级习题与解答——第八章_计算机网络(2)
  • FinalShell远程连接CentOS下方文件列表信息不显示且刷新报空指针异常
  • 贪心算法应用:线性规划贪心舍入问题详解
  • 设计模式学习笔记(二)
  • 轻量化录屏插件,MP4输出格式
  • 静态代理 设计模式
  • Salesforce知识点:触发器:自动化业务逻辑的核心工具详解
  • CentOS 8.5部署Zabbix6.0 agent2端
  • 【TestCenter】设置DHCP Option
  • Jenkins 安全清理孤立工作区(workspace)的 Shell 脚本:原理、实现与实战
  • WebDancer论文阅读
  • Node.js、npm 和 npx:前端开发的三剑客
  • Node.js 创建 UDP 服务