- 要实现同一网络节点上的应用程序之间能相互交换数据,如果CAN网络的硬件不支持回环功能,一种低效的方案是使用Socket CAN核心部分来实现软件回环
- CAN错误帧的详细格式定义在linux头文件中:include/linux/can/error.h
- 可过滤后传给用户:当一个物理层或者MAC层的错误被(CAN控制器)检测到之后,驱动创建一个相应的错误帧。错误帧可以被应用程序通过CAN的过滤机制请求得到。过滤机制允许选择需要的错误帧的类型。默认情况下,接收错误帧的功能是禁止的。
- 有两个CAN的协议可以选择,一个是原始套接字协议( raw socket protocol),另一个是广播管理协议BCM(broadcast manager)。你可以这样来打开一个套接字:
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
或者
s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
- 在成功创建一个套接字之后,你通常需要使用bind(2)函数将套接字绑定在某个CAN接口上(这和TCP/IP使用不同的IP地址不同,参见第3章)。在绑定 (CAN_RAW)或连接(CAN_BCM)套接字之后,你可以在套接字上使用read(2)/write(2),也可以使用send(2)/sendto(2)/sendmsg(2)和对应的recv*操作。当然也会有CAN特有的套接字选项,下面将会说明。
基本的CAN帧结构体和套接字地址结构体定义在include/linux/can.h:
/*
* 扩展格式识别符由 29 位组成。其格式包含两个部分:11 位基本 ID、18 位扩展 ID。
Controller Area Network Identifier structure
*
- bit 0-28 : CAN识别符 (11/29 bit)
- bit 29 : 错误帧标志 (0 = data frame, 1 = error frame)
- bit 30 : 远程发送请求标志 (1 = rtr frame)
- bit 31 :帧格式标志 (0 = standard 11 bit, 1 = extended 29 bit)
*/
typedef __u32 canid_t;
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 can_dlc; /* 数据长度: 0 .. 8 */
__u8 data[8] __attribute__((aligned(8)));
};
- 结构体的有效数据在data[]数组中,它的字节对齐是64bit的,所以用户可以比较方便的在data[]中传输自己定义的结构体和共用体。CAN总线中没有默认的字节序。在CAN_RAW套接字上调用read(2),返回给用户空间的数据是一个struct can_frame结构体。
就像PF_PACKET套接字一样,sockaddr_can结构体也有接口的索引,这个索引绑定了特定接口:
struct sockaddr_can {
sa_family_t can_family;
int can_ifindex;
union {
/* transport protocol class address info (e.g. ISOTP) */
struct { canid_t rx_id, tx_id; } tp;
/* reserved for future CAN protocols address information */
} can_addr;
};
- 指定接口索引需要调用ioctl()(比如对于没有错误检查CAN_RAW套接字):
int s;
struct sockaddr_can addr;
struct ifreq ifr;
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
strcpy(ifr.ifr_name, "can0" );
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));
(..)
- 为了将套接字和所有的CAN接口绑定,接口索引必须是0。这样套接字便可以从所有使能的CAN接口接收CAN帧。recvfrom(2)可以指定从哪个接口接收。在一个已经和所有CAN接口绑定的套接字上,sendto(2)可以指定从哪个接口发送。
从一个CAN_RAW套接字上读取CAN帧也就是读取struct can_frame结构体:
struct can_frame frame;
nbytes = read(s, &frame, sizeof(struct can_frame));
if (nbytes < 0) {
perror("can raw socket read");
return 1;
}
/* paranoid check ... */
if (nbytes < sizeof(struct can_frame)) {
fprintf(stderr, "read: incomplete CAN frame\n");
return 1;
}
/* do something with the received CAN frame */
写CAN帧也是类似的,需要用到write (2)函数:
nbytes = write(s, &frame, sizeof(struct can_frame));
- 如果套接字跟所有的CAN接口都绑定了(addr.can_index = 0),推荐使用recvfrom(2)获取数据源接口的信息:
struct sockaddr_can addr;
struct ifreq ifr;
socklen_t len = sizeof(addr);
struct can_frame frame;
nbytes = recvfrom(s, &frame, sizeof(struct can_frame),
0, (struct sockaddr*)&addr, &len);
/* get interface name of the received CAN frame */
ifr.ifr_ifindex = addr.can_ifindex;
ioctl(s, SIOCGIFNAME, &ifr);
printf("Received a CAN frame from interface %s", ifr.ifr_name);
- 对于绑定了所有接口的套接字,向某个端口发送数据必须指定接口的详细信息:
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_ifindex = ifr.ifr_ifindex;
addr.can_family = AF_CAN;
nbytes = sendto(s, &frame, sizeof(struct can_frame),
0, (struct sockaddr*)&addr, sizeof(addr));
CAN_RAW套接字的用法和CAN字符设备的用法是类似的。为了使用CAN套接字的新特性,在绑定原始套接字的时候将会默认开启以下特性:
- filter将会接收所有的数据
- 套接字仅仅接收有效的数据帧(=> no error frames)
- 发送帧的回环功能被开启(参见 3.2节)
- (回环模式下)套接字不接收它自己发送的帧
CAN_RAW套接字的接收可以使用CAN_RAW_FILTER套接字选项指定的多个过滤规则(过滤器)来过滤。
过滤规则(过滤器)的定义在 include/linux/can.h中:
struct can_filter {
canid_t can_id;
canid_t can_mask;
};
过滤规则的匹配:
<received_can_id> & mask == can_id & mask
/*
#define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */
#define CAN_ERR_FLAG 0x20000000U /* error frame */
*/
这和大家熟知的CAN控制器硬件过滤非常相似。可以使用 CAN_INV_FILTER这个宏将can_filter结构体的成员can_id中的比特位反转。和CAN控制器的硬件过滤形成鲜明对比的是,用户可以为每一个打开的套接字设置多个独立的过滤规则(过滤器):
/*
/* valid bits in CAN ID for frame formats */
#define CAN_SFF_MASK 0x000007FFU /* 标准帧格式 (SFF) */
#define CAN_EFF_MASK 0x1FFFFFFFU /* 扩展帧格式 (EFF) */
#define CAN_ERR_MASK 0x1FFFFFFFU /* 忽略EFF, RTR, ERR标志 */
*/
struct can_filter rfilter[2];
rfilter[0].can_id = 0x123;
rfilter[0].can_mask = CAN_SFF_MASK;
rfilter[1].can_id = 0x200;
rfilter[1].can_mask = 0x700;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
为了在指定的CAN_RAW套接字上禁用接收过滤规则,可以这样:
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
在一些极端情况下不需要读取数据,可以把过滤规则清零(所有成员设为0),这样原始套接字就会忽略接收到的CAN帧。在这种仅仅发送数据(不读取)的应用中可以在内核中省略接收队列,以此减少CPU的负载(虽然只能减少一点点)。

本文深入探讨了CAN网络编程的核心概念和技术细节,包括如何利用SocketCAN实现应用程序间的数据交换,解析CAN帧结构,以及如何通过设置过滤规则实现数据的精确接收。文章还介绍了原始套接字协议和广播管理协议的使用方法,以及如何在不同接口间发送和接收数据。
715

被折叠的 条评论
为什么被折叠?



