【通信协议】CAN总线通信协议

1. CAN总线简介

1.1 什么是CAN总线

CAN总线中文全称是控制器局部网络总线(Controller Area Net)。该协议是一种多主机串行异步通信总线,允许网络中的各个节点(设备)进行无中心控制的通信。

所谓无中心控制的通信,即在CAN总线网络中,没有一个中央控制器来管理所有节点的通信。相反,每个节点都是平等的,无需等待中央控制器的指令。任何节点在需要时都可以发送报文。并且进行非破坏性总线访问技术的传输管理。

所谓非破坏性总线访问技术,即当多个节点同时尝试发送数据时,CAN总线使用仲裁机制决定哪个节点的报文优先发送。这个过程是通过比较报文的标识符(ID)来完成的,优先级高的报文会先发送,一旦总线上高优先级报文被发送,低优先级的报文就会停止发送。并且,如果某个节点出现故障,只要它不主动发送错误信号干扰总线,就不会影响整个网络的通信。其他节点仍然可以正常发送和接收数据。如此,保证了高优先级的报文不会因总线竞争或总线上某一节点故障而丢失。
在这里插入图片描述

1.2 CAN总线工作原理

CAN总线的通信过程被分为4个阶段:

  • 发送报文:每个节点都可以在总线上发送报文。在发送前,节点需要判断总线是否空闲。
  • 仲裁机制:如果多个节点同时发送报文,总线则会使用仲裁机制决定哪个报文优先发送。仲裁是基于报文标识符(ID)的,ID越小优先级越高。
  • 数据传输:优先级最高的报文在总线上传输时,其他节点暂停发送并等待下一次总线空闲。
  • 错误检测与处理:CAN总线内置多种错误检测机制,包括CRC校验、位填充、帧校验和应答错误等。任何节点检测到错误都会中断当前传输并重发报文。

通俗来讲,CAN通信的过程可以理解为一场电话会议,电话能接通的前提是,此时会议上没有在进行通话,当一个人电话接通时,其它人就只能听,直到这个人通话结束,下一个人才能允许通话;当多个人同时拨打电话时,则会根据一定规则决定哪个人先通话。在通话过程中,讲话人会确定听话人是否成功接收到消息,如果说话人传递的信息有误,听话人会及时指出错误。

2. 物理层CAN总线协议

2.1 CAN总线的构成

CAN总线使用双绞线进行差分电压传输,两条信号线分别被称为CAN高和CAN低,两端有防止信号反射的终端电阻,大小通常是120Ω,因为双绞线的特性阻抗是120Ω,阻抗匹配能够最大程度地减少信号反射。.
能接入CAN总线的电控单元,通常由微控制器、CAN控制器和CAN收发器三个部分组成。

在这里插入图片描述

2.2 CAN总线电平信号

在这里插入图片描述
CAN2.0A和CAN2.0B标准规定,当高速CAN总线空闲时,CAN高和CAN低上的电压为2.5V,此时两者差值为0V,表示逻辑“1”,为逻辑隐性;当高速CAN总线有信号传输时,两条导线上的电压就会出现差异,CAN高上的电压为3.5V和CAN低上的电压为1.5V,此时两者差值为2V,表示逻辑“0”,为逻辑显性。

这里有两个问题需要着重说明:

  • 1.为什么需要用双绞线和差分信号对CAN通信传输?
    因为双绞线可以提高信号传输的抗干扰性,减小误差和噪声带来的干扰,当外部有干扰出现时,电压会出现突变,但是由于使用的是差分信号,电压会同时在CAN高和CAN低上出现突变,两条信号线的干扰会一定程度抵消。
    在这里插入图片描述
  • 2.为什么用逻辑“0”表示逻辑显性
    因为CAN通信协议使用“线与”规则进行冲突制裁,当多个CAN信号同时发送时,有的发1,有的发0,当两条信号线上为1和0电平、0和1电平、0和0电平时,通过线与规则,都可得逻辑0,看上去像是1被0覆盖了,因此规定为逻辑显性;只有当两条信号线上为1和1电平时,通过线与规则,才可得逻辑1,因此规定为逻辑隐性。

2.3 CAN总线的同步

CAN总线是一种异步通信协议,对异步串行通信而言,接收器内部会定时对数据帧采样,采样点会按照约定的频率采样数据,但是根据波特率采

以下是一个简单的C++程序,用于接收CAN0的报文并解析CAN报文: ```c++ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> #include <net/if.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <linux/can.h> #include <linux/can/raw.h> #define CAN_INTERFACE "can0" // CAN接口名称 #define CAN_ID 0x0CF02A59 // CAN报文ID int main() { int s; // 套接字文件描述符 struct sockaddr_can addr; // CAN地址结构体 struct ifreq ifr; // CAN接口请求结构体 struct can_frame frame; // CAN帧结构体 // 打开套接字 s = socket(PF_CAN, SOCK_RAW, CAN_RAW); if (s < 0) { perror("socket"); return 1; } // 设置CAN接口名称 strcpy(ifr.ifr_name, CAN_INTERFACE); ioctl(s, SIOCGIFINDEX, &ifr); // 设置CAN地址 addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; // 绑定CAN套接字 if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; } // 开始接收CAN报文并解析 while (1) { int nbytes = read(s, &frame, sizeof(struct can_frame)); if (nbytes < 0) { perror("read"); return 1; } if (frame.can_id == CAN_ID) { // 解析CAN报文 uint32_t x = ((uint32_t)frame.data[0] << 16) | ((uint32_t)frame.data[1] << 8) | frame.data[2]; uint32_t y = ((uint32_t)(frame.data[2] & 0xf) << 16) | ((uint32_t)frame.data[3] << 8) | frame.data[4]; uint32_t z = ((uint32_t)frame.data[5] << 16) | ((uint32_t)frame.data[6] << 8) | frame.data[7]; printf("x = %d, y = %d, z = %d\n", x, y, z); } } // 关闭套接字 close(s); return 0; } ``` 在上面的代码中,我们使用了Linux下的Socket编程接口,通过CAN_RAW协议CAN接口can0上创建了一个套接字,然后绑定到这个套接字上,接收CAN报文,并解析CAN报文中的数据。为了解析CAN报文,我们使用了位运算符和类型转换。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值