你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

ROS主控与STM32建立通信软硬件的经验分享

[复制链接]
STMCU小助手 发布时间:2023-2-2 17:12
微信图片_20230202171230.png
# y, o% l4 \8 z5 m
3 V  j& @; g7 n4 g+ o6 Q
以简单的智能车为例,一般会存在两个控制器,一个是运行ROS的主控,另一个是运行电机控制和传感器信息采集的单片机比如STM32。
4 L2 p$ F; L% ^' I
微信图片_20230202171228.jpg
% p. ^1 G3 Y/ c
+ N- H; N9 _" c, g( k" V  E+ s8 @- I/ {
由于存在多个控制器,完成一个机器人的具体任务,那么这多个控制器间则需要建立通信,本篇博客主要讲解:
4 m8 V5 \1 b% ?" X0 K5 h& c  U
  • 如何实现ROS主控和STM32之间的通信
  • ROS主控对STM32发送过来的数据做哪些处理
    " u( p8 c5 D- Z' }8 E2 s$ h6 v

0 h9 X* W' w. v$ f4 Y) m( G( J
以智能车的应用例程展开

. B+ e: D" R* X" S

( e% h$ |8 Z3 i& a. J* N: z
智能车控制器功能
在智能车里存在两个控制器:
  • ROS主控
  • STM32控制器

    7 _1 h" Y3 Z/ F% C: u, m1 v( \" B
9 Q; G8 a# v$ \" u. C2 j1 ^
只要能将ROS跑起来就可以作为ROS主控,ROS主控可以是:
  • jetson 系列,例如 nano、tx、nx
  • 树莓派
  • 工控机
    : A3 {2 `" I1 U% t$ ?! }0 F
  }2 |0 ]1 v) }% _
在智能车里,ROS主控主要实现的功能有:
  • 雷达信息采集
  • 摄像头信息采集
  • 路径规划
  • 定位

    : h& M6 m( y" ?) e* i

4 Q0 ]  U- s- y! Q
STM32 控制器主要实现的功能有:
  • 里程计信息采集
  • 陀螺仪信息采集
  • 电机控制

    " u7 G, B" U" G. E& ^
+ ]9 w* M2 a( C* Q) L) A5 s
通信内容

3 i, S4 q& m- e, t
微信图片_20230202171224.png
0 W& n8 D! o. [* w4 o* @

9 q& w# }  k+ i+ Z
ROS主控负责接收stm32发送过来的传感器数据

: L7 r$ k; q& C: [1 _
数据有里程计、imu、电池电压。其中里程计就是电机的转速,通过编码器采集到。
' t* _, }/ S. o. E0 U8 N. \
STM32负责接收ROS主控发送过来的运动底盘的目标速度,STM32再完成电机转速的控制,最终实现小车的移动任务

/ c( ]/ i6 |- ~# H
ROS主控与STM32之间需要做到一个双向的数据传输,这里就涉及到了两个控制器之间的通信问题,下面则介绍如何实现两者之间的通信
! S' ~9 C& R% i+ e4 |, r
, v) H* S7 a: E. p+ }6 c
硬件连接
: A  Z' j" T# r) \" C7 ]# O; o
微信图片_20230202171221.png
( ]+ o- [' f' e

% ~# b3 u" |& Q: h
ROS主控通过usb线连接到一个TTL电平转换芯片,再由这个电平转换芯片连接STM32芯片

; J3 u% P: u! p: f
电平转换芯片可以通过PCB设计在STM32芯片的电路板上,也可以使用一个USB转TTL的模块。

; N) p1 H" T% R" ]
微信图片_20230202171218.png
& w4 E& b+ g/ b8 ]6 h

. X2 F0 F9 ^  q
微信图片_20230202171215.png
' k; P' Y! U8 g, {

" c: g" E# U1 M; _6 {3 i! `
为什么两个控制器之间需要电平转换芯片?

- r' o- r3 q: X! i- B! |& U0 f8 r因为两个控制器之间通信层次逻辑是不同的,所有需要电平转换芯片。相当于两个主控是两种不同语言的人,电平转换芯片相当于一个翻译。
! T. n2 ?$ w& _6 q
电平转换芯片可以是:
  • cp2102
  • ch340
  • PL2303
  • FT232RL
      X6 C3 }5 q( @$ f4 B$ n
1 m4 q5 K# a& J+ P2 T) }

. N! j, X6 _* t# H/ ?) G4 |
软件设置
硬件连接上之后,需要一个软件设置

$ g" K1 Y: \: F& }2 y( K
需要软件设置原因:
ROS主控可能接入多个USB设备,或者接入两个型号一样的电平转换芯片。

$ Y2 C) l: U5 r不同USB设备占用的ROS主控的端口号在每次上电时可能会不一致,这样需要手动修改代码中的配置参数,比较麻烦,也无法做到自启动。

  m! y  }2 `+ C6 @; @$ u/ D+ g2 P3 i如果存在多个USB设备,但是每种USB设备的电平转换芯片不一样,那么我们可以根据芯片名称来知道端口 号,但是如果有两个芯片一样的电平转换芯片,则无法区分,这时候想做自启动那么必须要进行下面步骤的软件设置

  w/ O0 @) f) d  i4 f6 G
软件设置分为两步:
  • 第一步是更改电平转换芯片的serial,
  • 第二步是创建设备别名

    2 B2 J! Y1 w' b" r( f3 A- T' T) n
. R0 v$ Z& _/ J  t5 z4 t- M7 q
更新电平转换芯片的serial
( K, I$ t1 \2 ~0 e# `7 b6 t, s" ~
首先在win环境下安装更改芯片serial的软件
& z- ]) {" ^1 @2 _6 H0 b7 E  }$ `! G
CP21xx Customization Utility.exe
3 l: h& w( J( y* A" D, s
这个软件在网上下载就可以

' Z" U- }1 l9 R: X( Z, E$ ?7 L
打开这个软件,然后将芯片连接电脑的USB
, r" f4 @  S# F" t6 G' t
微信图片_20230202171209.png
1 S8 `8 ?' R/ n/ T7 {" o8 X
8 l- i% f5 V) Z+ e2 S1 [( z) o& b
然后将圆圈位置改为0002

  N+ f) f5 X7 k
然后点击Program Device

; J8 V" T: R! n9 ?/ x
微信图片_20230202171207.png
  J1 e  z) |: O$ n' G
/ G% O  d0 B  L- X* y9 _: k
点完之后要等下,在Status Logging窗口中出现下面信息,才说明修改好了

1 K0 `" P; h9 f& H7 E2 {
微信图片_20230202171204.png
$ ^" d/ W" e: b0 v2 ]4 }
: ~. ~* V, P1 c3 e' ?8 f  F1 x
创建设备别名
' P9 C" o& [3 m3 F! Y
需要创建设备别名原因:

& k9 H( |' y6 ?4 z在运行一个ros程序的时候需要提供一个端口名,这个端口名一般是ttyUSBx,设备每次插拔对应的这个端口名它都会不一样,需要创建一个设备别名,就是要将这个端口名来给它固定住。

5 v% {" b; s8 y
微信图片_20230202171201.png
- `1 l7 S" H9 y6 E
8 X  a- P, r& L
重新插拔 USB1端口的设备后,变为:

$ ?& L8 ]3 b# p+ r5 [4 @8 Y3 D
微信图片_20230202171159.png / k: s: y: |5 I  E2 t

$ D( d7 r) f6 T( l' M: ]
可以看到变成了/dev/ttyUSB2
. I' L& _* K6 g% E; M
端口号发生了变化
% d  `( D) @2 T& e7 V4 C
创建设备别名需要写一个脚本文件,如下:
  1. 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& e6 n: A3 a/ i7 r$ L; c
  2. 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.rules5 h, N; l  Q- l) [/ z, t

  3. 0 s9 X1 J' j- o1 y: n1 ^+ M
  4. service udev reload
    4 Y' h# a3 a% t$ z) M
  5. sleep 20 W1 S& `; ^; m, P8 j! [: P$ T
  6. service udev restart
复制代码

1 G% D3 L2 S! y: M% O
解释下上面的代码
4 t* ^" p0 `% D5 P% V
KERNEL==”ttyUSB* 不管是USB几的设备都进行判断

0 ~' y% w- s/ V1 f6 k5 v5 {7 fATTRS{idVendor}==”10c4” 这里的idVendor ,在前面win上修改 serial的时候出现过,就是10c4
! p1 ?3 g4 ~3 ~8 P+ g8 |
微信图片_20230202171155.png 1 t9 |4 C; C! P. n6 [

2 @; L' v6 [5 i0 }4 i6 I
ATTRS{idProduct}==”ea60” 这里的idProduct,在前面win上修改 serial的时候出现过,就是ea60
3 S9 t& s- j2 I0 e
微信图片_20230202171152.png : `' b4 B2 o* m! Y9 E' ^) U
6 E& B" F3 }& G5 Q. n4 x6 y
ATTRS{serial}==”0002” 这里的serial,就是前面我们改过的,这里就根据这个值的不同,定义不同的设备别名
* j& ]. v6 T% K" e1 S; \5 g
MODE:=”0777” 就是端口的权限

/ M. S" Y/ P, C) h2 ]SYMLINK+=”stm32_controller” 这里的stm32_controller,就是取的设备别名。
, h4 q! I0 d" \, z' ]  I! f! E
所有上面第一行代码的功能就是,将满足这些条件的端口的设备名称改为定义的设备别名。

& J3 }; M1 F3 |! d0 M
脚本的文件名称,取名为change_udev.sh。在执行前需要给这个脚本文件赋予权限。
  1. sudo chmod 777 change_udev.sh
复制代码

: E) G. j  J. x; J" D, ?
赋予权限后,再运行这个脚本
  1. sudo ./change_udev.sh
复制代码

/ H, a) a' T1 W( t  l
这样就运行了设备别名的设置,之后不管怎么插拔这两个USB设备,系统都将会自动的将这两个设备去给它赋予设置的设备别名。
7 [9 r) t$ l) G$ I3 p- U
使用设备别名

. H+ i! k0 y0 l5 i" W( _: t$ s0 |
在上面,设置了设备别名,下面来看如何使用我们的设备别名。
3 e: u5 c; d$ R2 p) m
例如我们将雷达的USB的设备别名改为了2d_lidar
4 F! [: y( w' f; z- z
雷达的roslaunch启动文件则可以写成如下:
) m# K7 `$ }3 Y
  1. <launch>' _, h7 k  K. Y7 U$ Y2 B) ~. m9 X
  2.   <node name="rplidarNode"          pkg="rplidar_ros"  type="rplidarNode" output="screen">; X3 S, p% B2 d& T9 S
  3.     <param name="serial_port"         type="string" value="/dev/2d_lidar"/>, p9 }1 S; v4 ~" K; k/ l
  4.     <param name="serial_baudrate"     type="int"    value="115200"/><!--A1/A2 -->
    * e+ k& `# A2 e3 O
  5.     <!-- <param name="serial_baudrate"     type="int"    value="256000"/> --><!--A3 -->
    4 z( Y/ h, J- X# j) n
  6.     <!-- <param name="serial_baudrate"     type="int"    value="1000000"/> --><!--S2 -->
    ' T7 _8 w$ _9 v2 d9 i
  7.     <param name="frame_id"            type="string" value="laser"/>" j, j( M2 d& I: y2 I4 {. E
  8.     <param name="inverted"            type="bool"   value="false"/>
    % A  x2 S# S# t$ d' O
  9.     <param name="angle_compensate"    type="bool"   value="true"/>    7 ^$ t* W# E! |5 |: M# [" k
  10.   </node>! @7 d& M; t( u& r& P, |
  11. </launch>
复制代码

2 U' s) Y# f. n  k" l: _& y+ z1 S
上面的代码中,
  1. < param name=”serial_port” type=”string” value=”/dev/2d_lidar”/ >
复制代码

6 S% z( d8 {& y- B+ _; c! i
这里,我们就将系统的设备别名/dev/2d_lidar,设置到了参数serial_port中
5 X0 w+ Q3 `3 p

" l% c% k. z/ L( ^6 |& I
ROS与STM32串口通信代码
! |% K$ w: `; l/ F  A
这里以一个智能车代码工程为例,抽取串口通信部分代码

0 R2 d6 U2 s- u
在头文件中,进行串口头文件的包含
  1. #include <serial/serial.h>
复制代码
: E; O4 P& [6 R% J& p8 X
在类的定义中,什么一个 serial 类的实例
  1. serial::Serial Stm32_Serial;  //声明串口对象
复制代码

6 J; J. q6 ?# y  ]' p4 n
并且在类的定义中,声明两个结构体,用来存储接收和要发送的数据
  1. RECEIVE_DATA Receive_Data; //The serial port receives the data structure //串口接收数据结构体
    - c8 n( ]7 V/ J* [3 e' P
  2. SEND_DATA Send_Data;       //The serial port sends the data structure //串口发送数据结构体
复制代码
7 P4 `5 H$ o) B& u! _
在类的构造函数中,配置这个串口对象的参数
  1. private_nh.param<std::string>("usart_port_name",  usart_port_name,  "/dev/stm32_controller"); //Fixed serial port number //固定串口号
    / H) S4 v1 v. I
  2.   private_nh.param<int>        ("serial_baud_rate", serial_baud_rate, 115200); //Communicate baud rate 115200 to the lower machine //和下位机通信波特率115200
