当前位置: 代码网 > 服务器>服务器>Linux > 在Linux中配置和使用CAN通信的详细指南

在Linux中配置和使用CAN通信的详细指南

2025年08月03日 Linux 我要评论
引言can(controller area network)是一种广泛用于嵌入式系统、汽车和工业控制中的通信协议。linux 支持 can 协议栈,并通过 socketcan 实现对 can 总线的访

引言

can(controller area network)是一种广泛用于嵌入式系统、汽车和工业控制中的通信协议。linux 支持 can 协议栈,并通过 socketcan 实现对 can 总线的访问。在这篇博客中,我们将深入讲解如何在 linux 系统中配置和使用 can 通信,详细介绍配置环境、测试案例、代码实现以及如何使用 can-utils 工具和自定义代码进行测试。

本文内容

  • 环境配置:包括有外设和没有外设两种情况。
  • 测试案例:如何使用 can-utils 和自定义代码测试 can 通信。
  • 代码实现:编写一个高效且线程安全的 can 通信代码,并详细注释每一部分。
  • 调试和测试:如何进行调试以及常见问题的解决方法。

1. 环境配置

1.1 安装和配置必备工具

在 linux 系统上使用 can 通信,首先需要安装一些必备的工具和库:

  • socketcan 驱动程序:这是 linux 内核中实现 can 协议栈的模块,通常在大多数 linux 发行版中已经默认启用。
  • can-utils 工具:一个用于测试和调试 can 总线通信的工具集。
  • 编译器和开发工具:用于编译 c++ 代码的工具。

安装依赖

首先,确保你安装了所需的开发工具和库:

sudo apt update
sudo apt install build-essential
sudo apt install can-utils  # 安装 can-utils 工具包
sudo apt install libsocketcan-dev  # 如果需要安装 socketcan 开发库

can-utils 包含多个实用工具,例如 cansendcandump,可以用于测试 can 总线的发送和接收。

1.2 配置虚拟 can 接口(没有外设的情况)

如果你没有物理 can 接口设备(如 usb-to-can 适配器),你可以使用虚拟 can 接口 vcan0 来进行测试。虚拟接口适用于不需要实际硬件的 can 总线仿真和开发。

启用虚拟 can 接口

加载 vcan 驱动模块

sudo modprobe vcan 

创建虚拟 can 接口 vcan0

sudo ip link add dev vcan0 type vcan
sudo ip link set vcan0 up

测试虚拟接口

使用 can-utils 工具测试虚拟 can 接口:

发送一个 can 帧:

cansend vcan0 123#deadbeef 

查看接收到的 can 数据:

candump vcan0 

这样,你就可以在没有实际硬件的情况下仿真 can 总线通信,进行开发和测试。

1.3 配置物理 can 接口(有外设的情况)

如果你有物理 can 外设(如 usb-to-can 适配器),你需要配置物理接口。

检查 can 适配器:首先,检查系统是否识别到了 can 适配器,运行以下命令:

ip link show 

你应该看到类似 can0can1 的接口。如果没有,请插入设备并确认驱动已加载。

启用物理 can 接口

假设你的物理接口为 can0,你可以通过以下命令启用接口,并设置传输速率(例如 500 kbps):

sudo ip link set can0 up type can bitrate 500000 

测试物理接口:同样,使用 can-utils 发送和接收数据:

发送数据:

cansend can0 123#deadbeef

查看数据:

candump can0 

现在,你已经成功配置了 can 环境,无论是通过虚拟接口进行仿真,还是通过物理接口进行实际通信。

2. 测试案例

2.1 使用 can-utils 工具测试

can-utils 提供了一些常用的命令行工具,可以快速地测试 can 总线的发送和接收。

cansend:用于向 can 总线发送数据。

发送一个数据帧:

cansend vcan0 123#deadbeef 

这会向 vcan0 接口发送一个带有 id 为 0x123,数据为 deadbeef 的 can 帧。

candump:用于查看 can 总线上的数据。

查看所有 can 总线接口的数据:

candump vcan0 

你将看到类似下面的输出,显示收到的数据帧:

vcan0 123 [4] dead 

canplayer:用于回放保存的 can 数据文件。

回放一个 can 数据文件:

canplayer -i can_logfile.log 

这个工具在处理实际的 can 数据日志时非常有用。

2.2 使用代码测试

代码测试:发送和接收 can 数据

