4. 扩展数据帧发送与接收验证
任务说明
本案例在标准帧基础上,配置CAN节点发送29位标识符的扩展数据帧,接收端通过扩展帧过滤器只接收目标ID,并解析出扩展帧ID与数据。使用Linux SocketCAN(vcan虚拟总线)实现,无需真实硬件即可验证。
环境与依赖
- Linux系统,内核已启用SocketCAN(
CONFIG_CAN、CONFIG_CAN_VCAN)。 - 工具:
iproute2(提供ip命令)、gcc。 - 使用
vcan0虚拟接口,发送端与接收端在同一总线上运行。
关键点
- 扩展帧标志:
can_id需置CAN_EFF_FLAG,实际ID为低29位。 - 过滤器:
can_filter中can_id同样需带CAN_EFF_FLAG,can_mask用CAN_EFF_MASK | CAN_EFF_FLAG精确匹配扩展ID。 - 接收端解析时用
CAN_EFF_MASK取出真实ID,用CAN_EFF_FLAG判断帧类型。
完整代码
单文件eff_demo.c,包含发送与接收两个角色,通过命令行参数区分。
#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>
#define EFF_ID 0x18DAF110u /* 29位扩展ID示例 */
static int open_can(const char *ifname) {
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (s < 0) { perror("socket"); exit(1); }
struct ifreq ifr;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
ifr.ifr_name[IFNAMSIZ - 1] = 0;
if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { perror("ioctl"); exit(1); }
struct sockaddr_can addr = {0};
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind"); exit(1);
}
return s;
}
static void do_send(const char *ifname) {
int s = open_can(ifname);
struct can_frame f = {0};
f.can_id = EFF_ID | CAN_EFF_FLAG; /* 关键:置扩展帧标志 */
f.can_dlc = 4;
f.data[0] = 0xDE; f.data[1] = 0xAD;
f.data[2] = 0xBE; f.data[3] = 0xEF;
if (write(s, &f, sizeof(f)) != sizeof(f)) { perror("write"); exit(1); }
printf("sent EFF id=0x%X dlc=%d\n", EFF_ID, f.can_dlc);
close(s);
}
static void do_recv(const char *ifname) {
int s = open_can(ifname);
struct can_filter rflt;
rflt.can_id = EFF_ID | CAN_EFF_FLAG; /* 只收该扩展ID */
rflt.can_mask = CAN_EFF_MASK | CAN_EFF_FLAG; /* 精确匹配 */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rflt, sizeof(rflt));
struct can_frame f;
int n = read(s, &f, sizeof(f));
if (n != sizeof(f)) { perror("read"); exit(1); }
if (f.can_id & CAN_EFF_FLAG) {
unsigned int id = f.can_id & CAN_EFF_MASK;
printf("recv EFF id=0x%X dlc=%d data=", id, f.can_dlc);
for (int i = 0; i < f.can_dlc; i++) printf("%02X ", f.data[i]);
printf("\n");
} else {
printf("recv non-EFF frame, ignored\n");
}
close(s);
}
int main(int argc, char **argv) {
if (argc != 3) {
fprintf(stderr, "usage: %s send|recv ifname\n", argv[0]);
return 1;
}
if (!strcmp(argv[1], "send")) do_send(argv[2]);
else if (!strcmp(argv[1], "recv")) do_recv(argv[2]);
else { fprintf(stderr, "unknown role\n"); return 1; }
return 0;
}
编译与运行
# 1. 建立虚拟CAN总线
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
# 2. 编译
gcc -o eff_demo eff_demo.c
# 3. 终端A:启动接收端(阻塞等待)
./eff_demo recv vcan0
# 4. 终端B:发送扩展帧
./eff_demo send vcan0
验证方法与通过条件
- 发送端输出:
sent EFF id=0x18DAF110 dlc=4。 - 接收端输出:
recv EFF id=0x18DAF110 dlc=4 data=DE AD BE EF。 - 判定通过:接收端打印的ID与发送端一致,且数据字节完全匹配,说明扩展帧ID被正确过滤与解析。
- 反向验证:将发送端
EFF_ID改为其他值(如0x18DAF111)重新编译发送,接收端因过滤器不匹配不会收到该帧,可确认过滤生效。
常见问题
- 忘记置
CAN_EFF_FLAG:帧会以标准帧发出,接收端过滤器不匹配。 - 过滤器
can_mask未包含CAN_EFF_FLAG:可能误收标准帧,需按上文设置。 vcan0未up:bind或write会失败,先执行ip link set up vcan0。