CAN 应用编程

本章我们学习CAN 应用编程,CAN 是目前应用非常广泛的现场总线之一,主要应用于汽车电子和工业领域,尤其是汽车领域,汽车上大量的传感器与模块都是通过CAN 总线连接起来的。CAN 总线目前是自动化领域发展的热点技术之一,由于其高可靠性,CAN 总线目前广泛的应用于工业自动化、船舶、汽车、医疗和工业设备等方面。
在我们的I.MX6U ALPHA/Mini 开发板上提供了一个CAN 接口,使用该接口可以进行CAN 总线通信,本章我们就来学习一下如何编写一个简单地应用程序来测试开发板上的CAN 接口。需要说明的是,本章自然不会深入给大家讲解CAN 应用编程,同样,CAN 总线技术也是一个非常专业的技术方向,笔者并不从事这方面的工作,对CAN 的使用、了解少之又少,所以本章同样旨在以引导大家入门为主!

CAN 基础知识

在学习CAN 应用编程之前,本小节简单地给大家介绍下CAN 相关的基础知识。
本小节内容参考了由瑞萨电子编写的《CAN 入门教程》,该手册在开发板资料包已经给大家提供了,路径为:4、参考资料->CAN 入门教程.pdf。

什么是CAN?

CAN 是Controller Area Network 的缩写(以下称为CAN),即控制器局域网络,是ISO 国际标准化的串行通信协议。
CAN 总线最初是由德国电气商博世公司开发,其最初动机就是为了解决现代汽车中庞大的电子控制系统之间的通讯,减少不断增加的信号线。于是,他们设计了一个单一的网络总线,所有的外围器件可以被挂接在该总线上。
在当前的汽车工业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来,车上的电子控制系统越来越多,譬如发动机管理控制、变速箱控制、汽车仪表、空调、车门控制、灯光控制、气囊控制、转向控制、胎压监测、制动系统、雷达、自适应巡航、电子防盗系统等。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需要,1986
年德国电气商博世公司开发出面向汽车的CAN 通信协议。此后,CAN 通过ISO11898 及ISO11519 进行了标准化,现在在欧洲已是汽车网络的标准协议,目前已是当前应用最广泛的现场总线之一。
CAN 是一种多主方式的串行通讯总线,基本设计规范要求有高的位速率,高抗电磁干扰性,而且能够检测出产生的任何错误。经过几十年的发展,现在,CAN 的高性能、高可靠性以及高实时性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。
以汽车电子为例,汽车上有空调、车门、发动机、大量传感器等,这些部件、模块都是通过CAN 总线连在一起形成一个网络,车载网络构想图如下所示:
图31.1.1 车载网络构想图
在这里插入图片描述

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 节点构成,其网络拓扑结构如下图所示:
图31.1.3 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 七层模型(具体详情参考
29.2 小节)。虽然CAN 传输协议参考了OSI 七层模型,但是实际上CAN 协议只定义了“传输层”、“数据链路层”以及“物理层”这三层,而应用层协议可以由CAN 用户定义成适合特别工业领域的任何方案。已在工业控制和制造业领域得到广泛应用的标准是DeviceNet,这是为PLC 和智能传感器设计的。在汽车工业,许多制造商都有他们自己的应用层协议标准。
图31.1.4 OSI 七层模型和CAN 协议

CAN 协议只参考了OSI 模型中的“传输层”、“数据链路层”以及“物理层”,因此出现了各种不同的应用层协议,比如用在自动化技术的现场总线标准DeviceNet,用于工业控制的CanOpen,用于乘用车的诊断协议OBD、UDS(统一诊断服务,ISO14229),用于商用车的CAN 总线协议SAEJ1939。
数据链路层分为MAC 子层和LLC 子层,MAC 子层是CAN 协议的核心部分。数据链路层的功能是将物理层收到的信号组织成有意义的消息,并提供传送错误控制等传输控制的流程。具体地说,就是消息的帧化、仲裁、应答、错误的检测或报告。数据链路层的功能通常在CAN 控制器的硬件中执行。
在物理层定义了信号实际的发送方式、位时序、位的编码方式及同步的步骤。但具体地说,信号电平、通信速度、采样点、驱动器和总线的电气特性、连接器的形态等均未定义。这些必须由用户根据系统需求自行确定。

CAN 帧的种类