我们将编写一个简单的代码示例,用于发送和接收 can 帧。

创建线程池:我们将使用线程池来处理高并发的 can 数据接收。

can 通信类:负责与 can 总线进行交互。

main 函数:启动接收线程并发送数据。

#include <iostream>              // 包含输入输出流,用于打印日志或调试信息
#include <string>                // 包含 string 类的定义,用于字符串操作
#include <cstring>               // 包含 c 风格字符串操作函数的定义
#include <unistd.h>              // 包含 posix 系统调用,例如 close, read, write
#include <net/if.h>              // 包含网络接口相关的定义
#include <sys/ioctl.h>           // 包含 i/o 控制相关的函数定义,例如 siocgifindex
#include <fcntl.h>               // 包含文件控制相关的定义
#include <linux/can.h>           // 包含 can 协议相关的定义
#include <linux/can/raw.h>       // 包含原始 can 套接字定义
#include <sys/socket.h>          // 包含 socket 套接字的相关定义
#include <thread>                // 包含多线程支持的定义
#include <atomic>                // 包含原子操作的定义,用于线程安全
#include <mutex>                 // 包含互斥量的定义,用于线程同步
#include <vector>                // 包含 vector 容器的定义
#include <queue>                 // 包含队列容器的定义
#include <functional>            // 包含函数对象的定义,用于队列任务
#include <condition_variable>    // 包含条件变量定义,用于线程同步
#include <chrono>                // 包含时间相关定义,用于控制线程等待时间
#include <iostream>              // 包含 i/o 相关功能
 
// 线程池类,用于管理多个线程,执行异步任务
class threadpool {
public:
    // 构造函数,初始化线程池,启动 numthreads 个线程
    threadpool(size_t numthreads) : stop(false) {
        // 创建并启动工作线程
        for (size_t i = 0; i < numthreads; ++i) {
            workers.push_back(std::thread([this]() { workerloop(); }));
        }
    }
 
    // 析构函数,停止线程池中的所有线程
    ~threadpool() {
        stop = true;             // 设置停止标志
        condvar.notify_all();    // 通知所有线程退出
        for (std::thread &worker : workers) {
            worker.join();        // 等待所有线程结束
        }
    }
 
    // 向线程池队列中添加一个任务
    void enqueue(std::function<void()> task) {
        {
            std::lock_guard<std::mutex> lock(queuemutex);  // 锁住队列,避免多线程访问冲突
            tasks.push(task);  // 将任务放入队列
        }
        condvar.notify_one();  // 唤醒一个等待的线程
    }
 
private:
    // 线程池中的工作线程函数
    void workerloop() {
        while (!stop) {  // 当 stop 为 false 时,线程继续工作
            std::function<void()> task;  // 定义一个任务对象
            {
                // 锁住队列,线程安全地访问任务队列
                std::unique_lock<std::mutex> lock(queuemutex);
                condvar.wait(lock, [this]() { return stop || !tasks.empty(); });  // 等待任务或停止信号
 
                // 如果 stop 为 true 且队列为空,退出循环
                if (stop && tasks.empty()) {
                    return;
                }
 
                task = tasks.front();  // 获取队列中的第一个任务
                tasks.pop();  // 从队列中移除该任务
            }
 
            task();  // 执行任务
        }
    }
 
    std::vector<std::thread> workers;           // 线程池中的所有线程
    std::queue<std::function<void()>> tasks;    // 任务队列,存储待处理的任务
    std::mutex queuemutex;                      // 互斥锁,用于保护任务队列
    std::condition_variable condvar;            // 条件变量,用于通知线程执行任务
    std::atomic<bool> stop;                     // 原子变量,用于控制线程池的停止
};
 
// can 通信类,用于发送和接收 can 消息
class cancommunication {
public:
    // 构造函数,初始化 can 通信
    cancommunication(const std::string &interfacename) : stopreceiving(false) {
        sock = socket(pf_can, sock_raw, can_raw);  // 创建原始 can 套接字
        if (sock < 0) {  // 如果套接字创建失败,输出错误并退出
            perror("error while opening socket");
            exit(exit_failure);
        }
 
        struct ifreq ifr;  // 网络接口请求结构体
        strncpy(ifr.ifr_name, interfacename.c_str(), sizeof(ifr.ifr_name) - 1);  // 设置接口名
        ioctl(sock, siocgifindex, &ifr);  // 获取网络接口的索引
 
        struct sockaddr_can addr;  // can 地址结构体
        addr.can_family = af_can;  // 设置地址族为 can
        addr.can_ifindex = ifr.ifr_ifindex;  // 设置接口索引
 
        if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {  // 绑定套接字到指定的 can 接口
            perror("error while binding socket");
            exit(exit_failure);
        }
    }
 
