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,如下图:
仲裁比特率 数据传输比特率
从上图可以看到,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的比特率进行的传输,下图是通讯过程中,用示波器抓取的一次波形,可以看到前后仲裁比特率,和中间数据比特率的对比:
感兴趣的客户可以联系英创的工程师获取完整的测试代码。
成都英创信息技术有限公司 028-8618 0660