(笔记)Linux CAN编程详解
一、Linux 系统中CAN 接口配置
在 Linux 系统中, CAN 总线接口设备作为网络设备被系统进行统一管理。在控制台下, CAN 总线的配置和以太网的配置使用相同的命令。
在控制台上输入命令:
1 | ifconfig –a |
可以得到以下结果:
在上面的结果中, eth0 设备为以太网接口, can0和can1 设备为两个 CAN 总线接口。接下来使用 ip 命令来配置 CAN 总线的位速率:
1 | ip link set can0 type cantq 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 |
二、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 2 3 4 5 6 7 8 9 | 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 结构体将数据封装成帧。 结构体定义如下:
1 2 3 4 5 | 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 位是帧的标志位,用来定义帧的类型,定义如下:
1 2 3 | #define CAN_EFF_FLAG 0x80000000U //扩展帧的标识 #define CAN_RTR_FLAG 0x40000000U //远程帧的标识 #define CAN_ERR_FLAG 0x20000000U //错误帧的标识,用于错误检查 |
数据发送使用 write 函数来实现。 如果发送的数据帧(标识符为 0x123)包含单个字节(0xAB)的数据,可采用如下方法进行发送:
1 2 3 4 5 6 7 | 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),可采用如下方法进行发送:
1 2 3 | struct can_frame frame; frame.can_id = CAN_RTR_FLAG | 0x123; write(s, &frame, sizeof (frame)); |
(3). 数据接收
数据接收使用 read 函数来完成,实现如下:
1 2 | 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 结构体来实现,定义如下:
1 2 3 4 | struct can_filter { canid_t can_id; canid_t can_mask; }; |
过滤的规则为:
1 | 接收到的数据帧的 can_id & mask == can_id & mask |
通过这条规则可以在系统中过滤掉所有不符合规则的报文,使得应用程序不需要对无关的报文进行处理。在 can_filter 结构的 can_id 中,符号位 CAN_INV_FILTER 在置位时可以实现 can_id 在执行过滤前的位反转。
用户可以为每个打开的套接字设置多条独立的过滤规则,使用方法如下:
1 2 3 4 5 6 | 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 = CAN_EFF_MASK; setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof (rfilter)); //设置规则 |
上述例子中,设置的规则代表:只接收can_id为0x123的标准帧,和0x200的扩展帧,其他的帧一律过滤掉,不用处理。
在极端情况下,如果应用程序不需要接收报文,可以通过将setsockopt的第四个参数设置为NULL,最后一个参数设置为0来设置不接收所有的can报文。这样的话,原始套接字就会忽略所有接收到的报文。在这种仅仅发送数据的应用中,可以在内核中省略接收队列,以此减少 CPU 资源的消耗,代码如下:
1 | setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); //禁用过滤规则 |
通过错误掩码可以实现对错误帧的过滤, 例如:
1 2 | 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)); |
(6). 回环功能设置
在默认情况下, 本地回环功能是开启的,可以使用下面的方法关闭回环/开启功能:
1 2 | int loopback = 0; // 0 表示关闭, 1 表示开启( 默认) setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof (loopback)); |
在本地回环功能开启的情况下,所有的发送帧都会被回环到与 CAN 总线接口对应的套接字上。 默认情况下,发送 CAN 报文的套接字不想接收自己发送的报文,因此发送套接字上的回环功能是关闭的。可以在需要的时候改变这一默认行为:
1 2 | int ro = 1; // 0 表示关闭( 默认), 1 表示开启 setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &ro, sizeof (ro)); |
三、Linux 系统中CAN 接口应用程序示例
该文档提供了一个很简单的程序示例,如下:
1. 报文发送程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | /* 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. 报文过滤接收程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /* 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; } |
原文地址:原文博客地址 https://blog.csdn.net/panfei263031/article/details/122977734
posted on 2023-01-04 16:50 tdyizhen1314 阅读(6839) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)