在STM32中实现ROS节点——Rosserial的用法

这篇具有很好参考价值的文章主要介绍了在STM32中实现ROS节点——Rosserial的用法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

内容介绍

本文介绍如何将stm32控制板作为一个单独的ROS节点接入整个机器人ROS系统。

前言

在一个完整的机器人硬件系统中,由于众多传感器接口和实时性的需求,不可避免的需要加入嵌入式控制器,现在的机器人大多使用了分布式ROS系统,这套系统主要基于linux运行,而以stm32为例的大多数嵌入式控制器不支持linux。于是,当工控机想要与stm32进行数据交换时,只能脱离ROS体系采用自定义通讯协议。
那么,能不能在STM32中使用ROS架构呢,答案是肯定的,通过rosserial模块,我们可以把一部分ROS的接口定义移植到STM32中编译,虽然不能实现完整的ROS移植,但是可以把整个STM32控制器作为一个单独的ROS节点加入到ROS系统中,实现消息定义与收发、服务创建等功能。本文接下来将介绍该实现方法。

生成要移植到stm32的自定义消息和服务

在ros工作空间catkin_ws中,我们可以创建自己的包,并在msg和srv目录中加入自己定义的消息和服务,这些消息和服务是STM32与工控机通讯时需要用到的,这样接下来生成roslibs时,就会自动包含这些自定义消息和服务。

生成针对stm32的移植库包roslibs

  1. 下载rosserial_stm32包:进入自己的ROS工作空间src目录,如~/catkin_ws/src,输入
git clone https://github.com/yoneken/rosserial_stm32.git
  1. 编译rosserial_stm32:在~/catkin_ws/目录下输入catkin_make编译新加入的包,之后记得输入source setup.bash使得编译内容生效
cd ~/catkin_ws
catkin_make
source /opt/ros/[版本]/setup.bash
source ~/catkin_ws/devel/setup.bash
  1. 生成待移植的头文件:
    在工作目录下新建一个文件夹,例如stm32_roslib,然后进入stm32_roslib新建一个Inc文件夹,首字母I要大写。在stm32_roslib目录(不是Inc目录)下执行命令:
cd ~
mkdir -p stm32_roslib/Inc
cd stm32_roslib
rosrun rosserial_stm32 make_libraries.py ./

这将在stm32_roslib/Inc目录下生成一堆文件夹,包含ROS自带的消息和服务,以及catkin_ws下所有包内自定义的消息和服务,均以C++头文件的方式定义,此外,还生成了ros.h、STM32Hardware.h、duration.cpp、time.cpp四个文件。这就是我们需要移植到stm32的项目中混合编译的内容。

注意,这里有两点需要说明:

  • 根据ros版本的不同,在执行make_libraries.py时可能报错SyntaxError: Missing parentheses in call to 'print'. Did you mean print(__usage__)?,这是由于脚本在python3执行,但是python3的语法下print需要加(),我们需要修改rosserial_stm32包下面的make_libraries.py文件
cd ~/catkin_ws/src/rosserial_stm32/src/rosserial_stm32 
vim make_libraries.py

第74行和第81行有两个print,把后面的内容加上括号即可。

# need correct inputs
if (len(sys.argv) < 2):
    print (__usage__)
    exit()

# get output path
path = sys.argv[1]
if path[-1] == "/":
    path = path[0:-1]
print ("\nExporting to %s" % path)
  • Inc下包含的目录很多,但是不一定所有的消息和服务我们在stm32都要用到,可以视情况删除一部分,减少stm32项目体量。

在Mdk中实现C和C++代码混合编译

将上一步生成的Inc文件夹整个拷贝出来,进入windows系统,将Inc文件夹复制到stm32的Mdk项目下面,例如,我们放置在stm32_ros_lib/Inc下。接下来,打开mdk,进入stm32的项目,由于我们生成的Inc下所有的头文件都是C++编写的,所以我们要开启Mdk的c++编译。

一定要注意,这里网上很多文章说应该添加–cpp修改mdk的编译配置,据我测试这么做限制很大,因为这样会让mdk对项目所有文件均采用c++编译器编译,如果你的项目添加了第三方模组,如freertos、虚拟串口VCP等,这些c文件都会编译报错。

兼容性最好的方法应该是采用mdk的c/c++混合编译模式,因为默认情况下,mdk会通过文件扩展名来选择对应的编译器,.c文件会用c编译器,.cpp文件会采用c++编译器,所以我们应该利用cpp文件,将所有与ROS有关的内容都写到单独的cpp文件(如rosserial_lib.cpp)里,然后在头文件rosserial_lib.h中将cpp的函数声明用extern C包装一下,其它c文件中即可使用#include rosserial_lib.h来包含cpp文件的内容了,至于cpp中调用其它c文件内容,本来就是向下兼容的,所以无需烦恼。

