七、ROS-CAN通信

  • 1. 测试官方样例
  • 2. 移植到ROS

1. 测试官方样例

我这里用的是CANalyst-II分析仪(创芯科技) Linux版
我的系统是:Ubuntu18.04.6 LTS
我的ROS版本:melodic

这个CAN设备是不用装驱动的,直接在终端输入lsusb就能查到CAN设备ID:04d8:0053 Microchip Technology, Inc.

lsusb

官方提供的Linux资料包里面就有二次开发样例
最重要的是libcontrolcan.so是动态链接库,调用库中的函数需要用到controlcan.h

将这4个文件复制到一个不含中文路径的目录下,我这里是复制到can_test这个文件夹里

修改main.cpp的内容:读取CAN1通道的数据(遥控器发给CAN1),并打印到终端

//功能:接收从CAN1发送过来的数据,并打印到终端#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include "controlcan.h"#include <ctime>
#include <cstdlib>
#include "unistd.h"main()
{printf(">>this is mytestdemo !\r\n");//指示程序已运行if(VCI_OpenDevice(VCI_USBCAN2,0,0)==1)//打开设备{printf(">>open deivce success!\n");//打开设备成功}else{printf(">>open deivce error!\n");exit(1);}//配置CANVCI_INIT_CONFIG config; config.AccCode = 0x80000000;config.AccMask = 0xFFFFFFFF;config.Filter = 2;   //只接收标准帧config.Timing0 = 0x01; config.Timing1 = 0x1C; //波特率定为250kps  0x01 0x1Cconfig.Mode = 0; //正常模式        //初始化CAN1:VCI_InitCAN(设备类型、设备索引、CAN通道索引、CAN配置参数)if(VCI_InitCAN(VCI_USBCAN2,0,0,&config)!=1){printf(">>Init CAN1 error\n");VCI_CloseDevice(VCI_USBCAN2,0);}if(VCI_StartCAN(VCI_USBCAN2,0,0)!=1){printf(">>Start CAN1 error\n");VCI_CloseDevice(VCI_USBCAN2,0);}//初始化用来接收的数据帧,帧结构体数组的长度设置为50VCI_CAN_OBJ rec[50];int reclen=0;int i,j;while(1){//VCI_InitCAN(设备类型、设备索引、CAN通道索引0就是CAN1、接收数组的首指针rec、接收数组的长度50、保留参数)    if((reclen = VCI_Receive(VCI_USBCAN2,0,0,rec,50,200)) >= 0)//调用接收函数,如果有数据,则进行处理{for(i=0; i<reclen; i++){//打印数据printf("data:0x"); for( j= 0; j < rec[i].DataLen; j++ ){printf(" %02X", rec[i].Data[j]); //%02X表示用2个位置数据一个16进制数}printf("\n");}}}
}

然后将libcontrolcan.so动态链接库的绝对路径添加到Makefile中
就可以将这个main.cpp编译成可执行文件test2333,可执行文件名可以自定义

all:g++ -o test2333 main.cpp /home/yao/My_Ros_WorkSpace/can_test/libcontrolcan.so  -lpthread clean:rm -f *.o

最后在终端中编译和执行即可

lsusb
cd /home/yao/My_Ros_WorkSpace/can_test
rm test2333
make clean && make
sudo ./test2333


遥控器↑:02 01 00 00 00 00 00 00
遥控器↓:02 02 00 00 00 00 00 00
遥控器←:02 04 00 00 00 00 00 00
遥控器→:02 08 00 00 00 00 00 00

OK,到这里说明我们的设备和程序都是可以正常工作的,没有问题!

特别注意:CAN1口旁边的2个120Ω上拉电阻,必须保证至少一个是拨到ON档
否则没办法接收CAN1口发送过来的数据

2. 移植到ROS

接下来,将这部分代码移植到ROS系统下

  • 首先创建工作空间
    用VScode打开工作空间,需要进行配置:看我前面的文章
cd ~/My_Ros_WorkSpace
mkdir can_workspace
cd  can_workspace
mkdir src
cd src
catkin_init_workspace
cd ..
catkin_make
catkin_make install
code .
  • 然后创建功能包
    选中src,右键Create Catkin Package
    录入功能包名字"can_pkg",回车
    录入依赖"roscpp rospy std_msgs",再回车

  • 创建节点:读取CAN1的数据
    选中can_pkg→src,右键新建一个发布者的实现代码mycan_pub.cpp
    修改 .vscode/c_cpp_properties.json,设置 "cppStandard"为 “c++17”并保存,否则会报错

