CAN总线摘自《Intel凌动平台嵌入式应用与实践》第五章

来源:互联网 发布:企业软件开发流程 编辑:程序博客网 时间:2024/05/05 04:11

阅读网址http://book.51cto.com/art/201304/390520.htm

4.5  CAN总线

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

4.5.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规范。

4.5.2  CAN总线的工作原理

当CAN 总线上的节点发送数据时,以报文形式广播给网络中的所有节点,总线上的所有节点都不使用节点地址等系统配置信息,只根据每组报文开头的11位标识符(CAN 2.0A规范)解释数据的含义来决定是否接收。这种数据收发方式称为面向内容的编址方案。

当某个节点要向其他节点发送数据时,这个节点的处理器将要发送的数据和自己的标识符传送给该节点的CAN总线接口控制器,并处于准备状态;当收到总线分配时,转为发送报文状态。数据根据协议组织成一定的报文格式后发出,此时网络上的其他节点处于接收状态。处于接收状态的每个节点对接收到的报文进行检测,判断这些报文是否是发给自己的以确定是否接收。

由于CAN 总线是一种面向内容的编址方案,因此很容易建立高水准的控制系统并灵活地进行配置。我们可以很容易地在CAN总线上加进一些新节点而无须在硬件或软件上进行修改。

当提供的新节点是纯数据接收设备时,数据传输协议不要求独立的部分有物理目的地址。此时允许分布过程同步化,也就是说,当总线上的控制器需要测量数据时,数据可由总线上直接获得,而无需每个控制器都有自己独立的传感器。


4.5.3  CAN总线的工作特点

CAN总线的有以下三方面特点:

可以多主方式工作,网络上的任意节点均可以在任意时刻主动地向网络上的其他节点发送信息,而不分主从,通信方式灵活。

网络上的节点(信息)可分成不同的优先级,可以满足不同的实时要求。

采用非破坏性位仲裁总线结构机制,当两个节点同时向网络上传送信息时,优先级低的节点主动停止数据发送,而优先级高的节点可不受影响地继续传输数据。


4.5.4  CAN总线协议的层次结构

与前面介绍的简单总线逻辑不同,CAN是一种复杂逻辑的总线结构。从层次上可以将CAN总线划分为三个不同层次,见图4-19。

1. 物理层

在物理层中定义实际信号的传输方法,包括位的编码和解码、位的定时和同步等内容,作用是定义不同节点之间根据电气属性如何进行位的实际传输。

在物理连接上,CAN总线结构提供两个引脚--CANH和CANL,总线通过CANH和CANL之间的差分电压完成信号的位传输。LAB8903实验箱中的CAN总线接口可以采用屏蔽双绞线来进行传输。接口与COM 4~COM 6接口相邻,引脚定义见表4-11。

在不同系统中,CAN总线的位速率不同;在系统中,CAN总线的位速率是唯一的,并且是固定的,这需要对总线中的每个节点配置统一的参数。

2. 传输层

传输层是CAN总线协议的核心。传输层负责把接收到的报文提供给对象层,以及接收来自对象层的报文。传输层负责位的定时及同步、报文分帧、仲裁、应答、错误检测和标定、故障界定。

3. 对象层

在对象层中可以为远程数据请求以及数据传输提供服务,确定由实际要使用的传输层接收哪一个报文,并且为恢复管理和过载通知提供手段。


4.5.5  CAN总线的报文结构

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

1. 数据帧

数据帧携带数据从发送器至接收器。总线上传输的大多是这种帧。从标识符长度上,又可以把数据帧分为标准帧(11位标识符)和扩展帧(29位标识符)。

数据帧由7个不同的位场组成:帧起始、仲裁场、控制场、数据场、CRC 场、应答场、帧结束。其中,数据场的长度为0~8个字节。标识符位于仲裁场中,报文接收节点通过标识符进行报文滤波。帧结构如图4-20所示。

2. 远程帧

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

3. 错误帧

任何单元,一旦检测到总线错误就发出错误帧。错误帧由两个不同的场组成,第一个场是由不同站提供的错误标志的叠加(错误标志),第二个场是错误界定符。帧结构如图4-22所示。

4. 过载帧

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


4.5.6  CAN总线配置

在Linux系统中,CAN总线接口设备作为网络设备被系统进行统一管理。在控制台下, CAN总线的配置和以太网的配置使用相同的命令。

在控制台上输入命令:

  1. ifconfig –a 

