机器人开发实战:Ubuntu 18.04下ROS Melodic与周立功CAN卡的深度集成指南
当你在机器人项目中第一次尝试让ROS系统与CAN总线设备对话时,很可能会遇到那个令人头疼的libusb_open failed错误。作为曾经花了整整一个周末才解决这个问题的过来人,我决定把完整的解决方案整理成这份避坑指南。
周立功CAN卡作为国内常见的工业级CAN通讯设备,在ROS开发社区中被广泛使用。但在Ubuntu 18.04上配置ROS Melodic环境时,从驱动安装到权限配置的每个环节都可能成为新手开发者的"拦路虎"。本文将带你一步步打通这个技术链路,特别针对那些官方文档没有明确说明的细节问题。
1. 环境准备与驱动安装
在开始之前,请确保你的Ubuntu 18.04系统已经安装了ROS Melodic完整版。如果你还没有安装,可以通过以下命令快速完成:
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 sudo apt update sudo apt install ros-melodic-desktop-full1.1 获取周立功CAN卡驱动
周立功官方提供了多种接口的CAN卡驱动,我们需要根据硬件型号选择对应的版本。对于常见的MiniPCIe和USB接口设备,驱动通常包含以下关键文件:
libusbcan.so:核心驱动库文件controlcan.h:开发头文件test/目录:测试程序源代码
提示:建议从官方网站下载最新驱动,如果使用网盘资源,务必核对文件的MD5校验值,避免版本不一致导致兼容性问题。
1.2 安装系统依赖
周立功CAN卡驱动依赖于libusb-1.0库,在安装驱动前需要确保系统已安装必要的依赖项:
sudo apt update sudo apt install -y libusb-1.0-0 libusb-1.0-0-dev build-essential安装完成后,将驱动包中的libusbcan.so文件复制到系统库目录:
sudo cp libusbcan.so /usr/local/lib/ sudo ldconfig2. 驱动测试与权限问题解决
2.1 编译测试程序
进入驱动包中的test目录,编译测试程序:
cd test make clean make编译成功后,你会看到一个名为test的可执行文件。不带参数运行它将显示使用说明:
./test正常输出应该类似于:
Usage: ./test [devtype] [devind] [chan] [baud] [master] [selfid] [remoteid] [count]2.2 常见错误:USB权限问题
当你尝试实际运行测试程序时,很可能会遇到如下错误:
libusb_open failed: ret=-3这个错误表明当前用户没有权限访问USB设备。我们可以通过以下两种方式解决:
临时解决方案(每次重启后失效):
sudo chmod -R 777 /dev/bus/usb/永久解决方案(推荐):
- 创建udev规则文件:
sudo touch /etc/udev/rules.d/50-usbcan.rules sudo gedit /etc/udev/rules.d/50-usbcan.rules- 在文件中添加以下内容(根据你的设备修改vendor和product ID):
SUBSYSTEMS=="usb", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", GROUP="users", MODE="0666"- 重新加载udev规则并重启服务:
sudo udevadm control --reload sudo service udev restart注意:确认你的设备vendor和product ID是否正确,可以通过
lsusb命令查看已连接的USB设备信息。
2.3 验证驱动正常工作
完成上述配置后,重新插拔CAN卡设备,然后运行测试命令:
./test 4 0 3 0x1400 2 0 3 1000如果一切正常,你应该能看到CAN消息的收发统计信息,这表示驱动已经正确安装并可以正常工作。
3. ROS工作空间配置
3.1 创建ROS功能包
首先创建一个新的ROS工作空间和功能包:
mkdir -p ~/can_ws/src cd ~/can_ws/src catkin_create_pkg ros_can roscpp rospy std_msgs cd ~/can_ws catkin_make source devel/setup.bash3.2 添加CAN消息类型
在ROS中,我们需要定义CAN帧的消息格式。在功能包中创建msg目录并添加Frame.msg文件:
uint32 id uint8 dlc uint8[8] data然后修改package.xml文件,添加消息依赖:
<build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>更新CMakeLists.txt中的相关配置:
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation ) add_message_files( FILES Frame.msg ) generate_messages( DEPENDENCIES std_msgs ) catkin_package( CATKIN_DEPENDS message_runtime roscpp rospy std_msgs )重新编译工作空间:
cd ~/can_ws catkin_make4. ROS与CAN卡的深度集成
4.1 编写CAN节点代码
在src目录下创建can_node.cpp文件,实现基本的CAN消息收发功能。以下是核心代码框架:
#include "ros/ros.h" #include "ros_can/Frame.h" #include "controlcan.h" class CanNode { public: CanNode() { // 初始化CAN设备 if(VCI_OpenDevice(VCI_USBCAN2, 0, 0) != STATUS_OK) { ROS_ERROR("Failed to open CAN device!"); ros::shutdown(); } // 配置CAN参数 VCI_INIT_CONFIG config; config.AccCode = 0x00000000; config.AccMask = 0xFFFFFFFF; config.Filter = 1; config.Mode = 0; config.Timing0 = 0x00; config.Timing1 = 0x1C; if(VCI_InitCAN(VCI_USBCAN2, 0, 0, &config) != STATUS_OK) { ROS_ERROR("Failed to init CAN!"); ros::shutdown(); } // 启动CAN设备 if(VCI_StartCAN(VCI_USBCAN2, 0, 0) != STATUS_OK) { ROS_ERROR("Failed to start CAN!"); ros::shutdown(); } // 创建ROS发布者和订阅者 pub_ = nh_.advertise<ros_can::Frame>("can_rx", 1000); sub_ = nh_.subscribe("can_tx", 1000, &CanNode::canTxCallback, this); } void canTxCallback(const ros_can::Frame::ConstPtr& msg) { // 处理发送CAN消息的逻辑 } void spin() { ros::Rate rate(100); while(ros::ok()) { // 处理接收CAN消息的逻辑 ros::spinOnce(); rate.sleep(); } } private: ros::NodeHandle nh_; ros::Publisher pub_; ros::Subscriber sub_; }; int main(int argc, char** argv) { ros::init(argc, argv, "can_node"); CanNode node; node.spin(); return 0; }4.2 编译与运行
更新CMakeLists.txt添加可执行文件:
add_executable(can_node src/can_node.cpp) target_link_libraries(can_node ${catkin_LIBRARIES})编译并运行节点:
cd ~/can_ws catkin_make rosrun ros_can can_node4.3 调试技巧
当CAN通讯出现问题时,可以按照以下步骤排查:
检查物理连接:
- 确认CAN线缆正确连接
- 检查终端电阻是否安装(通常需要120Ω)
验证驱动状态:
dmesg | grep usb lsmod | grep usb测试底层通讯:
- 使用
candump和cansend工具测试原始CAN通讯 - 安装can-utils工具包:
sudo apt install can-utils
- 使用
ROS层调试:
- 使用
rostopic echo查看CAN消息 - 使用
rqt_graph检查节点连接情况
- 使用
5. 高级配置与性能优化
5.1 多线程处理
对于高频率CAN消息处理,建议使用多线程模型:
#include <thread> void receiveThread() { while(ros::ok()) { // CAN消息接收处理 } } int main(int argc, char** argv) { ros::init(argc, argv, "can_node"); CanNode node; std::thread t(receiveThread); ros::spin(); t.join(); return 0; }5.2 消息过滤与处理
在ROS中实现高效的消息过滤:
void canTxCallback(const ros_can::Frame::ConstPtr& msg) { // 只处理特定ID的消息 if(msg->id == 0x123) { // 处理逻辑 } }5.3 实时性优化
对于实时性要求高的应用,可以考虑以下优化措施:
提高ROS节点的调度优先级:
#include <sched.h> void setRealtimePriority() { struct sched_param param; param.sched_priority = sched_get_priority_max(SCHED_FIFO); sched_setscheduler(0, SCHED_FIFO, ¶m); }使用零拷贝消息传递:
typedef boost::shared_ptr<ros_can::Frame const> FrameConstPtr; void canTxCallback(const FrameConstPtr& msg) { // 零拷贝处理 }优化ROS参数服务器配置:
rosparam set /rosdistro __name:=can_node __ns:=/ rosparam set /roslaunch/uris/host_ubuntu__12345 http://localhost:12345/
6. 实际应用案例:CAN总线控制机械臂
让我们看一个实际应用场景:通过CAN总线控制六轴机械臂。这个案例展示了如何将CAN通讯集成到完整的机器人系统中。
6.1 系统架构
[ROS Master] | |-- [CAN Interface Node] -- [CAN Bus] -- [Motor Controller 1] | -- [Motor Controller 2] | -- [...] | |-- [Motion Planning Node] | |-- [User Interface Node]6.2 机械臂控制消息定义
扩展之前的Frame.msg,添加机械臂专用消息:
uint32 id uint8 dlc uint8[8] data --- uint8 axis_id float32 position float32 velocity float32 torque6.3 核心控制逻辑
void sendJointCommand(uint8_t axis_id, float position, float velocity, float torque) { VCI_CAN_OBJ send[1]; send[0].ID = 0x100 + axis_id; send[0].SendType = 0; send[0].RemoteFlag = 0; send[0].ExternFlag = 0; send[0].DataLen = 8; // 将浮点数转换为字节数组 memcpy(&send[0].Data[0], &position, 4); memcpy(&send[0].Data[4], &velocity, 4); if(VCI_Transmit(VCI_USBCAN2, 0, 0, send, 1) != 1) { ROS_ERROR("Failed to send CAN message!"); } }6.4 状态反馈处理
void processFeedback(const VCI_CAN_OBJ& frame) { ros_can::Frame msg; msg.id = frame.ID; msg.dlc = frame.DataLen; memcpy(&msg.data[0], frame.Data, frame.DataLen); // 解析具体数据 if(frame.ID >= 0x200 && frame.ID <= 0x20F) { uint8_t axis_id = frame.ID - 0x200; float position, velocity, current; memcpy(&position, &frame.Data[0], 4); memcpy(&velocity, &frame.Data[4], 4); // 发布ROS消息 feedback_pub_.publish(msg); } }7. 常见问题解决方案
在实际项目中,我遇到了许多关于周立功CAN卡与ROS集成的问题。以下是几个最典型的案例及其解决方案:
7.1 驱动加载失败
症状:VCI_OpenDevice返回失败,dmesg显示usb 1-1: device descriptor read/64, error -110
解决方案:
- 检查USB供电是否充足,尝试更换USB端口
- 更新固件版本:
sudo apt install fxload sudo fxload -t fx2 -I /path/to/firmware.hex - 禁用USB自动挂起:
echo 'options usbcore autosuspend=-1' | sudo tee /etc/modprobe.d/usb-autosuspend.conf sudo update-initramfs -u
7.2 CAN消息丢失
症状:高频率发送时部分消息丢失
优化措施:
- 增加发送缓冲区:
VCI_INIT_CONFIG config; config.Timing0 = 0x01; // 提高波特率 config.Timing1 = 0x1C; - 使用批处理发送:
#define MAX_CAN_FRAMES 100 VCI_CAN_OBJ frames[MAX_CAN_FRAMES]; // 填充多个帧 VCI_Transmit(VCI_USBCAN2, 0, 0, frames, count); - 调整ROS参数:
rosparam set /can_node/queue_size 1000
7.3 实时性不稳定
症状:消息延迟波动大
调优方案:
- 设置CPU亲和性:
cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(2, &cpuset); // 绑定到特定核心 pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); - 禁用CPU频率调节:
sudo apt install cpufrequtils echo 'GOVERNOR="performance"' | sudo tee /etc/default/cpufrequtils sudo systemctl restart cpufrequtils - 优化内核参数:
echo 'vm.swappiness = 10' | sudo tee -a /etc/sysctl.conf echo 'fs.inotify.max_user_watches = 524288' | sudo tee -a /etc/sysctl.conf sudo sysctl -p
8. 扩展应用:CAN总线网络管理
对于需要多个CAN节点协同工作的复杂系统,实现总线网络管理至关重要。以下是基于周立功CAN卡的高级应用示例:
8.1 总线监控与统计
void monitorBusHealth() { VCI_ERR_INFO errInfo; VCI_ReadErrInfo(VCI_USBCAN2, 0, 0, &errInfo); ROS_INFO("CAN Bus Status: ErrCode=%u, PassiveErr=%u, ArbLost=%u", errInfo.ErrCode, errInfo.PassiveErr, errInfo.ArbLost); VCI_CAN_STATUS status; VCI_ReadCANStatus(VCI_USBCAN2, 0, 0, &status); ROS_INFO("Buffer Status: Send=%u/%u, Recv=%u/%u", status.SendNum, status.SendSize, status.RecvNum, status.RecvSize); }8.2 节点心跳检测
实现一个简单的网络管理协议:
struct HeartbeatMsg { uint32_t node_id; uint32_t timestamp; uint8_t state; }; void sendHeartbeat() { HeartbeatMsg hb; hb.node_id = 0x01; hb.timestamp = ros::Time::now().toNSec(); hb.state = 0x01; // 运行状态 VCI_CAN_OBJ frame; frame.ID = 0x700; // 网络管理地址 frame.DataLen = sizeof(HeartbeatMsg); memcpy(frame.Data, &hb, sizeof(HeartbeatMsg)); VCI_Transmit(VCI_USBCAN2, 0, 0, &frame, 1); }8.3 总线负载计算
float calculateBusLoad() { static uint32_t last_frame_count = 0; static ros::Time last_time = ros::Time::now(); VCI_CAN_STATUS status; VCI_ReadCANStatus(VCI_USBCAN2, 0, 0, &status); uint32_t current_count = status.RecvNum; ros::Time current_time = ros::Time::now(); float interval = (current_time - last_time).toSec(); float frame_rate = (current_count - last_frame_count) / interval; last_frame_count = current_count; last_time = current_time; // 假设标准CAN帧(11-bit ID + 8 bytes data) float bit_rate = frame_rate * (47 + 8 * 8); // 47位开销 + 数据位 float load_percentage = bit_rate / 500000 * 100; // 500kbps总线 return load_percentage; }