查看板子上的CAN设备是否使能:
如果查询到上图所示,说明CAN设备(也可称为CAN“网卡”)是可以工作的,否则需要做好底层设备树及驱动配置。将板子上CAN接口(该板子只有1路CAN)的CAN_H和CAN_L分别接到USBCAN盒的其中一路CAN输入的CAN_H和CAN_L上,尝试从开发板发送数据到USBCAN上位机以及从上位机通过USBCAN发送数据到开发板。Linux系统将CAN当作网络设备进行统一管理,所以CAN应用程序仍然采用经典的socket通信那一套。开发板发送数据的应用代码如下。需要注意的是,对于CAN协议族,socket应该使用原始套接字SOCK_RAW。因为仅有发送,所以通过setsockopt设置了过滤规则为不接收任何报文。发送的内容是每帧8个字节的数据:0xAB 0xBC 0xCD 0xDE 0xEF 0xFA 0xAB 0xBC,ID为0x123,发送周期为1s。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
int main(void)
{
struct ifreq ifr = {0}; // 存放网络请求相关信息
struct sockaddr_can can_addr = {0}; // CAN套接字地址信息
struct can_frame frame = {0}; // CAN报文帧
int sockfd; // CAN套接字描述符
int ret;
if((sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0){ // PFCAN即CAN协议族,使用原始套接字
perror("socket error");
exit(EXIT_FAILURE);
}
strcpy(ifr.ifr_name, "can0"); // 指定网卡名称为开发板的can0设备
if((ioctl(sockfd, SIOCGIFINDEX, &ifr)) != 0){ // 获取网络接口
perror("ioctl error");
exit(EXIT_FAILURE);
}
//printf("ifr.ifr_ifindex: %d", ifr.ifr_ifindex);
can_addr.can_family = AF_CAN; // CAN地址簇
can_addr.can_ifindex = ifr.ifr_ifindex; // 网卡序号
if((ret = bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr))) < 0){
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); // 设置过滤规则为不接收任何报文、仅发送数据
frame.can_id = 0x123; // 帧ID为0x123(标准帧)
frame.can_dlc = 8; // 一次发送8个字节
frame.data[0] = 0xAB;
frame.data[1] = 0xBC;
frame.data[2] = 0xCD;
frame.data[3] = 0xDE;
frame.data[4] = 0xEF;
frame.data[5] = 0xFA;
frame.data[6] = 0xAB;
frame.data[7] = 0xBC;
for(;;){
if((ret = write(sockfd, &frame, sizeof(frame))) != sizeof(frame)){ // 发送
perror("write error");
goto out;
}
sleep(1);
}
out:
close(sockfd);
exit(EXIT_SUCCESS);
}
编译后在开发板上运行,可以看到USBCAN的上位机接收结果如图,可见USBCAN上位机成功接收到了来自开发板的CAN报文。
开发板上CAN接收数据的应用代码如下。需要注意的是,由于是接收数据,这里设置了三组报文ID过滤,分别是0x123、0x234和0x345,掩码设置的都是0x7FF,也就是说设置开发板的CAN仅接收ID为0x123或0x234或0x345的报文,其它ID不会被接收。接收到数据后根据报文ID中的一些特殊位,可以进行错误帧、扩展帧、远程帧等类型的判断。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
int main(void)
{
struct ifreq ifr = {0};
struct sockaddr_can can_addr = {0};
struct can_frame frame = {0};
struct can_filter filters[3];
int sockfd;
int i;
int ret;
if((sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0){
perror("socket error");
exit(EXIT_FAILURE);
}
strcpy(ifr.ifr_name, "can0");
if((ioctl(sockfd, SIOCGIFINDEX, &ifr)) != 0){
perror("ioctl error");
exit(EXIT_FAILURE);
}
can_addr.can_family = AF_CAN;
can_addr.can_ifindex = ifr.ifr_ifindex;
if((ret = bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr))) < 0){
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
filters[0].can_id = 0x123;
filters[1].can_id = 0x234;
filters[2].can_id = 0x345;
for(i = 0;i < 3;i++)
filters[i].can_mask = 0x7FF; // ID掩码
setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, &filters, sizeof(filters)); // 设置接收过滤
for(;;){
if((read(sockfd, &frame, sizeof(struct can_frame))) < 0){
perror("read error");
break;
}
if(frame.can_id & CAN_ERR_FLAG){ // 校验错误帧
printf("Error frame!\n");
break;
}
if(frame.can_id & CAN_EFF_FLAG) // 校验扩展帧
printf("扩展帧 <0x%08x> \n", frame.can_id & CAN_EFF_MASK);
else
printf("标准帧 <0x%03x> \n", frame.can_id & CAN_SFF_MASK);
if(frame.can_id & CAN_RTR_FLAG){ // 校验远程帧
printf("remote request\n");
continue;
}
printf("can_dlc: [%d] \n", frame.can_dlc);
for(i = 0;i < frame.can_dlc;i++)
printf("data: %02x", frame.data[i]);
printf("\n");
}
close(sockfd);
exit(EXIT_SUCCESS);
}
编译后在开发板上运行。尝试在USBCAN上位机发送若干种ID及类型的报文:
开发板侧的接收情况如下。很明显可见,只有上面提到的那三种ID的报文才被接收了,并正确获取了帧类型。