可以得到以下结果:

  1. can0   Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
  2.        NOARP  MTU:16  Metric:1  
  3.        RX packets:0 errors:0 dropped:0 overruns:0 frame:0  
  4.        TX packets:0 errors:0 dropped:0 overruns:0 carrier:0  
  5.        collisions:0 txqueuelen:10   
  6.        RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)  
  7.        Interrupt:18   
  8. eth0   Link encap:Ethernet  HWaddr 00:50:c2:22:3b:0e    
  9.        UP BROADCAST MULTICAST  MTU:1500  Metric:1  
  10.        RX packets:0 errors:0 dropped:0 overruns:0 frame:0  
  11.        TX packets:0 errors:0 dropped:0 overruns:0 carrier:0  
  12.        collisions:0 txqueuelen:1000   
  13.        RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)  
  14. eth1   Link encap:Ethernet  HWaddr 00:50:c2:22:3b:60    
  15.        UP BROADCAST MULTICAST  MTU:1500  Metric:1  
  16.        RX packets:0 errors:0 dropped:0 overruns:0 frame:0  
  17.        TX packets:0 errors:0 dropped:0 overruns:0 carrier:0  
  18.        collisions:0 txqueuelen:1000   
  19.        RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)  
  20.        Interrupt:41 Base address:0xe000   
  21. lo     Link encap:Local Loopback    
  22. inet addr:127.0.0.1  Mask:255.0.0.0  
  23. inet6 addr: ::1/128 Scope:Host  
  24. UP LOOPBACK RUNNING  MTU:16436  Metric:1  
  25. RX packets:256 errors:0 dropped:0 overruns:0 frame:0  
  26. TX packets:256 errors:0 dropped:0 overruns:0 carrier:0  
  27. collisions:0 txqueuelen:0   
  28. RX bytes:19952 (19.9 KB)  TX bytes:19952 (19.9 KB)  

在上面的结果中,eth0和eth1设备为以太网接口,can0设备为CAN总线接口。接下来使用ip命令来配置CAN总线的位速率:

  1. ip link set can0 type can tq 125 prop-seg 6phase-seg1 7 phase-seg2 2 sjw 1 

也可以使用ip命令直接设定位速率:

  1. ip link set can0 type can bitrate 125000 

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

  1. ip -details link show can0 

当设置完成后,可以使用下面的命令使能can0设备:

  1. ifconfig can0 up 

使用下面的命令取消can0设备使能:

  1. ifconfig can0 down 

在设备工作中,可以使用下面的命令来查询工作状态:

  1. ip -details -statistics link show can0  

4.5.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中定义。套接字的初始化方法如下:

  1. int s;  
  2. struct sockaddr_can addr;  
  3. struct ifreq ifr;  
  4. s = socket(PF_CAN, SOCK_RAW, CAN_RAW);   //创建SocketCAN套接字  
  5. strcpy(ifr.ifr_name, "can0" );  
  6. ioctl(s, SIOCGIFINDEX, &ifr);      //指定can0设备  
  7. addr.can_family = AF_CAN;  
  8. addr.can_ifindex = ifr.ifr_ifindex;  
  9. bind(s, (struct sockaddr *)&addr, sizeof(addr));   //将套接字与can0绑定 

2. 数据发送

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

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

can_id为帧的标识符,如果发出的是标准帧,就使用can_id的低11位;如果为扩展帧,就使用0~28位。can_id的第29、30、31位是帧的标志位,用来定义帧的类型,定义如下:

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

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

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

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

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

3. 数据接收

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

  1. struct can_frame frame;  
  2. 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结构体来实现,定义如下:

  1. struct can_filter {  
  2. canid_t can_id;  
  3. canid_t can_mask;  
  4. }; 

过滤的规则为:

  1. 接收到的数据帧的can_id & mask == can_id & mask 

通过这条规则可以在系统中过滤掉所有不符合规则的报文,使得应用程序不需要对无关的报文进行处理。在can_filter结构的can_id中,符号位CAN_INV_FILTER在置位时可以实现can_id在执行过滤前的位反转。

用户可以为每个打开的套接字设置多条独立的过滤规则,使用方法如下:

  1. struct can_filter rfilter[2];  
  2. rfilter[0].can_id   = 0x123;  
  3. rfilter[0].can_mask = CAN_SFF_MASK; //#define CAN_SFF_MASK 0x000007FFU  
  4. rfilter[1].can_id   = 0x200;  
  5. rfilter[1].can_mask = 0x700;  
  6. setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter)); //设置规则 

在极端情况下,如果应用程序不需要接收报文,可以禁用过滤规则。这样的话,原始套接字就会忽略所有接收到的报文。在这种仅仅发送数据的应用中,可以在内核中省略接收队列,以此减少CPU资源的消耗。禁用方法如下:

  1. setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); //禁用过滤规则 

通过错误掩码可以实现对错误帧的过滤,例如:

  1. can_err_mask_t err_mask = ( CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF );  
  2. setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, err_mask, sizeof(err_mask)); 

