【Linux应用】CAN总线编程

前言

CAN是控制器局域网络(Controller Area Network,CAN)的简称,由德国BOSCH公司开发,并最终成为国际标准(ISO 11898-1)。CAN总线主要应用于工业控制和汽车电子领域,是国际上应用最广泛的现场总线之一。

1 CAN总线简介

CAN总线是一种串行通信协议,能有效地支持具有很高安全等级的分布实时控制。CAN总线的应用范围很广,从高速的网络到低价位的多路接线都可以使用CAN。在汽车电子行业里,使用CAN 连接发动机的控制单元、传感器、防刹车系统等,传输速度可达1Mbps。
在这里插入图片描述
与前面介绍的一般通信总线相比,CAN总线的数据通信具有突出的可靠性、实时性和灵活性,在汽车领域的应用最为广泛,世界上一些著名的汽车制造厂商都采用CAN总线来实现汽车内部控制系统与各检测和执行机构之间的数据通信。目前,CAN总线的应用范围已不仅仅局限于汽车行业,而且已经在自动控制、航空航天、航海、过程工业、机械工业、纺织机械、农用机械、机器人、数控机床、医疗器械及传感器等领域中得到了广泛应用。CAN总线规范从最初的CAN 1.2规范(标准格式)发展为兼容CAN 1.2 规范的CAN 2.0规范(CAN 2.0A为标准格式,CAN 2.0B为扩展格式),目前应用的CAN器件大多符合CAN 2.0规范。

2 CAN总线的工作原理

当CAN总线上的节点发送数据时,以报文形式广播给网络中的所有节点,总线上的所有节点都不使用节点地址等系统配置信息,只根据每组报文开头的 11 位标识符(CAN 2.0A 规范)解释数据的含义来决定是否接收。这种数据收发方式称为面向内容的编址方案。
当某个节点要向其他节点发送数据时,这个节点的处理器将要发送的数据和自己的标识符传送给该节点的CAN总线接口控制器,并处于准备状态;当收到总线分配时,转为发送报文状态。数据根据协议组织成一定的报文格式后发出,此时网络上的其他节点处于接收状态。处于接收状态的每个节点对接收到的报文进行检测,判断这些报文是否是发给自己的以确定是否接收。
由于CAN总线是一种面向内容的编址方案,因此很容易建立高水准的控制系统并灵活地进行配置我们可以很容易地在CAN总线上加进一些新节点而无须在硬件或软件上进行修改。
当提供的新节点是纯数据接收设备时,数据传输协议不要求独立的部分有物理目的地址。此时允许分布过程同步化,也就是说,当总线上的控制器需要测量数据时,数据可由总线上直接获得,而无需每个控制器都有自己独立的传感器。

3 CAN总线的工作特点

CAN总线的有以下三方面特点:
可以多主方式工作,网络上的任意节点均可以在任意时刻主动地向网络上的其他节点发送信息,而不分主从,通信方式灵活。网络上的节点(信息)可分成不同的优先级,可以满足不同的实时要求。采用非破坏性位仲裁总线结构机制,当两个节点同时向网络上传送信息时,优先级低的节点主动停止数据发送,而优先级高的节点可不受影响地继续传输数据。

4 CAN总线协议层次结构

与前面介绍的简单总线逻辑不同,CAN是一种复杂逻辑的总线结构。从层次上可以将 CAN 总线划分为三个不同层次:
(1) 物理层
在物理层中定义实际信号的传输方法,包括位的编码和解码、位的定时和同步等内容,作用是定义不同节点之间根据电气属性如何进行位的实际传输。
在物理连接上,CAN总线结构提供两个引脚:CANH和CANL,总线通过CANH和CANL之间的差分电压完成信号的位传输。
在不同系统中,CAN总线的位速率不同;在系统中,CAN总线的位速率是唯一的,并且是固定,这需要对总线中的每个节点配置统一的参数。
(2) 传输层
传输层是CAN总线协议的核心。传输层负责把接收到的报文提供给对象层,以及接收来自对象层的报文。传输层负责位的定时及同步、报文分帧、仲裁、应答、错误检测和标定、故障界定。
(3) 对象层
在对象层中可以为远程数据请求以及数据传输提供服务,确定由实际要使用的传输层接收哪一个报文,并且为恢复管理和过载通知提供手段。

5 CAN总线的报文结构

CAN 总线上的报文传输由以下 4 个不同的帧类型表示和控制。

