• CAN 通信原理学习


    CAN通信

    一:基本概述

    1.1 can总线是什么

    CAN 是 Controller Area Network 的缩写,是 ISO 国际标准化的串行通信协议。通俗来讲,CAN总线就是一种传输数据的线,用于在不同的ECU之间传输数据。
    CAN(Controller Area Network)是ISO国际标准化的串行通信协议。广泛应用于汽车、船舶等。具有已经被大家认可的高性能和可靠性。
    CAN控制器通过组成总线的2根线 (CAN-H和CAN-L)的电位差来确定总线的电平 ,在任一时刻,总线上有2种电平:显性电平和隐性电平。
    “显性”具有“优先”的意味,只要有一个单元输出显性电平,总线上即为显性电平,并且,“隐性”具有“包容”的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平。(显性电平比隐性电平更强)。
    总线上执行逻辑上的线“与”时,显性电平的逻辑值为“0”,隐性电平为“1”。
    下图显示了一个典型的CAN拓扑连接图。
    在这里插入图片描述

    连接在总线上的所有单元都能够发送信息,如果有超过一个单元在同一时刻发送信息,有最高优先级的单元获得发送的资格,所有其它单元执行接收操作。

    can的拓扑结构:

    在这里插入图片描述


    1.2 can总线协议的特点

    CAN总线协议具有下面的特点:

    1) 多主控制

    当总线空闲时,连接到总线上的所有单元都可以启动发送信息,这就是所谓的多主控制的概念。
    先占有总线的设备获得在总线上进行发送信息的资格。这就是所谓的CSMA/CR(Carrier Sense MultipleAccess/Collosion Avoidance)方法
    如果多个设备同时开始发送信息,那么发送最高优先级ID消息的设备获得发送资格。

    2) 信息的发送

    在CAN协议中,所有发送的信息要满足预先定义的格式。当总线没有被占用的时候,连接在总线上的任何设备都能起动新信息的传输,如果两个或更多个设备在同时刻启动信息的传输,通过ID来决定优先级。ID并不是指明信息发送的目的地,而是指示信息的优先级。如果2个或者更多的设备在同一时刻启动信息的传输,在总线上按照信息所包含的ID的每一位来竞争,赢得竞争的设备(也就是具有最高优先级的信息)能够继续发送,而失败者则立刻停止发送并进入接收操作。因为总线上同一时刻只可能有一个发送者,而其它均处于接收状态,所以,并不需要在底层协议中定义地址的概念。

    3) 系统的灵活性

    连接到总线上的单元并没有类似地址这样的标识,所以,添加或去除一个设备,无需改变软件和硬件,或其它设备的应用层软件。

    4) 通信速度

    可以设置任何通讯速度,以适应网络规模。
    对一个网络,所有单元必须有相同的通讯速度,如果不同,就会产生错误,并妨碍网络通讯,然而,不同网络间可以有不同的通讯速度。

    5) 远程数据请求

    可以通过发送“遥控帧”,请求其他单元发送数据。

    6) 错误检测、错误通知、错误恢复功能

    所有单元均可以检测出错误(错误检测功能)。
    检测到错误的单元立刻同时通知其它所有的单元(错误通知功能)。如果一个单元发送信息时检测到一个错误,它会强制终止信息传输,并通知其它所有设备发生了错误,然后它会重传直到信息正常传输出去(错误恢复功能)。

    7) 错误隔离

    在CAN总线上有两种类型的错误:暂时性的错误(总线上的数据由于受到噪声的影响而暂时出错);持续性的错误(由于设备内部出错(如驱动器坏了、连接有问题等)而导致的)。CAN能够区别这两种类型,一方面降低常出错单元的通讯优先级以阻止对其它正常设备的影响,另一方面,如果是一种持续性的错误,将这个设备从总线上隔离开。

    8) 连接

    CAN总线允许多个设备同时连接到总线上且在逻辑上没有数目上的限制。然而由于延迟和负载能力的限制,实际可连接得设备还是有限制的,可以通过降低通讯速度来增加连接的设备个数。相反,如果连接的设备少,通讯的速度可以增加。


    1.3 can的网络通信结构

    1.3.1

    实际上,CAN总线网络底层只采用了OSI基本参照模型中的数据链路层、传输层。而在CAN网络高层仅采用了OSI基本参照模型的应用层
    在这里插入图片描述


    1.3.2 can协议网络层次

    在CAN协议中,ISO标准只对数据链路层和物理层做了规定。对于数据链路层和物理层的一部分,ISO11898和ISO11519-2的规定是相同,但是在物理层的PMD子层和MDI子层是不同的。
    在这里插入图片描述

    在CAN总线,每一层网络中定义的事项如下:
    在这里插入图片描述


    二:socket can在通信网络中的应用

    socket can应用实例
    server端:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int can_recv() {
        int sock_fd;
        unsigned long nbytes, len;
        struct sockaddr_can addr;
        struct ifreq ifr;
        /*为了能够接收CAN报文,我们需要定义一个CAN数据格式的结构体变量*/
        struct can_frame frame;
        struct can_frame *ptr_frame;
    
        /* 建立套接字,设置为原始套接字,原始CAN协议 */
        sock_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
        /* 对CAN接口进行初始化,设置CAN接口名,即当我们用ifconfig命令时显示的名字 */
        strcpy(ifr.ifr_name, "can0");
        ioctl(sock_fd, SIOCGIFINDEX, &ifr);
        /*设置CAN协议 */
        addr.can_family = AF_CAN;
        addr.can_ifindex = 0;
        /*将刚生成的套接字与网络地址进行绑定*/
        bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr));
        /*开始接收数据*/
        nbytes = recvfrom(sock_fd, &frame, sizeof(struct can_frame), 0,
                          (struct sockaddr *)&addr, (socklen_t *)&len);
        /*get interface name of the received CAN frame*/
        ifr.ifr_ifindex = addr.can_ifindex;
        ioctl(sock_fd, SIOCGIFNAME, &ifr);
        printf("Received a CAN frame from interface %s\n", ifr.ifr_name);
        /*将接收到的CAN数据打印出来,其中ID为标识符,DLC为CAN的字节数,DATA为1帧报文的字节数*/
        printf("CAN frame:\nID = %x\nDLC = %x\nDATA = %s\n", frame.can_id,
               frame.can_dlc, frame.data);
    
        ptr_frame = &frame;
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    client

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int can_send() {
        int sock_fd;
        unsigned long nbytes;
        struct sockaddr_can addr;
        struct ifreq ifr;
        struct can_frame frame;
        /*建立套接字,设置为原始套接字,原始CAN协议 */
        sock_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
        /* 对CAN接口进行初始化,设置CAN接口名,即当我们用ifconfig命令时显示的名字 */
        strcpy((char *)(ifr.ifr_name), "can0");
        ioctl(sock_fd, SIOCGIFINDEX, &ifr);
        printf("can0 can_ifindex = %x\n", ifr.ifr_ifindex);
        addr.can_family = AF_CAN;
        addr.can_ifindex = ifr.ifr_ifindex;
        /*将刚生成的套接字与CAN套接字地址进行绑定*/
        bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr));
        /*设置CAN帧的ID号,可区分为标准帧和扩展帧的ID号*/
        frame.can_id = 0x1122;
        strcpy((char *)frame.data, "hello");
        frame.can_dlc = strlen((char *)frame.data);
        printf("Send a CAN frame from interface %s\n", ifr.ifr_name);
        /*开始发送数据*/
        nbytes = sendto(sock_fd, &frame, sizeof(struct can_frame), 0,
                        (struct sockaddr *)&addr, sizeof(addr));
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    上面两个程序看完后,大家可能会有疑问,为什么这两个程序没有listen()和accept()函数呢?
    其实这两个程序是独立的运行的,并不像字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM),需要先运行服务器进行侦听。SOCK_STREAM和SOCK_DGRAM的两个server和client程序是通过网络相互收发数据。
    而CAN的socket的server和client程序收发数据的对象是CAN总线。server从CAN总线上接收数据,client将数据发到CAN总线上,当CAN总线上有数据时,server才能接收数据,当CAN总线空闲时,client才能将数据发送出去。


    三 一个程序

    近写了个自认为不错的基于linux socket can程序,主要功能:

    1.程序具备全部CAN功能,包括CAN标准帧/扩展帧接收与发送、CAN总线错误判断、环回等功能
    2.适用基于LINUX SOCKET机制实现的CAN接口,可用于嵌入式LINUX的CAN测试
    3.程序采用标准LINUX命令行参数选项形式,接受用户参数

    int main(int argc, char **argv)
    {
        S_CanFrame sendframe, recvframe;
        byte *psendframe = (byte *)&sendframe;
        byte *precvframe = (byte *)&recvframe;
        u_canframe_data_t *psend_data = (u_canframe_data_t *)sendframe.data;
        const int can_frame_len = sizeof(S_CanFrame); 
    
        pid_t pid = -1;
        int   status;
    
        int  ret = 0;
        char buf[128] = {0};
        bool carry_bit = false;// 进位标志
    
        int segment_id;//id for shared memo
    
    
        if (parse_options(argc, argv))
        {
            usage();    return  0;
        }
    
        if (!find_can(port))
        {
            sprintf(buf, "\n\t错误:CAN%d设备不存在\n\n", port + 1);
            panic(buf);
            return  -1;
        }
    
        close_can(port);// 必须先关闭CAN,才能成功设置CAN波特率
        set_bitrate(port, bitrate);// 操作CAN之前,先要设置波特率
        open_can(port, bitrate);
    
        send_socket_fd = socket_connect(port);
        recv_socket_fd = socket_connect(port);
        //printf("send_socket_fd = %d, recv_socket_fd = %d\n", send_socket_fd, recv_socket_fd);
        if (send_socket_fd < 0 || send_socket_fd < 0)
        {
            disconnect(&send_socket_fd);
            disconnect(&recv_socket_fd);
            panic("\n\t打开socket can错误\n\n");
            return  -1;
        }
        set_can_filter();
        set_can_loopback(send_socket_fd, lp);
    
        printf_head();
    
        memset(&sendframe, 0x00, sizeof(sendframe));
        memset(&recvframe, 0x00, sizeof(recvframe));
    
        if (extended_frame) // 指定发送帧类型:扩展帧或标准帧
        {
            sendframe.can_id = (send_frame_id & CAN_EFF_MASK) | CAN_EFF_FLAG;
        } 
        else
        {
            sendframe.can_id = (send_frame_id & CAN_SFF_MASK);
        }
        sendframe.can_dlc = dlc;
        memcpy(sendframe.data, send_frame_data, dlc);
    
        
        segment_id = shmget(IPC_PRIVATE, sizeof(int), S_IRUSR | S_IWUSR);// allocate memo
        pframeno = (int *)shmat(segment_id, NULL, 0);// attach the memo
        if (pframeno == NULL)
        {
            panic("\n\t创建共享内存失败\n\n");
            return  -1;
        }
        *pframeno = 1;
    
        run = true;
    
        pid = fork();
        if(pid == -1) 
        { 
            panic("\n\t创建进程失败\n\n");
            return  -1;
        }
        else if(pid == 0) // 子进程,用于发送CAN帧
        {
            while (run && (send_frame_times > 0))
            {
                ret = send_frame(send_socket_fd, (char *)&sendframe, sizeof(sendframe));
                printf_frame(sendframe.can_id & CAN_EFF_MASK, sendframe.data, sendframe.can_dlc, 
                    ((sendframe.can_id & CAN_EFF_FLAG) ? true : false),
                    ret > 0 ? true : false, 
                    true);
                delay_ms(send_frame_freq_ms);
    
                if (send_frame_id_inc_en)
                {
                    sendframe.can_id++;
                    if (extended_frame)
                    {
                        sendframe.can_id = (sendframe.can_id & CAN_EFF_MASK) | CAN_EFF_FLAG;
                    } 
                    else
                    {
                        sendframe.can_id = (sendframe.can_id & CAN_SFF_MASK);
                    }
                }
    
                if (send_frame_data_inc_en && dlc > 0)
                {
                    if (dlc > 4 && psend_data->s.dl == ((__u32)0xFFFFFFFF))
                    {
                        carry_bit = true;// 发生进位
                    }
                    psend_data->s.dl++;
    
                    if (dlc <= 4)
                    {
                        if (psend_data->s.dl >= (1 << (dlc * 8)))
                        {
                            psend_data->s.dl = 0;
                        }
                    }
                    else if (dlc <= 8)
                    {
                        if (carry_bit)
                        {
                            psend_data->s.dh++;
                            if (psend_data->s.dh >= (1 << ((dlc - 4) * 8)))
                            {
                                psend_data->s.dh = 0;
                            }
    
                            carry_bit = false;
                        }
                    }
                }
    
                send_frame_times--;
            }
    
            exit(0);
        }
        else // 父进程,接收CAN帧
        {
            install_sig();
    
            while (run)
            {
                memset(precvframe, 0x00, can_frame_len);
                ret = recv_frame(recv_socket_fd, precvframe, can_frame_len, 5 * 1000);
                if (ret > 0)
                {
                    printf_frame(recvframe.can_id & CAN_EFF_MASK, recvframe.data, recvframe.can_dlc, 
                        ((recvframe.can_id & CAN_EFF_FLAG) ? true : false),
                        true, 
                        false);
                }
            }
    
            while(((pid = wait(&status)) == -1) && (errno == EINTR))
            {
                delay_ms(10);
            }
        }
    
        disconnect(&send_socket_fd);
        disconnect(&recv_socket_fd);
    
        shmdt(pframeno);// detach memo
        shmctl(segment_id, IPC_RMID, NULL);// remove
    
        return  0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
  • 相关阅读:
    SpringBoot自定义注解+异步+观察者模式实现业务日志保存
    创建一个普通的vue项目
    6-1 CCat类的静态数据【武汉理工大学】
    随机场的生成 random field generation-考虑相关长度-GSTOOL
    Linux安全之iptables高级特性
    c++ 批量导出c# 函数,格式
    Docker快速上手:使用Docker部署Drupal并实现公网访问
    Vite打包时使用plugin解决浏览器兼容问题
    基于微信小程序的个人健康数据管理平台设计与实现(源码+lw+部署文档+讲解等)
    IOS恢复
  • 原文地址:https://blog.csdn.net/weixin_48433164/article/details/126926258