linux环境下的CAN2.0通讯使用C语言编码

提示:Linux环境下使用c++通过Socket套接字实现CAN2.0通讯协议。本文源码可直接运行使用。


前言

本文介绍了如何在Linux环境下,使用C语言通过Socket套接字实现CAN协议通讯。对Socket套接字中需要使用的函数、传参及函数返回值等细节问题做出了详细的解释。另外需要提醒的是,在学习CAN协议时应先了解Socket套接字、g++和Linux操作命令等基础知识。


一、Socket实现CAN协议的通讯

1.socket函数及参数返回值详解

    if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        perror("SocketCAN socket");
        return 1;
    }

这行代码是用来创建套接字,用于接受客户端的连接请求并与之建立 CAN连接。接下来还需要调用 bind() 绑定地址和端口、write() 发送数据等操作。

当socket()函数的返回值<0时,表示创建失败。

在使用 socket 函数创建套接字时,包括地址族(Address Family)、套接字类型(Socket Type)和协议(Protocol)三个参数。具体参数如下:

地址族(Address Family):
PF_CAN:这是指定协议族的标识符,表示使用的是CAN协议族。在这里,PF_CAN用于指定要创建的套接字将使用CAN协议族。
AF_INET:IPv4 地址族,用于指定使用 IPv4 地址。
AF_INET6:IPv6 地址族,用于指定使用 IPv6 地址。
AF_UNIX 或 AF_LOCAL:本地通信地址族,用于在同一台计算机上的进程间通信。

套接字类型(Socket Type):
SOCK_RAW:原始套接字,可以直接访问底层网络协议,适用于特定的网络操作需求,表示套接字是一个原始套接字。原始套接字允许程序直接访问底层网络协议。在这里,SOCK_RAW表示创建的套接字将是一个原始套接字,以便可以直接发送和接收CAN数据帧。
SOCK_STREAM:面向连接的字节流套接字,提供可靠的、基于字节流的数据传输服务,对应 TCP 协议。
SOCK_DGRAM:无连接的数据报套接字,提供不可靠的数据传输服务,对应 UDP 协议。

协议(Protocol):
CAN_RAW:这是指定CAN协议的标识符,表示将使用原始CAN协议。原始CAN协议提供了对CAN总线上的数据帧的直接访问。在这里,CAN_RAW用于指定要使用的CAN协议类型。
通常设置为 0,表示根据地址族和套接字类型自动选择合适的协议。
对于 SOCK_STREAM 类型的套接字,通常会选择 IPPROTO_TCP(TCP 协议)。
对于 SOCK_DGRAM 类型的套接字,通常会选择 IPPROTO_UDP(UDP 协议)。

2.sockaddr_in结构体、inet_pton函数及参数返回值详解

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) < 0)
    {
        close(serverSocket);
        std::cout << "inet_pton error" << std::endl;
        return -1;
    }

先要定义一个sockaddr_in结构体serverAddr,然后设置了该结构体的成员sin_family为AF_INET 表示使用 IPv4 地址,sin_port 为服务器的端口号,sin_addr 为服务器的 IP 地址。最后一行代码使用了 inet_pton 函数将点分十进制的 IP 地址转换为网络字节顺序的二进制形式,并存储到 serverAddr.sin_addr 中。

sockaddr_in 结构体是用于表示 IPv4 地址和端口的数据结构,通常用于网络编程中。该结构体定义如下:

struct sockaddr_in {
    short int      sin_family;  // 地址族,一般设置为 AF_INET
    unsigned short sin_port;    // 端口号,网络字节顺序
    struct in_addr sin_addr;    // IPv4 地址结构体
    char           sin_zero[8]; // 未使用,填充使结构体大小与 sockaddr 相同
};

sin_family:地址族,一般设置为 AF_INET 表示使用 IPv4 地址。
sin_port:端口号,使用 unsigned short 类型表示,需要使用 htons() 函数将主机字节顺序转换为网络字节顺序。
sin_addr:struct in_addr 类型的结构体,用于表示 IPv4 地址。
sin_zero:填充字段,用于使整个结构体的大小与 struct sockaddr 相同,通常不使用。

3.bind函数及参数返回值详解

    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("SocketCAN bind");
        return 1;
    }

sockaddr_can 是一个结构体类型,bind函数将绑定CAN套接字到指定的CAN接口。返回值>0。

4.发送CAN数据帧

    // 发送CAN数据帧
    frame.can_id = 0x123; // 发送的CAN ID
    frame.can_dlc = 2; // 数据长度
    frame.data[0] = 0x11; // 数据字节1
    frame.data[1] = 0x22; // 数据字节2
    if (write(s, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
        perror("SocketCAN write");
        return 1;
    }

5.接收CAN数据帧

    ssize_t nbytes = read(s, &frame, sizeof(struct can_frame));
    if (nbytes < 0) {
        perror("SocketCAN read");
        return 1;
    } else if (nbytes < sizeof(struct can_frame)) {
        fprintf(stderr, "SocketCAN read: incomplete frame\n");
        return 1;
    }

二、Socket实现CAN协议通讯源码

// Node 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <net/if.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/can.h>
#include <linux/can/raw.h>

int main(void) {
    int s;
    struct sockaddr_can addr;
    struct ifreq ifr;
    struct can_frame frame;

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

    // 设置CAN接口名字
    strcpy(ifr.ifr_name, "can0");
    ioctl(s, SIOCGIFINDEX, &ifr);

    // 绑定CAN套接字到指定的CAN接口
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("SocketCAN bind");
        return 1;
    }

    // 发送CAN数据帧
    frame.can_id = 0x123; // 发送的CAN ID
    frame.can_dlc = 2; // 数据长度
    frame.data[0] = 0x11; // 数据字节1
    frame.data[1] = 0x22; // 数据字节2
    if (write(s, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
        perror("SocketCAN write");
        return 1;
    }

    // 接收CAN数据帧
    ssize_t nbytes = read(s, &frame, sizeof(struct can_frame));
    if (nbytes < 0) {
        perror("SocketCAN read");
        return 1;
    } else if (nbytes < sizeof(struct can_frame)) {
        fprintf(stderr, "SocketCAN read: incomplete frame\n");
        return 1;
    }

    // 打印接收到的CAN数据
    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");

    return 0;
}

节点2的发送和接收代码和节点3的发送和接收代码与上面的代码相似,只需将frame.can_id、frame.data[0]和frame.data[1]修改为相应节点的CAN ID和数据即可。

这样,每个节点都有自己的发送和接收进程,它们独立地发送和接收CAN数据。


总结

本文介绍了如何在Linux环境下,使用C语言通过Socket套接字实现CAN协议通讯。对Socket套接字中需要使用的函数、传参及函数返回值等细节问题做出了详细的解释。

  • 38
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值