Bootstrap

socket can 编程

Socket can
文档原名称:can.txt,位于 linux-3.2.0-m3352/Documentation/networking/can.txt。
CAN 控制器局域网络协议族之 Readme 文件(aka Socket CAN)。另外在该文档的后边附加一个CAN收发测试程序,用于与下位机通信。
目录
1 概述 / 什么是 Socket CAN?
2 目的 / 为什么要使用 socket API?
3 socket can 概念
3.1 接收列表
3.2 数据发送的本地回环模式(loopback)
3.3 网络安全问题(capabilities)
3.4 网络问题注意事项
4 怎样使用 Socket Can?
4.1 使用 can 过滤器的原始套接字
4.1.1 原始套接字选项- CAN_RAW_FILTER
4.1.2 原始套接字选项- CAN_RAW_ERR_FILTER
4.1.3 原始套接字选项- CAN_RAW_LOOPBACK
4.1.4 原始套接字选项- CAN_RAW_RECV_OWN_MSGS
4.1.5 原始套接字返回的消息标志
4.2 广播管理协议套接字(SOCK_DGRAM)
4.3 面向连接的传输协议(SOCK_SEQPACKET)
4.4 非连接传输协议(SOCK_DGRAM)
5 Socket CAN 核心模块
5.1 can.ko 模块参数
5.2 procfs 内容
5.3 编写自定义 CAN 协议模块
6 CAN 驱动程序
6.1 通用设置
6.2 数据发送的本地回环模式
6.3 CAN 控制器硬件过滤器
6.4 虚拟 CAN 驱动(vcan)
6.5 CAN 驱动程序接口
6.5.1 Netlink 接口-设置/获取器件属性
6.5.2 设置 CAN 位时序
6.5.3 启动和停止 CAN 器件
6.6 支持的 CAN 器件
7 学习 Socket CAN 相关资源
8 致谢
1 概述 / 什么是 Socket CAN?
Socketcan 套接字是 Linux 下 CAN 协议的实现方法。 CAN 协议通信技术在自动化领域,
嵌入式器件编程,和汽车领域等具有广泛的应用。尽管,在 Linux 下有多种基于字符设备的

CAN 协议的实现,但是 Socket CAN 运用 Berkeley socket API,linux 网络协议栈,将 CAN

器件驱动程序实现为网络接口。CAN socket API 设计实现尽可能的与 TCP/IP 协议相同,

允许程序员像使用网络编程一样,非常简单地学习怎样使用 CAN sockets。
2 初衷 / 为什么要使用 socket API?

在 Socket CAN 出现之前,Linux 下 CAN 通信的实现方法存在着许多的问题,这就是我

们为什么停止那些 CAN 实现方法的研究转而实现 Socket CAN 的原因。当时,大多数已经
存在的 CAN 实现方法,基于字符设备,提供相对较少的功能。通常,具体针对某一种设备
的驱动程序提供一种字符设备接口用来发送和接收 CAN 原始数据帧,直接同硬件打交道。
数据帧队列和更高层的传输协议像 ISO-TP 在用户空间程序中实现。另外,大多数基于字符
设备的实现方法仅仅提供一种每次只能打开一个设备的访问方法,就像串口通信接口。往往,
伴随着 CAN 控制器的变化,就需要实现新的驱动程序和应用程序因为新驱动程序接口改变
而需要发生变化的适应性。

Socket CAN 被设计用来解决这些局限性。新实现的协议族提供用户程序一个套接字接

口,它是基于 Linux 网络层构建的,所以,能够使用网络层提供的与队列相关的所有功能。
CAN 驱动程序向网络层注册自己为网络器件,所以,CAN 控制器的 CAN 数据帧能够被传
送到网络层和 CAN 通信协议模块,反之亦然。 CAN 协议通信模块为传输协议模块提供注册
API,所以,其它的 CAN 通信传输协议可以被动态的加载和卸载。事实上,CAN 核心模块
并不能提供任何协议,在没有载入任何协议时,也不能使用。与此同时,可以同时打开多个
套接字,不论它们是不是基于同一种 CAN 通信协议,它们都能实现监听和发送 CAN 数据
帧,不论它们是否具有相同的 CAN ID。几个使用相同接口监听具有相同的 CAN ID 的通信
帧的套接字,能够得到相同的 CAN 数据帧。如果,用户程序想要使用某一种通信协议,例
如 ISO-IP 协议,只需要在打开套接字的时候选择相应的协议即可,然后就能够读写用户数
据,而不闭关心 CAN ID,帧结构等信息了。

