socketcan

3 篇文章 7 订阅

参考<<Linux_Can编程详解>>
CAN原理介绍: https://www.cnblogs.com/spoorer/p/6649303.html

一.初始化工作

SocketCAN 中大部分的数据结构和函数在头文件 linux/can.h 中进行了定义。 
CAN 总线套接字的
创建采用标准的网络套接字操作来完成。网络套接字在头文件 sys/socket.h 中定义。

套接字的初始化方法如下:

int s;//类似于socket_fd

struct sockaddr_can addr;
struct ifreq ifr;
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);     //创建 SocketCAN 套接字
strcpy(ifr.ifr_name, "can0" );             //can0是ifconfig看到的,也可能是其他名字
ioctl(s, SIOCGIFINDEX, &ifr);              //指定 can0 设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与 can0 绑定

我在机器上ubuntu 16.04装了一个虚拟的can,名字为vcan,具体指令如下:

sudo ip link add dev vcan0 type vcan
sudo ip link set dev vcan0 down
sudo ip link set vcan0 mtu 72 
sudo ip link set dev vcan0 up
ifconfig

二.数据发送

在数据收发的内容方面, CAN 总线与标准套接字通信稍有不同,每一次通信都采用 can_ frame 结
构体将数据封装成帧。结构体定义如下:
struct can_frame {
canid_t  can_id;//CAN 标识符
__u8 can_dlc;//数据场的长度
__u8 data[8];//数据
};

can_id 为帧的标识符,如果发出的是标准帧,就使用 can_id 的低 11 位;
如果为扩展帧,就使用 0~ 28 位。 can_id 的第 29 、 30 、 31 位是帧的标志位,用来定义帧的类型,
定义如下:#define CAN_EFF_FLAG 0x80000000U //扩展帧的标识
#define CAN_RTR_FLAG 0x40000000U         //远程帧的标识
#define CAN_ERR_FLAG 0x20000000U         //错误帧的标识,用于错误检查


数据发送使用 write 函数来实现。
如果发送的数据帧 ( 标识符为 0x123) 包含单个字节 (0xAB) 的数据,可采用如下方法进行发送:

struct can_frame frame;  //定义数据结构体变量
frame.can_id = 0x123;    //如果为扩展帧,那么 frame.can_id = CAN_EFF_FLAG | 0x123;
frame.can_dlc = 1;       //数据长度为 1
frame.data[0] = 0xAB;    //数据内容为 0xAB

int nbytes = write(s, &frame, sizeof(frame)); //发送数据
//发送数据即就是给对应的fd上写数据,内容为frame,大小为sizeof(freme),与网络 socket一样的
if(nbytes != sizeof(frame))     //如果 nbytes 不等于帧长度,就说明发送失败
printf("Error\n!"); 



如果要发送远程帧 ( 标识符为 0x123) ,可采用如下方法进行发送:
struct can_frame frame;
frame.can_id = CAN_RTR_FLAG | 0x123;
write(s, &frame, sizeof(frame));

3.数据的接受

数据接收使用 read 函数来完成,实现如下:

//需要定义同样的一个数据结构体,因为所有传输的数据
struct can_frame frame;
int nbytes = read(s, &frame, sizeof(frame));
//同样的,从socketfd中读取数据,该数据也还是要写入相同的结构体中的

当然,套接字数据收发时常用的 send 、 sendto 、 sendmsg 以及对应的 recv 函数也都可以用于 CAN
总线数据的收发,因为底层都是调用sys_read  实际上所有操作被抽象为文件的读写

4.错误处理

当帧接收后,可以通过判断 can_id 中的 CAN_ERR_FLAG 位来判断接收的帧是否为错误帧。

如果为错误帧,可以通过 can_id 的其他符号位来判断错误的具体原因。
错误帧的符号位在头文件 linux/can/error.h 中定义。

5.数据过滤

在数据接收时,系统可以根据预先设置的过滤规则,实现对报文的过滤。
过滤规则使用 can_filter 结构体
来实现,定义如下:
struct can_filter {
canid_t can_id;
canid_t can_mask;};

过滤的规则为:
接收到的数据帧的 can_id &mask == 本地设置过滤规则中的 can_id & mask

