前言

在这里插入图片描述
通常复杂的机器人会存在多个控制器,不会是一个控制器完成所有的任务,这样会造成计算资源的过度集中,并且无法实现模块化的调试。

以简单的智能车为例,一般会存在两个控制器,一个是运行ROS的主控,另一个是运行电机控制和传感器信息采集的单片机比如STM32。
在这里插入图片描述

由于存在多个控制器,完成一个机器人的具体任务,那么这多个控制器间则需要建立通信,本篇博客主要讲解:

  • 如何实现ROS主控和STM32之间的通信
  • ROS主控对STM32发送过来的数据做哪些处理

以智能车的应用例程展开

智能车控制器功能

在智能车里存在两个控制器:

  • ROS主控
  • STM32控制器

只要能将ROS跑起来就可以作为ROS主控,ROS主控可以是:

  • jetson 系列,例如 nano、tx、nx
  • 树莓派
  • 工控机

在智能车里,ROS主控主要实现的功能有:

  • 雷达信息采集
  • 摄像头信息采集
  • 路径规划
  • 定位

STM32 控制器主要实现的功能有:

  • 里程计信息采集
  • 陀螺仪信息采集
  • 电机控制

通信内容

在这里插入图片描述
ROS主控负责接收stm32发送过来的传感器数据

数据有里程计、imu、电池电压。其中里程计就是电机的转速,通过编码器采集到。

STM32负责接收ROS主控发送过来的运动底盘的目标速度,STM32再完成电机转速的控制,最终实现小车的移动任务

ROS主控与STM32之间需要做到一个双向的数据传输,这里就涉及到了两个控制器之间的通信问题,下面则介绍如何实现两者之间的通信

硬件连接

在这里插入图片描述

ROS主控通过usb线连接到一个TTL电平转换芯片,再由这个电平转换芯片连接STM32芯片

电平转换芯片可以通过PCB设计在STM32芯片的电路板上,也可以使用一个USB转TTL的模块。
在这里插入图片描述
在这里插入图片描述

为什么两个控制器之间需要电平转换芯片?
因为两个控制器之间通信层次逻辑是不同的,所有需要电平转换芯片。相当于两个主控是两种不同语言的人,电平转换芯片相当于一个翻译。

电平转换芯片可以是:

  • cp2102
  • ch340
  • PL2303
  • FT232RL

软件设置

硬件连接上之后,需要一个软件设置

需要软件设置原因
ROS主控可能接入多个USB设备,或者接入两个型号一样的电平转换芯片。
不同USB设备占用的ROS主控的端口号在每次上电时可能会不一致,这样需要手动修改代码中的配置参数,比较麻烦,也无法做到自启动。
如果存在多个USB设备,但是每种USB设备的电平转换芯片不一样,那么我们可以根据芯片名称来知道端口 号,但是如果有两个芯片一样的电平转换芯片,则无法区分,这时候想做自启动那么必须要进行下面步骤的软件设置

软件设置分为两步
第一步是更改电平转换芯片的serial,
第二步是创建设备别名

更新电平转换芯片的serial

首先在win环境下安装更改芯片serial的软件
CP21xx Customization Utility.exe

这个软件在网上下载就可以
这里提供一个百度网盘的链接:链接: http://pan.baidu.com/s/1bni1WtT 密码: 75bs
在这里插入图片描述
打开这个软件,然后将芯片连接电脑的USB
在这里插入图片描述
然后将圆圈位置改为0002

然后点击Program Device
在这里插入图片描述
点完之后要等下,在Status Logging窗口中出现下面信息,才说明修改好了
在这里插入图片描述

创建设备别名

需要创建设备别名原因:
在运行一个ros程序的时候需要提供一个端口名,这个端口名一般是ttyUSBx,设备每次插拔对应的这个端口名它都会不一样,需要创建一个设备别名,就是要将这个端口名来给它固定住。
在这里插入图片描述
重新插拔 USB1端口的设备后,变为:
在这里插入图片描述
可以看到变成了/dev/ttyUSB2
端口号发生了变化

创建设备别名需要写一个脚本文件,如下:

echo  'KERNEL=="ttyUSB*", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60",ATTRS{serial}=="0002", MODE:="0777", GROUP:="dialout", SYMLINK+="stm32_controller"' >/etc/udev/rules.d/stm32_controller.rules
echo  'KERNEL=="ttyUSB*", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60",ATTRS{serial}=="0001", MODE:="0777", GROUP:="dialout", SYMLINK+="2d_lidar"' >/etc/udev/rules.d/2d_lidar.rules

service udev reload
sleep 2
service udev restart

解释下上面的代码
KERNEL==”ttyUSB* 不管是USB几的设备都进行判断
ATTRS{idVendor}==”10c4” 这里的idVendor ,在前面win上修改 serial的时候出现过,就是10c4

在这里插入图片描述
ATTRS{idProduct}==”ea60” 这里的idProduct,在前面win上修改 serial的时候出现过,就是ea60

