0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

ROS与移动底盘通信教程

3D视觉工坊 来源:CSDN-白鸟无言 2023-03-14 10:27 次阅读

本实验是实现机器人自主导航的重要步骤,对于轮式机器人,可以通过在底盘加装轮式里程计的方式来获得机器人的速度数据,这些数据可以用来辅助机器人实现自主定位,同时机器人还需要将控制指令发送给移动底盘,实现自主控制,本实验就将实现ROS与移动底盘的通信

实验环境:

· 软件环境:Ubuntu18.04 + ROS melodic、Windows + Keil 5、VSCode

· 硬件环境:Jetson Nano(以下称为ROS端)、小车(以下称为STM32端)

01 实验内容

ROS与STM32的通信流程如图所示

1cd04234-c1a0-11ed-bfe3-dac502259ad0.png

主要包含两个方面:

· 小车里程计数据的上传与接收

· 控制指令的下发与接收

1.1 原始消息内容

在ROS中,里程计数据主要包括机器人的位姿(位置和姿态),以及机器人的速度(线速度和角速度)。对于本实验所用到的麦轮地面机器人,只需要知道机器人的x轴与y轴线速度、x轴与y轴位置、z轴角速度、偏航角即可。

由于对速度积分可以得到位置,对角速度积分可以得到角度,所以STM32端上传的里程计数据只需要包括机器人的x轴与y轴线速度、z轴角速度,ROS端在接收到这些数据后,进行积分即可得到位置和角度。

另外,在本实验用到的STM32端集成了一个ICM20602姿态传感器,其中内置了姿态解算算法,可以获得准确的机器人姿态数据,因此本实验使用STM32端上传的偏航角来代替对角速度积分得到的航向角。

所以STM32上传的里程计数据包括机器人的x轴线速度、y轴线速度、z轴角速度、偏航角。

与里程计数据类似,对于麦轮地面机器人,控制指令只需要包括机器人的x轴速度、y轴速度、z轴角速度即可,机器人坐标系如图所示:
1ce12d4c-c1a0-11ed-bfe3-dac502259ad0.png

1.2 转换为字节数组

知道了消息的原始数据,还需要将它转换成传输效率更高的字节数组,如图:

1cf38b0e-c1a0-11ed-bfe3-dac502259ad0.png

在C/C++中,有很多种将原始数据转换为字节数组的方法,其中一种常用的方法是使用联合体(union)。

联合体的所有成员占用同一段内存,修改一个成员会影响其余成员,如果要实现一个float数据与字节数组的互相转换,我们可以定义如下的联合体:

typedefunion{
float data;
uint8_t data8[4];
}data_u;


这个联合体中有两个成员,一个是32位的float数据data,另一个同样是占据了32位字长的字节数组data8,根据联合体的性质,这两个成员所在的内存位置是一样的,也就是说,改变其中任何一个成员的值,另一个也会被改变。

利用这个性质,我们就可以实现float与字节数据的互相转换。

1.3 添加帧头和校验码

本实验选择常用的0xAA 0x55作为帧头,同时对原始数据转换得到的字节数组进行求和,将结果保存在1字节数据中,作为校验码。

准备工作:

1. 在ROS端安装serial功能包
sudoapt-getinstallros-melodic-serial
2. 在ROS端创建一个功能包,命名为xrobot,添加依赖项roscpp rospy tf serial

02 里程计数据的上传与接收

2.1 通信协议

里程计数据格式(19字节)

1d03fe4e-c1a0-11ed-bfe3-dac502259ad0.png

2.2 STM32端
/**
 * @brief 发送里程计数据
 */