具体来说,我们综合freertos的任务架构以及ROS节点的while循环写法,将ROS移植相关内容都放在同一个freertos任务中。
首先是rosserial_lib.h文件:

#ifndef ROSSERIAL__H_
#define ROSSERIAL__H_

#ifdef __cplusplus
 extern "C" {
#endif

void RosserialSetup(void);
void RosserialLoop(void);

#ifdef __cplusplus
}
#endif
#endif 

这里我们定义了两个函数,分别是节点初始化函数RosserialSetup(void)和节点循环函数RosserialLoop(void)

其次是freertos的任务,我们创建了一个ControlTask的任务,在进入无限循环前先执行节点初始化函数RosserialSetup(void),然后再无限循环中执行RosserialLoop(),注意到这里是通过#include "rosserial_lib.h",来调用C++函数RosserialSetup(void)RosserialLoop(void),由于在头文件中加入了extern "C",这种调用编译器是不会报错的。

#include "rosserial_lib.h"
void ControlTask(void *argument) {
  osDelay(500);
  RosserialSetup();
  osDelay(500);
  for(;;) {
    RosserialLoop();
  }
}

接下来,我们介绍和分析rosserial_lib.cpp里面两个函数的具体实现。如下代码所示,这是一段stm32中ROS节点的具体实现示例,ros.h是ros功能的核心头文件,std_msgs/String.h是ros自带的标准消息头文件,ts_vision_ctrl/final_data.h是用户自定义消息头文件,这些都包含在之前生成的Inc文件夹内。

基本写法和标准ROS节点类似,只是语法有些微区别,同样是定义nodehandle类、消息收发类型、接收消息回调函数,在 RosserialSetup(void)函数中进行节点初始化,注册需要收发的消息和服务,在RosserialLoop(void)函数中进行节点消息的更新发送,编写相关控制代码,最后执行nh.spinOnce()来响应各种回调函数(这里不能用spin(),否则任务循环会被阻塞)。注意由于RosserialLoop()函数本身在freertos的任务无限循环中执行,所以RosserialLoop()函数内部不再需要while循环。

#include "rosserial_lib.h"
#include "cmsis_os.h"
#include <ros.h>
#include <std_msgs/String.h>
#include <ts_vision_ctrl/final_data.h>

void command_callback(const std_msgs::String &rxbuff);
ros::NodeHandle nh;

std_msgs::String stm32_to_pc_word;
ts_vision_ctrl::final_data my_data;

ros::Subscriber<std_msgs::String> cmd_sub("pc_to_stm32", command_callback);
ros::Publisher publisher("stm32_to_pc", &stm32_to_pc_word);
ros::Publisher my_pub("stm32_my_data", &my_data);

void command_callback(const std_msgs::String &rxbuff) {
  stm32_to_pc_word = rxbuff;
  publisher.publish(&stm32_to_pc_word);
}

void RosserialSetup(void) {
  nh.initNode();
  nh.advertise(publisher);
  nh.advertise(my_pub);
  nh.subscribe(cmd_sub);
  my_data.heading = 3.5;
  my_data.x = 1.23;
  my_data.y = 2.56;
  my_data.header.frame_id = "position";
  my_data.header.seq = 0;
}

void RosserialLoop(void) {
  static int i = 0;
  my_data.header.seq = i;
  i++;
  my_pub.publish(&my_data);
  nh.spinOnce();
  osDelay(20);
}

修改mdk配置

在Mdk宏定义中加入,__USE_C99_MATH,这样可以避免roslib编译错误
在STM32中实现ROS节点——Rosserial的用法
在include paths中加入…/stm32_ros_lib/Inc目录
在STM32中实现ROS节点——Rosserial的用法
在左边project项目名称右键,选择Manage Project Items,在Groups新建一个组别,如RosLibs,添加早先用make_libraries.py生成Inc文件夹下的四个文件:ros.h、STM32Hardware.h、duration.cpp、time.cpp。其实两个头文件加不加都可以,我们只需要修改STM32Hardware.h的内容完成移植。
在STM32中实现ROS节点——Rosserial的用法

修改stm32 ROS通讯接口驱动