在这里插入图片描述
ATTRS{serial}==”0002” 这里的serial,就是前面我们改过的,这里就根据这个值的不同,定义不同的设备别名
MODE:=”0777” 就是端口的权限
SYMLINK+=”stm32_controller” 这里的stm32_controller,就是取的设备别名。

所有上面第一行代码的功能就是,将满足这些条件的端口的设备名称改为定义的设备别名。

脚本的文件名称,取名为change_udev.sh。在执行前需要给这个脚本文件赋予权限。

sudo chmod 777 change_udev.sh

赋予权限后,再运行这个脚本

sudo ./change_udev.sh

这样就运行了设备别名的设置,之后不管怎么插拔这两个USB设备,系统都将会自动的将这两个设备去给它赋予设置的设备别名。

使用设备别名

在上面,设置了设备别名,下面来看如何使用我们的设备别名。

例如我们将雷达的USB的设备别名改为了2d_lidar

雷达的roslaunch启动文件则可以写成如下:

<launch>
  <node name="rplidarNode"          pkg="rplidar_ros"  type="rplidarNode" output="screen">
    <param name="serial_port"         type="string" value="/dev/2d_lidar"/>
    <param name="serial_baudrate"     type="int"    value="115200"/><!--A1/A2 -->
    <!-- <param name="serial_baudrate"     type="int"    value="256000"/> --><!--A3 -->
    <!-- <param name="serial_baudrate"     type="int"    value="1000000"/> --><!--S2 -->
    <param name="frame_id"            type="string" value="laser"/>
    <param name="inverted"            type="bool"   value="false"/>
    <param name="angle_compensate"    type="bool"   value="true"/>    
  </node>
</launch>

上面的代码中,
< param name=”serial_port” type=”string” value=”/dev/2d_lidar”/ >
这里,我们就将系统的设备别名/dev/2d_lidar,设置到了参数serial_port中

ROS与STM32串口通信代码

这里以一个智能车代码工程为例,抽取串口通信部分代码

在头文件中,进行串口头文件的包含

#include <serial/serial.h>

在类的定义中,什么一个 serial 类的实例

serial::Serial Stm32_Serial;  //声明串口对象

并且在类的定义中,声明两个结构体,用来存储接收和要发送的数据

        RECEIVE_DATA Receive_Data; //The serial port receives the data structure //串口接收数据结构体
        SEND_DATA Send_Data;       //The serial port sends the data structure //串口发送数据结构体

在类的构造函数中,配置这个串口对象的参数

  private_nh.param<std::string>("usart_port_name",  usart_port_name,  "/dev/stm32_controller"); //Fixed serial port number //固定串口号
  private_nh.param<int>        ("serial_baud_rate", serial_baud_rate, 115200); //Communicate baud rate 115200 to the lower machine //和下位机通信波特率115200

这两个参数是在launch文件中设置的,代码里进行参数的读取。
usart_port_name 设置的USB设备别名
serial_baud_rate 串口通信的波特率要和stm32设置的一致

  try
  { 
    //Attempts to initialize and open the serial port //尝试初始化与开启串口
    Stm32_Serial.setPort(usart_port_name); //Select the serial port number to enable //选择要开启的串口号
    Stm32_Serial.setBaudrate(serial_baud_rate); //Set the baud rate //设置波特率
    serial::Timeout _time = serial::Timeout::simpleTimeout(2000); //Timeout //超时等待
    Stm32_Serial.setTimeout(_time);
    Stm32_Serial.open(); //Open the serial port //开启串口
  }
  catch (serial::IOException& e)
  {
    ROS_ERROR_STREAM("car_robot can not open serial port,Please check the serial port cable! "); //If opening the serial port fails, an error message is printed //如果开启串口失败,打印错误信息
  }

初始化串口配置,并开启串口
设置的参数包括:

  • 要开启的串口号
  • 设置波特率
  • 超时等待

判断串口是否被打开,打开输出终端打印信息

  if(Stm32_Serial.isOpen())
  {
    ROS_INFO_STREAM("car_robot serial port opened"); //Serial port opened successfully //串口开启成功提示
  }

ROS主控读取stm32发送的数据

之后便可以通过

Stm32_Serial.read(Receive_Data_Pr,sizeof(Receive_Data_Pr));

read函数读取串口接收到的字节,之后通过定义的通信协议再进行和校验与数据解析即可stm32向ROS主控发送数据。

ROS主控向stm32发送数据

ROS主控向stm32发送数据的代码如下:

将之前定义的发送数据的结构体 Send_Data的tx 中填入要发送的字节

  Send_Data.tx[0]=FRAME_HEADER; //frame head 0x7B //帧头0X7B
  Send_Data.tx[1] = 0; //set aside //预留位
  Send_Data.tx[2] = 0; //set aside //预留位

填好字节后,直接通过下面代码发送即可

  try
  {
    Stm32_Serial.write(Send_Data.tx,sizeof (Send_Data.tx)); //Sends data to the downloader via serial port //通过串口向下位机发送数据 
  }
  catch (serial::IOException& e)   
  {
    ROS_ERROR_STREAM("Unable to send data through serial port"); //If sending data fails, an error message is printed //如果发送数据失败,打印错误信息
  }