void DataTrans_Odom(void)
{
uint8_t _cnt = 0;
  data_u _temp; // 声明一个联合体实例,使用它将待发送数据转换为字节数组
uint8_t data_to_send[100] = {0}; // 待发送的字节数组


  data_to_send[_cnt++]=0xAA;
  data_to_send[_cnt++]=0x55;


uint8_t _start = _cnt;


float datas[] = {kinematics.odom.vel.linear_x, 
                     kinematics.odom.vel.linear_y, 
                     kinematics.odom.vel.angular_z, 
                     kinematics.odom.pose.theta
                    };


for(int i = 0; i < sizeof(datas) / sizeof(float); i++)
  {
// 将要发送的数据赋值给联合体的float成员
// 相应的就能更改字节数组成员的值
    _temp.data = datas[i];
    data_to_send[_cnt++]=_temp.data8[0];
    data_to_send[_cnt++]=_temp.data8[1];
    data_to_send[_cnt++]=_temp.data8[2];
    data_to_send[_cnt++]=_temp.data8[3]; // 最高位
  }


uint8_t checkout = 0;
for(int i = _start; i < _cnt; i++)
  {
    checkout += data_to_send[i];
  }
  data_to_send[_cnt++] = checkout;
// 串口发送
  SendData(data_to_send, _cnt); 
}
2.3 ROS端

采用状态机的方式来接收STM32端上传的里程计数据,每读取一字节数据,则在状态机中处理一次,部分程序如下:
uint8_tbuffer=0;
ser.read(&buffer, 1); // ser是串口类的一个实例,该语句表示从串口中读取一个字节
if(state == 0 && buffer == 0xAA)
{
    state++;
}
else if(state == 1 && buffer == 0x55)
{
    state++;
}
else if(state == 2)
{
    data_receive[data_cnt++]=buffer;
if(data_cnt == 17)
    {
/* 进行数据校验 */
uint8_t checkout = 0;
for(int k = 0; k < data_cnt - 1; k++)
        {
            checkout += data_receive[k];
        }
if(checkout == data_receive[data_cnt - 1]) // 串口接收到的最后一个字节是校验码 
        {
/* 校验通过,进行解码 */
float vx, vy, vth, th; // x轴线速度,y轴线速度,z轴角速度,偏航角
float* datas_ptr[] = {&vx, &vy, &vth, &th};
            data_u temp;
for(int i = 0; i < sizeof(datas_ptr) / sizeof(float*); i++)
            {
                temp.data8[0] = data_receive[4 * i + 0];
                temp.data8[1] = data_receive[4 * i + 1];
                temp.data8[2] = data_receive[4 * i + 2];
                temp.data8[3] = data_receive[4 * i + 3];              
                *(datas_ptr[i]) = temp.data;
            }
            th *= D2R; // 转换为弧度
        }
        data_cnt = 0;
        state = 0;
    }
}
else state = 0;
‍ ROS端在运行时可能会提示串口打开失败,有两种原因,一是串口号不对,使用dmesg | grep ttyS*列出检测到的串口号,逐个测试;

二是没有操作权限,使用sudo chmod 666 /dev/ttyACM0即可解决,也可以使用sudo usermod -aG dialout 用户名来获得永久权限,用户名可使用whoami查看。

2.4 里程计数据可视化

以上步骤仅仅得到了机器人的x轴线速度、y轴线速度、z轴角速度、偏航角,还需要进一步处理来获得完整的里程计数据。

STM32端返回的x轴线速度、y轴线速度是相对于自身的机体坐标系的速度,而机器人的位置信息是相对于世界坐标系的位置,所以在对速度进行积分前,要先将机体坐标系下的x轴线速度、y轴线速度转换到世界坐标系,如图:
1d196eaa-c1a0-11ed-bfe3-dac502259ad0.png

这个坐标变换可以通过一个简单的旋转矩阵来实现

1d2864c8-c1a0-11ed-bfe3-dac502259ad0.png

其中θ就是机器人的偏航角。相应的程序如下:
/*对速度进行积分得到位移*/
// 获取当前时间
current_time = ros::now();
// 获取积分间隔
double dt = (current_time - last_time).toSec();
last_time = current_time;
// 将机体系速度转换到里程计坐标系
double delta_x = (vx * cos(th) - vy * sin(th)) * dt;
double delta_y = (vx * sin(th) + vy * cos(th)) * dt;
// 速度积分
x += delta_x;
y += delta_y;
‍ 在机器人中,一般使用四元数/旋转矩阵的形式来表示机器人的姿态,而不是欧拉角形式。所以需要将STM32返回的偏航角转换为四元数,程序如下:
/*对速度进行积分得到位移*/
// 获取当前时间
current_time = ros::now();
// 获取积分间隔
double dt = (current_time - last_time).toSec();
last_time = current_time;
// 将机体系速度转换到里程计坐标系
double delta_x = (vx * cos(th) - vy * sin(th)) * dt;
double delta_y = (vx * sin(th) + vy * cos(th)) * dt;
// 速度积分
x += delta_x;
y += delta_y;
‍ 以上就获取了完整的机器人里程计数据,接下来需要将里程计数据发布到ROS中。
nav_msgs::Odometryodom;
geometry_msgs::TransformStamped odom_trans;


