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

news2024/12/25 23:11:27

本章我们学习 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.coloradmin.cn/o/1860314.html

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

相关文章

测评策略:提升美客多、亚马逊店铺排名的有效武器

在跨境电商平台上成功打造一家具有竞争力的店铺&#xff0c;特别是在美客多这样的知名平台上&#xff0c;确实需要卖家们投入大量的研究和精力进行精细运营。以下是我基于个人经验和深入研究&#xff0c;总结出的几个关键秘诀&#xff0c;旨在帮助卖家们提高销量并提升店铺的排…

React+TS前台项目实战(十四)-- 响应式头部导航+切换语言相关组件封装

文章目录 前言Header头部相关组件1. 功能分析2. 相关组件代码详细注释3. 使用方式4. Gif图效果展示 总结 前言 在这篇博客中&#xff0c;我们将封装一个头部组件&#xff0c;根据不同设备类型来显示不同的导航菜单&#xff0c;会继续使用 React hooks 和styled-components库来…

裸机写代码(Windows.Linux环境搭建)

目录 1.工具/原料 2.配置环境变量 2.1开发环境Windows搭建 2.1.1概述 2.1.1.1. 系统环境变量 2.1.1.2. 用户环境变量 2.1.1.3.根据你的实际情况选择配置用户变量还是系统变量&#xff0c; 2.1.1.4.环境变量各个变量名的作用 2.1.1.5.具体配置实例&#xff1a; 2.1.1.6…

[深度学习]循环神经网络RNN

RNN&#xff08;Recurrent Neural Network&#xff0c;即循环神经网络&#xff09;是一类用于处理序列数据的神经网络&#xff0c;广泛应用于自然语言处理&#xff08;NLP&#xff09;、时间序列预测、语音识别等领域。与传统的前馈神经网络不同&#xff0c;RNN具有循环结构&am…

【银河麒麟】云平台查看内存占用与实际内存占用不一致,分析处理过程,附代码

1.需求/问题描述 发现云平台查看内存占用与实际内存占用不一致。 2.分析过程 在系统中获取虚拟机内存使用率目前主要有两种方式&#xff0c;一种是通过virsh dommemstat获取&#xff0c;另外一种是通过qga接口获取。由于之前修复界面虚拟机cpu使用率时为qga接口获取&#xff…

安装VEX外部编辑器

Houdini20配置VEX外部编辑器方法_哔哩哔哩_bilibili 下载并安装Visual Studio Code软件&#xff1a;Download Visual Studio Code - Mac, Linux, Windows 在Visual Studio Code软件内&#xff0c;安装相关插件&#xff0c;如&#xff1a; 中文汉化插件vex插件 安装Houdini Expr…

八、yolov8模型预测和模型导出(目标检测)

模型查看 模型预测 模型导出 模型训练完成后&#xff0c;找到训练文件生成文件夹&#xff0c;里面包含wights、过程图、曲线图。 模型预测 1、在以下文件夹中放入需要预测的图&#xff1b; 2、找到detect文件下的predict.py文件&#xff0c;修改以下内容。 3、右键点击…

AI降重技术:论文查重率的智能解决方案

现在大部分学校已经进入到论文查重降重的阶段了。如果查重率居高不下&#xff0c;延毕的威胁可能就在眼前。对于即将告别校园的学子们&#xff0c;这无疑是个噩梦。四年磨一剑&#xff0c;谁也不想在最后关头功亏一篑。 查重率过高&#xff0c;无非以下两种原因。要么是作为“…

【编译原理】语法制导翻译

1.导入 语法制导翻译是处理语义的基本方法&#xff0c;它以语法分析为 基础&#xff0c;在语法分析得到语言结构的结果时&#xff0c;对附着于此结构 的语义进行处理&#xff0c;如计算表达式的值、生成中间代码等 2.语法与语义 语法与语义的关系 语法是指语言的结构、即语言的…

html5+css简易实现图书网联系我们页面

html5css简易实现图书网联系我们页面 完整代码已资源绑定

PD虚拟机支持M3吗 PD虚拟机怎样配置图形卡

