Skip to content

SocketCAN 编程实践

编程基础

SocketCAN 架构概述

SocketCAN 将 CAN 总线集成到 Linux 网络子系统,使得 CAN 通信可以使用标准的 Socket API 进行编程。

核心概念

Socket 类型

SocketCAN 使用 PF_CAN 协议族,支持多种 Socket 类型:

Socket 类型协议用途
SOCK_RAWCAN_RAW原始 CAN 帧收发(最常用)
SOCK_DGRAMCAN_BCM广播管理器(周期性发送)
SOCK_SEQPACKETCAN_ISOTPISO-TP 协议(诊断通信)

数据结构

CAN 2.0 帧结构

c
#include <linux/can.h>

struct can_frame {
    canid_t can_id;  /* 32位CAN ID + EFF/RTR/ERR标志 */
    __u8 can_dlc;    /* 数据长度(0-8) */
    __u8 __pad;      /* 填充字节 */
    __u8 __res0;     /* 保留字节 */
    __u8 __res1;     /* 保留字节 */
    __u8 data[8] __attribute__((aligned(8)));  /* 数据 */
};

CAN FD 帧结构

c
#include <linux/can.h>

struct canfd_frame {
    canid_t can_id;  /* 32位CAN ID + EFF/RTR/ERR标志 */
    __u8 len;        /* 数据长度(0-64) */
    __u8 flags;      /* CAN FD标志(BRS, ESI) */
    __u8 __res0;     /* 保留字节 */
    __u8 __res1;     /* 保留字节 */
    __u8 data[64] __attribute__((aligned(8)));  /* 数据 */
};

CAN ID 标志位

c
/* CAN ID 标志位定义 */
#define CAN_EFF_FLAG 0x80000000U  /* 扩展帧标志(29位ID) */
#define CAN_RTR_FLAG 0x40000000U  /* 远程帧标志 */
#define CAN_ERR_FLAG 0x20000000U  /* 错误帧标志 */

/* ID 掩码 */
#define CAN_SFF_MASK 0x000007FFU  /* 标准帧ID掩码(11位) */
#define CAN_EFF_MASK 0x1FFFFFFFU  /* 扩展帧ID掩码(29位) */

开发环境配置

必需的头文件

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>

编译配置

bash
# 编译CAN程序
gcc -o can_app can_app.c

# 如果需要链接其他库
gcc -o can_app can_app.c -lpthread

# 带调试信息编译
gcc -g -o can_app can_app.c

完整示例程序

示例1:基础收发程序

这是一个完整可编译运行的 CAN 收发程序,包含完整的错误处理。

c
/* can_basic.c - 基础CAN收发示例 */
#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>

int main(int argc, char *argv[])
{
    int s;  /* Socket文件描述符 */
    struct sockaddr_can addr;
    struct ifreq ifr;
    struct can_frame frame;
    int nbytes;

    /* 检查参数 */
    if (argc != 2) {
        fprintf(stderr, "用法: %s <CAN接口名>\n", argv[0]);
        fprintf(stderr, "示例: %s can0\n", argv[0]);
        return 1;
    }

    /* 1. 创建Socket */
    s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (s < 0) {
        perror("创建socket失败");
        return 1;
    }

    /* 2. 指定CAN接口 */
    strcpy(ifr.ifr_name, argv[1]);
    if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
        perror("获取接口索引失败");
        close(s);
        return 1;
    }

    /* 3. 绑定Socket到CAN接口 */
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("绑定socket失败");
        close(s);
        return 1;
    }

    printf("成功绑定到接口: %s\n", argv[1]);

    /* 4. 发送CAN帧 */
    memset(&frame, 0, sizeof(struct can_frame));
    frame.can_id = 0x123;  /* 标准帧ID */
    frame.can_dlc = 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;

    nbytes = write(s, &frame, sizeof(struct can_frame));
    if (nbytes < 0) {
        perror("发送CAN帧失败");
        close(s);
        return 1;
    }
    printf("发送CAN帧: ID=0x%03X DLC=%d\n", frame.can_id, frame.can_dlc);

    /* 5. 接收CAN帧 */
    printf("等待接收CAN帧...\n");
    nbytes = read(s, &frame, sizeof(struct can_frame));
    if (nbytes < 0) {
        perror("接收CAN帧失败");
        close(s);
        return 1;
    }

    /* 6. 解析并显示接收到的帧 */
    printf("接收CAN帧: ID=0x%03X DLC=%d 数据: ", frame.can_id, frame.can_dlc);
    for (int i = 0; i < frame.can_dlc; i++) {
        printf("%02X ", frame.data[i]);
    }
    printf("\n");

    /* 7. 关闭Socket */
    close(s);
    return 0;
}

