CAN总线入门
引言
「控制器局域网络」 (Controller Area Network,简称CAN)是一种由博世公司于1986年开发的通信协议,旨在解决汽车内部复杂布线问题,现已广泛应用于工业自动化、医疗设备等多个领域。CAN总线的设计优化了数据传输的可靠性和实时性,同时具备高度灵活的网络结构。
CAN总线:汽车的“神经系统”
「在汽车领域,控制器局域网络(CAN总线)被比作汽车的“神经系统”,负责实现车辆内部各个电子控制单元(ECUs)之间的通信。」 这些ECUs类似于人体的各个部分,通过CAN总线相互连接,使得一个部分的感知信息可以共享给其他部分。例如,一个现代汽车可能包含高达70个ECUs,如引擎控制单元、气囊、音响系统等,它们都可能需要与网络中的其他部分共享信息。
CAN总线系统的通信机制
CAN总线系统使每个ECU能够与所有其他ECUs通信,无需复杂的专用线路。具体来说,每个ECU可以准备并广播信息(如传感器数据)到CAN总线(包含两条线:CAN低电平和CAN高电平)。广播的数据被网络上的所有其他ECUs接收,每个ECU随后可以检查这些数据并决定是否接受。
CAN总线的物理层和数据链路层
技术上,控制器局域网络由数据链路层和物理层描述。在高速CAN中,ISO 11898-1规定了数据链路层,而ISO 11898-2则规定了物理层。
CAN总线物理层规范如下:
「信号传输」 :
-
差分信号:CAN总线使用差分信号传输技术,显著提高信号在噪声环境下的抗干扰能力。
「物理媒介和连接器」 :
-
高速CAN(ISO 11898-2):CAN节点必须通过两线总线连接,波特率可达1 Mbit/s(经典CAN)或5 Mbit/s(CAN FD)。
-
低速/容错CAN(ISO 11898-3):提供更高的容错能力,使用两根线的非屏蔽双绞线,通信速率最高可达125 kbps。
「电气特性」 :
-
电压水平:CAN总线的逻辑1(空闲状态)和逻辑0(主动状态)通过不同的电压水平区分。
-
终端电阻:为了防止信号反射,通常在CAN网络的两端加入120欧姆的终端电阻。
工作原理
CAN总线通过数据帧的结构化设计实现高效的数据传输。以下是其关键工作原理:
「1. 数据帧结构」 :
-
「起始位」 :标记数据帧的开始。
-
「仲裁字段」 (包含ID和RTR位):标识符(ID)决定了消息的优先级,ID越小,优先级越高。RTR(远程传输请求)位用于区分是数据帧还是请求帧。
-
「控制字段」 :包含数据长度代码(DLC),指示数据字段的字节数。
-
「数据字段」 :承载实际的数据,最多可以包含8字节。
-
「CRC校验序列」 :用于错误检测。
-
「确认位」 :接收方用来表示已成功接收数据帧。
-
「结束位」 :标记数据帧的结束。
「2. 消息发送和接收」 :
-
当一个节点需要发送数据时,它将数据帧放置到总线上。如果总线空闲,数据帧会被发送;如果总线正在被使用,节点会等待直到总线空闲。
-
所有连接到总线的节点都会监听传输的数据帧,并根据自己的需要处理这些帧。如果多个节点同时尝试发送数据,ID较小的帧将获得发送优先权,这种机制称为“非破坏性仲裁”。
「3. 错误处理」 :CAN 总线具备高级的错误检测和处理机制,包括:
-
「位填充」 :为了保持总线上的同步,如果在数据帧中连续出现五个相同的位,则自动插入一个相反的位。
-
「帧检查」 :接收节点会检查数据帧的格式和CRC校验序列,确保数据的完整性。
-
「错误标志」 :如果检测到错误,节点会发送错误帧,这会导致当前传输被中断并重新开始。
协议标准
「CAN 2.0A 和 CAN 2.0B」 :
-
「CAN 2.0A」 :使用11位的标识符(ID),适用于较小规模的网络。
-
「CAN 2.0B」 :引入29位标识符的扩展帧,允许更多设备在同一网络上通信,同时保持与旧标准的兼容性。
「CAN FD(Flexible Data-Rate)」 :
-
「扩展数据传输率和负载」 :允许在数据传输阶段使用更高的比特率,同时数据段的长度也从8字节增加到64字节。
-
「高效的资源利用」 :通过动态调整比特率,优化网络的数据吞吐量和带宽利用率。
CAN总线的四大优势
-
「简单低成本」 :ECUs通过单一CAN系统通信,避免了复杂的直接模拟信号线,减少了错误、重量、布线和成本。
-
「中央化系统网络」 :CAN总线提供“单点进入”,便于与所有网络ECUs通信,实现中央诊断、数据记录和配置。
-
「抗电磁干扰强」 :该系统对电气干扰和电磁干扰具有强大的抵抗能力,非常适合安全关键应用(如车辆)。
-
「帧ID优先级系统高效」 :CAN帧通过ID进行优先级排序,使得优先级最高的数据可以立即访问总线,不会中断其他帧或引起CAN错误。
Ubuntu上的监控和检测工具
SocketCAN
「SocketCAN」 是一个开源的软件框架,将CAN通信集成到Linux操作系统中,使得开发者可以使用标准的网络编程方式来操作CAN设备。以下是其主要特性和使用方式:
「1. 安装和配置」 :
-
在Ubuntu系统上安装
can-utils
:
sudo apt-get install can-utils
「2. 创建和配置虚拟CAN网络接口」 :
-
创建和启动虚拟CAN接口:
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
「3. 使用SocketCAN工具监控CAN数据」 :
-
使用
candump
监听CAN网络上的数据:
candump vcan0
-
使用
cansend
向CAN网络发送消息:
cansend vcan0 123#1122334455667788
Python-CAN
「Python-CAN」 是一个强大的库,允许开发者使用Python语言来控制和监控CAN网络。以下是其安装和基本使用方法:
「1. 安装Python-CAN」 :
-
使用pip安装:
pip install python-can
「2. 配置CAN接口」 :
-
确保CAN接口已正确配置并启用:
sudo ip link set vcan0 type vcan bitrate 500000
sudo ip link set vcan0 up
「3. 发送和接收CAN消息」 :
-
发送CAN消息的Python代码示例:
import can
def send_message():
bus = can.interface.Bus(channel='vcan0', bustype='socketcan')
message = can.Message(arbitration_id=0x123, data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], is_extended_id=False)
bus.send(message)
print("Message sent on {}".format(bus.channel_info))
if __name__ == "__main__":
send_message()
-
接收CAN消息的Python代码示例:
import can
def receive_message():
bus = can.interface.Bus(channel='vcan0', bustype='socketcan')
message = bus.recv() # 阻塞直到接收到消息
print("Received message: {}".format(message))
if __name__ == "__main__":
receive_message()
查看 CAN 的状态
CAN(Controller Area Network)的状态(state)通常分为以下几种:
-
「ERROR-ACTIVE」 :错误活动状态。此时,CAN 控制器正常运行并且能够发送和接收报文。若发生错误,它会增加内部错误计数器,但在错误计数器没有达到错误限制之前,控制器仍处于活动状态。
-
「ERROR-PASSIVE」 :错误被动状态。此时,CAN 控制器仍然能够发送和接收报文,但其错误计数器已超过一个特定阈值。控制器在这种状态下发送报文时,不会主动传输错误帧(error frames),而是被动等待总线空闲。
-
「BUS-OFF」 :总线关闭状态。此时,CAN 控制器的错误计数器已超过规定的最大值,因此停止参与总线通信。控制器必须通过软件重启或等待一段时间后自动重启,才能重新参与总线通信。
-
「STOPPED」 :停止状态。CAN 控制器在这个状态下不会发送或接收任何报文,通常这是由用户主动设置的。
-
「SLEEPING」 :休眠状态。在这个状态下,CAN 控制器的功耗较低,并且仅在收到特定的唤醒信号时恢复到活动状态。
在 Linux 系统中,你可以通过
ip link show
命令来查看 CAN 接口的状态。例如:
ip link show can0
输出会显示 CAN 接口的当前状态,如
ERROR-ACTIVE
、
ERROR-PASSIVE
或
BUS-OFF
。了解这些状态可以帮助你诊断和解决 CAN 总线通信中的问题。
判断当前正在使用哪个 CAN 接口
下面是几种方法来帮助你确定当前使用的 CAN 接口:
-
「检查接口状态」 :查看接口的
state
和berr-counter
来判断哪个接口正在活动传输。state
是ERROR-ACTIVE
,表示处于活动状态。berr-counter
的值,是传输过程中记录的错误数量。 -
「查看接口流量」 :可以使用
ip -s -d link show can0
命令来查看接口的流量统计信息,例如接收和发送的帧数。 -
「通过
candump
工具捕获数据」 :使用candump
工具来捕获并查看 CAN 总线上的数据流量:
candump can0
通过查看哪个接口输出的数据流量,可以判断当前使用的是哪个接口。
C++ 示例代码
使用 C++ 实现 CAN 总线数据读写
以下是一个完整的示例,展示如何使用C++在Ubuntu上通过SocketCAN框
架来读取和发送CAN消息:
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW); // 创建CAN套接字
if (s < 0) {
perror("Socket");
return 1;
}
struct sockaddr_can addr;
struct ifreq ifr;
strcpy(ifr.ifr_name, "vcan0");
ioctl(s, SIOCGIFINDEX, &ifr); // 指定vcan0接口
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { // 绑定套接字
perror("Bind");
return 1;
}
struct can_frame frame;
frame.can_id = 0x123;
frame.can_dlc = 8;
frame.data[0] = 0x11;
frame.data[1] = 0x22;
frame.data[2] = 0x33;
frame.data[3] = 0x44;
frame.data[4] = 0x55;
frame.data[5] = 0x66;
frame.data[6] = 0x77;
frame.data[7] = 0x88;
if (write(s, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) { // 发送CAN帧
perror("Write");
return 1;
}
std::cout << "Message sent successfully" << std::endl;
if (read(s, &frame, sizeof(struct can_frame)) < 0) { // 接收CAN帧
perror("Read");
return 1;
}
std::cout << "Received message with CAN ID " << std::hex << frame.can_id << std::endl;
std::cout << "Data: ";
for (int i = 0; i < frame.can_dlc; ++i) {
std::cout << std::hex << static_cast<int>(frame.data[i]) << " ";
}
std::cout << std::endl;
close(s); // 关闭套接字
return 0;
}
编译和运行
-
「保存代码」 :将上面的代码保存为
can_example.cpp
。 -
「编译代码」 :使用以下命令编译:
g++ -o can_example can_example.cpp
-
「运行代码」 :在运行之前,确保
vcan0
接口已经配置并启动。如果使用的是虚拟CAN接口,可以使用以下命令设置并启动:
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
然后运行编译好的程序:
./can_example
应该能看到显示:
Message sent successfully
另起一个终端会话往
vcan0
接口发送一组数据:
cansend vcan0 123#1122334455667788
应该就能看到输出并程序退出:
Received message with CAN ID 123
Data: 11 22 33 44 55 66 77 88
这个简单的程序将会发送一个CAN帧并试图读取任何响应的CAN帧,用于在开发初期验证CAN接口的功能性。
参考文献