通过这条规则可以在系统中过滤掉所有不符合规则的报文,使得应用程序不需要对无关的报文进行
处理。在 can_filter 结构的 can_id 中,符号位 CAN_INV_FILTER 在置位时可以实现 can_id 在执行过
滤前的位反转。
用户可以为每个打开的套接字设置多条独立的过滤规则,使用方法如下:
struct can_filter rfilter[2];
rfilter[0].can_id
= 0x123;
rfilter[0].can_mask = CAN_SFF_MASK; //#define CAN_SFF_MASK 0x000007FFU
rfilter[1].can_id
= 0x200;
rfilter[1].can_mask = 0x700;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));//设置规则
在极端情况下,如果应用程序不需要接收报文,可以禁用过滤规则。这样的话,原始套接字就会忽略所有
接收到的报文。在这种仅仅发送数据的应用中,可以在内核中省略接收队列,以此减少 CPU 资源的消耗。禁
用方法如下:
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); //禁用过滤规则
通过错误掩码可以实现对错误帧的过滤,例如:
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));
在默认情况下,本地回环功能是开启的,可以使用下面的方法关闭回环 / 开启功能:
int loopback = 0; // 0 表示关闭, 1 表示开启(默认)
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
在本地回环功能开启的情况下,所有的发送帧都会被回环到与 CAN 总线接口对应的套接字上。默认情况
下,发送 CAN 报文的套接字不想接收自己发送的报文,因此发送套接字上的回环功能是关闭的。可以在
需要的时候改变这一默认行为:
int ro = 1; // 0 表示关闭(默认), 1 表示开启
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &ro, sizeof(ro));

源程序:

//发送数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main()
{
	int s, nbytes;
	struct sockaddr_can addr;
	struct ifreq ifr;
	struct can_frame frame[2] = {{0}};
	s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//创建套接字
	strcpy(ifr.ifr_name, "vcan0" );
	ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 设备
	addr.can_family = AF_CAN;
	addr.can_ifindex = ifr.ifr_ifindex;
	bind(s, (struct sockaddr *)&addr, sizeof(addr));//将套接字与 can0 绑定
	//禁用过滤规则,本进程不接收报文,只负责发送
	setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
	//生成两个报文
	frame[0].can_id = 0x11;
	frame[0]. can_dlc = 1;
	frame[0].data[0] = 'Y';
	frame[0].can_id = 0x22;
	frame[0]. can_dlc = 1;
	frame[0].data[0] = 'N';//循环发送两个报文
	while(1)
	{
		nbytes = write(s, &frame[0], sizeof(frame[0]));
		//发送 frame[0]
		if(nbytes != sizeof(frame[0]))
		{
			printf("Send Error frame[0]\n!");
			break; //发送错误,退出
		}
		sleep(1);
		nbytes = write(s, &frame[1], sizeof(frame[1]));
		if(nbytes != sizeof(frame[0]))
		{
			printf("Send Error frame[1]\n!");
			break;
		}
	sleep(1);
	}

	close(s);
	return 0;
}

//接收数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main()
{
	int s, nbytes;
	struct sockaddr_can addr;
	struct ifreq ifr;
	struct can_frame frame;
	struct can_filter rfilter[1];
	s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //创建套接字
	strcpy(ifr.ifr_name, "vcan0" );
	ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 设备
	addr.can_family = AF_CAN;
	addr.can_ifindex = ifr.ifr_ifindex;
	bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与 can0 绑定
	//定义接收规则,只接收表示符等于 0x11 的报文
	rfilter[0].can_id= 0x11;
	rfilter[0].can_mask = CAN_SFF_MASK;
	//设置过滤规则
	setsockopt(s,SOL_CAN_RAW,CAN_RAW_FILTER,&rfilter,sizeof(rfilter));
	while(1)
	{
		nbytes = read(s,&frame,sizeof(frame));
		//接收报文//显示报文
		if(nbytes > 0)
		{
			printf("ID=0x%x DLC=%d data[0]=0x%x\n",frame.can_id,frame.can_dlc,frame.data[0]);
		}
	}

	close(s);
	return 0;
}

用C++实现一个SocketCan的通信:

**头文件:**
#pragma once
#include <iostream>
#include <sys/socket.h>
#include <ney/if.h>
#include <sys/ioctl.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <unistd.h>

class SocketCan {
public:
  SocketCan();
  ~SocketCan();
  bool Read();
  bool Write();

private:
  int sockfd_;
  bool ready_;
  
private:
  int Connect();
  void DisConnect();
  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值