打开STM32Hardware.h文件,STM32Hardware.h中的类STM32Hardware会在node_handle.h中调用
需要在这个类中实现read()write()的公共方法。这里就和stm32的硬件接口相关了,由于我们用的是rosserial,所以这里将会调用stm32的串口驱动与工控机进行通讯。

class STM32Hardware {
protected:
public:
  STM32Hardware() {}

  void init() {}

  int read() {
    if (Uart_Available()) {
      return Uart_Read();
    } else {
      return -1;
    }
  }

  void flush(void) {}

  void write(uint8_t *data, int length) { Uart_Write(data, length); }

  unsigned long time() { return HAL_GetTick(); } 
protected:
};

我们需要实现三个函数,分别是int read()、 void write(uint8_t *data, int length)以及unsigned long time()。其中read函数返回值是一个整型,其实是每次读取并返回一个字节,write函数每次则是发送长度为length的字节数组。为此,最好为串口配置一个接收环形缓冲区,每次串口接收到数据,就写入环形缓冲区。read()函数首先调用Uart_Available()函数,判断环形缓冲区是否有数据,如果有,就通过Uart_Read()函数从环形缓冲区读取一个字节并返回这个字节。

下面的代码是最简单的环形缓冲区实现,仅供参考,实际应用时,应考虑多任务对全局变量操作时可能产生的竞争冒险现象,添加信号量等同步机制。至于串口数据接收写入环形缓冲区,是在串口中断里面实现的。

int Uart_Available(void) {
  return ((uint32_t)(UART_RX_DATA_SIZE + uart_rxBufPtrIn - uart_rxBufPtrOut)) %
         UART_RX_DATA_SIZE;
}

//从接收缓冲区中读取
int Uart_Read(void) {
  // if the head isn't ahead of the tail, we don't have any characters
  if (uart_rxBufPtrIn == uart_rxBufPtrOut) {
    return -1;
  } else {
    unsigned char ch = uart_rxBuffer[uart_rxBufPtrOut];
    uart_rxBufPtrOut = (uint16_t)(uart_rxBufPtrOut + 1) % UART_RX_DATA_SIZE;
    return ch;
  }
}
//通过usb_vcp向外发送
void Uart_Write(uint8_t *Buf, uint16_t Len) {
  while (UART2_Transmit(Buf, Len) != HAL_OK) {
    osDelay(1);
  }
}

最后是time()函数,需要提供一个毫秒计数的系统时间,一般我们的freertos系统节拍都是1ms,因此直接返回HAL_GetTick()即可 ,这个函数返回的是32位的毫秒计数,超时时间很长,不用担心溢出问题。

测试

至此,stm32全部的ROS移植就完成了,将stm32项目编译后下载,然后将其对应的串口接入工控机,在工控机中执行

 rosrun rosserial_python serial_node.py _port:=/dev/ttyUSB0 _baud:=115200

注意上述ttyUSB0 要改成实际识别的串口名,波特率也要和stm32中设置的波特率匹配。如果一切正常,可以看到输出信息

[INFO] [1661936312.766461]: ROS Serial Python Node
[INFO] [1661936312.784234]: Connecting to /dev/ttyUSB0 at 115200 baud
[INFO] [1661936314.895818]: Requesting topics...
[INFO] [1661936314.915546]: Note: publish buffer size is 1024 bytes
[INFO] [1661936315.080871]: Setup publisher on odom [nav_msgs/Odometry]
[INFO] [1661936315.179145]: Setup publisher on imu [sensor_msgs/Imu]
[INFO] [1661936316.061922]: Setup publisher on /tf [tf/tfMessage]
[INFO] [1661936316.073155]: Note: subscribe buffer size is 1024 bytes
[INFO] [1661936316.077387]: Setup subscriber on cmd_vel [geometry_msgs/Twist]
...
...

此时,整个stm32嵌入式系统已经作为一个ROS节点运行在ROS系统内了,之后新打开一个终端,执行rostopic list,可以看到相关的收发消息

gm@controlboard:~$ rostopic list
/cmd_vel
/diagnostics
/imu
/odom
/rosout
/rosout_agg
/tf

补充说明

以上内容是基于stm32的标准串口实现,但是实际使用时,由于ROS数据传输量较大,标准串口的带宽不够用,因此大部分情况下,我们都使用STM32的usb虚拟串口VCP来取代标准串口,VCP带宽就完全能够满足ROS的常用需求了,下一篇文章,我们将详细介绍如何结合虚拟串口来实现Rosserial功能。文章来源地址https://www.toymoban.com/news/detail-423629.html

