linux can使用

117 阅读7分钟
  • CAN(Controller Area Network)​​ 是一种用于实时应用的串行通信协议,具有高可靠性、抗干扰能力强等特点。 主要用于汽车内部电子控制单元(ECU)之间的通信。
  • Linux 内核从 2.6.25 版本开始引入了 ​​SocketCAN​​ 子系统,它将 CAN 设备抽象成网络设备,使得我们可以使用类似网络套接字的 API 来操作 CAN 总线。

can 启动

不同工控机 can 启动方式会不同,这个需要参考厂家提供的手册。
以下列出通用的启动方式:


# 关闭
sudo ip link set down can0

# 设置 can0 的比特率为 500000
sudo ip link set can0 type can bitrate 500000

# 启动
sudo ip link set up can0

虚拟 CAN 接口(vcan)

如果没有物理 CAN 设备,可以用虚拟接口进行测试:


# 加载 vcan 内核模块
sudo modprobe vcan

# 创建虚拟 CAN 接口
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

注意,如果使用docker容器可能没有对应的内核模块,虚拟机(如 vmware) 一般是有的。

检查 CAN 接口

  • 通过 ifconfig -a 检查can接口:
    • ifconfig -a : 显示所有网络设备;
    • ifconfig -a [devname] : 显示指定设备;
ifconfig -a vcan0
vcan0     Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
          UP RUNNING NOARP  MTU:16  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

can工具 can-utils

  • 安装(ubuntu) : sudo apt-get install can-utils
  • 终端常用指令工具 candump , cansend
    • candump:监听并显示 CAN 总线上的数据。
    • cansend:向 CAN 总线发送一帧数据。
  • 直接输入 candump , cansend 可显示帮助描述。

candump 常用组合指令

描述指令
查看所有数据candump canX
查看指定帧0x181数据candump canX,181:7FF
查看指定帧0x181数据,并显示时间戳candump canX,181:7FF -ta
查看指定帧0x181,0x281数据candump canX,181:7FF,281:7FF
查看CANopen节点为1 的 txpdo 所有数据candump canX,081:0ff
查看CANopen节点为1 的 rxpdo 所有数据candump canX,001:0ff
  • canX: 这是一个占位符,表示CAN接口名称(例如 can0, can1 或虚拟接口 vcan0)。
  • 过滤语法(如 181:7FF)用于指定CAN ID和掩码,以监听特定帧。
  • 选项 -ta 表示添加时间戳(ASCII格式)。

candump 示例

candump can42

# 输出:
  can42  005   [4]  07 8F DF 75
  can42  006   [2]  68 E6
  can42  181   [6]  00 02 00 01 FF FE
  can42  281   [6]  00 01 00 03 00 00
  can42  381   [8]  49 03 01 05 00 07 69 20
  can42  005   [4]  07 8F DF 8E
  can42  006   [2]  68 E7
  can42  181   [6]  FF FF 00 03 FF F9
  can42  281   [6]  FF FF 00 00 00 00
  can42  381   [8]  49 03 01 05 00 07 69 20
  

candump can42,181:7FF

# 输出:
  can42  181   [6]  00 01 FF FE FF FD
  can42  181   [6]  FF FF FF FF 00 01
  can42  181   [6]  FF F9 00 02 FF FD
  can42  181   [6]  FF FA 00 01 FF FD
  can42  181   [6]  FF FD 00 03 FF FF
  can42  181   [6]  00 01 00 04 FF FF
  

candump can42,181:7FF -ta

# 输出:
 (1757998968.502590)  can42  181   [6]  00 02 00 06 00 03
 (1757998968.505086)  can42  181   [6]  00 02 00 03 FF FF
 (1757998968.507657)  can42  181   [6]  00 04 00 03 FF FD
 (1757998968.510075)  can42  181   [6]  00 02 00 01 FF FD
 (1757998968.512557)  can42  181   [6]  00 01 00 06 FF FC
 (1757998968.515106)  can42  181   [6]  00 00 00 02 00 01
 (1757998968.517700)  can42  181   [6]  00 01 00 06 FF FB

candump can42,081:0FF

# 输出:
  can42  381   [8]  49 23 01 0C FF F3 69 0A
  can42  181   [6]  FF FE 00 04 FF FE
  can42  281   [6]  00 02 00 03 FF FF
  can42  381   [8]  49 23 01 0C FF F3 69 0A
  can42  181   [6]  00 03 00 01 FF FC
  can42  281   [6]  00 00 00 02 00 02
  can42  381   [8]  49 23 01 0C FF F3 69 0A
  can42  181   [6]  00 00 00 04 FF FD

