在Linux C CAN 通信开发过程中使用的技术的思路和 TCP socket是一脉相承的。在开始CAN通信开发之前我们先学习一下CAN 通信的相关理论CAN通信的相关理论在大学里一般是没有讲的这里我讲的也不是很详尽想深入学习的人员可以在网上搜索资料。但是我不建议使用AI在理论学习阶段自己到网站上学习可以保证准确率换句话说现在的AI工具有很大成分在不懂装懂很可能给你提供错误的信息。什么是CANCANController Area Network控制器局域网是一种多主、广播、差分串行总线核心优势是非破坏性仲裁、高抗干扰、自动错误处理广泛用于汽车、工业、BMS、机器人等实时控制场景。CAN 网络拓扑与硬件组成CAN 是线性总线拓扑所有节点并联在一对双绞线上CAN_H、CAN_L总线两端必须接 120Ω 终端电阻阻抗匹配、防信号反射。单节点硬件结构MCU/CPU/DSP应用层处理、数据收发指令CAN 控制器帧封装 / 解包、仲裁、CRC、时序同步CAN 收发器电平转换TTL 差分信号总线CAN_H、CAN_L 双绞线前两个在CPU内部CAN收发器在开发板上的模块CAN总线以“压差大小”区分逻辑CAN总线通过CAN_H和CAN_L两根线之间的电压差值Vdiff 大小来区分逻辑0和逻辑1。逻辑0显性电平 Dominant当CAN_H为3.5VCAN_L为1.5V时差分电压约为2V。标准规定差分电压高于0.9V即被认为是显性电平。逻辑1隐性电平 Recessive当CAN_H和CAN_L均为2.5V时差分电压约为0V。标准规定差分电压低于0.5V即被认为是隐性电平。看到CAN的信号电平是不是感觉跟RS485很像在此扩展总结一下。CAN和RS485都是通过差分信号两根线之间的电压差来传输数据的但两者在具体的逻辑电平定义上存在重要差异。RS485总线以“电压极性”区分逻辑RS485通过A和B两根线之间的电压极性电压差的正负来判断逻辑0和逻辑1RS485标准中发送器Driver 和接收器Receiver 对同一电压差如AB的逻辑判定是相反的。这在实际工程应用中容易混淆需要特别注意。角色 发送器 (Driver)判断依据 A与B的电压差 (V_AB)逻辑1 A B 电压差2V ~ 6V逻辑0 A B 电压差-2V ~ -6V角色 接收器 (Receiver)判断依据 A与B的电压差 (V_AB)逻辑1 A B 电压差 200mV逻辑0 A B 电压差 -200mVTTL和RS232则属于单端信号Single-ended Signal。这两种信号的根本区别在于参考地单端信号的电压是相对于公共地GND 测量的而差分信号是相对于另一根信号线测量的。*各种通信信号电压不一样接线接错了直接烧模块如果是TTL接错了直接烧CPUCAN相关的理论只介绍这么多这也只是入门用来开发个程序通过CAN读取传感器数据控制电机板间通信够了如果想深入研究CAN协议那够你研究个2,3年。嗯...研究生2年里研究这个课题够了。我的这个课程主要目标是培训开发能力3-6个月研究哪个协议都不够。在Linux开发板上进行CAN通信分两步第一步硬件接线第二步使用libsocketcan库编程。CAN在接线上跟RS485也很像RS485里用A/B区分两根线CAN用L/H区分接线也是L对LH对H。编程中使用libsocketcan这个动态库跟modbus的库也很像稍有区别。板子线接好后我们来写两个程序通信像RS485的实验一样一个发数据一个收数据收数据使用epoll。在开始编程前我们需要确认开发板的CAN设备使用 ifconfig -a 命令列出所有网络设备。awlink0 和 awlink1 就是我们开发版上的两个CAN设备这是全志在CAN驱动中生成的设备名多数芯片厂通常都是 can0、can1这里注意下就行名字别用错了就可以。使能CAN 的命令可以在命令行中输入也可以在代码中使用 systemd 函数命令如下ip link set awlink0 type can bitrate 500000ip link set awlink0 up注意⚠️两个CAN 的波特率必须一致才能通信我们创建一个C工程命名 cpp_cansend,添加一个类用于控制CAN通信Class_CAN这个工程实现CAN发送数据让AI 帮我们写个框架代码然后修正提示词Linux C 使用 libsocketcan库编程实现数据收发包括初始化CAN和过滤代码简洁不要用lamdafor循环用展开的方式通俗易懂。整理代码class_can.h#ifndef CLASS_CAN_H #define CLASS_CAN_H #include string #include cstring #include unistd.h #include sys/socket.h #include net/if.h #include sys/ioctl.h #include linux/can.h #include linux/can/raw.h using namespace std; class Class_CAN { public: Class_CAN(); /* * 初始化CAN socket * param ifname 设备名如 can0 * return socket文件描述符失败返回-1 */ int can_init(const std::string ifname); /* * 设置CAN接收过滤器只接收指定ID的帧 * param sock socket文件描述符 * param can_id 要接收的CAN ID * param can_mask ID掩码0x7FF表示标准帧0x1FFFFFFF表示扩展帧 * return 成功返回0失败返回-1 */ int can_set_filter(uint32_t can_id, uint32_t can_mask); /* * 发送CAN帧 * param sock socket文件描述符 * param id CAN ID * param data 数据指针 * param len 数据长度最大8字节 * return 成功返回发送字节数失败返回-1 */ int can_send(uint32_t id, const uint8_t* data, uint8_t len); void stopcan(); }; #endif // CLASS_CAN_Hclass_can.cpp#include class_can.h #include iostream using namespace std; static int sock -1; Class_CAN::Class_CAN() {} int Class_CAN::can_init(const std::string ifname) { // 1. 创建原始CAN socket sock socket(PF_CAN, SOCK_RAW, CAN_RAW); if (sock 0) { perror(socket); return -1; } // 2. 绑定到指定CAN设备 struct sockaddr_can addr; struct ifreq ifr; strncpy(ifr.ifr_name, ifname.c_str(),ifname.length ()); if (ioctl(sock, SIOCGIFINDEX, ifr) 0) { perror(ioctl); close(sock); return -1; } // addr.can_id 0x55; addr.can_family AF_CAN; addr.can_ifindex ifr.ifr_ifindex; if (bind(sock, (struct sockaddr*)addr, sizeof(addr)) 0) { perror(bind); close(sock); return -1; } else{ coutinit ifname OK endl; } return sock; } int Class_CAN::can_set_filter(uint32_t can_id, uint32_t can_mask) { struct can_filter filter; filter.can_id can_id; filter.can_mask can_mask; if (setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FILTER, filter, sizeof(filter)) 0) { perror(setsockopt filter); return -1; } return 1; } int Class_CAN::can_send(uint32_t id, const uint8_t* data, uint8_t len) { struct can_frame frame; frame.can_id id; frame.can_dlc len; std::memset(frame.data, 0, sizeof(frame.data)); std::memcpy(frame.data, data, len); ssize_t nbytes write(sock, frame, sizeof(frame)); if (nbytes ! sizeof(frame)) { perror(write); return -1; } else { coutsend num: len : endl; for (int i 0; i frame.can_dlc; i) { printf(%02X , frame.data[i]); } printf(\n); } return nbytes; } void Class_CAN::stopcan() { close (sock); }然后新建一个文件夹 cpp_canread,拷贝文件然后使用QT creator 打开这个项目修改代码cmakelists中的项目名改一下都得改改成 cpp_canread保存修改工程名自己就变了。整理代码class_can.h#ifndef CLASS_CAN_H #define CLASS_CAN_H #include string #include cstring #include unistd.h #include sys/socket.h #include net/if.h #include sys/ioctl.h #include linux/can.h #include linux/can/raw.h using namespace std; class Class_CAN { public: Class_CAN(); /* * 初始化CAN socket * param ifname 设备名如 can0 * return socket文件描述符失败返回-1 */ int can_init(const std::string ifname); /* * 设置CAN接收过滤器只接收指定ID的帧 * param sock socket文件描述符 * param can_id 要接收的CAN ID * param can_mask ID掩码0x7FF表示标准帧0x1FFFFFFF表示扩展帧 * return 成功返回0失败返回-1 */ int can_set_filter(uint32_t can_id, uint32_t can_mask); /* * 发送CAN帧 * param sock socket文件描述符 * param id CAN ID * param data 数据指针 * param len 数据长度最大8字节 * return 成功返回发送字节数失败返回-1 */ int can_send(uint32_t id, const uint8_t* data, uint8_t len); void stopcan(); void can_read(); }; #endif // CLASS_CAN_Hclass_can.cpp#include class_can.h #include iostream #include sys/epoll.h #include fcntl.h using namespace std; static int sock -1; static int running 1; Class_CAN::Class_CAN() {} int Class_CAN::can_init(const std::string ifname) { // 1. 创建原始CAN socket sock socket(PF_CAN, SOCK_RAW, CAN_RAW); if (sock 0) { perror(socket); return -1; } // 2. 绑定到指定CAN设备 struct sockaddr_can addr; struct ifreq ifr; strncpy(ifr.ifr_name, ifname.c_str(),ifname.length ()); if (ioctl(sock, SIOCGIFINDEX, ifr) 0) { perror(ioctl); close(sock); return -1; } // addr.can_id 0x55; addr.can_family AF_CAN; addr.can_ifindex ifr.ifr_ifindex; if (bind(sock, (struct sockaddr*)addr, sizeof(addr)) 0) { perror(bind); close(sock); return -1; } else{ coutinit ifname OK endl; } // 4. 设置为非阻塞epoll 需要 int flags fcntl(sock, F_GETFL, 0); if (flags 0) { perror(fcntl F_GETFL); close(sock); return -1; } if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) 0) { perror(fcntl F_SETFL); close(sock); return -1; } return sock; } int Class_CAN::can_set_filter(uint32_t can_id, uint32_t can_mask) { struct can_filter filter; filter.can_id can_id; filter.can_mask can_mask; if (setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FILTER, filter, sizeof(filter)) 0) { perror(setsockopt filter); return -1; } return 1; } int Class_CAN::can_send(uint32_t id, const uint8_t* data, uint8_t len) { struct can_frame frame; frame.can_id id; frame.can_dlc len; std::memset(frame.data, 0, sizeof(frame.data)); std::memcpy(frame.data, data, len); ssize_t nbytes write(sock, frame, sizeof(frame)); if (nbytes ! sizeof(frame)) { perror(write); return -1; } else { coutsend num: nbytes : data endl; } return nbytes; } void Class_CAN::stopcan() { running -1; } void Class_CAN::can_read() { // 创建 epoll 实例并添加 socket int epfd epoll_create1(0); if (epfd 0) { perror(epoll_create1); close(sock); return ; } struct epoll_event ev; ev.events EPOLLIN; // 监听可读事件 ev.data.fd sock; if (epoll_ctl(epfd, EPOLL_CTL_ADD, sock, ev) 0) { perror(epoll_ctl); close(epfd); close(sock); return; } struct epoll_event events[1]; // 只关注一个 fd int timeout_ms 5; while (running 0) { int nfds epoll_wait(epfd, events, 1, timeout_ms); if(nfds 0) break; else if(nfds 0) continue; else{ if (events[0].events EPOLLIN) { struct can_frame frame; ssize_t nbytes read(sock, frame, sizeof(frame)); if (nbytes sizeof(frame)) { std::cout Received: ID0x std::hex frame.can_id std::dec , len (int)frame.can_dlc , data; for (int i 0; i frame.can_dlc; i) { printf(%02X , frame.data[i]); } printf(\n); } else if (nbytes 0) { perror(read); } else { std::cerr Incomplete frame std::endl; } } } } close(epfd); close(sock); }上传程序到开发板运行效果