嵌入式开发—CAN通信协议详解与应用(下)

书接上回:
嵌入式开发—CAN通信协议详解与应用(中)-CSDN博客

注:本文只是说明了如何进行基础的CAN收发操作,复杂CAN操作可以看这篇文章

Linux 底软开发——对CAN的详细操作(周期发送,异常检测,过滤报文)_linux can 接收过滤-CSDN博客


在Linux系统中,CAN(Controller Area Network)通信可以通过SocketCAN接口进行操作。SocketCAN是Linux内核为CAN总线提供的原生接口,允许使用类似于套接字(socket)的方式与CAN网络进行通信。它支持常用的CAN协议,包括标准帧和扩展帧,并且可以通过命令行工具或编程接口进行操作。

使用命令行工具配置CAN接口

1.查看CAN接口是否存在

CAN同样与网卡一样属于网络设备,因此使用ifconfig命令即可查看状态

ipconfig -a

在这里插入图片描述

2.设置CAN网络接口

(1)使用ip命令设置CAN接口(假设CAN接口名称为can0)的波特率。例如,设置波特率为500kbps:

sudo ip link set can0 type can bitrate 500000

(2)将CAN接口置于UP状态,以启用接口:

sudo ip link set up can0

(3)通过ip link命令可以查看CAN接口的状态:

ip link show can0

示例:

在这里插入图片描述

输出结果表明,can0接口已经启动(UP)并且物理连接正常(LOWER_UP),正在使用CAN协议通信。MTU为16字节,启用了回显功能,队列长度为10,队列调度算法为FIFO。

如果需要关闭CAN接口,可以使用以下命令

sudo ip link set down can0

3.使用candump监听CAN总线

candump属于can-utils是Linux中常用的CAN命令行工具,用于监听和显示CAN总线上的所有数据帧。

监听CAN总线上所有数据帧:

candump can0

该命令会显示从can0接口接收到的所有数据帧。

效果如下:

在这里插入图片描述

3.1过滤CAN ID

不同的candump版本,命令略微不同

使用 candump --help 查看具体的命令提示

[email protected]:/app/bin# candump --help
Usage: candump [<can-interface>] [Options]
Options:
 -f, --family=FAMILY	protocol family (default PF_CAN = 29)
 -t, --type=TYPE	socket type, see man 2 socket (default SOCK_RAW = 3)
 -p, --protocol=PROTO	CAN protocol (default CAN_RAW = 1)
     --filter=id:mask[:id:mask]...
			apply filter
 -h, --help		this help
 -o <filename>		output into filename
 -d			daemonize
     --version		print version information and exit
[email protected]:/app/bin# 

可以使用candump命令过滤特定的CAN ID,例如,只监听ID为0x123的数据帧:

过滤器的格式为id:mask,其中:

  • id 是你想过滤的CAN帧ID。
  • mask 是掩码,用来匹配ID的哪些位需要进行比较。

当数据帧的ID与指定的id按位与(AND)后与mask的结果相等时,数据帧会被显示。

在这里插入图片描述

4. 使用cansend发送CAN帧

cansend是用于发送CAN帧的工具。发送CAN帧的格式如下:

cansend can0 123#11223344556677

其中:

  • can0 是CAN接口名称。
  • 123 是CAN ID。
  • 11223344556677 是发送的数据,长度不超过8字节。

例如,发送一个ID为0x123,数据为0x11, 0x22, 0x33的CAN帧:

cansend can0 123#112233

注:有些cansend的版本并不支持这种发送命令,具体看CAN分析软件是否正常接受数据。

使用cansend --help查看具体命令格式

[email protected]:/app/bin# cansend --help
Usage: cansend [<can-interface>] [Options] <can-msg>
<can-msg> can consist of up to 8 bytes given as a space separated list
Options:
 -i, --identifier=ID	CAN Identifier (default = 1)
 -r  --rtr		send remote request
 -e  --extended	send extended frame
 -f, --family=FAMILY	Protocol family (default PF_CAN = 29)
 -t, --type=TYPE	Socket type, see man 2 socket (default SOCK_RAW = 3)
 -p, --protocol=PROTO	CAN protocol (default CAN_RAW = 1)
 -l			send message infinite times
     --loop=COUNT	send message COUNT times
 -v, --verbose		be verbose
 -b, --brs		bit rate switch
 -h, --help		this help
     --version		print version information and exit