在默认情况下,本地回环功能是开启的,可以使用下面的方法关闭回环/开启功能:

  1. int loopback = 0;  // 0表示关闭, 1表示开启(默认)  
  2. setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback)); 

在本地回环功能开启的情况下,所有的发送帧都会被回环到与CAN总线接口对应的套接字上。默认情况下,发送CAN报文的套接字不想接收自己发送的报文,因此发送套接字上的回环功能是关闭的。可以在需要的时候改变这一默认行为:

  1. int ro = 1; // 0表示关闭(默认), 1表示开启  
  2. setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &ro, sizeof(ro));  

4.5.8  CAN总线开发实例

下面通过实例来说明CAN总线接口的使用方法。在本例中,有两个进程,其中一个进程负责报文的发送(标识符为0x11和0x22),另一个进程负责报文的接收和过滤(只接收标识符为0x11的报文)。
 

  1. /* 1.报文发送程序 */  
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. #include <string.h> 
  5. #include <unistd.h> 
  6. #include <net/if.h> 
  7. #include <sys/ioctl.h> 
  8. #include <sys/socket.h> 
  9. #include <linux/can.h> 
  10. #include <linux/can/raw.h> 
  11. int main()  
  12. {  
  13.     int s, nbytes;  
  14. struct sockaddr_can addr;  
  15. struct ifreq ifr;  
  16. struct can_frame frame[2] = {{0}};  
  17. s = socket(PF_CAN, SOCK_RAW, CAN_RAW);      //创建套接字  
  18. strcpy(ifr.ifr_name, "can0" );  
  19. ioctl(s, SIOCGIFINDEX, &ifr);                   //指定can0设备  
  20. addr.can_family = AF_CAN;  
  21. addr.can_ifindex = ifr.ifr_ifindex;  
  22. bind(s, (struct sockaddr *)&addr, sizeof(addr));            //将套接字与can0绑定  
  23. //禁用过滤规则,本进程不接收报文,只负责发送  
  24.         setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);  
  25.         //生成两个报文  
  26. frame[0].can_id = 0x11;  
  27. frame[0]. can_dlc = 1;  
  28. frame[0].data[0] = 'Y';  
  29. frame[0].can_id = 0x22;  
  30. frame[0]. can_dlc = 1;  
  31. frame[0].data[0] = 'N';  
  32. //循环发送两个报文  
  33. while(1) {  
  34. nbytes = write(s, &frame[0], sizeof(frame[0]));     //发送frame[0]  
  35. if (nbytes != sizeof(frame[0])) {  
  36.                 printf("Send Error frame[0]\n!");  
  37.                 break;                              //发送错误,退出  
  38. }  
  39.     sleep(1);  
  40. nbytes = write(s, &frame[1], sizeof(frame[1]));     //发送frame[1]  
  41. if (nbytes != sizeof(frame[0])) {  
  42.                 printf("Send Error frame[1]\n!");  
  43.                 break;  
  44. }  
  45.     sleep(1);  
  46. }  
  47. close(s);  
  48. return 0;  
  49. }  
  50. /* 2. 报文过滤接收程序 */  
  51. #include <stdio.h> 
  52. #include <stdlib.h> 
  53. #include <string.h> 
  54. #include <unistd.h> 
  55. #include <net/if.h> 
  56. #include <sys/ioctl.h> 
  57. #include <sys/socket.h> 
  58. #include <linux/can.h> 
  59. #include <linux/can/raw.h> 
  60. int main()  
  61. {  
  62. int s, nbytes;  
  63. struct sockaddr_can addr;  
  64. struct ifreq ifr;  
  65. struct can_frame frame;  
  66. struct can_filter rfilter[1];  
  67. s = socket(PF_CAN, SOCK_RAW, CAN_RAW);      //创建套接字  
  68. strcpy(ifr.ifr_name, "can0" );  
  69. ioctl(s, SIOCGIFINDEX, &ifr);                   //指定can0设备  
  70. addr.can_family = AF_CAN;  
  71. addr.can_ifindex = ifr.ifr_ifindex;  
  72. bind(s, (struct sockaddr *)&addr, sizeof(addr));            //将套接字与can0绑定  
  73.         //定义接收规则,只接收表示符等于0x11的报文  
  74. rfilter[0].can_id   = 0x11;  
  75. rfilter[0].can_mask = CAN_SFF_MASK;  
  76. //设置过滤规则  
  77. setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));   
  78.         while(1) {  
  79.             nbytes = read(s, &frame, sizeof(frame));            //接收报文  
  80.             //显示报文  
  81. if (nbytes > 0) {  
  82. printf(“ID=0x%X DLC=%d data[0]=0x%X\n”, frame.can_id,  
  83. frame.can_dlc, frame.data[0]);  
  84. }  
  85. }  
  86. close(s);  
  87. return 0;  
  88. }  
0 0
原创粉丝点击