在用户程序空间基于字符设备提供的方法也能狗实现相似的功能,但是这种设计方法不

够完美,原因有两方面:
(1)复杂的使用方法。与传递给 socket(2)协议参数不同,使用 bind(2)选择 CAN 通信
接口和 CAN ID,应用程序必须使用 ioctl(2)实现许多的控制。
(2)代码复用性。字符设备不能使用 Linux 网络层的队列相关代码,所以必须为字符
设备重新实现这部分代码。
(3)抽象性。许多已存的基于字符设备的实现方法,针对 CAN 控制器而提供的特定
硬件驱动程序直接提供给用户程序字符设备接口使用。这与 Unix 系统下的字符和块设备是
不相同的。例如,你没有为某一种 UART 设备,某一种声卡,某一种 SCSI 或 IDE 控制器提
供访问你硬盘或者磁盘的字符设备接口。取而代之的是,建立一个抽象层,一方面为应用程
序提供统一的字符或者块设备接口,另一方面为特定的硬件驱动程序提供一个接口。这些抽
象层由子系统提供,像 tty 层,音频系统和 SCSI 或 IDE 系统。
最简单的实现 CAN 驱动的方法是使用没有抽象层或者只有部分抽象的字符设备,这也
是已经存在的驱动程序常用的方法。但是,最正确的方式是添加一个这样具有所有功能的抽
象层,像使用 CAN ID 注册功能,支持同时打开几个文件描述符和 CAN 通信帧的复用, CAN
通信帧的复杂队列的实现,以及提供注册器件驱动的 API。但是,通过使用 Linux 内核提供
的网络层,使得这一切变得不再困难,不再复杂。这就是 Socket CAN 所做的。
使用 Linux 内核的网络层协议框架实现 Linux CAN 通信是最自然,也是最恰当的方法
了。

3 socket can 概念

正如第二章所述,Socket CAN 提供给用户程序基于 Linux 网络层构建的套接字接口是
其主要设计目的。与众所周知的 TCP/IP 协议和以太网通信协议不同的是,CAN 总线没有
MAC 层地址,你能用于广播。CAN ID 仅仅用来 CAN 总线的仲裁。因此 CAN-ID 在总线上
必须是唯一的。当设计一个 CAN-ECU(Electronic Control Unit 电子控制单元)网络的时候,
CAN-ID 可以映射到具体的 ECU。因此 CAN-ID 可以当作发送源的地址来使用。

3.1 接收队列

允许多个应用程序同时访问网络导致了新的问题出现,那就是不同的应用程序可能会在
同一个 CAN 网络接口上对具有相同 CAN-ID 的帧感兴趣。Socket CAN 的核心部分实现了
Socket CAN 的协议族,通过高效的接收队列解决了这个问题。比如一个用户空间的程序打
开了一个原始 CAN 套接字,原始协议模块将向 CAN 套接字的核心模块申请用户空间需要
的一系列 CAN-ID。Socket CAN 的核心向 CAN 协议模块提供预约和解约 CAN-ID 的接口
-can_rx_(un)register(),无论这个 CAN-ID 是针对一个具体的 CAN 接口还是所有已知的 CAN
接口(参考第 5 章)
为了优化 CPU 的运行效率,每个设备都对应一个接收队列,这样比较容易实现各种报
文过滤规则。

3.2 数据发送的本地回环模式(loopback)

在其它种类的网络中,在相同或者不同网络节点上的应用程序都可以相互交换数据。

