您的位置:首页 > 运维架构 > Linux

Linux CAN 编程详解

2018-03-06 15:03 239 查看
转自:http://velep.com/archives/1181.html《Linux CAN编程详解》是一篇百度文库上的文档,主要描述了以下内容:
can总线介绍及其帧类型;
Linux 系统中CAN 接口配置;
Linux 系统中CAN 接口应用程序开发;
Linux 系统中CAN 接口编程实例
总体来说,这篇文档,对于嵌入式linux can应用编程还是有很大的帮助。特别是里面关于“Linux 系统中CAN 接口应用程序开发”的介绍,总结的很全面,讲述的比较清楚。本人编写的linux socket can程序cantool(一个基于linux socket can机制编写的can接口应用程序),在调试CAN帧发送功能的时候,就有参考过该文档“5. 过滤规则设置”一节内容。下面是该文档中个人认为比较有价值的内容部分。完整PDF文档下载地址:Linux CAN编程详解Linux 系统中CAN 接口配置在 Linux 系统中, CAN 总线接口设备作为网络设备被系统进行统一管理。在控制台下, CAN 总线的配置和以太网的配置使用相同的命令。在控制台上输入命令:
ifconfig –a可以得到以下结果:

在上面的结果中, eth0 设备为以太网接口, can0和can1 设备为两个 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 can0Linux 系统中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
};
过滤的规则为:接收到的数据帧的 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));
(6). 回环功能设置在默认情况下, 本地回环功能是开启的,可以使用下面的方法关闭回环/开启功能:
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));
Linux 系统中CAN 接口应用程序示例该文档提供了一个很简单的程序示例,如下:1. 报文发送程序
01
/* 1. 报文发送程序 */
02
#include <stdio.h>
03
#include <stdlib.h>
04
#include <string.h>
05
#include <unistd.h>
06
#include <net/if.h>
07
#include <sys/ioctl.h>
08
#include <sys/socket.h>
09
#include <linux/can.h>
10
#include <linux/can/raw.h>
11
 
12
int
 
main()
13
{
14
    
int
 
s, nbytes;
15
    
struct
 
sockaddr_can addr;
16
    
struct
 
ifreq ifr;
17
    
struct
 
can_frame frame[2] = {{0}};
18
    
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
//创建套接字
19
    
strcpy
(ifr.ifr_name, 
"can0"
 
);
20
    
ioctl(s, SIOCGIFINDEX, &ifr); 
//指定 can0 设备
21
    
addr.can_family = AF_CAN;
22
    
addr.can_ifindex = ifr.ifr_ifindex;
23
    
bind(s, (
struct
 
sockaddr *)&addr, 
sizeof
(addr));
//将套接字与 can0 绑定
24
    
//禁用过滤规则,本进程不接收报文,只负责发送
25
    
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
26
    
//生成两个报文
27
    
frame[0].can_id = 0x11;
28
    
frame[0]. can_dlc = 1;
29
    
frame[0].data[0] = 
'Y'
;
30
    
frame[0].can_id = 0x22;
31
    
frame[0]. can_dlc = 1;
32
    
frame[0].data[0] = 
'N'
;
33
    
//循环发送两个报文
34
    
while
(1)
35
    
{
36
        
nbytes = write(s, &frame[0], 
sizeof
(frame[0])); 
//发送 frame[0]
37
        
if
(nbytes != 
sizeof
(frame[0]))
38
        
{
39
            
printf
(
"Send Error frame[0]\n!"
);
40
            
break
//发送错误,退出
41
        
}
42
        
sleep(1);
43
        
nbytes = write(s, &frame[1], 
sizeof
(frame[1])); 
//发送 frame[1]
44
        
if
(nbytes != 
sizeof
(frame[0]))
45
        
{
46
            
printf
(
"Send Error frame[1]\n!"
);
47
            
break
;
48
        
}
49
        
sleep(1);
50
    
}
51
    
close(s);
52
    
return
 
0;
53
}
2. 报文过滤接收程序view source
01
/* 2. 报文过滤接收程序 */
02
#include <stdio.h>
03
#include <stdlib.h>
04
#include <string.h>
05
#include <unistd.h>
06
#include <net/if.h>
07
#include <sys/ioctl.h>
08
#include <sys/socket.h>
09
#include <linux/can.h>
10
#include <linux/can/raw.h>
11
 
12
int
 
main()
13
{
14
    
int
 
s, nbytes;
15
    
struct
 
sockaddr_can addr;
16
    
struct
 
ifreq ifr;
17
    
struct
 
can_frame frame;
18
    
struct
 
can_filter rfilter[1];
19
    
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); 
//创建套接字
20
    
strcpy
(ifr.ifr_name, 
"can0"
 
);
21
    
ioctl(s, SIOCGIFINDEX, &ifr); 
//指定 can0 设备
22
    
addr.can_family = AF_CAN;
23
    
addr.can_ifindex = ifr.ifr_ifindex;
24
    
bind(s, (
struct
 
sockaddr *)&addr, 
sizeof
(addr)); 
//将套接字与 can0 绑定
25
    
//定义接收规则,只接收表示符等于 0x11 的报文
26
    
rfilter[0].can_id = 0x11;
27
    
rfilter[0].can_mask = CAN_SFF_MASK;
28
    
//设置过滤规则
29
    
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, 
sizeof
(rfilter));
30
    
while
(1)
31
    
{
32
        
nbytes = read(s, &frame, 
sizeof
(frame)); 
//接收报文
33
        
//显示报文
34
        
if
(nbytes > 0)
35
        
{
36
            
printf
(“ID=0x%X DLC=%d data[0]=0x%X\n”, frame.can_id,
37
                
frame.can_dlc, frame.data[0]);
38
        
}
39
    
}
40
    
close(s);
41
    
return
 
0;
42
}
这个示例程序博主并未编译测试验证。更完整的程序详见本人编写的linux socket can程序cantool
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux can