Linux can编程

1、 CAN总线协议:

       控制器局域网CAN最初用于汽车的检测与控制,采用两根差分线(CAN_H和CAN_L)传输,能够实现多主机通信,详细内容可参考网址http://blog.sina.com.cn/s/blog_5049ed020102vzra.html和博客https://www.cnblogs.com/spoorer/p/6649303.html,个人认为这两篇文章已经讲的足够详细,无需我在此赘述,只大概记录几个要点。

     帧的种类:数据帧、遥控帧、错误帧、过载帧、帧间隔

     帧格式:数据帧和遥控帧标准格式:11位标识符ID
                    扩展格式:29位标识符ID

      数据帧:帧起始(1 位显性)---仲裁段(优先级ID + 1 位显性位(RTR))---控制段(6 位)---数据段(0~8 字节,从MSB开始输出)---CRC段(15位CRC顺序+1 位CRC界定符)---ACK段(ACK槽和ACK界定符)---结束段(7位隐性)

      仲裁机制:多个单元同时开始发送时各发送单元从仲裁位第一位开始仲裁,连续输出显性电平最多的单元获得发送权,具有相同ID 的遥控帧或数据帧竞争总线时,RTR为显性位(数据帧)的具有发送权

2、在Linux之下,很幸运,我们能够像实现网络编程一样的实现CAN的编程,采用socket方式建立连接,代码如下。

   重要头文件:linu/can.h     sys/socket.h

  重要结构体:struct ifreq ifr;

  struct can_frame {    //CAN一帧数据
        canid_t can_id;//CAN 标识符,标准帧低11位,扩展帧0~28位,高三位是帧的标识位,默认标准帧
        __u8 can_dlc;//数据场的长度
        __u8 data[8];//数据
    };
    struct sockaddr_can addr;   //CAN地址

  struct can_filter {   //过滤器
        canid_t can_id;    //ID
        canid_t can_mask;    //掩码
    };

   过滤规则:如果接收到的数据帧的can_id  & mask == can_id & mask则接收该帧,否则不接收。

     (1)初始化,通过system()调用系统命令可以对CAN口设置波特率,输入为CAN口的号数和波特率,输出为CAN文件描述符,

  int CanInit(unsigned int id, unsigned int baud)
{
    int can_fd;
    int ret;
    char dev[8] = {0};
    char cmd[128] = {0};
    struct sockaddr_can addr = {0};
    struct ifreq ifr = {0};
    
    sprintf(dev, "can%d", id);
    printf("can dev : %s \n", dev);
    //关闭can设备
    sprintf(cmd, "ifconfig %s down", dev);
    if(system(cmd) < 0){
        printf("can device shut down failed  \n");
        return -1;
    }
    
    //设置can设备波特率
    bzero(cmd, sizeof(cmd));
    sprintf(cmd, "ip link set %s type can bitrate %d ", dev, baud);
    if(system(cmd) < 0){
        printf("set can device baud rate failed  \n");
        return -1;
    }
    
    //打开can设备
    bzero(cmd, sizeof(cmd));
    sprintf(cmd, "ifconfig %s up", dev);
    if(system(cmd) < 0){
        printf("can device open failed  \n");
        return -1;
    }
    
    //创建套接字
    can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if(can_fd < 0){
        perror("can socket");
        return -1;
    }
    strncpy(ifr.ifr_name, dev, strlen(dev));
    printf("can_fd : %d\n",can_fd);
    
    ret = ioctl(can_fd, SIOCGIFINDEX, &ifr); //指定 can 设备
    if(ret < 0){
        perror("ioctl");
        return -1;
    }
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    ret = bind(can_fd, (struct sockaddr *)&addr, sizeof(addr));//将套接字与 can0 绑定
    if(ret < 0){
        perror("bind");
        return -1;
    }
    
    return can_fd;
}

(2)CAN发送数据,该进程只发送报文,不接收报文

  int  main()

{

int s, nbytes;
    struct can_frame frame[2] = {0};
    int loopback = 0;
    
    
    //生成两个报文
    frame[0].can_id  = 0x10118AF6;
    //frame[0].can_id  = 0x11;
    frame[0].can_dlc = 6;
    frame[0].data[0] = 0x32;
    frame[0].data[1] = 0x33;
    frame[0].data[2] = 0x34;
    frame[0].data[3] = 0x35;
    frame[0].data[4] = 0x36;
    frame[0].data[5] = 0x37;
    frame[1].can_id  = 0x1012F68A;
    frame[1].can_dlc = 4;
    frame[1].data[0] = 0x56;
    frame[1].data[1] = 0x57;
    frame[1].data[2] = 0x58;
    frame[1].data[3] = 0x59;
    
    
    s = CanInit(1, 500000);
    if(s < 0){
        printf("CanInit failed \n");
        sleep(1);
        close(s);
        //continue;
        return -1;
    }
    printf("CanInit success \n");
    
    //禁用过滤规则,本进程不接收报文,只负责发送
    setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
    //设置回环测试
    setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
                    &loopback, sizeof(loopback));


    //循环发送两个报文
    while(1)
    {
        nbytes = write(s, &frame[0], sizeof(frame[0])); //发送 frame[0]
        if(nbytes != sizeof(frame[0]))
        {
            perror("write frame");
            printf("Send Error frame[0]\n");
            close(s);
            break; //发送错误,退出
        }
        printf("send id: %#x,success \n",frame[0].can_id);
        sleep(2);
        
        nbytes = write(s, &frame[1], sizeof(frame[1])); //发送 frame[1]
        if(nbytes != sizeof(frame[0]))
        {
            printf("Send Error frame[1]\n!");
            close(s);
            break;
        }
        printf("send id: %#x,success \n",frame[1].can_id);
        sleep(2);
    }

    close(s);
    
    return 0;

}

 

(3)CAN接收报文,采用select方式,只接收上面进程发出的0x10118AF6, 即ID为0x10118AF6

int main()
{
    int s, nbytes;
    int maxfd;
    struct can_frame frame;
    struct can_filter rfilter = {0};
    int     i;
    int     ret;
    int     err_num = 0;
    int     loopback = 0;
    fd_set  readfs;
    struct     timeval timeout={1,0}; 

    s = CanInit(1, 500000);
    if(s < 0){
        printf("CanInit failed \n");
        return -1;
    }
    printf("CanInit success \n");
    
    //定义接收规则,只接收标识符等于   的报文
    rfilter.can_id = 0x8AF6;
    rfilter.can_mask = 0xffff;
    
    //设置过滤规则
    setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
    //设置回环测试
    setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &loopback, sizeof(loopback));
    maxfd = s+1;
    while(1)
    {
        FD_ZERO(&readfs);
        FD_SET(s, &readfs);
        
        ret = select(maxfd, &readfs, NULL, NULL, &timeout);
        if(ret < 0)
            return -1;
        if(0==ret){
            continue;
        }
        
        if(FD_ISSET(s, &readfs))
        {
            nbytes = read(s, &frame, sizeof(frame)); //接收报文,
            //显示报文
            if(sizeof(frame) == nbytes)
            {
                err_num = 0;
                printf("ID=0x%X DLC=%d ", frame.can_id, frame.can_dlc);
                for(i=0; i<frame.can_dlc; i++)
                        printf("%#x  ",frame.data[i]);
                printf("\n");
                
            }else{

               //此为容错机制,最多运行连续错误三次
                err_num++;
                if(3 == err_num){
                    printf("system errno \n");
                    break;
                }
                perror("read error");
                continue;
            }
        }
    }
    close(s);
    return 0;
    
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值