    // 析构函数,关闭 can 套接字
    ~cancommunication() {
        if (sock >= 0) {
            close(sock);  // 关闭套接字
        }
    }
 
    // 发送 can 消息
    void sendcanmessage(const can_frame &frame) {
        if (write(sock, &frame, sizeof(frame)) != sizeof(frame)) {  // 写入套接字发送数据
            perror("error while sending can message");
        }
    }
 
    // 接收 can 消息
    void receivecanmessages(threadpool &threadpool) {
        while (!stopreceiving) {  // 如果没有接收停止信号,继续接收数据
            can_frame frame;  // 定义一个 can 帧
            int nbytes = read(sock, &frame, sizeof(frame));  // 从套接字中读取数据
            if (nbytes < 0) {  // 如果读取失败,输出错误信息
                perror("error while receiving can message");
                continue;
            }
 
            // 将解析任务提交到线程池
            threadpool.enqueue([this, frame]() {
                this->parsecanmessage(frame);  // 解析 can 消息
            });
        }
    }
 
    // 停止接收数据
    void stopreceivingdata() {
        stopreceiving = true;  // 设置停止接收标志
    }
 
private:
    int sock;  // 套接字描述符
    std::atomic<bool> stopreceiving;  // 原子标志,表示是否停止接收数据
    std::mutex parsemutex;  // 解析数据时的互斥锁
 
    // 解析 can 消息
    void parsecanmessage(const can_frame &frame) {
        std::lock_guard<std::mutex> lock(parsemutex);  // 锁住互斥量,确保解析数据时的线程安全
        std::cout << "received can id: " << frame.can_id << std::endl;  // 打印 can id
        std::cout << "data: ";
        for (int i = 0; i < frame.can_dlc; ++i) {  // 遍历 can 数据字节
            std::cout << std::hex << (int)frame.data[i] << " ";  // 打印每个字节的十六进制表示
        }
        std::cout << std::endl;
    }
};
 
// 主函数
int main() {
    threadpool threadpool(4);  // 创建一个有 4 个线程的线程池
    cancommunication cancomm("vcan0");  // 创建一个 cancommunication 对象,使用虚拟 can 接口 "vcan0"
    
    // 启动一个线程来接收 can 消息
    std::thread receiverthread(&cancommunication::receivecanmessages, &cancomm, std::ref(threadpool));
 
    // 创建并发送一个 can 消息
    can_frame sendframe;
    sendframe.can_id = 0x123;  // 设置 can id 为 0x123
    sendframe.can_dlc = 8;  // 设置数据长度为 8 字节
    for (int i = 0; i < 8; ++i) {
        sendframe.data[i] = i;  // 填充数据
    }
 
    cancomm.sendcanmessage(sendframe);  // 发送 can 消息
 
    std::this_thread::sleep_for(std::chrono::seconds(5));  // 等待 5 秒,以便接收和处理消息
    
    cancomm.stopreceivingdata();  // 停止接收数据
    receiverthread.join();  // 等待接收线程结束
 
    return 0;  // 程序正常退出
}

代码注释总结:

线程池 (threadpool)

  • 提供了一个用于并发执行任务的线程池,通过 enqueue 函数将任务放入队列,工作线程从队列中取出任务执行。
  • 使用 std::mutex 保护任务队列的访问,并使用 std::condition_variable 实现线程间的同步。

can 通信 (cancommunication)

  • 提供了通过套接字进行 can 消息的发送与接收功能。
  • 使用 socket 创建原始 can 套接字,bind 绑定到指定的网络接口。
  • 发送和接收消息时,通过多线程处理接收到的数据,以提高并发性能。

主程序 (main)

  • 创建线程池和 can 通信对象。
  • 启动接收线程并发送测试消息。
  • 主线程等待 5 秒以确保接收到的 can 消息被处理。

这种方式可以在 linux 系统中使用 c++ 进行高效的 can 通信,实现消息的发送与接收,并且利用线程池提高并发性能。

以上就是在linux中配置和使用can通信的详细指南的详细内容,更多关于linux can通信的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com