CAN FD (CAN with Flexible Data rate)可以认为是传统CAN总线的升级版本,主要是对传统CAN总线协议的数据长度和传输速度上做了升级。CAN FD的数据帧可以支持最长64数据字节的长度,而传统CAN总线的数据长度为8字节。在传输速度上,标称(仲裁)比特率与传统CAN总线一致,最快支持到1M比特率,但是数据比特率则取决于现场总线环境,理论可以实现高达5 Mbit/s的数据比特率,同时CAN FD也是兼容传统CAN总线的。网上也有许多介绍CAN FD的资料,有兴趣的的话可以搜索更加详细的资料查看。
英创公司推出的ESM8000主板上带有两路支持CAN FD协议的CAN总线,另外基于ESM8000主板,英创公司还推出了支持6路CAN FD协议的CAN总线扩展方案,关于这套方案具体可以参考《ESM8000异构CPU实时应用——6路CAN-FD的实现》。不管是哪一种方案,英创公司都提供了现成驱动,将每一路CAN总线都映射为linux系统中的标准CAN设备。而用户通过标准的socketcan编程,就可以实现对每一路CAN总线的操作。Linux系统提供的socketcan已经对CAN FD提供了完整的支持,并且同时能够兼容原来的传统CAN总线,接下来我们就介绍具体的编程方法。
在socketcan中为了支持CAN FD,需要使用到数据帧结构canfd_frame,这个数据结构中同时支持了传统CAN总线的数据帧与CAN FD的数据帧。在使能了CAN FD功能后,通过canfd_frame就可以进行收发数据。CAN总线使用的帧的结构体定义在include/linux/can.h头文件中,其中原来传统CAN总线的数据帧can_frame具体内容如下:
/* CAN payload length and DLC definitions according to ISO 11898-1 */
#define CAN_MAX_DLC 8
#define CAN_MAX_DLEN 8
/**
* struct can_frame - basic CAN frame structure
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
* @can_dlc: frame payload length in byte (0 .. 8) aka data length code
* N.B. the DLC field from ISO 11898-1 Chapter 8.4.2.3 has a 1:1
* mapping of the 'data length code' to the real payload length
* @__pad: padding
* @__res0: reserved / padding
* @__res1: reserved / padding
* @data: CAN frame payload (up to 8 byte)
*/
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 FD协议的数据帧canfd_frame具体定义如下:
/* CAN FD payload length and DLC definitions according to ISO 11898-7 */
#define CANFD_MAX_DLC 15
#define CANFD_MAX_DLEN 64
/**
* struct canfd_frame - CAN flexible data rate frame structure
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
* @len: frame payload length in byte (0 .. CANFD_MAX_DLEN)
* @flags: additional flags for CAN FD
* @__res0: reserved / padding
* @__res1: reserved / padding
* @data: CAN FD frame payload (up to CANFD_MAX_DLEN byte)
*/
struct canfd_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 len; /* frame payload length in byte */
__u8 flags; /* additional flags for CAN FD */
__u8 __res0; /* reserved / padding */
__u8 __res1; /* reserved / padding */
__u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
};
通过对比很容易发现在canfd_frame中, can帧的id、can帧的数据长度以及can帧实际的数据和can_frame结构体中对应成员变量的偏移地址是完全一致的,只有数据的最大长度有所区别。所以传统CAN总线的数据帧结构体可以直接拷贝到CAN FD的数据帧结构体中,这也允许了用户通过canfd_fram结构体实现对不同结构数据帧的发送和接收。在同时存在传统CAN数据帧和CAN FD数据帧的情况下用户,也不会增加程序的复杂程度,通过判断数据长度就能够区别不同的数据帧结构。
了解了数据帧结构体,接下来就是带入到程序中实际操作了,我们首先需要启动CAN总线,在Linux系统中可以通过ip(8)工具来进行设置。这里需要特别注意,在使用CAN FD的情况下,设备之间设置的比特率和采样点必须一致,否则会无法通信。本次测试采用了德国PEAK公司的PCAN-USB FD设备和ESM8000主板连接进行收发数据,仲裁比特率设置为250K,数据传输比特率设置为2M,如下图:
![仲裁比特率](https://i-blog.csdnimg.cn/blog_migrate/4ecb27d02138f5aeb58c15dec00ff150.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ad65d53d56c0f0aaeb4914fb65972b8e.png)
从上图可以看到,250K波特率下采样点在75%的位置,而2M波特率下采样点在80%的位置。所以在ESM8000主板上也需要做同样的设置,通过ip(8)工具可以很容易的设置,具体代码如下:
/* 关闭can0 */
system("ifconfig can0 down");
/* 设置仲裁波特率和数据波特率,以及对应的采样点 */
system("ip link set can0 type can bitrate 250000 sample-point 0.75 dbitrate 2000000 dsample-point 0.8 fd on restart-ms 100");
/* 启动can0 */
system("ifconfig can0 up");
如果实际使用中,波特率和采样点不同,只需要更改其中波特率和采样点的设置即可,主板上的驱动会根据设置的值,自动计算出对应的参数并设置到CAN总线中。然后创建socketcan的套接字,这部分代码和传统CAN总线的socketcan操作完全一致:
/* 创建套节字 */
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
printf( "SOCK_RAW can sockfd:%d\n", s );
if( s < 0 )
{
return -1;
}
/* 是否开启回环功能 */
int loopback = 0; /* 0 = disabled, 1 = enabled (default) */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
/* 赋值实际的设备名 */
strcpy(ifr.ifr_name, "can0" );
ret = ioctl(s, SIOCGIFINDEX, &ifr);
if( ret < 0 )
{
return -1;
}
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
创建的CAN_RAW套接字对于CAN FD的支持默认是关闭的,可以能够通过使能CAN_RAW_FD_FRAMES这个套接字选项来增加对CAN FD的支持,当CAN_RAW_FD_FRAMES选项被使能,就可以同时支持发送can和can fd数据帧了,具体代码如下:
/* check if the frame fits into the CAN netdevice */
if (ioctl(s, SIOCGIFMTU, &ifr) < 0) {
perror("SIOCGIFMTU");
return 1;
}
if (ifr.ifr_mtu != CANFD_MTU) {
printf("CAN interface is not CAN FD capable - sorry.\n");
return 1;
}
enable_canfd = 1;
/* interface is ok - try to switch the socket into CAN FD mode */
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd, sizeof(enable_canfd))){
printf("error when enabling CAN FD support\n");
return 1;
}
/* 将can接口和can设备id绑定 */
bind(s, (struct sockaddr *)&addr, sizeof(addr));
到这里CAN设备的初始化就已经完成了,此时通过write和read函数,就可以通过对应的CAN接口进行数据通讯了,注意如果要支持CAN FD这里代入读写的数据帧结构体应该为canfd_frame。例程在这里实现了一个简单的回发功能,先读取设备的数据然后判断数据帧的类型并回发,具体代码如下:
for( i1=0; ;)
{
/* 读取数据 */
nbytes = read(s, &frame, sizeof(struct canfd_frame));
if (nbytes < 0) {
perror("can raw socket read");
return 1;
}
/* 判断数据帧的类型 */
if (nbytes == CANFD_MTU) {
printf("got CAN FD frame with length %d\n", frame.len);
/* frame.flags contains valid data */
/* 这里只是简单的回发 */
write(s, &frame, sizeof(struct canfd_frame));
} else if (nbytes == CAN_MTU) {
printf("got legacy CAN frame with length %d\n", frame.len);
/* frame.flags is undefined */
/* 这里只是简单的回发 */
write(s, &frame, sizeof(struct can_frame));
} else {
fprintf(stderr, "read: invalid CAN(FD) frame\n");
return 1;
}
}
使用PCAN的测试效果如下图:
上图中,上半部分为接收栏,下半部分为发送栏。可以看到我们通过PCAN同时发送了一个标准CAN帧和一个CAN FD帧,ESM8000主板收到后进行了回发,回发的也同样是一个标准CAN帧和一个CAN FD帧,通过软件上的Type上可以看到标识,CAN FD帧同时也支持了比特率切换,按照我们之前的设置,CAN FD帧的数据部分就应该以2M的比特率进行的传输,下图是通讯过程中,用示波器抓取的一次波形,可以看到前后仲裁比特率,和中间数据比特率的对比:
感兴趣的用户可以联系英创的工程师获取完整的测试代码。