重中之重:
因为要用到libcontrolcan.so和controlcan.h,所以要将这两个文件添加到这个工作空间下

controlcan.h:

  1. 先放到can_workspace/src/can_pkg/include/can_pkg目录下

  2. 然后配置includePath路径,在c_cpp_properties.json文件中添加controlcan.h所在的绝对路径 “/home/yao/My_Ros_WorkSpace/can_workspace/src/can_pkg/include/**”

  3. 然后就可以在mycan_pub.cpp中调用这个头文件了,注意引用头文件的写法,需要将头文件的相对路径加进来,否则会报错,应该这样写:#include “can_pkg/controlcan.h”

  4. 这样就可以调用controlcan.h中声明的函数编写代码了:

#include <ros/ros.h>
#include <std_msgs/String.h>#include "can_pkg/controlcan.h"using namespace std; //声明命名空间int main(int argc, char** argv)
{//初始化,节点名为can_publisherros::init(argc, argv,"can_publisher");if(VCI_OpenDevice(VCI_USBCAN2,0,0)==1)//打开设备{ROS_INFO_STREAM(">>open deivce success!");//打开设备成功}else{ROS_INFO_STREAM(">>open deivce error!");//打开设备成功exit(1);}//配置CANVCI_INIT_CONFIG config; config.AccCode = 0x80000000;config.AccMask = 0xFFFFFFFF;config.Filter = 2;   //只接收标准帧config.Timing0 = 0x01; config.Timing1 = 0x1C; //波特率定为250kps  0x01 0x1Cconfig.Mode = 0; //正常模式      //初始化CAN1:VCI_InitCAN(设备类型、设备索引、CAN通道索引、CAN配置参数)if(VCI_InitCAN(VCI_USBCAN2,0,0,&config)!=1){ROS_INFO_STREAM(">>Init CAN1 error");VCI_CloseDevice(VCI_USBCAN2,0);}if(VCI_StartCAN(VCI_USBCAN2,0,0)!=1){ROS_INFO_STREAM(">>Start CAN1 error");VCI_CloseDevice(VCI_USBCAN2,0);}//初始化用来接收的数据帧,帧结构体数组的长度设置为50VCI_CAN_OBJ rec[50];int reclen=0;int i,j;while(1){//VCI_InitCAN(设备类型、设备索引、CAN通道索引0就是CAN1、接收数组的首指针rec、接收数组的长度50、保留参数)  if((reclen = VCI_Receive(VCI_USBCAN2,0,0,rec,50,200)) >= 0)//调用接收函数,如果有数据,则进行处理{//顺便将接收到的数据打印到终端for(i=0; i < reclen; i++){printf("data:0x");for( j= 0; j < rec[i].DataLen; j++ ){printf(" %02X", rec[i].Data[j]); //%02X表示用2个位置数据一个16进制数}printf("\n");}}}return 0;
}

libcontrolcan.so:

  1. 在功能包can_pkg目录下创建一个lib文件夹
  2. 将libcontrolcan.so复制到刚刚新建的lib文件夹下面
  • 配置CMakeLists.txt
  1. 解除#include的注释:可以让编译器去include下找到can_pkg/controlcan.h
## 解除#include的注释:去找头文件
include_directories(
include${catkin_INCLUDE_DIRS}
)
  1. 指定动态链接库libcontrolcan.so的访问路径
## 手动添加link_directories():指定动态链接库的访问路径为功能包can_pkg下的lib
link_directories(lib${catkin_LIB_DIRS}
)
  1. 最后就是指定生成可执行文件和链接库
# 生成可执行文件
add_executable(mycan_pub src/mycan_pub.cpp)
# 链接库
target_link_libraries(mycan_pub  ${catkin_LIBRARIES}controlcan #链接动态链接库libcontrolcan.so,但是要注意这里写的时候仅保留创建时所用到的名称,所以要去掉前缀lib和后缀.so)
  • 编译执行
    Ctrl + S保存,Ctrl + Shift +B编译
  1. 终端1
roscore
  1. 终端2
source devel/setup.bash
rosrun can_pkg mycan_pub

报错:error setting config #1: could not set config 1: Operation not permitted
原因:这是因为Linux系统下将涉及到usb底层驱动的调用,运行时,一定要加sudo获取权限运行,否则USB
设备没有权限操作,比如之前运行那个用gcc编译的可执行文件test2333时,我也是加了sudo的

sudo ./test2333

但是用rosrun can_pkg mycan_pub启动节点的时候,貌似没办法加sudo,所以需要配置USB权限:

方法一:

  1. 创建一个新的 udev 规则,名称取为:99-myusb.rules
    注意:数字 99 最好不要改动,否则可能设置失败,而且要加 sudo
sudo vi /etc/udev/rules.d/99-myusb.rules
  1. 把以下两行代码复制到新建的 99-myusb.rules 文件中
    注意:按键盘上 Insert 键切换到“代替”输入模式,然后粘贴
##
ACTION=="add",SUBSYSTEMS=="usb", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="0053",
GROUP="users", MODE="0777"
  1. 按一次“Esc”键
  2. 直接输入“:wq”回车,即保存退出
  3. 插拔一下 USBCAN 设备或重启一下电脑
  4. 就可以不加 sudo 权限运行程序了,或者ROS下用rosrun启动节点也不会报错了

方法二:

cd /etc/udev/rules.d
sudo touch 99-myusb.rules
sudo gedit 99-myusb.rules
## 将下面这两句代码复制到文件中
ACTION=="add",SUBSYSTEMS=="usb", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="0053",
GROUP="users", MODE="0777"
## 重启电脑即可生效
  • 升级节点:读取完CAN1的数据,根据遥控器发过来的数据,将控制指令输出到CAN2,以便后续的控制操作
  1. 硬件连接:CANalyst-II的CAN1连接遥控器、CAN2可以再外接一个CANalyst-II并连到Windows系统,这样就可以将CAN2输出的控制指令在Windows系统上的USB_CAN TOOL调试软件上显示出来。注意:所有CAN口的上拉电阻都需要拨到ON档
  2. 代码如下:
#include <ros/ros.h>
#include <std_msgs/String.h>#include "can_pkg/controlcan.h"using namespace std; //声明命名空间//初始化启动CAN1并启动
bool Init_CAN1(int nDeviceType, int nDeviceInd, int nCANInd, VCI_INIT_CONFIG config) {VCI_ResetCAN(nDeviceType, nDeviceInd, nCANInd);if (VCI_InitCAN(nDeviceType, nDeviceInd, nCANInd, &config) != 1){VCI_CloseDevice(nDeviceType, nDeviceInd);ROS_INFO_STREAM(">>Init CAN1 error");return(0);}VCI_ClearBuffer(nDeviceType, nDeviceInd, nCANInd);if (VCI_StartCAN(nDeviceType, nDeviceInd, nCANInd) != 1){VCI_CloseDevice(nDeviceType, nDeviceInd);ROS_INFO_STREAM(">>Start CAN1 error");return(0);}else{ROS_INFO_STREAM(">>Start CAN1 success");return(1);}
}//初始化启动CAN2并启动
bool Init_CAN2(int nDeviceType, int nDeviceInd, int nCANInd, VCI_INIT_CONFIG config) {VCI_ResetCAN(nDeviceType, nDeviceInd, nCANInd);if (VCI_InitCAN(nDeviceType, nDeviceInd, nCANInd, &config) != 1){VCI_CloseDevice(nDeviceType, nDeviceInd);ROS_INFO_STREAM(">>Init CAN2 error");return(0);}VCI_ClearBuffer(nDeviceType, nDeviceInd, nCANInd);if (VCI_StartCAN(nDeviceType, nDeviceInd, nCANInd) != 1){VCI_CloseDevice(nDeviceType, nDeviceInd);ROS_INFO_STREAM(">>Start CAN2 error");return(0);}else{ROS_INFO_STREAM(">>Start CAN2 success");return(1);}
}int main(int argc, char** argv)
{//初始化,节点名为can_publisherros::init(argc, argv,"can_publisher");//根据遥控器指令发送相应的控制指令const unsigned char a0[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; //默认const unsigned char a1[] = { 0xF4,0x01,0x00,0x00,0x00,0x00,0x00,0x00 }; //↑const unsigned char a2[] = { 0x00,0x00,0xF4,0x01,0x00,0x00,0x00,0x00 }; //↓const unsigned char a3[] = { 0x00,0x00,0x00,0x00,0xF4,0x01,0x00,0x00 }; //←const unsigned char a4[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0xF4,0x01 }; //→//打开和初始化设备的一些参数配置int nDeviceType = 4; //设备类型:CANalyst-II就用4int nDeviceInd = 0; //设备索引:1个USB-CAN适配器就是0int nCANInd0 = 0;//CAN1int nCANInd1 = 1;//CAN2//打开设备:注意一个设备只能打开一次if(VCI_OpenDevice(nDeviceType,nDeviceInd,0)==1){ROS_INFO_STREAM(">>open deivce success!");//打开设备成功}else{ROS_INFO_STREAM(">>open deivce error!");//打开设备失败exit(1); //退出整个程序,终止进程:返回1给操作系统说明是非正常运行导致程序退出}//配置CANVCI_INIT_CONFIG config; config.AccCode = 0x80000000;config.AccMask = 0xFFFFFFFF;config.Filter = 2;   //只接收标准帧config.Timing0 = 0x01; config.Timing1 = 0x1C; //波特率定为250kps  0x01 0x1Cconfig.Mode = 0; //正常模式      //初始化用来接收的数据帧,帧结构体数组的长度设置为50int num = 50;VCI_CAN_OBJ rec[num];int reclen = 0;int i,j,k, order;//初始化用来发送的数据帧,帧结构体数组的长度设置为1VCI_CAN_OBJ send[1];send[0].ID = 0; //帧IDsend[0].SendType = 0; //发送帧类型:0为正常发送send[0].RemoteFlag = 0; //0为数据帧,1为远程帧send[0].ExternFlag = 0; //0为标准帧,1为拓展帧send[0].DataLen = 8; //数据长度8字节//CAN1和CAN2双通道初始化(设备类型、设备索引、CAN通道索引、CAN配置参数)if (Init_CAN1(nDeviceType, nDeviceInd, nCANInd0, config) && Init_CAN2(nDeviceType, nDeviceInd, nCANInd1, config)){while(1){//延时20ms:在满足应用的时效性情况下,尽量降低调用VCI_Receive频率,每隔30ms调用一次VCI_Receive为宜usleep(20000);//VCI_Receive(设备类型、设备索引、CAN通道索引0就是CAN1、接收数组的首指针rec、接收数组的长度50、保留参数)  if((reclen = VCI_Receive(nDeviceType,nDeviceInd,nCANInd0,rec,num,200)) >= 0)//调用接收函数,如果有数据,则进行处理{for(i=0; i < reclen; i++){//接收到的遥控器指令order = (int)rec[i].Data[1] + (int)rec[i].Data[2];//先将接收到的遥控器指令打印到终端printf("data:0x");for( j= 0; j < rec[i].DataLen; j++ ){printf(" %02X", rec[i].Data[j]); //%02X表示用2个位置数据一个16进制数}printf("\n");//然后根据遥控器的指令,让CAN2发布相应的控制指令switch (order) {case 0: //默认{for (k = 0; k < 8; k++){send[0].Data[k] = a0[k];}VCI_Transmit(nDeviceType, nDeviceInd, nCANInd1, send, 1);break;}case 1: //↑{for (k = 0; k < 8; k++){send[0].Data[k] = a1[k];}VCI_Transmit(nDeviceType, nDeviceInd, nCANInd1, send, 1);break;}case 2: //↓{for (k = 0; k < 8; k++){send[0].Data[k] = a2[k];}VCI_Transmit(nDeviceType, nDeviceInd, nCANInd1, send, 1);break;}case 4: //←{for (k = 0; k < 8; k++){send[0].Data[k] = a3[k];}VCI_Transmit(nDeviceType, nDeviceInd, nCANInd1, send, 1);break;}case 8: //→{for (k = 0; k < 8; k++){send[0].Data[k] = a4[k];}VCI_Transmit(nDeviceType, nDeviceInd, nCANInd1, send, 1);break;}default: //检查和处理错误情况{for (k = 0; k < 8; k++){send[0].Data[k] = a0[k];}VCI_Transmit(nDeviceType, nDeviceInd, nCANInd1, send, 1);break;}}}}}}return 0;
}
// roscore
// source devel/setup.bash
// rosrun can_pkg mycan_pub

七、ROS-CAN通信相关推荐

  1. ROS服务通信机制原理及示例代码

    ROS服务通信:节点间直接通信并获得应答.服务需要用户自己开发,可在功能包下创建srv文件夹并编写源代码,服务类型是功能包名称和.srv文件名称的组合,比如python中引入srv类型为,from g ...

  2. ROS——不同版本间ROS进行通信

    在相同版本间的ROS进行通信不在赘述了,修改/etc/hosts文件即可. 最近项目遇到在Ubuntu16.04 与Ubuntu18.04两个系统间进行ROS通信,ROS版本分别为Kinetic和Me ...

  3. 【ROS学习】ROS分布式通信

    我们都知道,把ROS系统运行在嵌入式平台上(比如树莓派)是可以运行的,但是性能远远没有强大的主机好,因此,如何将运算量大的内容在主机上计算,而嵌入式平台只需进行简单的运行通信呢?这就是本篇文章所要介绍 ...

  4. STM32和ROS串口通信常见问题汇总答疑

    STM32和ROS串口通信常见问题汇总答疑 大家好,我是白茶清欢,最近看了博客文章<stm32和ros的串口通信>有很多问题的评论,这里汇总回复一下. 问题1:运行时报错如下: rosru ...

  5. 【ROS话题通信】发布者和订阅者

    前言 本文记录ROS话题通信的学习过程,便于后续复习.首先明确,ROS中的话题通信,在ROS通信中非常重要,实现了分布式发布接收消息,也是实现了不同编程语言间的解耦,下面记录下自己学习过程中的相关代码 ...

  6. 【ROS入门-4】嘴对嘴讲解ROS的核心概念——ROS话题通信机制

    文章目录 前言 ROS系列文章 ROS的通信机制 话题(topic) 发布者 订阅者 消息(Message) 用C++来写话题通信的代码 发布者 订阅者 使用rqt_graph 源码附录 引用说明 参 ...

  7. 谈谈ROS的通信机制

    本文首先简单的介绍ROS是什么和核心概念,然后主要介绍ROS的通信机制. 1.ROS是什么 ROS(机器人操作系统,Robot Operating System),是专为机器人软件开发所设计出来的一套 ...

  8. ROS入门 通信架构

    一.Master和Node ROS的应用场景是很复杂的机器人,机器人中不仅部件复杂,而且各个部件之间的沟通也是十分重要的一部分,因此ROS引入了Master作为总管家. 首先前面提到过,ROS中每个进 ...

  9. ROS话题通信c++和python实现

    机器人是一种高度复杂的系统性实现,在机器人上可能集成各种传感器(雷达.摄像头.GPS...)以及运动控制实现,为了解耦合,在ROS中每一个功能点都是一个单独的进程,每一个进程都是独立运行的.更确切的讲 ...

最新文章

  1. 第十七届智能汽车竞赛-多车编队组入门讲解
  2. C++网络编程(一)
  3. d3.js 旋转图形_几何画板第9期图形的组合型运动
  4. php 获取内容页图片,织梦DEDECMS内容页获取图片URL地址的方法
  5. Oracle/PLSQL While Loop
  6. 雅思口语:填充词(句子)
  7. php中connect,PHP中的ftp_connect()函数
  8. linux 用户与工作组
  9. 双线跟BGP线路区别
  10. 前端学习笔记(注册页面综合案例html5)
  11. 10度角的三角函数计算(续)
  12. 电脑Tab键有什么功能?分享Tab键的6个妙用
  13. ABP——切换MySQL数据库
  14. vscode Trace/breakpoint trap 问题
  15. ESP32自定义分区表
  16. javaweb——>个人博客项目
  17. vue 自定义指令
  18. 怎么提高App推广效率
  19. matlab abel变换图片处理
  20. 雪碧图 css 使用方式与 Js使用方式

热门文章

  1. Idea 精准到类的打包方式:Artifacts 打包
  2. 【Python爬虫】第一课 Python爬虫环境与爬虫简介
  3. 巧解决阿里云虚拟主机免费版被DDOS攻击问题
  4. 32岁了学python来的及吗_你要悄悄的学Python,然后惊艳所有人,后来都学的怎么样呢?...
  5. python-KNN简单数据分类+dsift+手势识别
  6. #Wormhole# (开源)流式处理平台设计思想
  7. PAT甲级 1042 Shuffling Machine 模拟洗牌 map的使用
  8. wordpress.mu
  9. 【正点原子Linux连载】第三章 RV1126开发环境搭建 摘自【正点原子】ATK-DLRV1126系统开发手册
  10. 【正点原子FPGA连载】第六章Petalinux设计流程实战摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Linux开发指南