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判断帧类型。
发送端 can_id=0x18DAF110 vcan0 总线 扩展帧 EFF 接收端 过滤器匹配ID 发送:write() 写入扩展帧 接收:read() 返回匹配帧,解析ID与数据

完整代码

单文件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。