odom_trans.header.stamp = current_time;
odom_trans.header.frame_id = "odom";
odom_trans.child_frame_id = "base_link";


odom_trans.transform.translation.x = x;
odom_trans.transform.translation.y = y;
odom_trans.transform.translation.z = 0.0;
odom_trans.transform.rotation = odom_quat;
// 发布坐标变换
odom_broadcaster.sendTransform(odom_trans);


odom.header.stamp = current_time;
odom.header.frame_id = "odom";
odom.child_frame_id = "base_link";


// 设置机器人的位置和姿态
odom.pose.pose.position.x = x;
odom.pose.pose.position.y = y;
odom.pose.pose.position.z = 0.0;
odom.pose.pose.orientation = odom_quat;


// 设置机器人的速度
odom.twist.twist.linear.x = vx;
odom.twist.twist.linear.y = vy;
odom.twist.twist.angular.z = vth;


// 发布里程计消息
odom_pub.publish(odom);
‍ 运行后,打开PC上的Ubuntu,配置ip从而实现远程连接嵌入式处理器上的ROS系统。

配置完成后,重新打开一个终端,输入:rviz,打开ROS的可视化工具,按照下图操作即可 1d3b4cbe-c1a0-11ed-bfe3-dac502259ad0.png


可视化结果如下:

1d5e82f6-c1a0-11ed-bfe3-dac502259ad0.png

最后将该rviz配置保存至文件,点击File→Save Config As,将配置保存为xxxx.rviz。下次打开时,在命令行运行:rosrun rviz rviz -d xxxx.rviz即可。

03 控制指令的下发与接收

3.1 通信协议

控制指令格式(15字节)
1d6caeda-c1a0-11ed-bfe3-dac502259ad0.png

3.2 ROS端

在ROS端,首先需要接收从其他节点的控制消息,在ROS中常常使用geometry_msgs::Twist来传递控制指令,该消息格式包括两个三维向量,如下,分别是三轴线速度和三轴角速度:
geometry_msgs/Vector3linear
geometry_msgs/Vector3 angular
‍ 我们在控制指令的消息回调函数中,将控制指令下发给STM32,部分程序如下,其中使用了C++的lambda表达式来替换回调函数
ros::Subscribersub=nh.subscribe("/cmd_vel",10,[&](constgeometry_msgs::ConstPtr&cmd_vel){
uint8_t _cnt = 0;
    data_u _temp; // 声明一个联合体实例,使用它将待发送数据转换为字节数组
uint8_t data_to_send[100]; // 待发送的字节数组    
    data_to_send[_cnt++]=0xAA;
    data_to_send[_cnt++]=0x55;
uint8_t _start = _cnt;
float datas[] = {(float)cmd_vel->linear.x,
                     (float)cmd_vel->linear.y,
                     (float)cmd_vel->angular.z};    
for(int i = 0; i < sizeof(datas) / sizeof(float); i++){
// 将要发送的数据赋值给联合体的float成员
// 相应的就能更改字节数组成员的值
        _temp.data = datas[i];
        data_to_send[_cnt++]=_temp.data8[0];
        data_to_send[_cnt++]=_temp.data8[1];
        data_to_send[_cnt++]=_temp.data8[2];
        data_to_send[_cnt++]=_temp.data8[3]; // 最高位
    }
// 添加校验码
char checkout = 0;
for(int i = _start; i < _cnt; i++)
        checkout += data_to_send[i];
    data_to_send[_cnt++] = checkout;
// 串口发送
    ser.write(data_to_send, _cnt);
});
‍ 最后,创建一个控制指令发布节点,用来发布cmd_vel话题,在xrobot功能包下新建一个scripts文件夹,添加remote_ctrl.py,内容如下:
#!/usr/bin/envpython
# #-*- coding: UTF-8 -*- 