到了这里,关于在STM32中实现ROS节点——Rosserial的用法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 47.在ROS中实现global planner(3)- 使用A*实现全局规划

    接着之前45.在ROS中实现global planner(1)和46.在ROS中实现global planner(2)的铺垫,在ROS中实现 AStar Global Planner 照着之前的模板,修改下名称 再替换下所有的 sample_global_planner – astar_global_planner 基于这个我们新增之前的 astar 算法 拷贝astar_planner.h 和astar_planner.cpp 分别至 include 和

    2024年02月12日
    浏览(47)
  • 49.在ROS中实现local planner(2)- 实现Purepersuit(纯跟踪)算法

    48.在ROS中实现local planner(1)- 实现一个可以用的模板实现了一个模板,接下来我们将实现一个简单的纯跟踪控制,也就是沿着固定的路径运动,全局规划已经规划出路径点,基于该路径输出相应的控制速度 Pure Pursuit 路径跟随便是基于受约束移动机器人圆周运动的特性所开发

    2024年02月04日
    浏览(42)
  • 【ROS】如何让ROS中节点实现数据交换Ⅰ--ROS话题通信

    Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。 roscore 启动ros核心节点 roscd 将工作空间切换到指定ros功能包 catkin_create_pkg 将工作空间切换到指定ros功能包 ** rqt_graph 启动节点间的关系图 rosrun 包名 节点名称 启动节点 ( rosrun

    2024年02月02日
    浏览(47)
  • micro_ros移植到STM32F405RG ,micro_ros STM32裸机

    测试日期:2023年11月28日 工具链:STM32CubeIDE++GCC 参考资料:micro_ros_stm32cubemx_utils 注:本文内容仅用于学习参考,不适用于生产环境。 1、准备工作 1.1、安装STM32CubeIDE和STM32CubeMX 1.2、准备mirco_ros 支持cortex-m4的静态库,生成方法可参考我的一篇博文,或者直接下载地址 1.3、下载

    2024年01月17日
    浏览(30)
  • 【ROS】如何让ROS中节点实现数据交换Ⅱ --服务通信

    Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。 本章将介绍如何通过服务通信的方式实现节点数据交换以及ROS相关指令 在ros中,一个节点想要获取某种服务(例如: 一个节点想要获取此时的相机数据,节点就需要向相机发送一个请

    2024年02月03日
    浏览(42)
  • ROS与STM32通信(二)-pyserial

    ROS与STM32通信一般分为两种, STM32上运行ros节点实现通信 使用普通的串口库进行通信,然后以话题方式发布 第一种方式具体实现过程可参考上篇文章ROS与STM32通信-rosserial,上述文章中的收发频率不一致情况,目前还没解决,所以本篇文章采用第二种方式来实现STM32与ROS通信,

    2024年02月12日
    浏览(35)
  • ROS机器人制作(三)—— ROS上位机与stm32进行串口通信

    总代码在文末,需要完整的工程文件可以私聊(收一点点辛苦费) 首先创建一个功能包,用于发送和接收数据。 注意: 1.功能包依赖: roscpp std_msgs rosserial 2.当有两个c++文件进行编译时可以在功能包下的CMakeLists.txt文件中 第一部分是ROS上位机给stm32发送数据。第二部分是stm

    2024年02月15日
    浏览(58)
  • yocto stm32mp1集成ros

    本章节介绍yocto如何集成ros系统用来作机器人开发。 第一步首先需要下载meta-ros layer,meta-ros的链接如下:https://github.com/ros/meta-ros/tree/master,在我们的yocto源码layers路径下执行如下指令: 下载完成以后如下: 完成以后需要进入到meta-ros里面切换匹配yocto版本的分支,比如我当前

    2024年02月09日
    浏览(31)
  • STM32 使用microros与ROS2通信

    本文主要介绍如何在STM32中使用microros与ROS2进行通信,在ROS1中标准的库是rosserial,在ROS2中则是microros,目前网上的资料也有一部分了,但是都没有提供完整可验证的demo,本文将根据提供的demo一步步给大家进行演示。 1、首先如果你用的不是STM32F4的话,则需要自己去生成 micro_r

    2024年02月08日
    浏览(46)
  • ROS实现一个节点同时发布订阅多个话题(C++版)

      如果想在一个节点同时发布订阅多个话题就要使用到多线程机制,在C++中如何使用多线程,在C++中开多线程模板已经有了介绍,就是下面这个:    但是有一点需要注意的是,创建节点的涉及到一个主线程,如果想同时发布订阅是不能使用主线程的(也就是不能主线程发

    2024年02月11日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包