书接上回:
嵌入式开发—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数据
接收数据