根据cansend工具的帮助信息,<can-msg> 部分需要以空格分隔的字节列表形式发送。也就是说,数据部分不再使用#符号连接,而是使用空格分隔每个字节。

具体用法如下

cansend can0 -i 0x321 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 --loop=10

在这里插入图片描述

此时数据显示正常,如果数据与预期不一致,请检查cansend命令支持的形式

使用Linux库函数来完成CAN通信的收发

在Linux系统编程中,发送和接收CAN数据可以通过使用原生的socket API与PF_CAN协议族来实现。

主要步骤

  1. 创建并配置CAN套接字。
  2. 绑定到特定的CAN接口(如can0)。
  3. 发送和接收CAN帧。

CAN数据发送的代码示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

int main() {
    
    
    int s;  // 套接字
    struct sockaddr_can addr;
    struct ifreq ifr;
    struct can_frame frame;  // CAN帧结构

    // 创建套接字
    s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (s < 0) {
    
    
        perror("socket");
        return 1;
    }

    // 指定CAN接口,例如can0
    strcpy(ifr.ifr_name, "can0");
    ioctl(s, SIOCGIFINDEX, &ifr);  // 获取接口索引

    // 绑定套接字到CAN接口
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    
    
        perror("bind");
        return 1;
    }

    // 准备要发送的CAN帧
    frame.can_id = 0x321;  // CAN ID
    frame.can_dlc = 8;     // 数据长度为8字节
    frame.data[0] = 0x11;
    frame.data[1] = 0x22;
    frame.data[2] = 0x33;
    frame.data[3] = 0x44;
    frame.data[4] = 0x55;
    frame.data[5] = 0x66;
    frame.data[6] = 0x77;
    frame.data[7] = 0x88;

    // 发送CAN帧
    if (write(s, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
    
    
        perror("write");
        return 1;
    }

    printf("CAN frame sent\n");

    // 关闭套接字
    close(s);

    return 0;
}

接收CAN帧的代码示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

int main() {
    
    
    int s;
    struct sockaddr_can addr;
    struct ifreq ifr;
    struct can_frame frame;

    // 创建套接字
    s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (s < 0) {
    
    
        perror("socket");
        return 1;
    }

    // 指定CAN接口,例如can0
    strcpy(ifr.ifr_name, "can0");
    ioctl(s, SIOCGIFINDEX, &ifr);  // 获取接口索引

    // 绑定套接字到CAN接口
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    
    
        perror("bind");
        return 1;
    }

    // 接收CAN帧
    while (1) {
    
    
        int nbytes = read(s, &frame, sizeof(struct can_frame));
        if (nbytes < 0) {
    
    
            perror("read");
            return 1;
        }

        // 打印CAN帧
        printf("Received CAN frame with ID: 0x%X, DLC: %d\n", frame.can_id, frame.can_dlc);
        printf("Data: ");
        for (int i = 0; i < frame.can_dlc; i++) {
    
    
            printf("%02X ", frame.data[i]);
        }
        printf("\n");
    }

    // 关闭套接字
    close(s);

    return 0;
}

编译命令:

gcc -o can_send can_send.c
gcc -o can_receive can_receive.c

关键点解析

在Linux中进行CAN(Controller Area Network)通信时,使用了以下三个重要的结构体:struct sockaddr_canstruct ifreqstruct can_frame。它们分别用于套接字地址、网络接口配置、以及CAN帧的处理。下面是对这三个结构体的详细解析。

  1. struct sockaddr_can 用于将套接字绑定到特定的CAN接口,包含地址族和接口索引。
  2. struct ifreq 用于配置和操作网络接口属性,最常见的是用于获取CAN接口的索引。
  3. struct can_frame 用于表示CAN协议中的数据帧,包含CAN ID、数据长度和实际的CAN数据。
1. struct sockaddr_can

这个结构体用于表示CAN套接字的地址,它是AF_CAN协议族(Linux中的CAN协议)的地址结构。在套接字绑定到CAN接口时,它会使用此结构体。

定义

在头文件 <linux/can.h> 中定义:

struct sockaddr_can {
    
    
    sa_family_t can_family;   // 地址族,必须是AF_CAN
    int can_ifindex;          // 网络接口索引(类似can0, can1)
    union {
    
    
        struct {
    