复制代码

+ v% R/ ]+ l+ v' K, ^
这两个参数是在launch文件中设置的,代码里进行参数的读取。

# `4 I0 j/ |% {/ w& X/ Dusart_port_name 设置的USB设备别名

% O: g5 ~0 x/ \0 S' R# Qserial_baud_rate 串口通信的波特率要和stm32设置的一致

& c4 @  r1 Q  G
  1. try
    7 q; h5 x6 f8 n: n8 z2 i
  2.   { ! W: u2 A3 Q$ `: a* r
  3.     //Attempts to initialize and open the serial port //尝试初始化与开启串口; Z* ]0 _- L6 R' q3 ]
  4.     Stm32_Serial.setPort(usart_port_name); //Select the serial port number to enable //选择要开启的串口号* T8 {9 o4 y0 L0 @) Q
  5.     Stm32_Serial.setBaudrate(serial_baud_rate); //Set the baud rate //设置波特率
    $ l) {% O' e! i5 ~
  6.     serial::Timeout _time = serial::Timeout::simpleTimeout(2000); //Timeout //超时等待+ K4 c9 a# i+ f8 J4 `+ I$ O
  7.     Stm32_Serial.setTimeout(_time);
    " ]/ |) r6 S1 z
  8.     Stm32_Serial.open(); //Open the serial port //开启串口7 x2 h0 K# ^+ P' t. u, l8 S
  9.   }" U7 O( k9 d! R' o
  10.   catch (serial::IOException& e)6 G& L1 C- G, P$ ]' _( z  R
  11.   {" D6 h' `2 `5 N9 n$ E- F
  12.     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 //如果开启串口失败,打印错误信息+ z& ]( |0 g4 v/ ?- I! A
  13. $ ~7 |  y4 w1 y% v: I7 W
复制代码

( \) v+ C9 k3 Z
初始化串口配置,并开启串口
( I. I- z) h. |. C0 _
设置的参数包括:
  • 要开启的串口号
  • 设置波特率
  • 超时等待
    " R8 Z& X" l; }% n