如上插图所示。为了保证应用程序 A 在两个例子中能够接收到同样的信息(例 2 中 A
和 B 在同一个 CAN 设备上),必须实现 CAN 数据发送的本地回环模式。
Linux 下的网络设备仅仅处理物理媒介上帧的发送和接受。总线仲裁机制下,高优先级
的帧会将低优先级的帧延后。为了正确反映实际的通信活动,回环必须在正确传输成功之后
进行。如果 CAN 网络接口的硬件不支持回环功能,一种低效的方案是使用 Socket CAN 核
心部分来实现软件回环。具体的情况请参考 6.2 小节。
CAN 网络的回环功能是默认开启的。由于 RT-SocketCAN(实时 CAN 通信协议) 的特
殊需求,每个套接字的回环功能可以被独立关闭。CAN 原始套接字的控制选项请参考 4.1
小节。
当你在同一个节点上运行 CAN 分析命令“candump”或者“cansniffer”的时候就会发
现回环功能真的很有用。

3.3 网络安全问题(capabilities)

CAN 是一种现场总线,仅仅用来广播消息,而没有任何路由和安全概念。大部分情况
下,应用程序都需要直接处理 CAN 原始帧。所以,和其它类型的网络一样,CAN 网络对所
有的用户(而不仅仅是 root 用户)访问没有任何限制。由于当前 CAN_RAW 和 CAN_BCM
的实现仅仅支持对 CAN 接口的读写,所以允许所有的用户访问 CAN 并不影响其它类型网
络的安全性。为了使能非 root 用户对 CAN_RAW 和 CAN_BCM 协议套接字的访问,必须在
编译内核的时候选上 Kconfig 的 CAN_RAW_USER/CAN_BCM_USER 选项。3.4 网络故障检测
使用 CAN 总线的时候,可能会遇到物理和 mac(media access control)层的问题。为了
方便用户分析物理收发器的硬件错误、总线仲裁错误和不同的 ECU( Electrical Conversion
Unit,电气转换装置)引起的错误,对于底层(物理和 mac 层)的监测和记录是至关重要的。
拥有精确时间戳的错误监测对于诊断错误是非常重要的。基于以上原因,CAN 接口驱动可
以选择性将产生的错误帧,使用和其它的 CAN 帧一样的方式传递给用户程序。无论什么时
候,一个物理层或者 MAC 层的错误被(CAN 控制器)检测到之后,驱动程序创建一个相应的
错误帧。错误帧可以被应用程序通过 CAN 的过滤机制请求得到。过滤机制允许选择需要的
错误帧的类型。默认情况下,接收错误帧的功能是禁止的。
CAN 错误帧的详细格式定义在 linux 头文件中:include/linux/can/error.h。

4 怎样使用 Socket CAN

就像 TCP/IP,在使用 CAN 网络之前,你首先需要打开一个套接字。CAN 的套接字使
用到了一个新的协议族,所以在调用 socket(2)这个系统函数的时候需要将 PF_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 不同,因为它使用不同的 MAC 层地址,参见第 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)
    /
    struct can_frame {
    canid_t can_id;/
    32 bit CAN_ID + EFF/RTR/ERR flags /
    __u8 can_dlc; /
    data length code: 0 … 8 */
    __u8 data[8] attribute((aligned(8)));
    };

结构体中 data[]数组,它的字节对齐是 64bit 的。所以,用户通过定义自己的结构体和
共同体,可以轻松的访问 CAN 数据载荷。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;
}/
处理接收的 CAN 数据帧 /
写 CAN 帧也是类似的,需要用到 write (2)函数:
nbytes = write(s, &frame, sizeof(struct can_frame));
如果套接字跟所有的 CAN 接口都绑定了(addr.can_index = 0),推荐使用 recvfrom(2)
如果需要原始 CAN 接口信息的话:
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);
/
获取接收数据帧的接口名称 /
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));

4.1 使用 can 过滤器的原始套接字
CAN_RAW 套接字的用法和 CAN 字符设备的用法是类似的。为了使用 CAN 套接字的
新特性,在绑定原始套接字的时候将会默认开启以下特性:

  • filter 将会接收所有的数据
  • 套接字仅仅接收有效的数据帧(=> no error frames)
  • 发送帧的回环功能被开启(参见 3.2 节)
  • (回环模式下)套接字不接收它自己发送的帧
    这些特性的设置可以在绑定之前和之后修改。为了使用 CAN_RAW 套接字相关的选项,
    必须包含<linux/can/raw.h>。

4.1.1 原始套接字选项- CAN_RAW_FILTER

