初识 Socket CAN

1,202 阅读4分钟

Socket CAN 编程是一种在 Linux 系统下实现 CAN(Controller Area Network)协议的编程方法,用于在不同设备之间进行高效、可靠的通信1。以下是关于 Socket CAN 编程的详细介绍:

一、Socket CAN 简介

  • 起源与发展:CAN 最初是由德国的 BOSCH 公司为汽车监测、控制系统而设计的。随着技术的发展,它在汽车、工业自动化、机器人等领域得到了广泛应用。Linux 下最早使用 CAN 的方法是基于字符设备来实现的,与之不同的是 Socket CAN 使用伯克利的 socket 接口和 Linux 网络协议栈,这种方法使得 CAN 设备驱动可以通过网络接口来调用,让熟悉网络编程的程序员能够比较容易地学习和使用1。

  • 工作原理:Socket CAN 子系统是在 Linux 下 CAN 协议的一种实现。它利用了伯克利的 socket 接口和 Linux 网络协议栈,将 CAN 设备驱动实现为网络接口。通过这种方式,应用程序可以使用标准的网络编程函数来进行 CAN 通信,就像进行网络数据传输一样。

  • 应用场景:Socket CAN 广泛应用于汽车电子、工业控制、机器人等领域,用于实现设备之间的实时通信和控制。例如,在汽车中,它用于发动机控制、车身电子系统、自动驾驶等;在工业自动化中,用于机器人控制、生产线监控等。

二、Socket CAN 编程基础

  • 网络模型:Socket CAN 基于 TCP/IP 协议栈,使用套接字(socket)作为通信端点。它将 CAN 总线视为一个网络,每个 CAN 节点都有一个唯一的标识符(CAN ID),用于在总线上进行数据传输和仲裁。

  • 数据帧结构:CAN 数据帧是 Socket CAN 通信的基本单位,它由帧起始、仲裁域、控制域、数据域、CRC 校验域、应答域和帧结束等部分组成。其中,仲裁域用于确定数据帧的优先级,数据域则包含了实际要传输的数据。

  • 通信模式:Socket CAN 支持多种通信模式,如点对点通信、广播通信和多播通信。在点对点通信中,数据帧只能在两个特定的节点之间传输;广播通信则允许一个节点向总线上的所有其他节点发送数据;多播通信则可以将数据帧发送到一组特定的节点。

三、Socket CAN 编程接口

  • 创建套接字:使用socket()函数创建一个 CAN 套接字,指定协议族为PF_CAN,套接字类型为SOCK_RAWSOCK_DGRAM,具体取决于使用的 CAN 协议版本。例如:
int sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
  • 绑定套接字:将创建的套接字与具体的 CAN 接口进行绑定,以便接收和发送数据。可以使用bind()函数来实现,例如:
struct sockaddr_can can_addr;
can_addr.can_family = AF_CAN;
can_addr.can_ifindex = ifr.ifr_ifindex;
bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr));
  • 设置过滤规则:可以通过设置过滤规则来选择性地接收特定 CAN ID 的数据帧。使用setsockopt()函数来设置过滤规则,例如:
struct can_filter rfilter[2];
rfilter[0].can_id = 0x60A;
rfilter[0].can_mask = 0x7FF;
rfilter[1].can_id = 0x60B;
rfilter[1].can_mask = 0x7FF;
setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
  • 数据发送与接收:使用write()函数发送数据帧,使用read()函数接收数据帧。例如:
struct can_frame frame;
frame.can_id = 123;
frame.can_dlc = 3;
frame.data[0] = 0xA0;
frame.data[1] = 0xB0;
frame.data[2] = 0xC0;
write(sockfd, &frame, sizeof(frame));

struct can_frame recv_frame;
read(sockfd, &recv_frame, sizeof(recv_frame));

四、Socket CAN 编程示例

以下是一个简单的 Socket CAN 编程示例,用于发送和接收 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 sockfd;
    struct sockaddr_can can_addr;
    struct ifreq ifr;
    struct can_frame frame;
    int nbytes;

    // 创建套接字
    sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (sockfd < 0) {
        perror("socket");
        return -1;
    }

    // 设置CAN接口
    strcpy(ifr.ifr_name, "can0");
    ioctl(sockfd, SIOCGIFINDEX, &ifr);
    can_addr.can_family = AF_CAN;
    can_addr.can_ifindex = ifr.ifr_ifindex;
    bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr));

    // 发送数据帧
    frame.can_id = 0x123;
    frame.can_dlc = 8;
    memset(frame.data, 0, 8);
    frame.data[0] = 0xAA;
    frame.data[1] = 0xBB;
    frame.data[2] = 0xCC;
    frame.data[3] = 0xDD;
    frame.data[4] = 0xEE;
    frame.data[5] = 0xFF;
    frame.data[6] = 0x00;
    frame.data[7] = 0x11;
    nbytes = write(sockfd, &frame, sizeof(frame));
    if (nbytes!= sizeof(frame)) {
        perror("write");
        return -1;
    }

    // 接收数据帧
    nbytes = read(sockfd, &frame, sizeof(frame));
    if (nbytes < 0) {
        perror("read");
        return -1;
    }
    printf("Received CAN frame: ID=0x%X, DLC=%d, Data=", frame.can_id, frame.can_dlc);
    for (int i = 0; i < frame.can_dlc; i++) {
        printf("%02X ", frame.data[i]);
    }
    printf("\n");

    close(sockfd);
    return 0;
}

五、总结

Socket CAN 编程为在 Linux 系统下进行 CAN 通信提供了一种方便、高效的方法。通过使用 Socket CAN 接口,开发人员可以轻松地实现 CAN 设备之间的通信,从而为各种应用提供支持。在实际应用中,需要根据具体的需求和硬件平台选择合适的 CAN 控制器和驱动程序,并结合 Socket CAN 编程接口进行开发。同时,还需要注意数据的准确性和可靠性,以及网络的稳定性和安全性。