Linux CAN编程详解

原文博客地址  http://velep.com/archives/1181.html

通过读这篇博客是我搜索can通讯以来讲解的最详细的一篇,还有其自己写的一刻关于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 can0

Linux 系统中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. 报文过滤接收程序

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

(0)

相关推荐

  • ctf中关于syscall系统调用的简单分析

    0x01 我在动态调试这个程序的时候,发现 syscall调用 系统函数 的过程很有趣,于是便记录下来 希望对大家 能带来些帮助,这里 以 buu 平台上的 ciscn2019s_3 为例,给大家详细 ...

  • linux 中/proc 详解

    Linux-proc proc 文件系统   在Linux中有额外的机制可以为内核和内核模块将信息发送给进程-- /proc 文件系统.最初设计的目的是允许更方便的对进程信息进行访问(因此得名),现在 ...

  • 最强Java并发编程详解:知识点梳理,BAT面试题等

    来源:cnblogs.com/pengdai/p/12026959.html 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时 ...

  • Linux /dev目录详解

    在linux下,/dev目录是很重要的,各种设备都在下面.下面简单总结一下: dev是设备(device)的英文缩写./dev这个目录对所有的用户都十分重要.因为在这个目录中包含了所有Linux系统中 ...

  • linux vi 命令详解

    vi编辑器是所有Unix及Linux系统下标准的编辑器,它的强大不逊色于任何最新的文本编辑器,这里只是简单地介绍一下它的用法和一小部分指令.由于对Unix及Linux系统的任何版本,vi编辑器是完全相 ...

  • linux管道pipe详解

    管道 管道的概念: 管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递.调用pipe系统函数即可创建一个管道.有如下特质: 1. 其本质是一个伪文件(实为内核缓冲区) 2. 由两个 ...

  • Linux /dev目录详解和Linux系统各个目录的作用

    -------------------------------------------------------------------------------- /proc文件系统下的多种文件提供的系 ...

  • Linux awk 命令详解

    awk是行处理器:相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或处理缓慢的问题,常用来格式化文本信息. awk处理过程:依次对每一行进行处理,然后输出.①读取被匹配到的行数据:②按照输入分隔 ...

  • S7-1200plc作为从站的编程详解

    S7-1200 作为Modbus RTU 从站 S7-1200 支持Modbus RTU通信模式的模块可作为Modbus RTU从站.以下以 CPU1215C DC/DC/DC和CM1241 RS48 ...

  • TCP/IP通信原理,Python网络编程详解!

    TCP/IP通信原理,Python网络编程详解!