Arm Linux Can

文章介绍了如何在Linux系统中安装和使用can-utils工具,包括CAN网络的开启、关闭、波特率设置及数据的发送与接收。此外,详细阐述了C语言进行CAN编程的步骤,如初始化、数据结构、错误处理和过滤规则设置,并提供了报文发送和接收的示例程序。
摘要由CSDN通过智能技术生成

一:can-utils 安装

在这里插入图片描述
生产如下工具:
在这里插入图片描述

二:can-utils 使用

ip link set can0 down
ip link set can0 type can bitrate 250000 loopback off//设置波特率,关闭回环测试
ip link set can0 up

说明:

  1. loopback off:关闭回环模式。测试发现关闭后,可以实现CAN设备与外界的收发通信。
  2. loopback on:打开回环模式。测试发现打开后,可以实现自发自收,不依赖外部CAN设备。

can网络关闭

ip link set can0 down

can波特率设置

ip link set can0 type can bitrate 500000 loopback off

查询can0设备的参数设置

ip -details link show can0

can网络开启

ip link set can0 up

查询工作状态

ip -details -statistics link show can0

can发送数据

数据发送格式:
cansend can0 XXX#11223344
eg:
cansend can0 12345678#112233
XXX:表示标识符ID,ID越小优先级越高CAN数据冲突时,优先发送。
112233344:表示发送的数据,以16进制表示。最小发送数据单位是字节,可发0~8个字节。

can接受数据

candump can0 或 candump can0 &

三:can回环测试

ip link set down can0
ip link set can0 type can loopback on
ip link set up can0
candump can0 -L &
cansend can0 123#1122334455667788
ip -details link show can0

四:C语言CAN编程

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

初始化

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

int init(char *drvcan_name)
{
    int ret;
    struct sockaddr_can addr;
    struct ifreq ifr;
    struct can_filter rfilter[1];
    int fd;
    char can_down_cmd[64] = "";
    char can_init_cmd[64] = "";
    char can_up_cmd[64] = "";

    if (drvcan_name == NULL)
    {
        printf("drvcan_name is null");
        return -1;
    }

    sprintf(can_down_cmd, CAN_DOWN, drvcan_name);
    sprintf(can_init_cmd, CAN_INIT, drvcan_name);
    sprintf(can_up_cmd, CAN_UP, drvcan_name);

    system(can_down_cmd);
    system(can_init_cmd);
    system(can_up_cmd);

    fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (fd < 0)
    {
        printf("socket error\n");
        return -2;
    }

    sprintf(ifr.ifr_name, "%s", drvcan_name);
    ret = ioctl(fd, SIOCGIFINDEX, &ifr);
    if (ret < 0)
    {
        printf("ioctl error ret:%d", ret);
        return -3;
    }

    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    bind(fd, (struct sockaddr *)&addr, sizeof(addr));

    /*设置过滤规则*/
    // rfilter[0].can_id = 0x2;
    // rfilter[0].can_mask = 0;
    // Setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
    // setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, NULL);

    /*启动接收线程*/
    thread_exit_state = EM_THREAD_STATE_RUN;
    pthread_t _threadID;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_create(&_threadID, &attr, can_recv_thread, fd);
    pthread_attr_destroy(&attr);
    return fd;
}

数据结构

在数据收发的内容方面,CAN 总线与标准套接字通信稍有不同,每一次通信都采用 can_frame 结构体将数据封装成帧。 结构体定义

/**
 * 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_id 为帧的标识符,如果发出的是标准帧,就使用 can_id的低11位。如果为扩展帧,就使用 0~ 28 位。can_id 的第 29、 30、 31 位是帧的标志位,用来定义帧的类型,定义如下:

/* special address description flags for the CAN_ID */
#define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB 扩展帧*/
#define CAN_RTR_FLAG 0x40000000U /* remote transmission request 远程帧*/
#define CAN_ERR_FLAG 0x20000000U /* error message frame 错误帧*/

/* valid bits in CAN ID for frame formats */
#define CAN_SFF_MASK 0x000007FFU /* standard frame format (SFF) */
#define CAN_EFF_MASK 0x1FFFFFFFU /* extended frame format (EFF) */
#define CAN_ERR_MASK 0x1FFFFFFFU /* omit EFF, RTR, ERR flags */

数据发送

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)); //发送数据
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));

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

错误处理

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

过滤规则

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

/**
 * struct can_filter - CAN ID based filter in can_register().
 * @can_id:   relevant bits of CAN ID which are not masked out.
 * @can_mask: CAN mask (see description)
 *
 * Description:
 * A filter matches, when
 *
 *          <received_can_id> & mask == can_id & mask
 *
 * The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can
 * filter for error message frames (CAN_ERR_FLAG bit set in mask).
 */
struct can_filter {
	canid_t can_id;
	canid_t can_mask;
};

接收到的数据帧的 can_id & mask == can_id & mask。
通过这条规则可以在系统中过滤掉所有不符合规则的报文,使得应用程序不需要对无关的报文进行处理。

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 接口应用程序示例

报文发送程序

#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, "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. 报文过滤接收程序 */
#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, "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;
}
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值