1、 CAN总线协议:
控制器局域网CAN最初用于汽车的检测与控制,采用两根差分线(CAN_H和CAN_L)传输,能够实现多主机通信,详细内容可参考网址http://blog.sina.com.cn/s/blog_5049ed020102vzra.html和博客https://www.cnblogs.com/spoorer/p/6649303.html,个人认为这两篇文章已经讲的足够详细,无需我在此赘述,只大概记录几个要点。
帧的种类:数据帧、遥控帧、错误帧、过载帧、帧间隔
帧格式:数据帧和遥控帧标准格式:11位标识符ID
扩展格式:29位标识符ID
数据帧:帧起始(1 位显性)---仲裁段(优先级ID + 1 位显性位(RTR))---控制段(6 位)---数据段(0~8 字节,从MSB开始输出)---CRC段(15位CRC顺序+1 位CRC界定符)---ACK段(ACK槽和ACK界定符)---结束段(7位隐性)
仲裁机制:多个单元同时开始发送时各发送单元从仲裁位第一位开始仲裁,连续输出显性电平最多的单元获得发送权,具有相同ID 的遥控帧或数据帧竞争总线时,RTR为显性位(数据帧)的具有发送权
2、在Linux之下,很幸运,我们能够像实现网络编程一样的实现CAN的编程,采用socket方式建立连接,代码如下。
重要头文件:linu/can.h sys/socket.h
重要结构体:struct ifreq ifr;
struct can_frame { //CAN一帧数据
canid_t can_id;//CAN 标识符,标准帧低11位,扩展帧0~28位,高三位是帧的标识位,默认标准帧
__u8 can_dlc;//数据场的长度
__u8 data[8];//数据
};
struct sockaddr_can addr; //CAN地址
struct can_filter { //过滤器
canid_t can_id; //ID
canid_t can_mask; //掩码
};
过滤规则:如果接收到的数据帧的can_id & mask == can_id & mask则接收该帧,否则不接收。
(1)初始化,通过system()调用系统命令可以对CAN口设置波特率,输入为CAN口的号数和波特率,输出为CAN文件描述符,
int CanInit(unsigned int id, unsigned int baud)
{
int can_fd;
int ret;
char dev[8] = {0};
char cmd[128] = {0};
struct sockaddr_can addr = {0};
struct ifreq ifr = {0};
sprintf(dev, "can%d", id);
printf("can dev : %s \n", dev);
//关闭can设备
sprintf(cmd, "ifconfig %s down", dev);
if(system(cmd) < 0){
printf("can device shut down failed \n");
return -1;
}
//设置can设备波特率
bzero(cmd, sizeof(cmd));
sprintf(cmd, "ip link set %s type can bitrate %d ", dev, baud);
if(system(cmd) < 0){
printf("set can device baud rate failed \n");
return -1;
}
//打开can设备
bzero(cmd, sizeof(cmd));
sprintf(cmd, "ifconfig %s up", dev);
if(system(cmd) < 0){
printf("can device open failed \n");
return -1;
}
//创建套接字
can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if(can_fd < 0){
perror("can socket");
return -1;
}
strncpy(ifr.ifr_name, dev, strlen(dev));
printf("can_fd : %d\n",can_fd);
ret = ioctl(can_fd, SIOCGIFINDEX, &ifr); //指定 can 设备
if(ret < 0){
perror("ioctl");
return -1;
}
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
ret = bind(can_fd, (struct sockaddr *)&addr, sizeof(addr));//将套接字与 can0 绑定
if(ret < 0){
perror("bind");
return -1;
}
return can_fd;
}
(2)CAN发送数据,该进程只发送报文,不接收报文
int main()
{
int s, nbytes;
struct can_frame frame[2] = {0};
int loopback = 0;
//生成两个报文
frame[0].can_id = 0x10118AF6;
//frame[0].can_id = 0x11;
frame[0].can_dlc = 6;
frame[0].data[0] = 0x32;
frame[0].data[1] = 0x33;
frame[0].data[2] = 0x34;
frame[0].data[3] = 0x35;
frame[0].data[4] = 0x36;
frame[0].data[5] = 0x37;
frame[1].can_id = 0x1012F68A;
frame[1].can_dlc = 4;
frame[1].data[0] = 0x56;
frame[1].data[1] = 0x57;
frame[1].data[2] = 0x58;
frame[1].data[3] = 0x59;
s = CanInit(1, 500000);
if(s < 0){
printf("CanInit failed \n");
sleep(1);
close(s);
//continue;
return -1;
}
printf("CanInit success \n");
//禁用过滤规则,本进程不接收报文,只负责发送
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
//设置回环测试
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
&loopback, sizeof(loopback));
//循环发送两个报文
while(1)
{
nbytes = write(s, &frame[0], sizeof(frame[0])); //发送 frame[0]
if(nbytes != sizeof(frame[0]))
{
perror("write frame");
printf("Send Error frame[0]\n");
close(s);
break; //发送错误,退出
}
printf("send id: %#x,success \n",frame[0].can_id);
sleep(2);
nbytes = write(s, &frame[1], sizeof(frame[1])); //发送 frame[1]
if(nbytes != sizeof(frame[0]))
{
printf("Send Error frame[1]\n!");
close(s);
break;
}
printf("send id: %#x,success \n",frame[1].can_id);
sleep(2);
}
close(s);
return 0;
}
(3)CAN接收报文,采用select方式,只接收上面进程发出的0x10118AF6, 即ID为0x10118AF6
int main()
{
int s, nbytes;
int maxfd;
struct can_frame frame;
struct can_filter rfilter = {0};
int i;
int ret;
int err_num = 0;
int loopback = 0;
fd_set readfs;
struct timeval timeout={1,0};
s = CanInit(1, 500000);
if(s < 0){
printf("CanInit failed \n");
return -1;
}
printf("CanInit success \n");
//定义接收规则,只接收标识符等于 的报文
rfilter.can_id = 0x8AF6;
rfilter.can_mask = 0xffff;
//设置过滤规则
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
//设置回环测试
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &loopback, sizeof(loopback));
maxfd = s+1;
while(1)
{
FD_ZERO(&readfs);
FD_SET(s, &readfs);
ret = select(maxfd, &readfs, NULL, NULL, &timeout);
if(ret < 0)
return -1;
if(0==ret){
continue;
}
if(FD_ISSET(s, &readfs))
{
nbytes = read(s, &frame, sizeof(frame)); //接收报文,
//显示报文
if(sizeof(frame) == nbytes)
{
err_num = 0;
printf("ID=0x%X DLC=%d ", frame.can_id, frame.can_dlc);
for(i=0; i<frame.can_dlc; i++)
printf("%#x ",frame.data[i]);
printf("\n");
}else{
//此为容错机制,最多运行连续错误三次
err_num++;
if(3 == err_num){
printf("system errno \n");
break;
}
perror("read error");
continue;
}
}
}
close(s);
return 0;
}