8 H+ ~. M. E. y5 x. v
判断串口是否被打开,打开输出终端打印信息
  1. if(Stm32_Serial.isOpen())
    ! o, Q2 O8 A# c; `2 {. W. `4 n0 [* Q
  2.   {
    7 V" D: H- Y1 t1 t, x7 W' t9 v7 Q7 I- }
  3.     ROS_INFO_STREAM("car_robot serial port opened"); //Serial port opened successfully //串口开启成功提示
    5 b8 p' U2 v# ?: G- B
  4.   }
复制代码

2 g8 z0 j! {' y, \+ _6 {( E. c& n
ROS主控读取stm32发送的数据
$ W- Y" D+ P. u+ H, ^1 g! Y
之后便可以通过
  1. Stm32_Serial.read(Receive_Data_Pr,sizeof(Receive_Data_Pr));
复制代码

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

$ p* d8 L1 l( A
ROS主控向stm32发送数据

# J0 w. O: T0 H
ROS主控向stm32发送数据的代码如下:
% L6 {6 J: W. ^" i
将之前定义的发送数据的结构体 Send_Data的tx 中填入要发送的字节
  1. Send_Data.tx[0]=FRAME_HEADER; //frame head 0x7B //帧头0X7B" e; L; g8 s# \, ^( b4 _0 [
  2. Send_Data.tx[1] = 0; //set aside //预留位
    ; I' E/ V* f% ?' y' w) |- w
  3. Send_Data.tx[2] = 0; //set aside //预留位
复制代码

& W: j+ `: m( U9 @5 p, c/ h
填好字节后,直接通过下面代码发送即可

* R1 x% j7 u* i% S! O
  1. try
    / g1 u) M% L# n1 a3 h- T
  2.   {0 [, U6 v6 k5 q/ i* T
  3.     Stm32_Serial.write(Send_Data.tx,sizeof (Send_Data.tx)); //Sends data to the downloader via serial port //通过串口向下位机发送数据
    # u- L5 S3 ~1 a
  4.   }9 g: e' o2 R7 ], O/ W8 b, G
  5.   catch (serial::IOException& e)   
    + ~- c* \5 S9 R/ l7 Y( b" H
  6.   {
    + _- Y" [& m" T  E# {$ p1 L
  7.     ROS_ERROR_STREAM("Unable to send data through serial port"); //If sending data fails, an error message is printed //如果发送数据失败,打印错误信息# I5 B- @/ {7 z" X
  8.   }
复制代码
收藏 评论0 发布时间:2023-2-2 17:12

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版