CAN总线
CAN 是控制器局域网络(Controller Area Network,CAN)的简称,由德国BOSCH公司开发,并
最终成为国际标准(ISO 11898-1)。CAN总线主要应用于工业控制和汽车电子领域,是国际上应用最广
泛的现场总线之一。
1 CAN总线简介
CAN 总线是一种串行通讯协议,能有效地支持具备很高安全等级的分布实时控制。CAN 总线的应
用范围很广,从高速的网络到低价位的多路接线均可以使用CAN。在汽车电子行业里,使用CAN 链接
发动机的控制单元、传感器、防刹车系统等,传输速度可达1 Mbps。
与前面介绍的通常通讯总线相比,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总线配置
在Linux系统中,CAN总线接口设备做为网络设备被系统进行统一管理。在控制台下, CAN总线
的配置和以太网的配置使用相同的命令。
在控制台上输入命令:
ifconfig –a
能够获得如下结果:
can0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
NOARP MTU:16 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:10
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) Interrupt:18
eth0 Link encap:Ethernet HWaddr 00:50:c2:22:3b:0e
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
eth1 Link encap:Ethernet HWaddr 00:50:c2:22:3b:60
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
Interrupt:41 Base address:0xe000 lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:256 errors:0 dropped:0 overruns:0 frame:0
TX packets:256 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0 RX bytes:19952 (19.9 KB) TX bytes:19952 (19.9 KB)
在上面的结果中,eth0和eth1设备为以太网接口,can0设备为CAN总线接口。接下来使用ip命
令来配置CAN总线的位速率:
ip link set can0 type cantq 125 prop-seg 6phase-seg1 7 phase-seg2 2 sjw 1
也可使用ip命令直接设定位速率:
ip link set can0 type can bitrate 125000
当设置完成后,能够经过下面的命令查询can0设备的参数设置:
ip -details link show can0
当设置完成后,可使用下面的命令使能can0设备:
ifconfig can0 up
使用下面的命令取消can0设备使能:
ifconfig can0 down
在设备工做中,可使用下面的命令来查询工做状态:
ip -details -statistics link show can0
7 CAN总线应用开发接口
因为系统将CAN设备做为网络设备进行管理,所以在CAN总线应用开发方面,Linux提供了
SocketCAN接口,使得CAN总线通讯近似于和以太网的通讯,应用程序开发接口更加通用,也更加灵
活。
此外,经过https://gitorious.org/linux-can/can-utils 网站发布的基于SocketCAN的can-utils工具
套件,也能够实现简易的CAN总线通讯。
下面具体介绍使用SocketCAN实现通讯时使用的应用程序开发接口。
(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的第2九、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中定义。
5. 过滤规则设置
在数据接收时,系统能够根据预先设置的过滤规则,实现对报文的过滤。过滤规则使用can_filter结构体
来实现,定义以下:
struct can_filter {
canid_t can_id;
canid_t can_mask;};
过滤的规则为:
接收到的数据帧的can_id &mask== can_id & mask
经过这条规则能够在系统中过滤掉全部不符合规则的报文,使得应用程序不须要对无关的报文进行
处理。在can_filter结构的can_id中,符号位CAN_INV_FILTER在置位时能够实现can_id在执行过
滤前的位反转。
用户能够为每一个打开的套接字设置多条独立的过滤规则,使用方法以下:
struct can_filter rfilter[2];
rfilter[0].can_id = 0x123;
rfilter[0].can_mask = CAN_SFF_MASK; //#define CAN_SFF_MASK 0x000007FFU
rfilter[1].can_id = 0x200;
rfilter[1].can_mask = 0x700;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));//设置规则
在极端状况下,若是应用程序不须要接收报文,能够禁用过滤规则。这样的话,原始套接字就会忽略全部
接收到的报文。在这种仅仅发送数据的应用中,能够在内核中省略接收队列,以此减小CPU资源的消耗。禁
用方法以下:
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); //禁用过滤规则
经过错误掩码能够实现对错误帧的过滤,例如:
can_err_mask_t err_mask = ( CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF );
setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, err_mask, sizeof(err_mask));
在默认状况下,本地回环功能是开启的,可使用下面的方法关闭回环/开启功能:
int loopback = 0; // 0表示关闭, 1表示开启(默认)
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
在本地回环功能开启的状况下,全部的发送帧都会被回环到与CAN总线接口对应的套接字上。默认状况
下,发送CAN报文的套接字不想接收本身发送的报文,所以发送套接字上的回环功能是关闭的。能够在
须要的时候改变这一默认行为:
int ro = 1; // 0表示关闭(默认), 1表示开启
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &ro, sizeof(ro));
/* 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;
}linux