Linux SOcket CAN 学习笔记

本文深入探讨了CAN网络编程的核心概念和技术细节,包括如何利用SocketCAN实现应用程序间的数据交换,解析CAN帧结构,以及如何通过设置过滤规则实现数据的精确接收。文章还介绍了原始套接字协议和广播管理协议的使用方法,以及如何在不同接口间发送和接收数据。
  • 要实现同一网络节点上的应用程序之间能相互交换数据,如果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的负载(虽然只能减少一点点)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值