(1) 数据帧

数据帧携带数据从发送器至接收器。总线上传输的大多是这种帧。从标识符长度上,又可以把数据帧分为标准帧(11 位标识符)和扩展帧(29 位标识符)。
数据帧由 7 个不同的位场组成:帧起始、仲裁场、控制场、数据场、CRC场、应答场、帧结束。其中,数据场的长度为0~8个字节。标识符位于仲裁场中,报文接收节点通过标识符进行报文滤波。帧结构如图所示
在这里插入图片描述

(2) 远程帧

由总线上的节点发出,用于请求其他节点发送具有同一标识符的数据帧。当某个节点需要数据时,可以发送远程帧请求另一节点发送相应数据帧。与数据帧相比,远程帧没有数据场,结构如图所示。
在这里插入图片描述

(3) 错误帧

任何单元,一旦检测到总线错误就发出错误帧。错误帧由两个不同的场组成,第一个场是由不同站提供的错误标志的叠加(错误标志),第二个场是错误界定符。
在这里插入图片描述

(4) 过载帧

过载帧用于在先行的和后续的数据帧(或远程帧)之间提供附加延时。过载帧包括两个场:过载标志和过载界定符。

6.CAN链路层处理数据

CAN-bus整个链路层处理数据的流程是:
在这里插入图片描述

7 CAN接口配置

在 Linux 系统中,CAN总线接口设备作为网络设备被系统进行统一管理。在控制台下,CAN总线的配置和以太网的配置使用相同的命令。
在这里插入图片描述
在上面的结果中,eth0 和 eth1 设备为以太网接口,can0 设备为 CAN 总线接口。接下来使用 ip 命 令来配置 CAN 总线的位速率:

ip link set can0 type cantq 125 prop-seg 6phase-seg1 7 phase-seg2 2 sjw 1
ip link set can0 type can bitrate 500000 triple-sampling on
ifconfig can0 up 
//发送数据
cansend can0 145#1122334455667788
cansend can0  0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 -i 0x66 --loop=20
//-i表示CAN_ID,--loop 表示发送20个包。
candump can0

当设置完成后,可以通过下面的命令查询 can0 设备的参数设置:

ip -details link show can0

关于cansend用法见下
在这里插入图片描述

8 CAN总线应用开发

(1) 初始化

SocketCAN 中大部分的数据结构和函数在头文件 linux/can.h 中进行了定义。CAN 总线套接字的 创建采用标准的网络套接字操作来完成。网络套接字在头文件 sys/socket.h 中定义。套接字的初始化方法如下:

  int s; 
  struct sockaddr_can addr; 
  struct ifreq ifr; 

  s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//创建 SocketCAN 套接字
  strcpy(ifr.ifr_name, "can0" ); 
  
  ioctl(s, SIOCGIFINDEX, &ifr);//指定 can0 设备 
  addr.can_family = AF_CAN; 
  addr.can_ifindex = ifr.ifr_ifindex; 

  bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与 can0 绑定

(2) 数据发送

在数据收发的内容方面,CAN总线与标准套接字通信稍有不同,每一次通信都采用 can_ frame 结 构体将数据封装成帧。结构体定义如下:

struct can_frame {
	canid_t can_id;//CAN 标识符 
	__u8 can_dlc;//数据场的长度 
	__u8 data[8];//数据 
};

can_id为帧的标识符,如果发出的是标准帧,就使用can_id的低 11 位;如果为扩展帧,就使用0~28位。can_id的第29、30、31位是帧的标志位,29位标识是数据帧还是错误消息,30位说明是否是远程帧,31位说明是标准帧还是扩展帧。用来定义帧的类型,定义如下:

#define CAN_EFF_FLAG 0x80000000U //扩展帧的标识 
#define CAN_RTR_FLAG 0x40000000U //远程帧的标识 
#define CAN_ERR_FLAG 0x20000000U //错误帧的标识,用于错误检查

数据发送使用 write 函数来实现。如果发送的数据帧(标识符为 0x123)包含单个字节(0xAB)的数据,可采用如下方法进行发送:

  struct can_frame frame; 
  frame.can_id = 0x123;//如果为扩展帧,那么 frame.can_id = CAN_EFF_FLAG | 0x123; 
  frame.can_dlc = 1; //数据长度为 1 
  frame.data[0] = 0xAB; //数据内容为 0xAB 
  
  int nbytes = write(s, &frame, sizeof(frame)); //发送数据 
  
  if(nbytes != sizeof(frame)) //如果nbytes不等于帧长度,就说明发送失败 
   printf("Error\n!");