cansend 常用组合指令

描述指令
启动CANopen 总线cansend canX 000#0100
发送0x201数据cansend canX 201#08000000
发送远程帧0x701数据cansend canX 701#R
  • 其他复杂指令(如CANopen sdo 读写),还是使用 CANopenNode 之类的工具,或python脚本。

c++ 代码示例

  • 正常按 socket 对象读写就可以。
#pragma once

#include <errno.h>
#include <fcntl.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>

#include <algorithm>
#include <cstring>
#include <memory>
#include <string>
#include <vector>

class LinuxCAN {
  public:
  LinuxCAN(const std::string& name = "can0") : name_(name) {}
  ~LinuxCAN() { this->Close(); }

  public:
  bool Open() {
    // 0.close previous
    this->Close();

    struct sockaddr_can addr;
    struct ifreq ifr;
    int ret = 0;

    // 1.Create socket
    fd_ = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (fd_ < 0) {
      printf("socket PF_CAN failed .\n");
      return false;
    }
    // 2.Specify can channel
    strcpy(ifr.ifr_name, name_.c_str());

    ret = ioctl(fd_, SIOCGIFINDEX, &ifr);
    if (ret < 0) {
      printf("ioctl SIOCGIFINDEX failed \n");
      return false;
    }

    // 3.Bind the socket to can
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    ret = bind(fd_, (struct sockaddr*)&addr, sizeof(addr));
    if (ret < 0) {
      printf("bind failed \n");
      return false;
    }

    ///* 数据过滤配置参数 */
    // const int size = this_config.filter_size;
    // struct can_filter rfilter[20];

    // /* 数据加入过滤器 */
    // for (int i = 0; i < size; i++) {
    //   rfilter[i].can_id = this_config.rfilter[i];
    //   rfilter[i].can_mask = CAN_SFF_MASK;
    // }
    // setsockopt(fd_, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter,sizeof(rfilter));
  }

  bool Close() {
    if (fd_ >= 0) {
      close(fd_);
      fd_ = -1;
    }
    return true;
  }

  public:
  uint32_t Mask(uint32_t id, bool SFF = true, bool EFF = false) {
    uint32_t ret = id;
    if (EFF) {
      ret &= CAN_EFF_MASK;
    }
    if (SFF) {
      ret &= CAN_SFF_MASK;
    }
    return ret;
  }

  /// blocking , return 0 if success
  int RecvOnce(uint32_t& id, std::vector<uint8_t>& data) {
    if (fd_ < 0) {
      printf("error : CAN not open \n");
      return -1;
    }

    can_frame fr;
    int nbytes;
    nbytes = read(fd_, &fr, sizeof(can_frame));

    if (nbytes < 0) {
      printf("error : CAN read error : %d , restart... \n", errno);
      return -1;
    } else {
      id = fr.can_id;

      data.resize(fr.can_dlc);
      for (int i = 0; i < fr.can_dlc; i++) {
        data[i] = fr.data[i];
      }
    }
    return 0;
  }

  /// blocking , return 0 if success
  int SendOnce(uint32_t id, const std::vector<uint8_t>& data) {
    if (fd_ < 0) {
      printf("error : CAN not open \n");
      return -1;
    }

    int nbytes;
    struct can_frame fr;
    memset(&fr, 0, sizeof(fr));

    fr.can_id = id;
    fr.can_dlc = std::min(data.size(), (size_t)8);

    std::memcpy(fr.data, data.data(), fr.can_dlc);

    nbytes = write(fd_, &fr, sizeof(fr));

    if (nbytes < 0) {
      printf("error : CAN send error : %d , restart... \n", errno);
      return -1;
    }

    return 0;
  }

  private:
  std::string name_;
  int fd_ = -1;
};

实际使用遇到的问题

  • 有的工控机can设备运行一段时间会报错,无法再收发数据 (可能是总线有干扰);
    • 解决:通过 ip link 重启can设备;
  • No buffer space available 报错;
    • 解决:由于缓冲队列空间不足,设置缓冲队列 :echo 4096 > /sys/class/net/can0/tx_queue_len
    • 有的工控机 上述操作不管用,最后还是需要运行过程检查can设备状态并重启;
  • can总线上有多个波特率设备,或can总线没接好,无法收到数据;
    • 解决:使用can卡检查can总线状态,排查问题点,检查哪个can设备有问题;
  • 有的工控机can设备运行一段时间会报错,且无法通过 ip link 重启can设备,或重启后依旧有问题,必须重启工控机才能使用;
    • 解决:工控机固件驱动的问题,需要联系工控机厂家去处理更新固件;

参考