CAN_RAW 套接字的接收可以使用 CAN_RAW_FILTER 套接字选项指定 0-N#过滤器来
实现。
过滤规则(过滤器)的定义在 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 的负载(虽然只能减少一点点)。

4.1.2 原始套接字选项- CAN_RAW_ERR_FILTER

正如 3.4 节所说,CAN 接口驱动可以产生错误帧,选择性地将错误帧和正常帧以相同
的方式传给应用程序。可能产生的错误被分为不同的种类,使用适当的错误掩码可以过滤它
们。为了注册所有可能的错误情况,CAN_ERR_MASK(0x1FFFFFFFU)这个宏可以用来
作为错误掩码。这个错误掩码定义在 linux/can/error.h。
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));

4.1.3 原始套接字选项- CAN_RAW_LOOPBACK

为了满足众多应用程序的需要,本地回环功能默认是开启的(详细情况参考 3.2 节)。
但是在一些嵌入式应用场景中(比如只有一个用户在使用 CAN 总线),回环功能可以被关
闭(各个套接字之间是独立的):int loopback = 0; /* 0 = disabled, 1 = enabled (default) */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

4.1.4 原始套接字选项- CAN_RAW_RECV_OWN_MSGS

在本地回环功能开启的情况下,所有的发送帧都会被回环到在相应 CAN 接口上注册了
同样 CAN-ID(和发送帧的相同)的套接字上。发送 CAN 帧的套接字被假设不想接收自己
发送的 CAN 帧,因此在发送套接字上的回环功能默认是关闭的。可以在需要的时候改变这
一默认行为:
int recv_own_msgs = 1; /* 0 = disabled (default), 1 = enabled */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
&recv_own_msgs, sizeof(recv_own_msgs));

4.1.5 原始套接字返回的消息标志

当使用 recvmsg()时,msg->msg_flags 可能会包含下面的标志:
MSG_DONTROUTE: 当接收的数据帧被创建在本地主机上时设置
MSG_CONFIRM: 当接收到数据帧的套接字再发送该数据的时候设置。这个标志可以被
解释为发送任务的确认,当驱动器支持数据帧的回显时。为了不接受这样的数据帧,
CAN_RAW_RECV_OWN_MSGS 必须被设置。

4.2 广播管理协议套接字(SOCK_DGRAM)

4.3 面向连接的传输协议(SOCK_SEQPACKET)
4.4 非连接传输协议(SOCK_DGRAM)

5Socket CAN 核心模块

Socket CAN 核心模块实现协议族 PF_CAN。核心模块在运行时动态载入 CAN 协议。核
心模块为 CAN 协议模块提供接口来申请需要的 CAN ID(详见 3.1 章)。

5.1 can.ko 模块参数

-stats_timer: 为了计算 Socket CAN 核心模块的统计信息(例如,每秒帧数和每秒最大
帧数),这个间隔 1S 的定时器被 can.ko 模块调用,默认情况下是开启的。在模块命令行中
使用 stattimer=0 可以禁止该定时器。

5.2 procfs 接口

就像 3.1 节描述的那样,CAN 套接字核心借助于一些带有过滤规则的队列向 CAN 协议
模块传递接收到的 CAN 帧。可以在 procfs 中查看这些接收队列的的过滤规则和匹配规则的
次数。所有的条目都包了设备名和协议模块标识:
foo@bar:~$ cat /proc/net/can/rcvlist_all
receive list ‘rx_all’:
(vcan3: no entry)
(vcan2: no entry)
(vcan1: no entry)
device
can_id
can_mask function userdata
matches ident
vcan0
000
00000000 f88e6370 f6c6f400
0 raw
(any: no entry)在本例中,应用程序接收来自 vcan0 的所有数据传送。
rcvlist_all - 没有过滤规则的队列
rcvlist_eff - 扩展帧(EFF)的队列
rcvlist_err - 错误帧队列
rcvlist_fil - 通过过滤规则的队列
rcvlist_inv - 未通过过滤规则的队列
rcvlist_sff - 标准帧的队列
/proc/net/can 目录下其余的 procfs 文件
stats
-Socket CAN 核心模块的统计信息(rx/tx frames,匹配率等)
reset -复位统计信息
version -打印 Socket CAN 核心模块的版本信息和 ABI 版本信息

5.3 写自己的 CAN 协议模块