如果要发送远程帧(标识符为 0x123),可采用如下方法进行发送:

 	struct can_frame frame; 
	frame.can_id = CAN_RTR_FLAG | 0x123; 
	write(s, &frame, sizeof(frame));

(3) 数据接收

数据接收使用 read 函数来完成,实现如下:

struct can_frame frame; 
int nbytes = read(s, &frame, sizeof(frame));

当然,套接字数据收发时常用的send、sendto、sendmsg以及对应的 recv 函数也都可以用于 CAN总线数据的收发。

(4) 错误处理

当帧接收后,可以通过判断 can_id 中的 CAN_ERR_FLAG 位来判断接收的帧是否为错误帧。如果为错误帧,可以通过can_id的其他符号位来判断错误的具体原因。错误帧的符号位在头文件linux/can/error.h 中定义。

9 代码

 /* 1.报文发送程序 */ 
 #include <stdio.h> 
 #include <stdlib.h> 
 #include <string.h> 
 #include <unistd.h> 
 #include <net/if.h> 
 #include <sys/ioctl.h> 
 #include <sys/socket.h> 
 #include <linux/can.h> 
 #include <linux/can/raw.h> 
 
 int main() 
 { 
 	int s, nbytes; 
	struct sockaddr_can addr; 
	struct ifreq ifr; 
	struct can_frame frame[2] = {{0}}; 
	
	s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//创建套接字 
	strcpy(ifr.ifr_name, "can0" ); 
	ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 设备 
	addr.can_family = AF_CAN; 
	addr.can_ifindex = ifr.ifr_ifindex; 
	bind(s, (struct sockaddr *)&addr, sizeof(addr));//将套接字与 can0 绑定 
	
	//禁用过滤规则,本进程不接收报文,只负责发送 
	setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); 
	//生成两个报文 
	frame[0].can_id = 0x11; 
	frame[0]. can_dlc = 1; 
	frame[0].data[0] = 'Y'; 
	frame[0].can_id = 0x22; 
	frame[0]. can_dlc = 1; 
	frame[0].data[0] = 'N'; 
	//循环发送两个报文 
	while(1) 
	{ 
		nbytes = write(s, &frame[0], sizeof(frame[0])); //发送 frame[0] 
		if(nbytes != sizeof(frame[0])) 
		{ 
			printf("Send Error frame[0]\n!"); 
			break; //发送错误,退出 
	 	} 
		 sleep(1); 
		 
		nbytes = write(s, &frame[1], sizeof(frame[1])); //发送 frame[1] 
		if(nbytes != sizeof(frame[0])) 
		{ 
			printf("Send Error frame[1]\n!"); 
			break; 
	 	} 
		sleep(1); 
	} 
	 close(s); 
	 return 0; 
} 
 
 /* 2. 报文过滤接收程序 */ 
 #include <stdio.h> 
 #include <stdlib.h> 
 #include <string.h> 
 #include <unistd.h> 
 #include <net/if.h> 
 #include <sys/ioctl.h> 
 #include <sys/socket.h> 
 #include <linux/can.h> 
 #include <linux/can/raw.h> 
 
 int main() 
 { 
	 int s, nbytes; 
	 struct sockaddr_can addr; 
	 struct ifreq ifr; 
	 struct can_frame frame; 
	 struct can_filter rfilter[1];
	  
	 s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //创建套接字 
	 strcpy(ifr.ifr_name, "can0" ); 
	 ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 设备 
	 addr.can_family = AF_CAN; 
	 addr.can_ifindex = ifr.ifr_ifindex; 
	 bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与 can0 绑定 
	 
	 //定义接收规则,只接收表示符等于 0x11 的报文 
	 rfilter[0].can_id = 0x11; 
	 rfilter[0].can_mask = CAN_SFF_MASK; 
	 //设置过滤规则 
	 setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter)); 
	 while(1)
	 { 
		 nbytes = read(s, &frame, sizeof(frame)); //接收报文 
		//显示报文 
		 if(nbytes > 0)
		 {
			 printf(“ID=0x%X DLC=%d data[0]=0x%X\n”, frame.can_id, 
			 frame.can_dlc, frame.data[0]); 
		 } 
	} 
	close(s); 
	return 0; 
}

加入讨论

在这里插入图片描述

  • 11
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值