最近有很多人在问M3芯片的苹果电脑和M2相比&#xff0c;有哪些提升的功能。实际上&#xff0c;M3芯片的苹果电脑拥有与M2相同的CPU与GPU数量&#xff0c;但比M2多50亿个晶体管&#xff0c;并引入了动态缓存、增强型神经网络引擎等技术&#xff0c;性能、功能均进一步加强。面对…

【motan rpc 懒加载】异常

文章目录 升级版本解决问题我使用的有问题的版本配置懒加载错误的版本配置了懒加载 但是不生效 lazyInit"true" 启动不是懒加载 会报错一次官方回复 升级版本解决问题 <version.motan>1.2.1</version.motan><dependency><groupId>com.weibo…

Kotlin设计模式:享元模式(Flyweight Pattern)

Kotlin设计模式&#xff1a;享元模式&#xff08;Flyweight Pattern&#xff09; 在移动应用开发中&#xff0c;内存和CPU资源是非常宝贵的。享元模式&#xff08;Flyweight Pattern&#xff09;是一种设计模式&#xff0c;旨在通过对象重用来优化内存使用和性能。本文将深入探…

LabVIEW程序闪退问题

LabVIEW程序出现闪退问题可能源于多个方面&#xff0c;包括软件兼容性、内存管理、代码质量、硬件兼容性和环境因素。本文将从这些角度进行详细分析&#xff0c;探讨可能的原因和解决方案&#xff0c;并提供预防措施&#xff0c;以帮助用户避免和解决LabVIEW程序闪退的问题。 1…

STM32学习-HAL库 串口通信

学完标准库之后&#xff0c;本来想学习freertos的&#xff0c;但是看了很多教程都是移植的HAL库程序&#xff0c;这里再学习一些HAL库的内容&#xff0c;有了基础这里直接学习主要的外设。 HAL库对于串口主要有两个结构体UART_InitTypeDef和UART_HandleTypeDef&#xff0c;前者…

【CT】LeetCode手撕—56. 合并区间

目录 题目1- 思路2- 实现⭐56. 合并区间——题解思路 3- ACM 实现 题目 原题连接&#xff1a;56. 合并区间 1- 思路 模式识别&#xff1a;合并区间 ——> 数组先排序 思路 1.先对数组内容进行排序 ——> 定义 left、right 根据排序后的结果&#xff0c;更新 right2.遍…

Spring Boot整合Druid:轻松实现SQL监控和数据库密码加密

文章目录 1 引言1.1 简介1.2 Druid的功能1.3 竞品对比 2 准备工作2.1 项目环境 3 集成Druid3.1 添加依赖3.2 配置Druid3.3 编写测试类测试3.4 访问控制台3.5 测试SQL监控3.6 数据库密码加密3.6.1 执行命令加密数据库密码3.6.2 配置参数3.6.3 测试 4 总结 1 引言 1.1 简介 Dru…

如何处理消息积压问题

什么是MQ消息积压&#xff1f; MQ消息积压是指消息队列中的消息无法及时处理和消费&#xff0c;导致队列中消息累积过多的情况。 消息积压后果&#xff1a; ①&#xff1a;消息不能及时消费&#xff0c;导致任务不能及时处理 ②&#xff1a;下游消费者处理大量的消息任务&#…

品牌为什么需要3D营销?

在对比传统品牌营销手段时&#xff0c;线上3D互动营销以其更为生动的展示效果脱颖而出。它通过构建虚拟仿真场景&#xff0c;创造出一个身临其境的三维空间&#xff0c;充分满足了客户对实体质感空间的期待。不仅如此&#xff0c;线上3D互动营销还能实现全天候24小时无间断服务…

计量中的标准物是什么?仪器校准机构如何管理标准物?

计量标准中&#xff0c;标准物是常常使用的一种计量消耗品。为什么说是“消耗品”&#xff1f;因为大部分标准物都是使用就会磨损的&#xff0c;甚至不少标准物还是一次性的&#xff0c;并且这些标准物通常价格还不便宜&#xff0c;也是计量机构校准的主要成本之一&#xff0c;…