CAN 通信协议定义了5 种类型的报文帧,分别是:数据帧、遥控帧、错误帧、过载帧、间隔帧。通信是通过这5 种类型的帧进行的。其中数据帧和遥控帧有标准格式和扩展格式两种,标准格式有11 位标识符
(ID),扩展格式有29 个标识符(ID)。这5 种类型的帧如下表所示:
在这里插入图片描述
其中数据帧是使用最多的帧类型,这里重点介绍一下数据帧,数据帧结构如下图所示:
图31.1.5 数据帧的构成

图31.1.5 给出了数据帧标准格式和扩展格式两种帧结构,图中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 函数在30.2.1 小节中给大家详细介绍过,第一个参数用于指定通信域,在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()函数在30.2.2 小节中给大家详细介绍过,这里不再重述!
这里出现了两个结构体:struct ifreq 和struct sockaddr_can,其中struct ifreq 定义在<net/if.h>头文件中,而struct sockaddr_can 定义在<linux/can.h>头文件中,这些结构体就不给大家解释了,有兴趣的自己去查,笔者也不太懂!

设置过滤规则

在我们的应用程序中,如果没有设置过滤规则,应用程序默认会接收所有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 接口,即can0,如下图如所示:
在这里插入图片描述
图31.3.2 ALPHA 板上的can 接口

在这里插入图片描述
图31.3.3 Mini 板上的can 接口
在开发板出厂系统中,提供了一些工具用于测试can,我们可以使用这些工具进行测试。由于我们板子上只有一个can 接口,如果想要进行测试,可以使用两块开发板,将它们的CAN 接口进行对接,进行收发测试,如果大家手中只有一块开发板,那这种方式自然是行不通的;除此之外,我们还可以使用一些测试
CAN 的设备进行测试,譬如CAN 分析仪,这里笔者使用CAN 分析仪来进行测试。
如果大家有购买CAN 分析仪等这类CAN 测试设备,就直接使用它进行测试即可!关于CAN 分析仪以及配套上位机的使用方法,请大家参考它们提供的使用说明书,如果不会使用,请咨询厂家的技术支持!
在测试之前,使用CAN 分析仪或者其它测试CAN 的设备连接到ALPHA|Mini 开发板底板上的CAN 接口处,CANH 连接到仪器的CANH、CANL 连接到CAN 仪器的CANL。并且打开CAN 分析仪配套的上位机软件,笔者使用的是创芯科技的推出的一款CAN 分析仪,其配套的上位机软件界面如下所示:
图31.3.4 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

图31.3.5 使用cansend 命令发送数据

在这里插入图片描述
“#”号前面的123 表示帧ID,后面的数字表示要发送的数据,此时上位机便会接收到开发板发送过来的数据,如下所示:
图31.3.6 上位机软件收到数据

接着测试开发板接收CAN 数据,首先在开发板上执行candump 命令:

candump -ta can0

接着通过CAN 分析仪上位机软件,向开发板发送数据,如下所示:
在这里插入图片描述
此时开发板便能接收到上位机发送过来的数据,如下所示:
在这里插入图片描述
测试完成之后,按Ctrl + C 退出candump 程序,关于CAN 的测试就到这里了。

CAN 数据发送实例

通过前面的学习,本小节我们来编写一个非常简单地CAN 数据发送的示例代码,代码笔者已经写好了,如下所示。
本例程源码对应的路径为:开发板光盘->11、Linux C 应用编程例程源码->31_can->can_write.c。

#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);
}

代码比较简单,这里就不再给做过多的解释说明了,直接编译:
图31.3.9 编译示例代码

将编译得到的可执行文件can_write 拷贝到开发板Linux 系统/home/root 目录下,然后运行该程序,程序运行后,会每隔1 秒中通过can0 发送一帧数据,一次发送6 个字节数据,帧ID 为0x123,此时CAN 上位机便会接收到开发板发送过来的数据,如下所示:
在这里插入图片描述

CAN 数据接收实例

本小节我们来编写一个非常简单地CAN 数据接收的示例代码,代码笔者已经写好了,如下所示。
本例程源码对应的路径为:开发板光盘->11、Linux C 应用编程例程源码->31_can->can_read.c。

#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);
}

编译示例代码:
在这里插入图片描述
将编译得到的可执行文件can_read 拷贝到开发板Linux 系统/home/root 目录下,然后执行程序,接着通过CAN 上位机向开发板发送数据,此时开发板便会接收到上位机发送过来的数据,如下所示:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zhuguanlin121/article/details/132594924
CAN