【Linux】CAN编程详解

ubuntu搭建虚拟can环境

sudo modprobe vcan
# Create a vcan network interface with a specific name
sudo ip link add dev vcan0 type vcan
sudo ip link set dev vcan0 down
sudo ip link set vcan0 mtu 72 支持CAN FD
sudo ip link set dev vcan0 up
ifconfig
# Create a can network interface with a specific name
sudo ip link add dev can0 type vcan
sudo ip link set dev can0 down
sudo ip link set can0 mtu 72 支持CAN FD
sudo ip link set dev can0 up
ifconfig -a

Can总线配置

接下来使用 ip 命令来配置 CAN 总线的位速率:
ip link set can0 type cantq 125 prop-seg 6phase-seg1 7 phase-seg2 2 sjw 1
也可以使用 ip 命令直接设定位速率:
ip link set can0 type can bitrate 125000
当设置完成后,可以通过下面的命令查询 can0 设备的参数设置:
ip -details link show can0
当设置完成后,可以使用下面的命令使能 can0 设备:
ifconfig can0 up
使用下面的命令取消 can0 设备使能:
ifconfig can0 down
在设备工作中,可以使用下面的命令来查询工作状态:
ip -details -statistics link show can0

Linux系统中CAN接口应用程序开发

由于系统将CAN设备作为网络设备进行管理,因此在CAN总线应用开发方面,Linux提供了Socket CAN接口,使得CAN总线通信近似于和以太网的通信,应用程序开发接口更加通用,也更加灵活。

此外,通过https://gitorious.org/linux-can/can-utils网站发布的基于Socket CAN的can-utils工具套件,也可以实现简易的CAN总线通信。

下面具体介绍使用Socket CAN实现通信时使用的应用程序开发接口。

初始化

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

int s;
struct sockaddr_can addr;
struct ifreq ifr;
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //创建SocketCAN套接字
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr); //指定can0设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与can0绑定

数据发送

在数据收发的内容方面,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 = CAN_EFF_FLAG | 0x123; */
frame.can_id = 0x123;
frame.can_dlc = 1;
frame.data[0] = 0xAB;
int nbytes = write(s, &frame, sizeof(frame));
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));

数据接收

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

struct can_frame frame;
int nbytes = read(s, &frame, sizeof(frame));

当然,套接字数据收发时常用的send、sendto、sendmsg以及对应的recv函数也都可以用于CAN总线数据的收发。

错误处理

当帧接收后,可以通过判断can_id中的CAN_ERR_FLAG位来判断接收的帧是否为错误帧。如果为错误帧,可以通过can_id的其他符号位来判断错误的具体原因。

错误帧的符号位在头文件linux/can/error.h中定义。

过滤规则设置

在数据接收时,系统可以根据预先设置的过滤规则,实现对报文的过滤。过滤规则使用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));

Linux系统中CAN接口应用程序示例

该文档提供了一个很简单的程序示例,如下:

1、报文发送程序

/* 1. 报文发送程序 */
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.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, "can0");
    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])); //发送 frame[1]
        if (nbytes != sizeof(frame[0])) {
    
    
            printf("Send Error frame[1]\n!");
            break;
        }
        sleep(1);
    }
    close(s);
    return 0;
}

2、报文过滤接收程序

/* 2. 报文过滤接收程序 */
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.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, "can0");
    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;
}

猜你喜欢

转载自blog.csdn.net/m0_61011506/article/details/129109204