编译和运行

bash
# 编译
gcc -o can_basic can_basic.c

# 运行(需要先配置can0接口)
sudo ip link set can0 type can bitrate 500000
sudo ip link set can0 up
./can_basic can0

示例2:CAN FD 通信程序

支持 CAN FD 高速数据传输的完整示例。

c
/* canfd_example.c - CAN FD收发示例 */
#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>

int main(int argc, char *argv[])
{
    int s;
    struct sockaddr_can addr;
    struct ifreq ifr;
    struct canfd_frame frame;
    int enable_canfd = 1;
    int nbytes;

    if (argc != 2) {
        fprintf(stderr, "用法: %s <CAN接口名>\n", argv[0]);
        return 1;
    }

    /* 创建Socket */
    s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (s < 0) {
        perror("创建socket失败");
        return 1;
    }

    /* 启用CAN FD支持 */
    if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
                   &enable_canfd, sizeof(enable_canfd)) < 0) {
        perror("启用CAN FD失败");
        close(s);
        return 1;
    }

    /* 指定并绑定CAN接口 */
    strcpy(ifr.ifr_name, argv[1]);
    if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
        perror("获取接口索引失败");
        close(s);
        return 1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("绑定socket失败");
        close(s);
        return 1;
    }

    printf("CAN FD模式已启用,绑定到接口: %s\n", argv[1]);

    /* 构造CAN FD帧(64字节数据) */
    memset(&frame, 0, sizeof(struct canfd_frame));
    frame.can_id = 0x456;
    frame.len = 64;  /* CAN FD最大长度 */
    frame.flags = CANFD_BRS;  /* 启用比特率切换 */

    /* 填充测试数据 */
    for (int i = 0; i < 64; i++) {
        frame.data[i] = i;
    }

    /* 发送CAN FD帧 */
    nbytes = write(s, &frame, sizeof(struct canfd_frame));
    if (nbytes < 0) {
        perror("发送CAN FD帧失败");
        close(s);
        return 1;
    }
    printf("发送CAN FD帧: ID=0x%03X 长度=%d 标志=0x%02X\n",
           frame.can_id, frame.len, frame.flags);

    /* 接收CAN FD帧 */
    printf("等待接收CAN FD帧...\n");
    nbytes = read(s, &frame, sizeof(struct canfd_frame));
    if (nbytes < 0) {
        perror("接收失败");
        close(s);
        return 1;
    }

    /* 显示接收到的数据 */
    printf("接收CAN FD帧: ID=0x%03X 长度=%d\n", frame.can_id, frame.len);
    printf("数据: ");
    for (int i = 0; i < frame.len; i++) {
        printf("%02X ", frame.data[i]);
        if ((i + 1) % 16 == 0) printf("\n      ");
    }
    printf("\n");

    close(s);
    return 0;
}

CAN FD 配置和运行

bash
# 编译
gcc -o canfd_example canfd_example.c

# 配置CAN FD接口
sudo ip link set can0 type can bitrate 1000000 dbitrate 5000000 fd on
sudo ip link set can0 up

# 运行
./canfd_example can0

参考资源

官方文档

开源项目

驱动智能连接,赋能科技未来