Unix/C/C++进阶--SocketCAN 编程

1 介绍

1.1 socketcan 简介

socketcan子系统是在Linux下CAN协议(Controller Area Network)实现的一种实现方法。 CAN是一种在世界范围内广泛用于自动控制、嵌入式设备和汽车领域的网络技术。Linux下最早使用CAN的方法是基于字符设备来实现的,与之不同的是Socket CAN使用伯克利的socket接口和linux网络协议栈,这种方法使得can设备驱动可以通过网络接口来调用。Socket CAN的接口被设计的尽量接近TCP/IP的协议,让那些熟悉网络编程的程序员能够比较容易的学习和使用。

1.2 can 发展历程

1、CAN ( Controller Area Network ) 即控制器局域网络。由于其高性能、高可靠性、及独特的设计,CAN越来越受到人们的重视。CAN最初是由德国的BOSCH公司为汽车监测、控制系统而设计的。现代汽车越来越多地采用电子装置控制,如发动机的定时、注油控制,加速、刹车控制(ASC)及复杂的抗锁定刹车系统(ABS)等。由于这些控制需检测及交换大量数据,采用硬接信号线的方式不但烦琐、昂贵,而且难以解决问题,采用CAN总线上述问题便得到很好地解决。
2、1983-1986年 大众与Bosch制定 软件协议,由Intel 生产控制器。
3、1990年 首次应用于汽车 奔驰 S级 12 缸发动机的汽车。
4、1991年9月,NXP半导体公司制定并发布CAN技术规范CAN2.0A/B,其中CAN2.0A协议规范定义了标准帧格式,CAN2.0B协议规范定义了扩展帧格式。
5、1993年11月,ISO组织正式颁布CAN国际标准ISO11898(高速应用,数据传输速率小于1Mbps)和ISO11519(低速应用,数据传输速率小于125Kbps)。
6、1996年 用于奥迪 A8 D2自动变速器 3.7升 V8 01V AG5的汽车。
7、1997年 用于帕萨特 B5 AG。
8、1998年 用于宝来、高尔夫 A4 AG。
9、VAN Bus 用于标志、雷诺、雪铁龙等,菲利普公司产品。
10、J1850-HBCC 用于福特,莫托罗拉公司产品。
11、J1850-DLC 用于通用,莫托罗拉公司产品。

1.3 can总线优点

1、数据传递更安全可靠;
2、低成本(通信介质可采用双绞线,同轴电缆和光导纤维,一般采用廉价的双绞线即可,无特殊要求);
3、高速实时传递;
4、有条件实现单线功能;
5、适用于各种汽车;
6、开放的标准。

2 知识点

2.1 CAN详解–书籍、网站

链接:CAN详解–书籍、网站

2.2 CAN详解–CAN与com口介绍

链接:CAN详解–CAN与com口介绍

在这里插入图片描述
在这里插入图片描述

2.3 CAN详解–各家CAN分析仪与软件的比较

链接:CAN详解–各家CAN分析仪与软件的比较

2.4 转载:CAN总线终端电阻

链接:转载:CAN总线终端电阻

在这里插入图片描述

2.5 如何破解汽车–CAN协议(can协议是明文)

链接:如何破解汽车–CAN协议

在这里插入图片描述

在这里插入图片描述

2.6 can–测试(收发、是否回传、网络状态)

链接:can–测试

  • can 收发数据
can-utils
candump can0 
cansend can0 123#DEADBEEF 
  • 设置回传
ifconfig can0 down
ip link set can0 type can bitrate 50000 loopback on
ip link set can0 up

terminal1 接收:
candump can0 
terminal2 发送
cansend can0 123#DEADBEEF 
  • 不回传
ifconfig can0 down
ip link set can0 type can bitrate 50000 
ip link set can0 up

terminal1 接收:
candump can0 
terminal2 发送
cansend can0 123#DEADBEEF 
  • 查看can网络状态
ip -d -s link show can0
  • 是否打开3次采样
ip link set can0 type can bitrate 125000 triple-sampling on
triple-sampling on:表示打开3次采样,在较低波特率下,建议使用该参数。
如果波特率较高,例如达到500Kbps,建议将其关闭:triple-sampling off,如果需要,也可打开
  • 是否重启
ip link set can0 type can bitrate 500000 restart-ms 100 triple-sampling on
restart-ms 100 设置总线 bus-off 时延时100ms自动重启;
  • 传输队列长度 Transmit Queue Length (txqueuelen)
设置过大,响应慢,可能会增加网络时延;设置过小,响应快,不过对于大量数据,可能丢包率较高
ip link set can0 txqueuelen 1023 type can bitrate 500000

2.7 CAN详解–协议详解

链接:CAN详解–协议详解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.8 机器人开发–CanOpen

链接:机器人开发–CanOpen

CANopen由非营利组织CiA(CAN in Automation)进行标准的起草及审核工作,基本的 CANopen 设备及通讯子协定定义在 CAN in Automation (CiA) draft standard 301中。针对个别设备的子协定以 CiA 301 为基础再进行扩充。如针对 I/O 模组的 CiA401 及针对运动控制的 CiA402。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • COB-ID
    在这里插入图片描述

3 SocketCAN 编程

SocketCAN 开发用到的头文件

#include <linux/can.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <linux/can/raw.h>
#include <unistd.h>

定义命令宏(建议放入开机命令中)

#define ip_cmd_set_can0_params "ip link set can0 txqueuelen 1023 type can bitrate 500000 restart-ms 100 triple-sampling on"
#define ip_cmd_can0_up         "ifconfig can0 up"
#define ip_cmd_can0_down       "ifconfig can0 down"