要在 PF_CAN 中增加一个新的协议,必须在 include/linux/can.h 中为新的协议增加相应
的定义。包含 include/linux/can.h 这个文件便可以使用增加的原型和定义。内核除了提供了
注册 CAN 协议和 CAN 设备的通知列表的功能,也提供了在一个特定 CAN 接口上注册感兴
趣的 CAN 帧或者发送 CAN 帧的功能。
can_rx_register

  • 在一个特定接口上注册希望接收到的 CAN 帧的信息(yll:
    这个函数的定义在内核的 net/can/af_can.c 中)
    can_rx_unregister
  • 注销上面的申请
    can_send
  • 发送 CAN 帧(可以选择是否开启本地回环)
    详细的信息请参考内核源码中的 net/can/af_can.c、net/can/raw.c、net/can/bcm.c。

6 CAN 驱动程序

写基于网络层的 CAN 驱动程序要比写基于字符设备的 CAN 驱动程序简单的多。与其
它网络通信器件驱动程序一样,你主要处理:
-TX:把套接字缓存中的 CAN 帧发送到 CAN 控制器中
-RX:把 CAN 控制器中的数据放入套接字缓存中
详细内容请参阅《Documentation/networking/netdevices.txt》。基于网络层的 CAN 器件驱
动程序的不同之处如下所述:

6.1 通用设置

dev->type
= ARPHRD_CAN; /* 网络器件的硬件类型 /
dev->flags = IFF_NOARP; /
CAN 没有 arp */
dev->mtu
= sizeof(struct can_frame);
结构体 can_frame 是每一个套接字 buffer 的数据载荷,它遵循协议族 PF_CAN。

6.2 数据发送的本地回环模式

如 3.2 章所述,CAN 网络驱动程序应该支持本地回环功能,这与 tty 设备的回显功能类
似。在这种情况下,如果驱动支持这个功能,则要设置 IFF_ECHO 标志来防止 PF_CAN 核
心回显发送帧(又称回环)。
dev->flags = (IFF_NOARP | IFF_ECHO);

6.3 CAN 控制器的硬件过滤

为了减小一些嵌入式系统的中断负载,一些 CAN 控制器支持多个 CAN-ID 或者多个
CAN-ID 区间的过滤功能。硬件过滤功能在不同的控制器之间差异很大,并且不能同时满足多个用户的不同过滤需求。在单用户应用中使用控制器的硬件过滤或许还有意义,但是在一
个多用户系统中驱动层的过滤将会影响所有用户。 PF_CAN 核心内置的过滤规则集合允许对
每个套接字独立的设置多个过滤规则。因此使用硬件过滤属于嵌入式系统中“手动调整”的
范畴。从 2002 年开始笔者一直使用拥有四路 SJA1000 CAN 控制器的 MPC603e @133MHz,
总线负载虽然很高,但是到目前为止还没有什么问题。

6.4 虚拟 CAN 驱动(vcan)

与网络驱动相似,vcan 提供了一个虚拟的本地 CAN 接口。一个有效的 CAN 地址由下
面两方面组成:
-CAN ID
-该 CAN ID 将要发送的总线(例如 can0)
所以在一般的应用场景中往往需要多个 vcan 接口。
vcan 接口允许在没有控制器硬件的情况下进行发送和接收。vcan 网络设备的命名一般
采用‘vcanX’ ,比如 can1 vcan2 等。当编译为单独的模块的时候, vcan 驱动的模块名为 vcan.ko。
vcan 驱动从 linux2.6.24 开始支持 netlink 接口,使得创建 vcan 网络设备变的可能。可以
使用 ip(8)命令工具管理 vcan 网络设备的创建和移除:

  • 创建一个 vcan 网络接口:
    $ ip link add type vcan
  • 使用给定的名字 'vcan42’创建 一个 vcan 网络接口:
    $ ip link add dev vcan42 type vcan
  • 移除 vcan 网络接口’vcan42’:
    $ ip link del vcan42

6.5 CAN 网络设备驱动接口