import rospy
from geometry_msgs.msg import Twist
from std_msgs.msg import String
import os, sys
import  tty, termios


pub = rospy.Publisher("cmd_vel", Twist)
rospy.init_node("remote_ctrl")
rate = rospy.Rate(rospy.get_param("-hz", 20))


cmd = Twist()
cmd.linear.x = 0.0
cmd.linear.y = 0.0
cmd.angular.z = 0.0


while not rospy.is_shutdown():    
    tty.setraw(sys.stdin.fileno())  
    ch = sys.stdin.read( 1 )  
if ch == "w":
        cmd.linear.x = 0.2
        cmd.linear.y = 0
        cmd.angular.z = 0
elif ch == "s":
        cmd.linear.x = -0.2
        cmd.linear.y = 0
        cmd.angular.z = 0
elif ch == "a":
        cmd.linear.x = 0
        cmd.linear.y = 0
        cmd.angular.z = 0.5
elif ch == "d":
        cmd.linear.x = 0
        cmd.linear.y = 0
        cmd.angular.z = -0.5
elif ch == "q":
break
else:
        cmd.linear.x = 0
        cmd.linear.y = 0
        cmd.angular.z = 0
    rospy.loginfo(str( cmd.linear.x) + " " + str(cmd.linear.y) + " " + str(cmd.angular.z) + "
")
    pub.publish(cmd)
    rate.sleep()
‍ 运行robot_node和remote_ctrl节点,可以得到如下的节点图:
1d7d1234-c1a0-11ed-bfe3-dac502259ad0.png

3.3 STM32端

部分程序如下:
/**
 * @brief 从串口读取单个字节
 * @param  data             读取的字节数据
 */
void GetOneByte(uint8_t data)
{
  static u8 state = 0;
  static u8 cnt = 0;
if(state == 0 && data == 0xAA)
  {
    state++;
  }
else if(state == 1 && data == 0x55)
  {
    state++;
  }
else if(state == 2)
  {
    data_receive[cnt++] = data;
if(cnt >= 13)
    {
// 校验
      u8 checkout = 0;
for(int i = 0; i < cnt - 1; i++)
      {
        checkout += data_receive[i];
      }
if(checkout == data_receive[cnt - 1])
      {
// 校验通过,进行解码
        DataDecoder(data_receive);
      }
      state = 0;
      cnt = 0;
    }
  }
else state = 0;
}


/**
 * @brief 数据解码
 * @param  data             待解码数组
 */
void DataDecoder(u8 *data)
{
    data_u temp;
// x轴线速度
    temp.data8[0] = data[0];
    temp.data8[1] = data[1];
    temp.data8[2] = data[2];
    temp.data8[3] = data[3];
    kinematics.exp_vel.linear_x = temp.data;
// y轴线速度
    temp.data8[0] = data[4];
    temp.data8[1] = data[5];
    temp.data8[2] = data[6];
    temp.data8[3] = data[7];
    kinematics.exp_vel.linear_y = temp.data;
// z轴角速度
    temp.data8[0] = data[8];
    temp.data8[1] = data[9];
    temp.data8[2] = data[10];
    temp.data8[3] = data[11];
    kinematics.exp_vel.angular_z = temp.data;  
}

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 机器人
    +关注

    关注

    210

    文章

    28190

    浏览量

    206464
  • 通信
    +关注

    关注

    18

    文章

    5969

    浏览量

    135839
  • STM32
    +关注

    关注

    2266

    文章

    10870

    浏览量

    354737
  • Ubuntu
    +关注

    关注

    5

    文章

    560

    浏览量

    29553
  • ROS
    ROS
    +关注

    关注

    1

    文章

    276

    浏览量

    16963

原文标题:ROS与移动底盘通信

文章出处:【微信号:3D视觉工坊,微信公众号:3D视觉工坊】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    rosserial、ros_lib移植到STM32讲解 精选资料分享

    这边博客主要是对前面两篇博客的一个补充(ROS使用STM32F407ZGT6作为底盘控制器、ros下使用rosserial和STM32F1/STM32F4系列进行通信(MDK5工程))
    发表于 08-04 06:13

    ROS与STM32是如何进行通信

    ROS与STM32通信2020.8.1主要内容制作ROS包,将控制命令传给STM32,并将接收到的数据作为话题进行发布STM32接收数据并将姿态数据传回给ROS接收:期望角速度、期望线
    发表于 08-11 07:25

    最实用的STM32和ROS机器人的串口通信方案

    全网最实用的STM32和ROS机器人的串口通信方案小白学移动机器人同名公众号:小白学移动机器人创作声明:内容包含虚构创作内容中的情节存在虚构加工,仅供参考全网最实用的STM32和
    发表于 08-20 06:33

    移动机器人底盘主要包含哪些设备

    移动机器人底盘主要包含电机,电机驱动器,底盘控制器和其它设备。底盘控制器与电脑通信,把电脑指令解析后发送给电机驱动器,同时控制器
    发表于 09-07 06:15

    如何完成ROS与STM32之间的串口通信

    如何去实现ROS与STM32串口通信测试功能?如何完成ROS与STM32之间的串口通信呢?
    发表于 12-10 06:54

    如何搭建实体机器人ros底盘

    目录介绍一、底盘主控板二、嵌入式开发板1. 与上位机pc的关系2. 与STM32主控板的关系介绍自下而上的分析实体机器人(差分轮速机器人)搭建中的关键过程。一、底盘主控板本部分搭建实体机器人ros
    发表于 01-20 07:36

    通信教程的04_SPI接口说明及原理

    通信教程04_SPI接口说明及原理
    的头像 发表于 02-05 12:29 3916次阅读

    通信教程03_I2C简史 基础原理及协议

    通信教程03_I2C简史,基础原理及协议
    的头像 发表于 02-05 13:14 2969次阅读

    通信教程02 几种常见串行通信及基础原理

    通信教程02_几种常见串行通信及基础原理
    的头像 发表于 02-26 16:12 9772次阅读

    通信教程01 什么是并行通信?什么是串行通信

    通信教程01_什么是并行通信?什么是串行通信
    的头像 发表于 02-26 16:27 1.1w次阅读

    ROS与STM32通信

    ROS与STM32通信2020.8.1主要内容制作ROS包,将控制命令传给STM32,并将接收到的数据作为话题进行发布STM32接收数据并将姿态数据传回给ROS接收:期望角速度、期望线
    发表于 12-24 19:00 11次下载
    <b class='flag-5'>ROS</b>与STM32<b class='flag-5'>通信</b>

    Arduino长距离通信教程–LoRaLib库

    为了控制 Arduino长距离通信教程–LoRenz 开发板中构建的LoRenz开发板,我开发了LoRaLib——用于SX1278芯片的开源Arduino库。
    的头像 发表于 02-24 09:51 1578次阅读
    Arduino长距离<b class='flag-5'>通信教</b>程–LoRaLib库

    ROS1的通信架构的基础通信方式及相关概念

    ROS通信架构是ROS的灵魂所在,它包括数据处理,进程运行,消息传递等** 。这篇文章主要介绍ROS1的通信架构的基础
    的头像 发表于 05-19 17:23 3371次阅读
    <b class='flag-5'>ROS</b>1的<b class='flag-5'>通信</b>架构的基础<b class='flag-5'>通信</b>方式及相关概念

    ROS移动底盘通信试验内容

    ROS与STM32的通信流程如图所示 主要包含两个方面: 小车里程计数据的上传与接收 控制指令的下发与接收 1.原始消息内容 在ROS中,里程计数据主要包括机器人的位姿(位置和姿态),以及机器人
    的头像 发表于 11-16 16:36 383次阅读
    <b class='flag-5'>ROS</b>与<b class='flag-5'>移动</b><b class='flag-5'>底盘</b>的<b class='flag-5'>通信</b>试验内容

    ROS通信接口机制介绍

    ROS通信接口 接口可以让程序之间的依赖降低,便于我们使用别人的代码,也方便别人使用我们的代码,这就是ROS的核心目标,减少重复造轮子。 ROS有三种常用的
    的头像 发表于 12-01 15:03 775次阅读
    <b class='flag-5'>ROS</b><b class='flag-5'>通信</b>接口机制介绍