本章我们学习 CAN 应用编程, CAN 是目前应用非常广泛的现场总线之一,主要应用于汽车电子和工业领域,尤其是汽车领域,汽车上大量的传感器与模块都是通过 CAN 总线连接起来的。 CAN 总线目前是自动化领域发展的热点技术之一,由于其高可靠性, CAN 总线目前广泛的应用于工业自动化、船舶、汽车、医疗和工业设备等方面。在我们的 I.MX6U ALPHA/Mini 开发板上提供了一个 CAN 接口,使用该接口可以进行 CAN 总线通信,本章我们就来学习一下如何编写一个简单地应用程序来测试开发板上的 CAN 接口。 需要说明的是,本章自然不会深入给大家讲解 CAN 应用编程,同样, CAN 总线技术也是一个非常专业的技术方向,笔者并不从事这方面的工作,对 CAN 的使用、了解少之又少,所以本章同样旨在以引导大家入门为主!
本章将会讨论如下主题内容。
⚫ CAN 总线介绍
⚫ CAN 应用编程介绍
CAN 基础知识
什么是 CAN?
CAN 是 Controller Area Network 的缩写(以下称为 CAN), 即控制器局域网络, 是 ISO 国际标准化的串行通信协议。CAN 总线最初是由德国电气商博世公司开发,其最初动机就是为了解决现代汽车中庞大的电子控制系统之间的通讯,减少不断增加的信号线。于是,他们设计了一个单一的网络总线,所有的外围器件可以被挂接在该总线上。在当前的汽车工业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来,车上的电子控制系统越来越多,譬如发动机管理控制、变速箱控制、汽车仪表、空调、车门控制、灯光控制、气囊控制、转向控制、胎压监测、制动系统、雷达、自适应巡航、电子防盗系统等。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个 LAN,进行大量数据的高速通信”的需要, 1986年德国电气商博世公司开发出面向汽车的 CAN 通信协议。此后, CAN 通过 ISO11898 及 ISO11519 进行了标准化,现在在欧洲已是汽车网络的标准协议,目前已是当前应用最广泛的现场总线之一。
CAN 是一种多主方式的串行通讯总线,基本设计规范要求有高的位速率,高抗电磁干扰性,而且能够检测出产生的任何错误。经过几十年的发展, 现在, CAN 的高性能、高可靠性以及高实时性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。以汽车电子为例,汽车上有空调、车门、发动机、大量传感器等,这些部件、模块都是通过 CAN 总线连在一起形成一个网络,车载网络构想图如下所示:
CAN 的特点
CAN 通信协议具有以下特点:
(1)、多主控制
在总线空闲时,所有的单元都可开始发送消息(多主控制)。
最先访问总线的单元可获得发送权(CSMA/CA 方式*1)。
多个单元同时开始发送时,发送高优先级 ID 消息的单元可获得发送权。
(2)、消息的发送
在 CAN 协议中,所有的消息都以固定的格式发送。总线空闲时,所有与总线相连的单元都可以开始发送新消息。两个以上的单元同时开始发送消息时,根据标识符(Identifier 以下称为 ID)决定优先级。 ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。
(3)、系统的柔软性
与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。
(4)、通信速度
根据整个网络的规模,可设定适合的通信速度。在同一网络中,所有单元必须设定成统一的通信速度。即使有一个单元的通信速度与其它的不一样,此单元也会输出错误信号,妨碍整个网络的通信。不同网络间则可以有不同的通信速度。
(5)、远程数据请求
可通过发送“遥控帧” 请求其他单元发送数据。
(6)、具有错误检测功能·错误通知功能·错误恢复功能
所有的单元都可以检测错误(错误检测功能)。
检测出错误的单元会立即同时通知其他所有单元(错误通知功能)。
正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新
发送此消息直到成功发送为止(错误恢复功能)。
(7)、故障封闭
CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
(8)、连接
CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。
CAN 的电气属性
CAN 总线使用两根线来连接各个单元: CAN_H 和 CAN_L, CAN 控制器通过判断这两根线上的电位差来得到总线电平, CAN 总线电平分为显性电平和隐性电平两种。显性电平表示逻辑“0”,此时 CAN_H 电平比 CAN_L 高,分别为 3.5V 和 1.5V,电位差为 2V。隐形电平表示逻辑“1”,此时 CAN_H 和 CAN_L 电压都为 2.5V 左右,电位差为 0V。 CAN 总线就通过显性和隐形电平的变化来将具体的数据发送出去,如下图所示:
CAN 总线上没有节点传输数据的时候一直处于隐性状态,也就是说总线空闲状态的时候一直处于隐性。
CAN 网络拓扑
CAN 是一种分布式的控制总线, CAN 总线作为一种控制器局域网,和普通的以太网一样,它的网络由很多的 CAN 节点构成, 其网络拓扑结构如下图所示:
CAN 网络的每个节点非常简单,均由一个 MCU(微控制器)、一个 CAN 控制器和一个 CAN 收发器构成, 然后通过 CAN_H 和 CAN_L 这两根线连接在一起形成一个 CAN 局域网络。 CAN 能够使用多种物理介质,例如双绞线、光纤等。最常用的就是双绞线。信号使用差分电压传送,两条信号线被称为“CAN_H”和“CAN_L” ,在我们的开发板上, CAN 接口使用了这两条信号线, CAN 接口也只有这两条信号线。由此可知, CAN 控制器局域网和普通的以太网一样,每一个 CAN 节点就相当于局域网络中的一台主机。途中所有的 CAN 节点单元都采用 CAN_H 和 CAN_L 这两根线连接在一起, CAN_H 接 CAN_H、CAN_L接 CAN_L, CAN 总线两端要各接一个 120Ω的端接电阻,用于匹配总线阻抗,吸收信号反射及回拨,提高数据通信的抗干扰能力以及可靠性。CAN 总线传输速度可达 1Mbps/S,最新的 CAN-FD 最高速度可达 5Mbps/S,甚至更高, CAN-FD 不在本章讨论范围,感兴趣的可以自行查阅相关资料。 CAN 传输速度和总线距离有关,总线距离越短,传输速度越快。
CAN 总线通信模型
CAN 总线传输协议参考了 OSI 开放系统互连模型,也就是前面所介绍的 OSI 七层模型。 虽然 CAN 传输协议参考了 OSI 七层模型,但是实际上 CAN 协议只定义了“传输层” 、 “数据链路层”以及“物理层”这三层,而应用层协议可以由 CAN 用户定义成适合特别工业领域的任何方案。已在工业控制和制造业领域得到广泛应用的标准是 DeviceNet,这是为 PLC 和智能传感器设计的。在汽车工业,许多制造商都有他们自己的应用层协议标准。
CAN 协议只参考了 OSI 模型中的“传输层” 、 “数据链路层”以及“物理层”, 因此出现了各种不同的应用层协议,比如用在自动化技术的现场总线标准 DeviceNet,用于工业控制的 CanOpen,用于乘用车的诊断协议 OBD、 UDS(统一诊断服务, ISO14229),用于商用车的 CAN 总线协议 SAEJ1939。数据链路层分为 MAC 子层和 LLC 子层, MAC 子层是 CAN 协议的核心部分。数据链路层的功能是将物理层收到的信号组织成有意义的消息,并提供传送错误控制等传输控制的流程。具体地说,就是消息的帧化、仲裁、应答、错误的检测或报告。数据链路层的功能通常在 CAN 控制器的硬件中执行。在物理层定义了信号实际的发送方式、位时序、位的编码方式及同步的步骤。但具体地说,信号电平、
通信速度、采样点、驱动器和总线的电气特性、连接器的形态等均未定义。这些必须由用户根据系统需求自行确定。
CAN 帧的种类
CAN 通信协议定义了 5 种类型的报文帧,分别是:数据帧、遥控帧、错误帧、过载帧、间隔帧。 通信是通过这 5 种类型的帧进行的。 其中数据帧和遥控帧有标准格式和扩展格式两种,标准格式有 11 位标识符(ID),扩展格式有 29 个标识符(ID)。这 5 种类型的帧如下表所示:
帧 | 用途 |
数据帧 | 用于发送单元向接收单元传送数据的帧。 |
遥控帧 | 用于接收单元向具有相同 ID 的发送单元请求数据的帧。 |
错误帧 | 用于当检测出错误时向其它单元通知错误的帧。 |
过载帧 | 用于接收单元通知其尚未做好接收准备的帧。 |
间隔帧 | 用于将数据帧及遥控帧与前面的帧分离开来的帧。 |
其中数据帧是使用最多的帧类型,这里重点介绍一下数据帧,数据帧结构如下图所示:
图中给出了数据帧标准格式和扩展格式两种帧结构,图中 D 表示显性电平 0、 R 表示隐性电平 1,D/R 表示显性或隐性,也就是 0 或 1,我们来简单分析一下数据帧的这 7 个段。
数据帧由 7 个段构成:
(1)、帧起始
表示数据帧开始的段。
(2)、 仲裁段
表示该帧优先级的段。
(3)、控制段
表示数据的字节数及保留位的段。
(4)、数据段
数据的内容,可发送 0~8 个字节的数据。
(5)、 CRC 段
检查帧的传输错误的段。
(6)、 ACK 段
表示确认正常接收的段。
(7)、帧结束
表示数据帧结束的段。
关于更加详细的内容,请大家参考由瑞萨电子编写的《CAN 入门教程》 ,本小节内容到此结束!接下来向大家介绍 CAN 的应用编程。
SocketCan 应用编程
由于 Linux 系统将 CAN 设备作为网络设备进行管理,因此在 CAN 总线应用开发方面, Linux 提供了SocketCAN 应用编程接口,使得 CAN 总线通信近似于和以太网的通信,应用程序开发接口更加通用,也更加灵活。SocketCAN 中大部分的数据结构和函数在头文件 linux/can.h 中进行了定义,所以,在我们的应用程序中一定要包含<linux/can.h>头文件。
创建 socket 套接字
CAN 总线套接字的创建采用标准的网络套接字操作来完成, 网络套接字在头文件<sys/socket.h>中定义。创建 CAN 套接字的方法如下:
int sockfd = -1;
/* 创建套接字 */
sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if(0 > sockfd) {
perror("socket error");
exit(EXIT_FAILURE);
}
socket 函数在前面小节中给大家详细介绍过,第一个参数用于指定通信域,在 SocketCan 中,通常将其设置为PF_CAN,指定为CAN通信协议;第二个参数用于指定套接字的类型,通常将其设置为SOCK_RAW;第三个参数通常设置为 CAN_RAW。
将套接字与 CAN 设备进行绑定
譬如,将创建的套接字与 can0 进行绑定,示例代码如下所示:
......
struct ifreq ifr = {0};
struct sockaddr_can can_addr = {0};
int ret;
......
strcpy(ifr.ifr_name, "can0"); //指定名字
ioctl(sockfd, SIOCGIFINDEX, &ifr);
can_addr.can_family = AF_CAN; //填充数据
can_addr.can_ifindex = ifr.ifr_ifindex;
/* 将套接字与 can0 进行绑定 */
ret = bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr));
if (0 > ret) {
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
bind()函数在前面小节中给大家详细介绍过,这里不再重述!这里出现了两个结构体: struct ifreq 和 struct sockaddr_can,其中 struct ifreq 定义在<net/if.h>头文件中,而 struct sockaddr_can 定义在<linux/can.h>头文件中:
在 Linux 网络编程中,struct ifreq
结构体用于描述网络接口的各种参数和属性。它通常与 ioctl
系统调用结合使用,以执行网络接口的各种操作,比如获取接口的名称、IP 地址、MAC 地址等。
struct ifreq
的定义通常如下(摘自 <net/if.h> 头文件):
struct ifreq {
char ifr_name[IFNAMSIZ]; /* Interface name, e.g., "eth0" */
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
struct sockaddr ifr_broadaddr;
struct sockaddr ifr_netmask;
struct sockaddr ifr_hwaddr;
short ifr_flags;
int ifr_ifindex;
int ifr_ivalue;
int ifr_mtu;
struct ifmap ifr_map;
char ifr_slave[IFNAMSIZ];
char ifr_newname[IFNAMSIZ];
void * ifr_data;
};
};
下面是对各字段的解释:
-
ifr_name
:一个字符数组,用于存储网络接口的名称(例如,eth0
,wlan0
)。IFNAMSIZ
是一个宏,定义了接口名称的最大长度。 -
联合体(
union
):包含了多种不同类型的数据成员,每个成员表示网络接口的不同属性。只能同时使用其中一个成员。ifr_addr
:接口地址(通常是 IP 地址)。ifr_dstaddr
:点对点接口的目标地址。ifr_broadaddr
:广播地址。ifr_netmask
:网络掩码。ifr_hwaddr
:硬件地址(MAC 地址)。ifr_flags
:接口标志。ifr_ifindex
:接口索引。ifr_ivalue
:整数值,常用于接口的某些配置。ifr_mtu
:接口的 MTU(最大传输单元)。ifr_map
:设备映射。ifr_slave
:slave 设备的名称(用于绑定)。ifr_newname
:新的接口名称(用于重命名)。ifr_data
:指向用户定义的数据指针。
struct sockaddr_can
是一个专门用于 CAN(Controller Area Network)协议的 socket 地址结构体。它用于在 Linux 系统中处理 CAN 协议通信。struct sockaddr_can
结构体通常定义在 <linux/can.h>
头文件中。以下是它的定义:
#include <linux/can.h>
#include <linux/can/raw.h>
#include <sys/socket.h>
struct sockaddr_can {
sa_family_t can_family; // 地址族(应该是 AF_CAN)
int can_ifindex; // 网络接口的索引
union {
// CAN协议特定的地址
struct { canid_t rx_id, tx_id; } tp;
} can_addr;
};
以下是各个字段的解释:
can_family
:地址族,应该设置为AF_CAN
,表示这是一个 CAN 协议地址。can_ifindex
:网络接口的索引。可以使用if_nametoindex
或ioctl
获取。can_addr
:一个联合体,用于特定 CAN 协议的地址。目前,它只包含tp
,这是用于传输协议(Transport Protocol)的特定地址结构体。
使用 struct sockaddr_can
通常涉及以下步骤:
- 创建一个 CAN 套接字。
- 设置
struct sockaddr_can
结构体的字段。 - 绑定套接字到指定的 CAN 接口。
设置过滤规则
在我们的应用程序中,如果没有设置过滤规则,应用程序默认会接收所有 ID 的报文;如果我们的应用程序只需要接收某些特定 ID 的报文(亦或者不接受所有报文,只发送报文),则可以通过 setsockopt 函数设置过滤规则, 譬如某应用程序只接收 ID 为 0x60A 和 0x60B 的报文帧,则可将其它不符合规则的帧全部给过滤掉, 示例代码如下所示:
struct can_filter rfilter[2]; //定义一个 can_filter 结构体对象
// 填充过滤规则,只接收 ID 为(can_id & can_mask)的报文
rfilter[0].can_id = 0x60A;
rfilter[0].can_mask = 0x7FF;
rfilter[1].can_id = 0x60B;
rfilter[1].can_mask = 0x7FF;
// 调用 setsockopt 设置过滤规则
setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
struct can_filter 结构体中只有两个成员, can_id 和 can_mask。
如果应用程序不接收所有报文, 在这种仅仅发送数据的应用中,可以在内核中省略接收队列,以此减少CPU 资源的消耗。 此时可将 setsockopt()函数的第 4 个参数设置为 NULL,将第 5 个参数设置为 0,如下所示:
setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
数据发送/接收
在数据收发的内容方面, CAN 总线与标准套接字通信稍有不同,每一次通信都采用 struct can_frame 结构体将数据封装成帧。结构体定义如下:
struct can_frame {
canid_t can_id; /* CAN 标识符 */
__u8 can_dlc; /* 数据长度(最长为 8 个字节) */
__u8 __pad; /* padding */
__u8 __res0; /* reserved / padding */
__u8 __res1; /* reserved / padding */
__u8 data[8]; /* 数据 */
};
can_id 为帧的标识符,如果是标准帧,就使用 can_id 的低 11 位;如果为扩展帧,就使用 0~28 位。can_id 的第 29、 30、 31 位是帧的标志位,用来定义帧的类型,定义如下:
/* special address description flags for the CAN_ID */
#define CAN_EFF_FLAG 0x80000000U /* 扩展帧的标识 */
#define CAN_RTR_FLAG 0x40000000U /* 远程帧的标识 */
#define CAN_ERR_FLAG 0x20000000U /* 错误帧的标识,用于错误检查 */
/* mask */
#define CAN_SFF_MASK 0x000007FFU /* <can_id & CAN_SFF_MASK>获取标准帧 ID */
#define CAN_EFF_MASK 0x1FFFFFFFU /* <can_id & CAN_EFF_MASK>获取标准帧 ID */
#define CAN_ERR_MASK 0x1FFFFFFFU /* omit EFF, RTR, ERR flags */
(1)、数据发送
对于数据发送,使用 write()函数来实现,譬如要发送的数据帧包含了三个字节数据 0xA0、 0xB0 以及0xC0,帧 ID 为 123,可采用如下方法进行发送:
struct can_frame frame; //定义一个 can_frame 变量
int ret;
frame.can_id = 123;//如果为扩展帧,那么 frame.can_id = CAN_EFF_FLAG | 123;
frame.can_dlc = 3; //数据长度为 3
frame.data[0] = 0xA0; //数据内容为 0xA0
frame.data[1] = 0xB0; //数据内容为 0xB0
frame.data[2] = 0xC0; //数据内容为 0xC0
ret = write(sockfd, &frame, sizeof(frame)); //发送数据
if(sizeof(frame) != ret) //如果 ret 不等于帧长度,就说明发送失败
perror("write error");
如果要发送远程帧(帧 ID 为 123),可采用如下方法进行发送:
struct can_frame frame;
frame.can_id = CAN_RTR_FLAG | 123;
write(sockfd, &frame, sizeof(frame));
(2)、数据接收
数据接收使用 read()函数来实现,如下所示:
struct can_frame frame;
int ret = read(sockfd, &frame, sizeof(frame));
(3)、错误处理
当应用程序接收到一帧数据之后,可以通过判断 can_id 中的 CAN_ERR_FLAG 位来判断接收的帧是否为错误帧。如果为错误帧,可以通过 can_id 的其他符号位来判断错误的具体原因。错误帧的符号位在头文件<linux/can/error.h>中定义。
/* error class (mask) in can_id */
#define CAN_ERR_TX_TIMEOUT 0x00000001U /* TX timeout (by netdevice driver) */
#define CAN_ERR_LOSTARB 0x00000002U /* lost arbitration / data[0] */
#define CAN_ERR_CRTL 0x00000004U /* controller problems / data[1] */
#define CAN_ERR_PROT 0x00000008U /* protocol violations / data[2..3] */
#define CAN_ERR_TRX 0x00000010U /* transceiver status / data[4] */
#define CAN_ERR_ACK 0x00000020U /* received no ACK on transmission */
#define CAN_ERR_BUSOFF 0x00000040U /* bus off */
#define CAN_ERR_BUSERROR 0x00000080U /* bus error (may flood!) */
#define CAN_ERR_RESTARTED 0x00000100U /* controller restarted */
......
......
回环功能设置
在默认情况下, CAN 的本地回环功能是开启的,可以使用下面的方法关闭或开启本地回环功能:
int loopback = 0; //0 表示关闭, 1 表示开启(默认)
setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
在本地回环功能开启的情况下,所有的发送帧都会被回环到与 CAN 总线接口对应的套接字上。
CAN 应用编程实践
本小节我们来编写简单地 CAN 应用程序。 在 Linux 系统中, CAN 总线设备作为网络设备被系统进行统一管理。在控制台下, CAN 总线的配置和以太网的配置使用相同的命令。
使用 ifconfig 命令查看 CAN 设备,如下所示:
设备连接好之后,通过上位机软件启动 CAN 分析仪, 接下来我们进行测试。在进行测试之前需要对开发板上的 can 设备进行配置,执行以下命令:
ifconfig can0 down #先关闭 can0 设备
ip link set can0 up type can bitrate 1000000 triple-sampling on #设置波特率为 1000000
注意, CAN 分析仪设置的波特率要和开发板 CAN 设备的波特率一致!配置完成之后,接着可以使用 cansend 命令发送数据
cansend can0 123#01.02.03.04.05.06.07.08
#”号前面的 123 表示帧 ID,后面的数字表示要发送的数据,此时上位机便会接收到开发板发送过来的数据,如下所示:
接着测试开发板接收 CAN 数据,首先在开发板上执行 candump 命令:
candump -ta can0
接着通过 CAN 分析仪上位机软件,向开发板发送数据,如下所示:
此时开发板便能接收到上位机发送过来的数据,如下所示:
CAN 数据发送实例
//描述 : 一个简单地CAN数据发送示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
int main(void)
{
struct ifreq ifr = {0};
struct sockaddr_can can_addr = {0};
struct can_frame frame = {0};
int sockfd = -1;
int ret;
/* 打开套接字 */
sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if(0 > sockfd) {
perror("socket error");
exit(EXIT_FAILURE);
}
/* 指定can0设备 */
strcpy(ifr.ifr_name, "can0");
ioctl(sockfd, SIOCGIFINDEX, &ifr);
can_addr.can_family = AF_CAN;
can_addr.can_ifindex = ifr.ifr_ifindex;
/* 将can0与套接字进行绑定 */
ret = bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr));
if (0 > ret) {
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 设置过滤规则:不接受任何报文、仅发送数据 */
setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
/* 发送数据 */
frame.data[0] = 0xA0;
frame.data[1] = 0xB0;
frame.data[2] = 0xC0;
frame.data[3] = 0xD0;
frame.data[4] = 0xE0;
frame.data[5] = 0xF0;
frame.can_dlc = 6; //一次发送6个字节数据
frame.can_id = 0x123;//帧ID为0x123,标准帧
for ( ; ; ) {
ret = write(sockfd, &frame, sizeof(frame)); //发送数据
if(sizeof(frame) != ret) { //如果ret不等于帧长度,就说明发送失败
perror("write error");
goto out;
}
sleep(1); //一秒钟发送一次
}
out:
/* 关闭套接字 */
close(sockfd);
exit(EXIT_SUCCESS);
}
CAN 数据接收实例
//描述 : 一个简单地CAN数据读取示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
int main(void)
{
struct ifreq ifr = {0};
struct sockaddr_can can_addr = {0};
struct can_frame frame = {0};
int sockfd = -1;
int i;
int ret;
/* 打开套接字 */
sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if(0 > sockfd) {
perror("socket error");
exit(EXIT_FAILURE);
}
/* 指定can0设备 */
strcpy(ifr.ifr_name, "can0");
ioctl(sockfd, SIOCGIFINDEX, &ifr);
can_addr.can_family = AF_CAN;
can_addr.can_ifindex = ifr.ifr_ifindex;
/* 将can0与套接字进行绑定 */
ret = bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr));
if (0 > ret) {
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 设置过滤规则 */
//setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
/* 接收数据 */
for ( ; ; ) {
if (0 > read(sockfd, &frame, sizeof(struct can_frame))) {
perror("read error");
break;
}
/* 校验是否接收到错误帧 */
if (frame.can_id & CAN_ERR_FLAG) {
printf("Error frame!\n");
break;
}
/* 校验帧格式 */
if (frame.can_id & CAN_EFF_FLAG) //扩展帧
printf("扩展帧 <0x%08x> ", frame.can_id & CAN_EFF_MASK);
else //标准帧
printf("标准帧 <0x%03x> ", frame.can_id & CAN_SFF_MASK);
/* 校验帧类型:数据帧还是远程帧 */
if (frame.can_id & CAN_RTR_FLAG) {
printf("remote request\n");
continue;
}
/* 打印数据长度 */
printf("[%d] ", frame.can_dlc);
/* 打印数据 */
for (i = 0; i < frame.can_dlc; i++)
printf("%02x ", frame.data[i]);
printf("\n");
}
/* 关闭套接字 */
close(sockfd);
exit(EXIT_SUCCESS);
}