CAN 网络设备驱动提供了进行安装、配置和监控 CAN 网络设备的接口。可以使用
IPROUTE2 工具集中的“ip”命令通过 netlink 接口配置 CAN 设备,比如设置波特率。本章
剩余的部分将会简介如何使用这一工具。另外这些接口使用一些通用的数据结构并且提供了
一系列常用的功能,这些功能都是 CAN 网络设备驱动需要用到的。请参考 SJA1000 或者
MSCAN 的驱动去了解如何使用它们。模块的名字是 can-dev.ko。

6.5.1 Netlink 接口–设置/获取设备属性

CAN 设备必须使用 netlink 来配置。在"include/linux/can/netlink.h"有对 netlink 消息类型
的定义和简短描述。IPROUTE2 工具集中的“ip”命令可以使用 CAN 的 netlink 支持,下面
是使用示例:

  • 设置 CAN 设备属性:
    $ ./ip link set can0 type can help

Usage: ip link set DEVICE type can

[ bitrate BITRATE [ sample-point SAMPLE-POINT] ] |
[ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1
   phase-seg2 PHASE-SEG2 [ sjw SJW ] ]

[ loopback { on | off } ]
[ listen-only { on | off } ]
[ triple-sampling { on | off } ]
[ one-shot { on | off } ]
[ berr-reporting { on | off } ]

[ restart-ms TIME-MS ]
[ restart ]