     canid_t rx_id, tx_id; } tp;
    };
};

成员解释

  • can_family:指定地址的协议族。在CAN通信中,can_family 设置为 AF_CAN,用于表示CAN协议。
  • can_ifindex:表示与CAN相关的网络接口索引。它通过 ioctl 获取,常见的接口有can0can1等。使用 SIOCGIFINDEX 获取索引。
  • tp(可选):仅在某些高级传输层协议(如ISO-TP)中使用。一般CAN通信不需要使用这个字段。

示例

struct sockaddr_can addr;
addr.can_family = AF_CAN;          // 使用CAN协议
addr.can_ifindex = ifr.ifr_ifindex; // 绑定到can0或can1接口
2. struct ifreq

struct ifreq 主要用于配置网络接口属性,例如获取网络接口的索引、设置设备的参数等。在CAN通信中,它通常用于获取指定接口(如can0)的索引,以便与CAN设备关联。

定义

在头文件 <net/if.h> 中定义:

struct ifreq {
    
    
    char ifr_name[IFNAMSIZ];   // 接口名称,例如 "can0"
    union {
    
    
        struct sockaddr ifr_addr;   // 用于套接字地址的各种配置
        struct sockaddr ifr_dstaddr;
        struct sockaddr ifr_broadaddr;
        struct sockaddr ifr_netmask;
        short ifr_flags;           // 接口标志,例如 IFF_UP
        int ifr_ifindex;           // 接口索引
        int ifr_metric;
        int ifr_mtu;               // 最大传输单元
        struct ifmap ifr_map;
        char ifr_slave[IFNAMSIZ];
        char ifr_newname[IFNAMSIZ];
        char *ifr_data;
    };
};

成员解释

  • ifr_name:表示网络接口的名称,例如 "can0""eth0"。这是一个字符串数组,定义接口的名称。
  • ifr_ifindex:存储网络接口的索引。通过调用 ioctl 并使用 SIOCGIFINDEX 命令可以获取接口的索引。
  • 其它字段(如ifr_flags)可以用于设置和获取接口状态,但在基本的CAN通信中不常用。

示例

struct ifreq ifr;
strcpy(ifr.ifr_name, "can0");         // 指定接口为 can0
ioctl(s, SIOCGIFINDEX, &ifr);         // 获取接口索引并存入 ifr.ifr_ifindex
3.struct can_frame

这是CAN帧的结构体,表示CAN网络上传输的数据帧。每个CAN帧包含一个标识符(CAN ID)、数据长度(DLC),以及最多8个字节的实际数据。

定义

在头文件 <linux/can.h> 中定义:

struct can_frame {
    
    
    canid_t can_id;  // 32 位 CAN ID (11 或 29 位有效位), 包含标志位
    __u8    can_dlc; // 数据长度码 (0..8)
    __u8    __pad;   // 填充
    __u8    __res0;  // 保留
    __u8    __res1;  // 保留
    __u8    data[8]; // 数据字段 (最多8字节)
};

成员解释

  • can_id:CAN帧的标识符。根据CAN协议,这个字段有11位标准ID或29位扩展ID,CAN ID可以包括额外的标志位,如远程传输请求 (RTR) 和错误标志 (ERR):

    • CAN_EFF_FLAG:表示该帧使用29位扩展ID。
    • CAN_RTR_FLAG:表示远程传输请求帧 (remote transmission request)。
    • CAN_ERR_FLAG:表示错误帧。
  • can_dlc:数据长度码,表示数据字段data中实际传输的字节数。DLC的值范围是0到8,CAN帧最多携带8字节的数据。

  • data:一个字节数组,用于存储实际传输的数据,最多可以容纳8个字节。

示例

struct can_frame frame;
frame.can_id = 0x321;     // 设置标准CAN ID
frame.can_dlc = 8;        // 数据长度为8字节
frame.data[0] = 0x11;     // 数据
frame.data[1] = 0x22;
frame.data[2] = 0x33;
frame.data[3] = 0x44;
frame.data[4] = 0x55;
frame.data[5] = 0x66;
frame.data[6] = 0x77;
frame.data[7] = 0x88;

效果示意:

发送0x321,0x111数据

在这里插入图片描述

接收数据

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46999174/article/details/142338017