// 使用系统调用函数运行以上命令,也可以自行在终端中运行,建议写shell脚本,开机运行
system(ip_cmd_set_can0_params); // 设置参数
system(ip_cmd_can0_up);  // 开启can0接口

初始化

int can_fd;
struct sockaddr_can addr;
struct ifreq ifr;
can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); // 创建 SocketCAN 套接字
strcpy(ifr.ifr_name, "can0" );
ioctl(can_fd, SIOCGIFINDEX, &ifr); // 指定 can0 设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(can_fd, (struct sockaddr *)&addr, sizeof(addr)); // 将套接字与 can0 绑定
int socket (int domain, int type, int protocol)
在 SocketCan 中,第一个参数通常将其设置为PF_CAN ,指定为 CAN 通信协议

原始套接字两种协议:RAW,BCM
int can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)
int can_fd = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);

使用 ioctl() 函数 将套接字与 can 设备绑定
ioctl(can_fd, SIOCGIFINDEX, &ifr); // 指定 can0 设备,并获取设备索引

struct sockaddr_can addr;
addr.can_family = AF_CAN;  // 指定协议族
addr.can_ifindex = ifr.ifr_ifindex;  // 设备索引
// 将套接字与 can0 绑定
int bind_res = bind(can_fd, (struct sockaddr *)&addr, sizeof(addr));

数据发送

CAN 总线与标准套接字通信稍有不同,每一次通信都采用 can_ frame 结构体将数据封装成帧。 结构体定义如下:

struct can_frame {
	canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
	__u8    can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */
	__u8    __pad;   /* padding */
	__u8    __res0;  /* reserved / padding */
	__u8    __res1;  /* reserved / padding */
	__u8    data[CAN_MAX_DLEN] __attribute__((aligned(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 /* EFF/SFF is set in the MSB */
// 远程帧的标识
#define CAN_RTR_FLAG 0x40000000U /* remote transmission request */
// 错误帧的标识,用于错误检查
#define CAN_ERR_FLAG 0x20000000U /* error message frame */

使用 write() 函数和 can_frame 结构体发送数据

struct can_frame frame;  // 声明 can 帧结构体,can_frame 定义在头文件 can.h 中
 
frame.data[0] = 0xFF;  // 要发送的(最多)8个字节的数据
frame.data[1] = 0xFF;
frame.data[2] = 0xFF;
frame.data[3] = 0xFF;
frame.data[4] = 0xFF;
frame.data[5] = 0xFF;
frame.data[6] = 0xFF;
frame.data[7] = 0xFC;
 
/************ 写数据 ************/
frame.can_dlc = 8;  // 设置数据长度(CAN协议规定一帧最多有八个字节的有效数据)
frame.can_id = 1; 	// 设置 ID 号,假设这里 ID 号为1
// 实际的 ID 号要根据是标准帧(11位)还是拓展帧(29)位来设置
// eg: frame.can_id = CAN_EFF_FLAG | 0x1;
write(can_fd, &frame, sizeof(frame));  // 写数据

数据接收

使用 read() 函数和 can_frame 结构体接收数据

struct can_frame frame;   // can_frame 结构体定义在头文件 can.h 中
read(can_fd, &frame, sizeof(frame));  // 读取数据,读取到的有效数据保存在 frame.data[] 数组中

错误处理

当帧接收后,可以通过判断 can_id 中的 CAN_ERR_FLAG 位来判断接收的帧是否为错误帧。 如果为错误帧,可以通过 can_id 的其他符号位来判断错误的具体原因。

过滤规则设置

过滤规则使用 can_filter 结构体来实现,定义如下:

/**
 * struct can_filter - CAN ID based filter in can_register().
 * @can_id:   relevant bits of CAN ID which are not masked out.
 * @can_mask: CAN mask (see description)
 *
 * Description:
 * A filter matches, when
 *
 *          <received_can_id> & mask == can_id & mask
 *
 * The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can
 * filter for error message frames (CAN_ERR_FLAG bit set in mask).
 */
struct can_filter {
	canid_t can_id;
	canid_t can_mask;
};

#define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */
#define CAN_RAW_FILTER_MAX 512 /* maximum number of can_filter set via setsockopt() */

通过这条规则可以在系统中过滤掉所有不符合规则的报文,使得应用程序不需要对无关的报文进行处理。
其中 can_id 的作用是过滤器 filter ,can_mask 用来做 filter 的掩码,匹配过滤器的规则如下:

<received_can_id> & mask == can_id & mask

用户可以为每个打开的套接字设置多条独立的过滤规则,使用方法如下:

static struct can_filter filterCan0[3] = {
    {0, 0x07FFF800},
    {1, 0x07FFF801},
    {2, 0x07FFF802}
};

setsockopt(_fd0, SOL_CAN_RAW, CAN_RAW_FILTER, filterCan0, 3 * sizeof(struct can_filter ))
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); // 禁用过滤规则

通过错误掩码可以实现对错误帧的过滤, 例如:
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));

回环功能设置

在默认情况下, 本地回环功能是开启的,可以使用下面的方法关闭回环/开启功能:

int loopback = 0; // 0 表示关闭, 1 表示开启( 默认)
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

参考

1、can-utils 工具套件
2、can–测试
3、CAN详解–书籍、网站
4、CAN详解–CAN与com口介绍
5、CAN详解–各家CAN分析仪与软件的比较
6、转载:CAN总线终端电阻
7、如何破解汽车–CAN协议
8、CAN详解–协议详解
9、机器人开发–CanOpen
10、Linux SocketCAN 编程(C++,启用多线程接收)
11、Linux CAN编程详解
12、linux+v2.6.34/Documentation/networking/can.txt
13、tx2 can通信之开机自动加载can模块
14、Linux上使用CAN通信的方法
15、嵌入式Linux中的CAN(FD)总线——驱动配置

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

worthsen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值