Where: BITRATE       := { 1..1000000 }
       SAMPLE-POINT  := { 0.000..0.999 }
       TQ            := { NUMBER }
       PROP-SEG      := { 1..8 }
       PHASE-SEG1    := { 1..8 }
       PHASE-SEG2    := { 1..8 }
       SJW           := { 1..4 }
       RESTART-MS    := { 0 | NUMBER }
  • 显示 CAN 设备的详情和统计信息:
    $ ip -details -statistics link show can0
    2: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
    link/can
    can state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
    bitrate 20000 sample-point 0.875
    tq 3125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
    d_can: tseg1 1…16 tseg2 1…8 sjw 1…4 brp 1…1024 brp-inc 1
    clock 24000000
    re-started bus-errors arbit-lost error-warn error-pass bus-off
    0 0 0 0 0 0
    RX: bytes packets errors dropped overrun mcast
    88279 88279 0 0 0 0
    TX: bytes packets errors dropped carrier collsns
    4246 4246 0 0 0 0
    0
    下面是上面一些名词的解释:
    “” : 表 示 选 中 的 CAN 控 制 器 的 模 式 : LOOPBACK,
    LISTEN-ONLY, 或 TRIPLE-SAMPLING。
    “state ERROR-ACTIVE” : CAN 控 制 器 的 当 前 状 态 : “ERROR-ACTIVE”,
    “ERROR-WARNING”, “ERROR-PASSIVE”, “BUS-OFF” or “STOPPED”
    “restart-ms 100”:自动重启的延时时间。如果设为非零值, 在总线关闭的情况下,
    在设定的数量毫秒后 CAN 控制器被自动触发。这个功能默认是关闭的。
    “bitrate 125000 sample_point 0.875” : 使 用 bits/sec 作 为 单 位 显 示 位 时 间 并 显 示
    0.000 ~ 0.999 的 采 样 点 位 置 。 如 果 内 核 中 使 能 了 统 计 位 时 间 的 功 能
    (CONFIG_CAN_CALC_BITTIMING=y),位时间可以使用"bitrate"参数来设置。可选的
    "sample-point"也是可以配置的。默认使用 0.000 这个推荐值。
    “tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1”
    以 ns 为单位显示时间份额(tq-time quanta)、传播段(prop-seg : propagation
    segment)、相位缓冲段 1 和 2(phase-seg:phase buffer),以 tq 为单位显示同步跳转宽
    度(sjw:synchronisation jump width)。这些变量允许定义与硬件无关的位时序,这也
    是 Bosch CAN 2.0 spec 所推荐的。
    (参考第八章 http://www.semiconductors.bosch.de/pdf/can2spec.pdf)。
    “sja1000: tseg1 1…16 tseg2 1…8 sjw 1…4 brp 1…64 brp-inc 1 clock 8000000”
    显示 CAN 控制器的比特时序常量,这里的例子以 sja1000 为例。时间段(tseg -time
    segment)1 和 2 的最小和最大值,以 tq 为单位的同步跳转宽度,比特速率的预分频器

(brp–pre-scaler)和 CAN 系统时钟(以 HZ 为单位)。这些常量可以被用户空间的比特

时序统计算法所使用。
“re-started bus-errors arbit-lost error-warn error-pass bus-off”
显示重启的次数、总线和仲裁丢失错误,错误主动(error-warning)、错误被动
(error-passive)、和总线关闭的状态变化。接收的过载错误在 统计信息的"overrun"域
下面列出。

6.5.2 设置 CAN 的比特时序

CAN 比特时序参数可以使用硬件无关的定义方法。这些参数是: “tq”, “prop_seg”,
“phase_seg1”, “phase_seg2” 和 “sjw”:
$ ip link set canX type can tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
在内核选项 CONFIG_CAN_CALC_BITTIMING 被使能的情况下,如果比特率(波特率)
参数 "bitrate"被设置了,CAN 的这些参数将会生效:
$ ip link set canX type can bitrate 125000
请注意,这条命令在大部分使用标准波特率的 CAN 控制器上工作良好,但是使用一些
少 见 的 波 特 率 值 ( 如 115000 ) 和 时 钟 频 率 值 将 会 失 败 。 禁 用 内 核 的
CONFIG_CAN_CALC_BITTIMING 选项可以节省一些内存空间并且允许用户空间的命令工
具完全的控制比特时序参数。使用 CAN 控制器的比特时序常量就可以达到这个目的(用户
空间控制比特时序)。下面的命令将会列出这些变量:
$ ip -details link show can0

sja1000: clock 8000000 tseg1 1…16 tseg2 1…8 sjw 1…4 brp 1…64 brp-inc 1

6.5.3 启动和停止 CAN 网络设备

一个 CAN 网络设备可以使用"ifconfig canX up/down" 或者 "ip link set canX up/down"来
开启和关闭。为了避免错误的默认值,必须在启动 CAN 设备之前设置它的比特时序参数:
$ ip link set canX up type can bitrate 125000
如果总线上出现太多的错误设备可能进入总线关闭状态(也就是从总线脱离)。进入总
线关闭状态之后设备不会再发送和接收信息。给"restart-ms"设置一个非零值可以开启总线关
闭自动恢复的功能(也就是进入总线关闭状态后重新开启),下面是一个示例:
$ ip link set canX type can restart-ms 100
应用程序可以通过监测 CAN 的错误帧意识到已经进入总线关闭状态,并且可以使用以
下命令重启:
$ ip link set canX type can restart
注意,重启也会生成一个 CAN 错误帧(参见 3.4 节)。

6.6 支持 CAN 硬件

请检查“drivers/net/can”里的“Kconfig”文件,获取支持的 CAN 硬件的列表。在 Socket
CAN 项目网站上(查看第 7 章)
,列出了更多的可用驱动,以及对早些旧内核版本的支持。

7 Socket CAN 资源

你可在 BerliOS OSS 项目网站的 CAN 套接字页面中发现更多资源,比如用户空间工具、
对旧版内核的支持、更多的驱动、邮件列表等:
http://developer.berlios.de/projects/socketcan如果你有任何问题或者发现了 BUG,不要迟疑,立马发送邮件到 CAN 套接字的用户邮
件列表。但是在发送之前请首先搜索邮件记录中是否已经有了相同的问题。
8 致谢(略)

以下是testcan.c程序:

=================================================================================================

#include <stdio.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/socket.h>
#include <linux/can.h>
#include <linux/can/error.h>
#include <linux/can/raw.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#ifndef AF_CAN
#define AF_CAN 29
#endif
#ifndef PF_CAN
#define PF_CAN AF_CAN
#endif

static void print_frame(struct can_frame *fr)
{
int i;
printf("%08x\n", fr->can_id & CAN_EFF_MASK);
//printf("%08x\n", fr->can_id);
printf(“dlc = %d\n”, fr->can_dlc);
printf(“data = “);
for (i = 0; i < fr->can_dlc; i++)
printf(”%02x “, fr->data[i]);
printf(”\n”);
}

#define errout(_s) fprintf(stderr, “error class: %s\n”, (_s))
#define errcode(_d) fprintf(stderr, “error code: %02x\n”, (_d))

static void handle_err_frame(const struct can_frame *fr)
{
if (fr->can_id & CAN_ERR_TX_TIMEOUT) {
errout(“CAN_ERR_TX_TIMEOUT”);
}
if (fr->can_id & CAN_ERR_LOSTARB) {
errout(“CAN_ERR_LOSTARB”);
errcode(fr->data[0]);
}
if (fr->can_id & CAN_ERR_CRTL) {
errout(“CAN_ERR_CRTL”);
errcode(fr->data[1]);
}
if (fr->can_id & CAN_ERR_PROT) {
errout(“CAN_ERR_PROT”);
errcode(fr->data[2]);
errcode(fr->data[3]);
}
if (fr->can_id & CAN_ERR_TRX) {
errout(“CAN_ERR_TRX”);
errcode(fr->data[4]);
}
if (fr->can_id & CAN_ERR_ACK) {
errout(“CAN_ERR_ACK”);
}
if (fr->can_id & CAN_ERR_BUSOFF) {
errout(“CAN_ERR_BUSOFF”);
}
if (fr->can_id & CAN_ERR_BUSERROR) {
errout(“CAN_ERR_BUSERROR”);
}
if (fr->can_id & CAN_ERR_RESTARTED) {
errout(“CAN_ERR_RESTARTED”);
}
}
#define myerr(str) fprintf(stderr, “%s, %s, %d: %s\n”, FILE, func, LINE, str)

static int test_can_rw(int fd, int master)
{
int ret, i;
struct can_frame fr, frdup;
struct timeval tv;
fd_set rset;

while (1) {
    tv.tv_sec = 1;
    tv.tv_usec = 0;
    FD_ZERO(&rset);
    FD_SET(fd, &rset);

    ret = select(fd+1, &rset, NULL, NULL, NULL);
    if (ret == 0) {
        myerr("select time out");
        return -1;
    }

    ret = read(fd, &frdup, sizeof(frdup));
    if (ret < sizeof(frdup)) {
        myerr("read failed");
        return -1;
    }
    if (frdup.can_id & CAN_ERR_FLAG) { /* 出错设备错误 */
        handle_err_frame(&frdup);
        myerr("CAN device error");
        continue;
    }
    print_frame(&frdup);
    ret = write(fd, &frdup, sizeof(frdup));
    if (ret < 0) {
        myerr("write failed");
        return -1;
    }
}

return 0;

}
/***************************************************************************************
**函数名称: main()
**函数说明:
**创建时间:
***************************************************************************************/
int main(int argc, char *argv[])
{
int s;
int ret;
struct sockaddr_can addr;
struct ifreq ifr;
int master;

/*
 * 创建socket can套接字
 */
srand(time(NULL));
    s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (s < 0) {
        perror("socket PF_CAN failed");
        return 1;
    }

    strcpy(ifr.ifr_name, "can0");
    ret = ioctl(s, SIOCGIFINDEX, &ifr);
    if (ret < 0) {
        perror("ioctl failed");
        return 1;
    }

    addr.can_family = PF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    ret = bind(s, (struct sockaddr *)&addr, sizeof(addr));
    if (ret < 0) {
        perror("bind failed");
        return 1;
    }
if (0)
{
    struct can_filter filter[2];
    filter[0].can_id = 0x200 | CAN_EFF_FLAG;
    filter[0].can_mask = 0xFFF;

    filter[1].can_id = 0x20F | CAN_EFF_FLAG;
    filter[1].can_mask = 0xFFF;


    ret = setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &filter, sizeof(filter));
    if (ret < 0) {
        perror("setsockopt failed");
        return 1;
    }
    
}
test_can_rw(s, master);

close(s);
return 0;

}

使用方法:

(1)/sbin/ip link set can0 type can bitrate 1000000#设置波特率为 1M,当然也可以设置为其它的波特率

(2)ifconfig can0 up//打开CAN0接口

(3)ifconfig can0//查看CAN0相关信息

运行:

[root@M3352 opt]# ./cantest
08e00100
dlc = 1
data = 02
08e00100
dlc = 1
data = 02
08e00100
dlc = 1
data = 02

上面就是我的运行信息。通过下位机给上位机发送CAN ID=0x08e00100,数据长度=1,数据内容=0x02的CAN帧。

;