Unix/C/C++进阶--SocketCAN 编程
- 1 介绍
- 1.1 socketcan 简介
- 1.2 can 发展历程
- 1.3 can总线优点
- 2 知识点
- 2.1 CAN详解--书籍、网站
- 2.2 CAN详解--CAN与com口介绍
- 2.3 CAN详解--各家CAN分析仪与软件的比较
- 2.4 转载:CAN总线终端电阻
- 2.5 如何破解汽车--CAN协议(can协议是明文)
- 2.6 can--测试(收发、是否回传、网络状态)
- 2.7 CAN详解--协议详解
- 2.8 机器人开发--CanOpen
- 3 SocketCAN 编程
- 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)总线——驱动配置