CAN总线入门


引言

「控制器局域网络」(Controller Area Network,简称CAN)是一种由博世公司于1986年开发的通信协议,旨在解决汽车内部复杂布线问题,现已广泛应用于工业自动化、医疗设备等多个领域。CAN总线的设计优化了数据传输的可靠性和实时性,同时具备高度灵活的网络结构。

CAN总线入门


CAN总线:汽车的“神经系统”

「在汽车领域,控制器局域网络(CAN总线)被比作汽车的“神经系统”,负责实现车辆内部各个电子控制单元(ECUs)之间的通信。」 这些ECUs类似于人体的各个部分,通过CAN总线相互连接,使得一个部分的感知信息可以共享给其他部分。例如,一个现代汽车可能包含高达70个ECUs,如引擎控制单元、气囊、音响系统等,它们都可能需要与网络中的其他部分共享信息。

CAN总线入门


CAN总线系统的通信机制

CAN总线系统使每个ECU能够与所有其他ECUs通信,无需复杂的专用线路。具体来说,每个ECU可以准备并广播信息(如传感器数据)到CAN总线(包含两条线:CAN低电平和CAN高电平)。广播的数据被网络上的所有其他ECUs接收,每个ECU随后可以检查这些数据并决定是否接受。

CAN总线入门


CAN总线入门


CAN总线入门

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总线入门


工作原理

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总线的四大优势

  1. 「简单低成本」:ECUs通过单一CAN系统通信,避免了复杂的直接模拟信号线,减少了错误、重量、布线和成本。

    CAN总线入门


  1. 「中央化系统网络」:CAN总线提供“单点进入”,便于与所有网络ECUs通信,实现中央诊断、数据记录和配置。

    CAN总线入门


  1. 「抗电磁干扰强」:该系统对电气干扰和电磁干扰具有强大的抵抗能力,非常适合安全关键应用(如车辆)。

    CAN总线入门


  1. 「帧ID优先级系统高效」:CAN帧通过ID进行优先级排序,使得优先级最高的数据可以立即访问总线,不会中断其他帧或引起CAN错误。

    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)通常分为以下几种:

  1. 「ERROR-ACTIVE」:错误活动状态。此时,CAN 控制器正常运行并且能够发送和接收报文。若发生错误,它会增加内部错误计数器,但在错误计数器没有达到错误限制之前,控制器仍处于活动状态。

  2. 「ERROR-PASSIVE」:错误被动状态。此时,CAN 控制器仍然能够发送和接收报文,但其错误计数器已超过一个特定阈值。控制器在这种状态下发送报文时,不会主动传输错误帧(error frames),而是被动等待总线空闲。

  3. 「BUS-OFF」:总线关闭状态。此时,CAN 控制器的错误计数器已超过规定的最大值,因此停止参与总线通信。控制器必须通过软件重启或等待一段时间后自动重启,才能重新参与总线通信。

  4. 「STOPPED」:停止状态。CAN 控制器在这个状态下不会发送或接收任何报文,通常这是由用户主动设置的。

  5. 「SLEEPING」:休眠状态。在这个状态下,CAN 控制器的功耗较低,并且仅在收到特定的唤醒信号时恢复到活动状态。

在 Linux 系统中,你可以通过 ip link show 命令来查看 CAN 接口的状态。例如:

ip link show can0

输出会显示 CAN 接口的当前状态,如 ERROR-ACTIVEERROR-PASSIVEBUS-OFF。了解这些状态可以帮助你诊断和解决 CAN 总线通信中的问题。

判断当前正在使用哪个 CAN 接口

下面是几种方法来帮助你确定当前使用的 CAN 接口:

  1. 「检查接口状态」:查看接口的 stateberr-counter 来判断哪个接口正在活动传输。stateERROR-ACTIVE,表示处于活动状态。berr-counter 的值,是传输过程中记录的错误数量。

  2. 「查看接口流量」:可以使用 ip -s -d link show can0  命令来查看接口的流量统计信息,例如接收和发送的帧数。

  3. 「通过 candump 工具捕获数据」:使用 candump 工具来捕获并查看 CAN 总线上的数据流量:

candump can0

通过查看哪个接口输出的数据流量,可以判断当前使用的是哪个接口。

C++ 示例代码

使用 C++ 实现 CAN 总线数据读写

以下是一个完整的示例,展示如何使用C++在Ubuntu上通过SocketCAN框

架来读取和发送CAN消息:

#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

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;
}

编译和运行

  1. 「保存代码」:将上面的代码保存为can_example.cpp
  2. 「编译代码」:使用以下命令编译:
g++ -o can_example can_example.cpp
  1. 「运行代码」:在运行之前,确保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接口的功能性。

参考资料:

https://www.csselectronics.com/pages/can-bus-simple-intro-tutorial

https://www.ni.com/en/shop/seamlessly-connect-to-third-party-devices-and-supervisory-system/controller-area-network–can–overview.html

https://www.intechopen.com/chapters/77277

原文始发于微信公众号(AI酷应用):CAN总线入门

版权声明:admin 发表于 2024年5月16日 下午11:44。
转载请注明:CAN总线入门 | CTF导航

相关文章