Linux_应用篇(24) CAN 应用编程基础

本章我们学习 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:一个字符数组,用于存储网络接口的名称(例如,eth0wlan0)。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_nametoindexioctl 获取。
  • can_addr:一个联合体,用于特定 CAN 协议的地址。目前,它只包含 tp,这是用于传输协议(Transport Protocol)的特定地址结构体。

使用 struct sockaddr_can 通常涉及以下步骤:

  1. 创建一个 CAN 套接字。
  2. 设置 struct sockaddr_can 结构体的字段。
  3. 绑定套接字到指定的 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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/37173.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

活动|华院计算受邀参加2024全球人工智能技术大会(GAITC),探讨法律大模型如何赋能社会治理

6月22至23日&#xff0c;备受瞩目的2024全球人工智能技术大会&#xff08;GAITC&#xff09;在杭州市余杭区未来科技城隆重举行。本届大会以“交叉、融合、相生、共赢”为主题&#xff0c;集“会、展、赛”为一体&#xff0c;聚“产、学、研”于一堂。值得一提的是&#xff0c;…

深度解析RocketMq源码-消费者索引ConsumeQueue

1.绪论 rocketmq的broker中关于消息持久化的组件主要包含三个&#xff0c;分别是&#xff1a;持久化消息到文件中的组件commitLog&#xff1b;根据消息key索引commitLog日志的indexFile&#xff1b;消费者根据topic和queueId查询commitLog日志的consumeQueue。前面已经介绍com…

使用QGIS进行研究区域制图实战

目录 前言 一、QGIS的版本和数据介绍 1、关于QGIS版本 2、需要准备的数据 二、准备制图 1、制作全国区位图 2、矢量和遥感信息的编辑 三、出图编辑 1、设置主题信息 2、打印布局制作 3、美化地图 总结 前言 俗话说“一图胜千言”&#xff0c;在地理信息的领域中&…

ai智能语音机器人在电销里发挥怎样的作用

得益于语音识别技术的的进步&#xff0c;人工智能发展越来越成熟。相信作为企业的管理者&#xff0c;都遇到过这样的事&#xff1a;一个电销新人刚刚入行&#xff0c;需求经过一两个月的学习培训才能成为一名合格的销售人员。在这段学习的期间&#xff0c;企业投入的成本是没有…

来聊聊nacos

先关注下下方公众号呗&#xff1a; 第1部分&#xff1a;引言 微服务的挑战 尽管微服务架构带来了许多好处&#xff0c;如敏捷性、可扩展性和容错性&#xff0c;但它也带来了一些挑战&#xff0c;特别是在服务发现、配置管理、服务间通信和运维管理方面。这些挑战需要有效的解…

混凝土搅拌站中的智能化系统应用

随着科技的飞速发展&#xff0c;混凝土搅拌站已经进入了现代化、智能化的新时代。现代自动化、智能化技术的应用&#xff0c;使得混凝土搅拌站更加高效、准确、可靠&#xff0c;同时也提高了生产效率和质量。本文将带你深入探索混凝土搅拌站中运用到现代自动化、智能化的方方面…

Java代码基础算法练习-删除有序数组中的重复项-2024.05.07

任务描述&#xff1a; 有一批同学需要计算各自的出生年月是否闰年。请使用算法计算出他们的出生年份是否闰年。 解决思路&#xff1a; 如果要一次性输出结果&#xff0c;就是先输入数字n&#xff0c;确定首先循环几次&#xff0c;在每次循环中进行闰年判断操作&#xff0c;每次…

国外的Claude3.5 Sonnet Artifacts和国内的CodeFlying孰强孰弱?

在Claude 3.5 Sonnet发布后&#xff0c;最受大家关注的问题应该就是它在编写代码能力上的变化。 要知道在Claude3.0发布以来的这几个月就因为它的编写代码能力而一直受到人们的诟病。 那Anthropic这次终于是不负众望&#xff0c;在Claude 3.5 Sonnet中更新了一个叫做Artifact…

【STM32】SysTick系统滴答定时器

1.SysTick简介 CM4内核的处理和CM3一样&#xff0c;内部都包含了一个SysTick定时器&#xff0c;SysTick 是一个24 位的倒计数定时器&#xff0c;当计到0 时 &#xff0c;将 从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除&#xf…

