书接上回:
 嵌入式开发—CAN通信协议详解与应用(中)-CSDN博客
注:本文只是说明了如何进行基础的CAN收发操作,复杂CAN操作可以看这篇文章
Linux 底软开发——对CAN的详细操作(周期发送,异常检测,过滤报文)_linux can 接收过滤-CSDN博客
 
文章目录
- 使用命令行工具配置CAN接口
- 1.查看CAN接口是否存在
- 2.设置CAN网络接口
- 3.使用candump监听CAN总线
- 3.1过滤CAN ID
 
- 4. 使用`cansend`发送CAN帧
 
- 使用Linux库函数来完成CAN通信的收发
- 主要步骤
- CAN数据发送的代码示例
- 接收CAN帧的代码示例
- 关键点解析
- 1. `struct sockaddr_can`
- 2. `struct ifreq`
- 3.`struct can_frame`
 
- 效果示意:
 
 
在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 查看具体的命令提示
root@MADC3.5-A:/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
root@MADC3.5-A:/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查看具体命令格式
root@MADC3.5-A:/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协议族来实现。
主要步骤
- 创建并配置CAN套接字。
- 绑定到特定的CAN接口(如can0)。
- 发送和接收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_can、struct ifreq 和 struct can_frame。它们分别用于套接字地址、网络接口配置、以及CAN帧的处理。下面是对这三个结构体的详细解析。
- struct sockaddr_can用于将套接字绑定到特定的CAN接口,包含地址族和接口索引。
- struct ifreq用于配置和操作网络接口属性,最常见的是用于获取CAN接口的索引。
- 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获取,常见的接口有- can0、- can1等。使用- 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数据

接收数据



![QQ频道机器人零基础开发详解(基于QQ官方机器人文档)[第五期]](https://i-blog.csdnimg.cn/direct/f5176254dfbd4b03be03728af676df06.png#pic_center)
