优维“统一开放平台”:开放、开发、集成、客制化

基于丰富完善的产品体系&#xff0c;优维重磅推出了统一开放平台。这款由优维自主设计与研发&#xff0c;集数据开发、能力开放、能力集成、客制化为一体的统一开放平台&#xff0c;具备应用市场、应用开发、连接能力、采控平台、API集市、开发者工具等功能模块&#xff0c;可为…

【RNN练习】LSTM-火灾温度预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前期准备工作 import torch.nn.functional as F import numpy as np import pandas as pd import torch from torch import nn1. 导入数据 data pd.read_cs…

LLM大模型实战 —— DB-GPT阿里云部署指南

简介&#xff1a; DB-GPT 是一个实验性的开源应用&#xff0c;它基于FastChat&#xff0c;并使用vicuna-13b作为基础模型, 模型与数据全部本地化部署, 绝对保障数据的隐私安全。 同时此GPT项目可以直接本地部署连接到私有数据库, 进行私有数据处理&#xff0c; 目前已支持SQL生…

慌慌张张,匆匆忙忙,又是学习的一天

今天学进程 进程的状态 &#xff08;本科的考点我记得哈哈&#xff09; 什么是线程 线程的状态 线程和进程的区别 一个共享 一个私有 独立 多线程的优缺点 线程的分类 内核支持线程 用户级线程 组合方式线程 协程coroutine 进程 分配资源的最小单位 线程 是cpu调度的最小…

Spring AI之后,阿里推出Spring Cloud Alibaba AI,接入体验篇——Java也能方便用 AI

阿里推出Spring Cloud Alibaba AI&#xff0c;接入体验篇——Java也能方便用 AI 1.Spring AI2.Spring Cloud Alibaba AI3. 接入体验 1.Spring AI Spring AI 是 Spring 官方社区项目&#xff0c;旨在简化 Java AI 应用程序开发&#xff0c;让 Java 开发者像使用 Spring 开发普通…

NSSCTF-Web题目18(反序列化)

目录 [NISACTF 2022]babyserialize 1、题目 2、知识点 3、思路 [SWPUCTF 2022 新生赛]ez_ez_unserialize 4、题目 5、知识点 6、思路 [NISACTF 2022]babyserialize 1、题目 2、知识点 反序列化、绕过过滤、命令执行 3、思路 <?php include "waf.php";…

基于Vue,mysql,JavaEE的简单投票与投票管理系统

项目介绍 ​ 本项目&#xff0c;基于Vue2.6,mysql,JavaEE 实现简单的投票与投票管理系统 项目地址 VotingSystem: 投票系统1.0 管理员和普通用户 (gitee.com) 有问题请评论私聊哦 项目分类 数据库 创建投票人&#xff0c;被投票人&#xff0c;投票关系&#xff08;追踪谁…

float8格式

产生背景 在人工智能神经元网络中&#xff0c;一个参数用1字节表示即可&#xff0c;或者说&#xff0c;这是个猜想&#xff1a;因为图像的颜色用8比特表示就够了&#xff0c;所以说&#xff0c;猜想神经元的区分度应该小于256。 数字的分配 8比特有256个码位&#xff0c;分为…

【操作与配置】WSL配置LINUX

WSL2&#xff08;Windows Subsystem for Linux 2&#xff09;是Microsoft开发的一项技术&#xff0c;允许用户在Windows操作系统上运行Linux发行版。WSL2是WSL&#xff08;Windows Subsystem for Linux&#xff09;的第二版&#xff0c;带来了许多改进和新特性。 官网&#xff…

EXCEL 复制后转置粘贴

nodepad 转置参考&#xff1a; https://editor.csdn.net/md/?articleId140014651 1. WPS复制后转置粘贴 复制-》右键-》顶部第一行-》粘贴行列转置&#xff0c;如下图&#xff1a; 2. Excel office365 本地版 2. Excel office365 在线版

涨知识!推荐6个非常好用的App!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/我从 50 个应用程序中选出了 6 个超级实用的应用程序。 每款应用程序都是最核心、最有益的知识提升工具&#xff01;每天打开它们&#xff0c;提神…