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

RT-Thread+STM32实现智能车目标识别系统的教程

[复制链接]
gaosmile 发布时间:2020-5-14 17:29
引言

0 g2 }  f' t( x这篇文档主要介绍 RT-Thread 如何使用串口或者无线和 ROS 连接,会包含这么些内容:
第一部分:ROS 环境搭建
第二部分:RT-Thread rosserial 软件包
第二部分:RT-Thread 添加 USART2 和 PWM
第三部分:RT-Thread 使用 ESP8266 AT 固件联网
  ]( }; E; @# c9 |/ b8 [( B
这里先介绍一下什么是 ROS?为什么要和 ROS 连接?
机器人操作系统 ROS (Robots Operating System) 最早是斯坦福大学的一个软件框架,现在不管是工业机器人,还是娱乐用的机器人都运行着 ROS。
  j9 w5 @. A; L5 @; ]
微信图片_20200514172615.jpg
图片来源网络,如有侵权请联系删除
一个机器人通常有很多个部件、传感器,为了保证机器人不会因为某一个传感器故障,导致整个系统瘫痪,所以采用了分布式的节点,利用不同节点之间的通讯收集传感器数据和控制指令,这篇文档后面会使用到的通讯协议就是 rosserial。和 ROS 连接的好处在于,一方面由 ROS 管理各个机器人节点更稳定,另一方面 ROS 现在已经有了非常多成熟的软件包,使用 ROS 就可以非常方便的为自己的机器人添加摄像头图像识别、激光雷达建图导航等高级功能。不过这篇文档只会涉及 RT-Thread 和 ROS 建立基本的连接,实现小车的运动控制,之后可能会有后续文档介绍如何连接激光雷达建图,并进行全局路径规划。
这篇文章假定大家都已经会用 RT-Thread 的 env 工具下载软件包,生成项目上传固件到 stm32 上,并且熟悉 Ubuntu 的基本使用。

  a) @: j& q! K/ J
1 ROS 简介: T8 k) ]* u9 d3 f

; h+ }* r+ n$ D; I3 Z% K
这里的开发环境搭建其实是需要搭建 2 份,一份是小车上的 ARM 开发板 (树莓派,NanoPi 什么的),另一个则是自己的电脑,因为我们希望把电脑作为 ROS 从节点,连接到小车上的 ROS 主节点,不过开发板和电脑的 ROS 安装是一模一样的。
! P% W/ b( |2 @6 }5 d5 {
既然要和 ROS 连接,那么首先就得要有一个正常运行的 ROS。安装 ROS 其实非常简单,这里推荐使用 Ubuntu 18 (开发板推荐系统用 Armbian),因为官方对 Ubuntu 的支持优先级是最高的,安装教程也可以参照 官网:http://wiki.ros.org/melodic/Installation/Ubuntu
( y2 O( _1 d, y) R  l  O3 H$ x/ ?& r( b2 |  {

1 }' v5 d* z4 o$ M* H只需要输入下面的 4 行命令,就在 Ubuntu 上装好了 ROS。2 @) q/ o* M% @; r* q7 f
1 sudo sh -c 'echo "deb http://mirror.tuna.tsinghua.edu.cn/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
$ j* U& j, d; e2 B, N7 H  U
3 e! t: p4 B8 S
2 sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C6545 v6 r' P5 G9 T
3 sudo apt update
0 U) [2 s  {/ r. U" `
4 sudo apt install ros-melodic-ros-base
- b3 I9 c/ a6 w( b) {& Q) f
" p$ c6 M' `( N* v- v. R上面我使用了清华大学的镜像源,这样从国内下载 ROS 会快很多,而且我只安装了 ROS 的基本软件包,没有安装图形化软件包 gviz,gazebo 什么的,因为后面也没有用到。
( J7 X( W+ F7 M3 ?+ a
5 \1 N! B+ k  Q2 j1 u1 ]6 c" l
- f7 @% N- G# q5 Y* s; i8 G  I) z; v
1.2 ROS 环境初始化
+ D7 X, i) m& Z% Z, [% D/ J8 u  D5 x' Y# g5 s' j) _
; L$ I7 r# ?8 f# d+ \4 v- z  `
ROS 安装好之后还需要进行初始化,不过也是只有短短几行命令:
& B; I8 N( j0 Y& q7 u( U1 sudo rosdep init
" U' \- Y) {5 p5 Q+ D) A
: t6 E' M- R, p8 ^& v) P  R
2 rosdep update
; C, G/ O. F0 ~. G& P3% |. M$ ~/ R+ i7 h3 n
4 echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc
+ Q9 J: ?, x* f! K2 V; C5 source ~/.bashrc
2 O6 G) b3 a, u0 A

- k5 o9 s, ^1 t, v; k( s3 T1.3 启动 ROS  Z1 E2 E3 ^* [9 G" j
7 d$ ^$ s1 m6 R4 H4 F
启动 ROS 的话我们需要确保它是常驻后台运行的,所以我们可以使用 tmux:8 f# I" U2 [* `7 t- H
1 roscore, V1 B- {, j1 R& ]1 U, n6 F$ R* N: x

, F' T0 ~( R; N0 j& P
在 tmux 里启动了 ROS 主节点后,我们就可以 Ctrl + B D 退出了,而 ROS 主节点依旧在后台运行。
: U& G9 m' Y9 H& V( R
1.4 参考文献2 RT-Thread 串口连接 ROS1 u$ ~, y5 [. o: r+ d4 A" Y+ ?
这一部分会介绍如何使用串口将运行着 RT-Thread 的 STM32 开发板和运行着 ROS 的 ARM 开发板连接,看起来差不多就是这样。
9 y5 K$ d1 L/ G+ v* {, i
微信图片_20200514172618.jpg
这里说明一下不同开发板的分工,STM32 运行着 RT-Thread 负责控制电机,接收传感器信息;ARM 运行着 ROS 负责进行全局控制,例如给小车发出前进的指令。
( _2 E2 O7 k$ N$ ^/ j
; B  t- ]% O' @& F, j2.1 RT-Thread 配置- o. W* }2 R, H
. i# U3 K7 O2 Q  D, I

0 _+ G/ I4 [( x* k7 c首先我们需要打开 usart2,因为 usart1 被 msh 使用了,保留作为调试还是挺方便的。
3 e6 Y1 S6 y* ` 微信图片_20200514172621.png
' C: @. I1 y9 p0 H  k( y7 u
: H/ q* \8 {' {1 h; F+ j) ?5 g
6 e; f5 n- x6 |7 j  c$ Z3 b, K
在 CubeMX 里我打开了 USART2,另外还打开了 4 路 PWM,因为我后面使用了 2 个电机,每个电机需要 2 路 PWM 分别控制前进和后退。接下来还需要在 menuconfig 里面打开对应的选项,考虑到有的开发板默认的 bsp 可能没有这些选项,可以修改 board/Kconfig 添加下面的内容。
1 N- ?6 m- j; P: N! G+ w
, M6 I0 c; X/ ~, l
4 ~. M" b* p) C1 }2 A
串口的配置:
" ]) g% ^3 k8 }- v* r5 M. l1 menuconfig BSP_USING_UART
1 F' K' G. T) M1 Q

, D. H- M( W# @6 z( o 2    bool "Enable UART"2 G* c+ C0 Q0 J
3    default y
: f9 ]  X# P; a! x 4    select RT_USING_SERIAL
) h' S* S: }- a4 @/ a, _/ m/ T, B 5    if BSP_USING_UART/ W3 E4 y4 u: G4 r" G4 ]0 @" ?& e
6        config BSP_USING_UART1# u8 d8 ]6 e3 t" @' z, G% D3 K
7            bool "Enable UART1"
  Q) |* O  Q* v+ b 8            default y. P) |  m& D% E6 y* x- I
9
2 f9 q  q  \6 f3 ]10        config BSP_UART1_RX_USING_DMA& y% O6 w6 `, e* H5 n
11            bool "Enable UART1 RX DMA"0 C1 z7 U% s3 j+ z
12            depends on BSP_USING_UART1 && RT_SERIAL_USING_DMA
4 o, c2 X5 Q2 r$ q: Y. O" A  `. s( d13            default n- z+ J5 o+ C5 V% N; o9 D+ Z& l
145 s& s* M" u& ~6 _) R
15        config BSP_USING_UART2
) q& p# u5 D% C1 _3 G16            bool "Enable UART2"& ?  \% ?5 y- O$ U# I; \! H9 ~  m
17            default y' l. O; `/ u; y: v; W
18
+ t7 w* |. j) @6 h/ q: _! t8 M19        config BSP_UART2_RX_USING_DMA
0 N# o) S) G' l20            bool "Enable UART2 RX DMA"
6 p" J' f% G5 s; q+ q21            depends on BSP_USING_UART2 && RT_SERIAL_USING_DMA
3 [" J% C, m* T/ F& u22            default n+ ~0 q4 J# X" `* P. ?5 r/ ~' e. w) }
23    endif
: z! H# ^" z0 A- F

) Z5 L: V; g2 I5 F) O( R" xPWM 的配置: 8 ]3 J. i( c) C
1 menuconfig BSP_USING_PWM
: B5 ^( x3 z: O% H$ ^
0 X/ K4 W7 R' s( H- M) J2 g
2    bool "Enable pwm"
. q: G1 r/ k* }9 U 3    default n
; q1 Q0 V' u- _" D% b) V4 C, E& N 4    select RT_USING_PWM2 w6 ^0 C( E1 T
5    if BSP_USING_PWM) W  ]! R  \2 |5 e7 C
6    menuconfig BSP_USING_PWM38 g; I0 A0 H: k7 Z9 @
7        bool "Enable timer3 output pwm". _3 O4 o9 C  k7 z6 S
8        default n
3 j6 ^8 U: n# c; a, O 9        if BSP_USING_PWM3
! Q; c: r+ ?7 Z$ c- X# q0 n; R10            config BSP_USING_PWM3_CH17 b7 f7 ~5 @3 a/ \9 v2 l
11                bool "Enable PWM3 channel1"" i+ h# m' v; F2 N* [4 `
12                default n' D: u/ b9 c8 A- _
13            config BSP_USING_PWM3_CH2
, _3 E2 r+ ?$ b' H5 N! }5 ^+ G14                bool "Enable PWM3 channel2"
. K3 |6 H' s8 \! D9 k" r15                default n
9 m/ H" q9 j/ b8 \$ X( C- i1 Z7 M8 Y16            config BSP_USING_PWM3_CH3
4 Y) r) T! Z- Z/ x& n0 h17                bool "Enable PWM3 channel3"/ G, T/ u% L: t4 c- Q( T2 m
18                default n
/ l( w: G) m) ~9 l# W9 r19            config BSP_USING_PWM3_CH4
, N/ M& m4 ~0 H! j20                bool "Enable PWM3 channel4"
6 t( A" _+ V; O# v/ J; b$ [21                default n4 w; n8 b6 h8 |
22        endif- `) i- ^8 K) c9 ^
23    endif

0 U9 ]4 K$ p; O7 k" Z! Z% O: w& g0 [* }
这样我们在 env 下就可以看到有对应的配置了,
微信图片_20200514172623.jpg
微信图片_20200514172626.jpg
除此之外,我们还需要选择 rosserial 软件包:$ j( b0 F$ [, D4 }+ M5 Y
微信图片_20200514172629.jpg
) J5 E1 h# C3 d1 a/ ]
可以看到上面默认的串口就是 USART2,这样我们就可以生成对应的工程了:  Y) y' c$ s2 D, V+ C% v, P
1pkgs --update
5 O, ]  h4 p( y1 [# _- W- H$ P1 g8 [" U2scons --target=mdk5 -s
. s( k2 M2 c3 t" A# \
4 c  R- Y* e- u9 t1 J4 p

2 n/ @2 B! W9 U- f如果我们打开 Keil 项目,首先需要把 main.c 修改为 main.cpp,因为 rosserial 很多数据格式的定义都是用 C++ 写的,所以如果要使用 rosserial 库,我们先得把后缀改为 cpp,这样 Keil 就会用 C++ 编译器编译。" r! y+ `8 N/ J
微信图片_20200514172632.jpg
下面是 main.cpp 的内容,其实就是初始化了电机,然后发布了 2 个话题 (topic),一个是 /vel_x 告诉 ROS 当前小车的速度,一个是 /turn_bias 告诉 ROS 当前小车的旋转速度。同时又订阅了一个话题 /cmd_vel,用来接收从 ROS 发出的控制指令。' k8 R7 L8 X6 P3 l$ Y) w$ l
6 Q$ T: b# n/ G
3 u4 @, }% B/ `
代码不是特别长,我也添加了一些注释,所以这里就不一行行分析了。
- G/ ]; n1 E5 y1 #include <rtthread.h>
5 x" U7 G8 D4 j" |
2 k: n0 \3 `$ u' J
2 #include <rtdevice.h>& Y5 k% }1 l( C: R8 d. `
3 #include <board.h>
; K  h# J0 ^" g8 N6 N" L 4
* Q; h* L4 }0 M' F 5 #include <ros.h>1 N+ e4 ^; o" R* u+ u
6 #include <std_msgs/Float64.h>
; [0 ]4 `, u$ }. V 7 #include <geometry_msgs/Twist.h>: i8 H% r7 ~; g$ I& m7 F. N. C
8 #include "motors.h". Q$ m1 u" H# |9 ~+ f7 S1 x
9; k3 r. C; g7 N) r2 u! N; j- r
10 ros::NodeHandle  nh;
$ y  d# Y; J' }! R11 MotorControl mtr(1, 2, 3, 4);   //Motor
6 }7 ]% t( h' q& |  z126 K8 X( Q1 K8 P6 }8 q+ X0 Y
13 bool msgRecieved = false;
  G% c! g( l  f8 c6 d3 W& _14 float velX = 0, turnBias = 0;
( Z8 Y5 ?" q$ v% W1 f15 char stat_log[200];: v; Y4 @3 J6 [
16% q% F" e: I$ e2 `8 p
17 // 接收到命令时的回调函数4 A% p- e# z5 I4 ~% S
18 void velCB( const geometry_msgs::Twist& twist_msg)
! r0 J# H- t' \! D5 x19 {9 I+ d' g' o' Q; {7 c! f2 P
20   velX = twist_msg.linear.x;* J( K! p% C" M7 y* R. ~  w
21   turnBias = twist_msg.angular.z;( f6 m0 V$ v) y2 h" f" x
22   msgRecieved = true;1 e% x) i' F/ ]$ A
23 }0 j! ]" F" i# u; S: @5 H& r
24  s( o* z! P! _( G
25 //Subscriber+ w4 o! A4 D" R; z9 P2 C' K6 j) \' {
26 ros::Subscriber<geometry_msgs::Twist> sub("cmd_vel", velCB );
4 b# B& `: o5 C9 C0 n27' i9 r+ E0 A: ?* \
28 //Publisher+ @# |9 n, M0 I# Z; x
29 std_msgs::Float64 velX_tmp;
5 J- m3 d  k* R7 R# K, q2 w# w30 std_msgs::Float64 turnBias_tmp;
0 D1 r6 v* A' X$ p& W31 ros:ublisher xv("vel_x", &velX_tmp);
  y% `$ x8 T9 @3 A8 U32 ros:ublisher xt("turn_bias", &turnBias_tmp);
# n4 ?* f% f, V2 W33
8 E$ i3 O+ a- C; P& x; ]9 J34 static void rosserial_thread_entry(void *parameter)
4 J) [  W' @: _6 t35 {: t! T0 D+ T. E* u& J
36     //Init motors, specif>y the respective motor pins
/ z3 j" j8 B3 j- S" ~7 d37     mtr.initMotors();
/ G/ g7 I/ T3 V# d, T# U& z8 t) J4 N38: ?, ]. P1 s8 G: D7 N
39     //Init node>
0 K2 v: R$ d: u, Q40     nh.initNode();8 z) @9 p( S# g& K# p: M1 F
412 K# L6 S$ y4 i- @3 r( t8 ^4 K
42     // 订阅了一个话题 /cmd_vel 接收控制指令
  T$ C. O. d' `5 t; _/ w$ u( c! {43     nh.subscribe(sub);
, L. ^; K6 ?1 h- w3 i/ d448 k' W; i$ q: N
45     // 发布了一个话题 /vel_x 告诉 ROS 小车速度" o3 t8 l( {; {$ k
46     nh.advertise(xv);
6 T! U& J! h8 v/ [4 g47) }) k0 v4 P0 [0 S
48     // 发布了一个话题 /turn_bias 告诉 ROS 小车的旋转角速度1 e9 u5 M) Z7 c7 o5 h  p: v; v; Z
49     nh.advertise(xt);" B$ l0 T$ R! B4 C
50
! n7 |$ o* |; m, o& }; O2 s51     mtr.stopMotors();+ r9 }, E0 C, ?
52
* B5 _/ M9 q# E1 h) @53     while (1)% d  t8 @# _2 j
54     {! n& j# U4 H7 H0 I, Y* ?9 R1 ?5 r
55       // 如果接收到了控制指令  [2 \- S; b+ ?( v; H* o8 V
56       if (msgRecieved) 8 S7 z4 @8 J: l9 y* ~8 X) B8 L0 b  ^
57       {  l$ f8 [' q" W) q# `9 K
58         velX *= mtr.maxSpd;
, S0 O2 \+ b) }: R59         mtr.moveBot(velX, turnBias);
! e8 R9 W/ U, b4 P3 E  W, H60         msgRecieved = false;0 A$ p  [  _0 X" e8 t& ?4 j
61       }8 M3 n$ o( K& y, a
62
/ u  o! S* K# K0 V: v$ }; |) L63       velX_tmp.data = velX;
9 M7 |+ I$ s9 S% f1 L0 ^- C64       turnBias_tmp.data = turnBias/mtr.turnFactor;
/ E% U( h- }6 L656 z7 a, p5 G* @" G
66       // 更新话题内容
) J- ]3 W2 I1 N" p' Y- g67       xv.publish( &velX_tmp );
* \; Q6 M! D. J0 k; a8 B* \0 d68       xt.publish( &turnBias_tmp );- B! s8 ?% d' b8 Q/ @
692 A6 X7 H( M" U) D- D. {5 B
70       nh.spinOnce();
: h3 Z% o% o- z0 @  p71     }; K  R  \) N: u7 F2 P
72 }
2 z$ R5 I( b5 x' e* g8 S6 k5 q73; k" R0 w( l4 ]( U+ S" Z6 V8 v$ h
74 int main(void)
8 w9 b6 i/ j* A4 e+ p) F: d1 U75 {
, R0 ]6 T+ E7 q* {76     // 启动一个线程用来和 ROS 通信3 l5 W- A" e4 `
77     rt_thread_t thread = rt_thread_create("rosserial",     rosserial_thread_entry, RT_NULL, 2048, 8, 10);
3 {3 _7 i8 O! g3 }! H9 X) G78     if(thread != RT_NULL)
7 N+ }3 {2 |' N# m# Y79     {9 ~- i9 k9 J& T" t# i* T% A
80         rt_thread_startup(thread);7 ^( w  t8 Y1 _; M0 t! ~) c
81         rt_kprintf("[rosserial] New thread rosserial\n");, h3 P2 P1 M, S9 M
82     }
6 F; \6 }! R* \: `83     else6 a4 m$ g! I* H6 |$ g  }
84     {
$ E" v6 R) |4 U7 L85         rt_kprintf("[rosserial] Failed to create thread rosserial\n");
+ j- \1 n( k) q: y; N" X86     }
" I  h7 P# m( O$ d2 j6 H87     return RT_EOK;
" V" K4 i2 }$ q) L+ x2 k! b
88 }
2 R! _8 \, c" k" T7 s+ I- N; ?( ^3 d( M+ L0 j
另外还有对应的电机控制的代码,不过这个大家的小车不同,驱动应当也不一样,我这里由于小车电机上没有编码器,所以全部是开环控制的。

6 ], i" z; I, w8 Q* G5 Q3 Y
motors.h
- G& T" R# b- x- ]& D" k; j' |1#include <rtthread.h>! v+ C; Y2 U+ U! |+ |. s" D
: L  d7 \# r. A0 M! o9 ]- l
2
1 R3 n0 G/ c  I4 h6 O" e3 ] 3class MotorControl {6 S  r: L" A% B. r
4  public:
9 l7 c* Y; J" s2 ]# X! z. @ 5    //Var0 L8 q  o5 ~9 e! x) Q: [1 y
6    rt_uint32_t  maxSpd;
) d/ o" `* d5 L9 x" W$ b4 ] 7    float moveFactor;9 {+ K" f: H: m, N& S& `+ N
8    float turnFactor;
! E$ F8 ?0 e/ P" C/ ~. ~2 s 9; H- Z7 t  N" P
10    MotorControl(int fl_for, int fl_back,
5 ^2 Q, w0 Y. }  u, }11                 int fr_for, int fr_back);+ b/ s0 ^5 e# c9 ^. C
12    void initMotors();/ c. f0 A. O7 Y# v: ^7 h
13    void rotateBot(int dir, float spd);6 H4 T4 j- u; p( A7 q6 \
14    void moveBot(float spd, float bias);: j: f- a% W. a, c( {( q
15    void stopMotors();, ]5 A; j$ x1 E
16  private:4 d& Y$ Q, ]2 J! [  a4 w
17    struct rt_device_pwm *pwm_dev;
# f, R5 @5 n6 w8 O18    //The pins0 p6 P9 L" w) M+ @3 d
19    int fl_for;% L% M5 W2 {) G3 r  }* B
20    int fl_back;
9 D) N$ M/ e+ `1 N( w) K21    int fr_for;
. g( v0 G7 m+ J1 J- G; M22    int fr_back;
$ u+ o, D6 z9 k/ i' E23    int bl_for;0 J( l9 S+ N9 o7 ~, B( Z
24    int bl_back;! q# o+ ^. z8 l  z' ^& ^4 Z9 S
25    int br_for;
* Z1 E4 ?8 J; b26    int br_back;1 E$ ^  R0 P* n8 [: U, x
27};

1 g. G# w7 V/ d7 G/ \
9 e* ?1 W& d8 O' G% qmotors.c  
2 b* F- e& G/ t4 C% W1#include <rtthread.h>
$ e, ]. t% P/ N; A; b
- G5 L: ?5 k2 h/ v  i! ~
  2#include <rtdevice.h>
8 d" M) L3 b" @# h: \3 M  3#include "motors.h"# e$ U1 o2 {8 ^3 c* a
  4
; j% N+ O, a/ ?2 V, o  5#define PWM_DEV_NAME "pwm3"
' Y) n/ E" y, ~1 @) v  6
; n3 R9 m3 F4 [  7MotorControl::MotorControl(int fl_for, int fl_back," T) ^0 O6 q/ O5 Q5 \5 H
  8                           int fr_for, int fr_back) ; q) ?- f+ u" Q6 T
  9{. Y* Y; v, j  I" V' M$ v5 _
10    this->maxSpd = 500000;) q1 L0 F7 w: b& Q% o3 h2 `+ n; W
11    this->moveFactor = 1.0;
6 _& T* }  b: U0 ^5 b 12    this->turnFactor = 3.0;. ^- {" K' a: Q9 C% o. i
131 Y& Z$ c) O! L) z$ x
14    this->fl_for = fl_for;
# [( r( j* \/ `0 ?& } 15    this->fl_back = fl_back;. z, H6 Y1 H; o. G$ e
16
1 Q+ _  W3 O& S1 I 17    this->fr_for = fr_for;1 ?# M6 W$ L: O0 |+ u' s5 x
18    this->fr_back = fr_back;
8 l) N: U4 [( a2 x 19}2 a$ J1 L) c: k
20
3 F" F. ]% I, d  t0 i 21void MotorControl::initMotors() {
+ p' o: E5 ]  N; H  w* u 22    /* 查找设备 */1 p" j- a4 [6 L: q" O1 q! Y
23    this->pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);0 c/ k( b" Y' w
24    if (pwm_dev == RT_NULL)
) ~8 F6 b, \! t/ x, r' M 25    {
% ~  u2 o' o- p! W$ r1 [6 R 26        rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM_DEV_NAME);
7 A! ]# N$ Y" G4 {6 M 27    }: m# A* ~7 f: k. o# n2 r4 P& f
28    rt_kprintf("pwm found %s device!\n", PWM_DEV_NAME);
) \( S4 K: J* ?- g6 s9 A& { 29    rt_pwm_set(pwm_dev, fl_for, maxSpd, 0);6 r2 j7 _+ o$ ?  n% n+ E! l+ f
30    rt_pwm_enable(pwm_dev, fl_for);
* v5 f( q: a) Q/ C& P 31$ i3 `" L, s6 g9 H  d
32    rt_pwm_set(pwm_dev, fl_back, maxSpd, 0);
( k  H5 }3 P2 @4 | 33    rt_pwm_enable(pwm_dev, fl_back);% `# |2 Y1 I* [/ T- x" R
34
% D1 M9 `# q* d: m9 l 35    rt_pwm_set(pwm_dev, fr_for, maxSpd, 0);
/ ]% o: _5 J4 ^( {. s 36    rt_pwm_enable(pwm_dev, fr_for);
" t; ?+ x% D0 I6 E$ a$ j9 L6 l 37' [) R: |4 A, {. N  D8 v2 K% ?* D
38    rt_pwm_set(pwm_dev, fr_back, maxSpd, 0);
) h/ P- E  y. @) ~9 S 39    rt_pwm_enable(pwm_dev, fr_back);. q$ h" p3 _6 S1 l
40}& m. _' a- P* j% t. u1 i& n! ~. {
41! U0 X2 [: C* i& h) z9 T& X8 c
42// 小车运动! h) |9 e/ O9 G6 u% K$ U
43void MotorControl::moveBot(float spd, float bias) {& k. Q" \9 Q! _5 K) S
44    float sL = spd * maxSpd;: \5 }7 w2 E9 x9 B7 h& x- ~9 j. `
45    float sR = spd * maxSpd;
: Z: h7 c1 ]) Y6 A 46    int dir = (spd > 0) ? 1 : 0;! C3 l  @/ W1 \$ o9 U3 p2 I
47- p4 d7 G4 c* F
48    if(bias != 0)
( I3 u. q: _0 {2 F 49    {
6 P" v* P1 }6 B, d: J- Q# [ 50        rotateBot((bias > 0) ? 1 : 0, bias);
. G. w+ @, J- z# i/ P 51        return;+ W$ Y! w, S  x; {
52    }& i' D9 X" n# B$ c9 o+ R+ x6 G
53
$ J4 {7 g( s- G, P9 m- Q" Q 54    if( sL < -moveFactor * maxSpd)+ n: P7 L7 F$ Z" Z
55    {
2 H1 Z& l/ Q2 q) J 56        sL = -moveFactor * maxSpd;
* {% d1 B  i, Q# H2 a+ o- J- C1 i) R 57    }
: [( ?' H3 x( t0 F 58    if( sL > moveFactor * maxSpd)! W+ g. |# c- l0 D- ^5 a2 z
59    {
* Z  W5 K+ K2 K! e" n# E; G1 n 60        sL = moveFactor * maxSpd;
8 H, \  G; e( M" S2 ^' ~3 l  G 61    }: V( }, S. Q- O  L! e' W
62
# W+ O0 _" k6 k3 ?  C7 t; j 63    if( sR < -moveFactor * maxSpd)
1 n& a* M* A5 w: W- C 64    {
! W- r- K, A7 E( d. k8 m0 M 65        sR = -moveFactor * maxSpd;
+ r, x7 A5 j+ N0 ^# K 66    }6 z- [! g0 |. y+ z* h- L
67    if( sR > moveFactor * maxSpd)
( t8 W2 O5 l1 |: @. Z& h 68    {
/ s+ ^  B) ^0 Q# w! u( {7 _! W1 e 69        sR = moveFactor * maxSpd;/ c$ T# U$ s1 y$ e& a! N0 V) ~
70    }) b9 z" t2 F) Z1 G; N/ q# S
71
5 ^7 r5 V$ c! u$ J, [ 72    if (sL < 0) 9 L* S* @) p' |8 K+ x
73    {! e2 B- Y; u- v% d6 r) G% \; i
74        sL *= -1;* O) g0 r( @, E, a/ m
75    }
/ T) H' `2 [! ]* p 76
, ~) ^% R1 x2 L+ \8 c 77    if (sR < 0) ' j" _( g) a5 U2 _1 |* ]0 u
78    {, u2 v2 W1 q( B( e) R3 ~/ Q1 q
79        sR *= -1;4 |: p% |; l) ~( B
80    }
. ^& B9 O- G: z 81
1 v/ v4 c$ ?% I! @! z 82    rt_kprintf("Speed Left: %ld\n", (rt_int32_t)sL);
% H0 Q4 P. u7 o7 Q+ z 83    rt_kprintf("Speed Right: %ld\n", (rt_int32_t)sR);/ H) C% H; k' d
84
& K. I3 O4 M% Q0 n0 u 85    if(dir)9 r5 F0 L' ^6 a5 M
86    {3 B! h) I* U( v( n" |
87        rt_pwm_set(pwm_dev, fl_for, maxSpd, (rt_int32_t)sL);
% x$ m- e) s9 w2 x/ P 88        rt_pwm_set(pwm_dev, fl_back, maxSpd, 0);% g# @+ ~( h1 O
89        rt_pwm_set(pwm_dev, fr_for, maxSpd, (rt_int32_t)sR);
! X. T- B: p' [! o 90        rt_pwm_set(pwm_dev, fr_back, maxSpd, 0);' L9 P) o/ z7 v1 f
91    }
: @! j' ~4 C( T 92    else; X4 `, e' ]2 B; v$ j/ t; N
93    {8 e% \' `9 y7 m+ M- X; o
94        rt_pwm_set(pwm_dev, fl_for, maxSpd, 0);! d0 u2 A4 h' A3 O; P5 U! K
95        rt_pwm_set(pwm_dev, fl_back, maxSpd, (rt_int32_t)sL);
+ ]) N. \6 W( ^7 f5 m$ V% | 96        rt_pwm_set(pwm_dev, fr_for, maxSpd, 0);3 a  n1 Z! f# ~+ a/ q0 h* k
97        rt_pwm_set(pwm_dev, fr_back, maxSpd, (rt_int32_t)sR);
+ H2 o5 K, K2 R# W" @* v 98    }
6 `, @) G" f$ z$ k 993 Y9 F4 E( G4 u8 A
100    rt_thread_mdelay(1);# _: W/ R! C2 e! b. Y. z& U3 i! d
101}
! B) W, Z3 p( O2 B1027 \3 B$ M$ o' p2 m  Z4 G. [
103& Y3 l% u/ _* V/ I
104// 小车旋转
8 y( w$ R/ }9 L. X8 _8 R105void MotorControl::rotateBot(int dir, float spd) {4 J8 p. _( X- X6 ]
106    float s = spd * maxSpd;1 _3 s7 [& W; X
107    if (dir < 0)
' D5 Q9 |. }) a% h  j% P' Z" U108    {
2 v0 a/ v" @% c3 N! O109        s *= -1;9 O+ {  o% u' w/ {2 b$ m2 n
110    }
% Z* w) R' J/ `# X* u/ H) s111    if(dir)" X1 T3 n* r4 J
112    {0 H$ `! ^$ t# `. O
113        // Clockwise
" \% a  R1 \" Q) M# B' [114        rt_pwm_set(pwm_dev, fl_for, maxSpd, (rt_int32_t)s);
0 `5 ]+ u: F: s$ K) J115        rt_pwm_set(pwm_dev, fl_back, maxSpd, 0);& z6 c9 D3 d4 R
116        rt_pwm_set(pwm_dev, fr_for, maxSpd, 0);
* A% N: n9 z2 L! Q6 k2 Z$ P$ Y$ q117        rt_pwm_set(pwm_dev, fr_back, maxSpd, (rt_int32_t)s);$ z+ t. o1 U7 x! x( [) \* n3 Q6 h
118    }
+ a2 h, R1 C" E) k119    else
# w) E; _) J- M6 y120    {
& t7 l5 |1 r' F& _* B121        // Counter Clockwise
* w# F' f5 s) F. i7 p6 J122        rt_pwm_set(pwm_dev, fl_for, maxSpd, 0);
, y, \8 k, u* {# J123        rt_pwm_set(pwm_dev, fl_back, maxSpd, (rt_int32_t)s);
3 z* f/ h) L( O8 V124        rt_pwm_set(pwm_dev, fr_for, maxSpd, (rt_int32_t)s);
+ B, a- D3 |: e5 v125        rt_pwm_set(pwm_dev, fr_back, maxSpd, 0);$ O& R$ W5 u2 V! G
126    }/ o8 z1 A2 }5 P& r9 i
127    rt_thread_mdelay(1);
( G; ?1 |' r( H: k+ {- U128}
- |4 t3 x$ K4 m129
7 }* ]/ b  K  a/ H% s130//Turn off both motors
* v& e* [7 f  a( }4 ~! ^1 i* R0 R' g131void MotorControl::stopMotors()
5 S9 }' G* `  l  r0 Y( b1 u- i7 P& b" a# x132{
6 ^  c/ w9 c; @5 V9 Z133    rt_pwm_set(pwm_dev, fl_for, maxSpd, 0);
7 d9 _) O0 S3 C. u# |134    rt_pwm_set(pwm_dev, fl_back, maxSpd, 0);7 _/ d7 q+ c  u4 S" z$ K
135    rt_pwm_set(pwm_dev, fr_for, maxSpd, 0);
* C$ I' V- e) K2 n9 {0 G* ~; A136    rt_pwm_set(pwm_dev, fr_back, maxSpd, 0);
- B4 u9 n$ T/ _* T7 K1 r
137}- h3 X% j, v1 P) y9 \

$ x" F! P9 D/ @' y- b9 P8 v一共只需要这么一点代码就可以实现和 ROS 的连接了,所以其实 ROS 也不是那么神秘,它就是因为简单好用所以才这么受欢迎的。既然 RT-Thread 已经配置好了,下一步就是 ROS 的配置了。3 {) N6 v3 _- E; F! L

0 y: S: Z& o* P  `5 H  L

) j0 i# y8 f5 a& b" Q, A2 G2 n2.2 ROS 配置
7 `& f$ c5 o* O" n( I) u3 V1 t1 `6 j. u: w/ g0 M" ~

* O$ c8 z5 ~* L8 r我们把上面 RT-Thread 的固件传到板子上以后,可以用一个 USB-TTL 一边和 STM32 控制板的 USART2 连接,另一边插到 ARM 控制板的 USB 口,接下来就可以建立连接了,在 ARM 板上输入命令:
5 G0 J1 k5 r& z! ^! u* P1 $ rosrun rosserial_python serial_node.py /dev/ttyUSB0
; |2 B1 W# r. q1 M. X, R9 @8 P) _& L; @! U
如果看到下面的输出,那就成功建立连接了:8 l' f9 s+ M4 U6 a0 h9 C
1 tpl@nanopineoplus2:~$ rosrun rosserial_python serial_node.py /dev/ttyUSB09 O8 Z0 Q6 O9 [
/ j5 S( s! U/ e$ w* I4 \
2 [INFO] [1567239474.258919]: ROS Serial Python Node
5 `: ~: T/ O4 m% ]4 E3 [INFO] [1567239474.288435]: Connecting to /dev/ttyUSB0 at 57600 baud2 h/ q9 W2 n7 |( I/ {+ @
4 [INFO] [1567239476.425646]: Requesting topics...
7 ]* }, M6 y$ \# _! B5 [INFO] [1567239476.464336]: Note: publish buffer size is 512 bytes
1 S! _4 n- X4 A5 b/ G* e6 [INFO] [1567239476.471349]: Setup publisher on vel_x [std_msgs/Float64]! W: ~. N. `3 M. O& X
7 [INFO] [1567239476.489881]: Setup publisher on turn_bias [std_msgs/Float64]
% V- h* y5 k# m& J( ~& r' T8 [INFO] [1567239476.777573]: Note: subscribe buffer size is 512 bytes
! C7 k- L/ y/ r! j
9 [INFO] [1567239476.785032]: Setup subscriber on cmd_vel [geometry_msgs/Twist]. m! j3 w. H/ p3 C1 F% E

; T, c3 B% q' `" Q2.3 ROS 控制小车
- z8 }6 }( j9 i" }% {8 m/ i
1 y$ j' Z" m3 z& r9 t3 \+ g  H. V
, q4 F% q" i# D* \  e) x
既然已经成功建立连接了,下一步就是写小车控制的代码了。  H9 q, ]; L/ C' X. u, a( g
) Z$ N9 X" ]8 A& P$ P$ d$ s4 a

& X$ _8 k; ]8 r6 k3 i- n我们先初始化一个工作区间:
5 ^/ _% C+ Y$ M7 C- Y6 m; X- Z1 $ mkdir catkin_workspace && cd catkin_workspace2 ?$ V0 r% S7 N7 M# f1 f# n' N
% }  d- @3 u2 E$ a2 Y/ `  Q
2 $ catkin_init_workspace" B* p8 E- l, X" x6 a) h& o
# Q8 t5 {5 T) i- Z$ i7 y: Q$ H
接下来创建一个软件包:1$ cd src" h& }7 Y" w+ N. n
2$ catkin_create_pkg my_first_pkg rospy

* w* }* `9 H$ G" n, e! V, o: Y
: d- j9 z2 ^9 V. p: g' I: `! ~1 S. V& Y这样就会自动在 src 目录创建一个 ROS 软件包了。
; \* U/ R% n6 z: \$ c2 q  V2 m7 L* k( I6 K2 p1 _5 _

7 k: l  y0 j; B' A+ Z我们在 catkin_workspace/src/my_first_pkg/src 目录下新建一个文件 ros_cmd_vel_pub.py: - x  _+ B% \6 F; [1 [3 \
1#!/usr/bin/python+ x  ?- P, f/ Z  A
- u/ z- V- _% p3 T0 ]$ L# d$ p
2; I+ U/ C4 _4 Z) X0 l+ ?
3import rospy
: V: r/ A5 l( W+ Y 4from geometry_msgs.msg import Twist
0 {2 @" O! L$ Q, i* ^- v: k) \3 p 5from pynput.keyboard import Key, Listener
/ X+ {) x& A3 {6 L" Y9 e 6
  Z# W, |/ c+ E# r) r3 A, Q4 f 7vel = Twist()
3 V3 |5 v" K1 o  h 8vel.linear.x = 0
0 F  e0 l5 z8 v5 _# |- A 9
( W$ L- x* i9 J. P4 Q- [10def on_press(key):
0 q! Q  m: l1 s3 k9 Q11
. f1 Z1 ~2 R& p/ Z. J3 N12    try:
; k! Q+ `' Q$ u, J- C! P0 M# F3 a13        if(key.char == 'w'):5 v+ M' F, z5 L. |: ]* e: R
14            print("Forward")
. y2 y  R4 N' V2 ~9 F5 Q15            vel.linear.x = 0.8
; ~1 E, P6 O4 [5 r" F2 @16            vel.angular.z = 0. H  q" t. _* h0 \
17
1 l* U) o6 V" m( V& b( p18        if(key.char == 's'):! _3 I: U7 d% t
19            print("Backward"): u( T) g* \* x3 Q
20            vel.linear.x = -0.83 s/ A6 e4 ]) k9 ~
21            vel.angular.z = 0
4 G2 y3 `' v9 l! w4 A" e; B22
8 i1 C; W1 L* z  z8 h" G23        if(key.char == 'a'):7 R* O, X4 t* e
24            print("Counter Clockwise")5 |% ~3 G8 a& h
25            vel.linear.x = 0/ [% b3 Z/ M7 e" j* d- J
26            vel.angular.z = -0.8
7 `8 I" b( p! o& y. _. C27
3 ]- s" [5 `( J3 w8 m# [28        if(key.char == 'd'):$ K2 D% x8 @- W6 R
29            print("Clockwise")
! H. \5 R) e' }- Q2 [7 k+ [2 P/ V30            vel.linear.x = 0
  u) w0 |1 u: {7 o) u31            vel.angular.z = 0.8
  l5 h& b3 E$ ]/ c: r- V' \8 n322 [" x" o3 p, D% a
33        return False$ P+ c3 _2 a; O
34
& ]& P, a$ x5 y$ l/ h1 ]35    except AttributeError:
1 _2 G5 r9 M( {5 Y  |6 Q9 q36        print('special key {0} pressed'.format(key))
; Y. \; u1 r* f& n; E; r37        return False& w3 Z8 j8 x" d; B+ g+ X; R
38+ v& d  F) D7 f, j# S  i# i" v* ^
39def on_release(key):
. b$ C) t) O) u, ~6 n& ~3 g40    vel.linear.x = 0
0 b  r. c* x: R1 X. k0 \41    vel.angular.z = 07 V, ^( H0 L9 ~/ o/ T: D
42, ^4 k& g7 I1 v
43    return False
" n& G8 H* I6 r% ^6 x44" `# ^$ g. s/ u6 \& f4 B0 C/ U
45# Init Node
- v* c, }# d  Y* K46rospy.init_node('my_cmd_vel_publisher')
9 O9 K5 }; b: Z+ M% a7 g" J47pub = rospy.Publisher('cmd_vel', Twist, queue_size=10)/ \; ^, c  _9 d4 v4 \6 X
48
, j6 y8 e3 L& Y4 i4 r5 m1 c49# Set rate
% q2 P; s5 A8 D1 l50rate = rospy.Rate(10)
8 i4 R4 I7 x0 @- ^51
0 e" U' [- w% R4 m( z52listener = Listener(on_release=on_release, on_press = on_press)
  t7 m" I7 {5 D- S) T5 ]53
7 o- d6 {. z* h+ F2 B( z$ Q# n54while not rospy.is_shutdown():$ O; g/ c6 T5 h0 D$ c1 G
55    print(vel.linear.x). v" L) ~  z3 u3 v/ Y
56    pub.publish(vel)/ s/ I2 G% F2 Z7 U9 N+ G5 D
57    vel.linear.x = 0* K) j7 }# _: e2 w" x7 r$ A
58    vel.angular.z = 06 t/ Y7 K- N/ }" d. e
59    rate.sleep()
5 l* q' X) O/ s! r/ H' ^( H$ g( _60
9 D3 q# b8 t: y8 w9 y+ B61    if not listener.running:  u: ^. j2 T- W. Z( M' h
62        listener = Listener(on_release=on_release, on_press = on_press)9 C% R, W7 O: f. i6 ]
63        listener.start()
, R8 X' t. f; H( F' @( \

; m1 ?2 X, S6 e# K2 X+ h6 z$ P- G* u这就是我们的 python 控制程序了,可以使用键盘的 wasd 控制小车前进后退,顺时针、逆时针旋转。我们需要给它添加可执行权限:4 Y! r% L9 _$ M) b3 K  i2 M. A2 G
1 $ chmod u+x ./ros_cmd_vel_pub.py# u5 d5 N2 ?7 [# `7 ~
- }' Q2 x5 K/ T! l2 s

1 k4 w  w- G0 b! g) j- i
这样就可以编译软件包了,在 catkin_worspace 目录下。
1 $ catkin_make
3 _( ^3 o3 u7 D( G6 ?7 y2 $ source devel/setup.bash

) o# }5 ~( M- U7 G

" t2 x. l+ O+ j% Z2 }2 O5 U# S
我们终于就可以启动程序从电脑上控制小车运动了: $ ~* E9 Q# K: M( N
1 rosrun my_first_pkg ros_cmd_vel_pub.py
1 ?9 h# ], S/ v/ k- d
1 n, H& l$ T$ _$ t% ^/ P7 n1 U9 X, O. y, ~' x# }" r, C
可以看到用 ROS 实现小车控制其实代码量并不算多,只需要在自己小车原有的代码上发布一些话题,告诉 ROS 小车当前的状态,并且订阅一个话题接收 ROS 的控制指令就可以了。
! o% H. t/ S9 w' h- O. \) a- F$ Q4 L1 L8 N# ]; f& j
) ]( o: T& I( X
2.4 参考文献
. B; E$ g& b* M, W, Z' b4 Z3 RT-Thread 无线连接 ROS
7 L) E7 m+ m1 ]: n
& w: |+ y% W" J4 O% |3.1 rosserial 配置
+ f. x: a+ _( ]: Z9 q( d& Q, P$ i( k' t2 f  o. s- r

, e. |4 a2 p3 P! B2 \5 j+ k/ ^其实无线连接和有线连接几乎是一模一样的,只不过是先用 ESP8266 使自己的控制板能连上网,然后用 tcp 连接和 ROS 通信,关于 RT-Thread 使用 ESP8266 上网的教程可以参照 官网:http://www.rt-thread.org/document/site/application-note/components/at/an0014-at-client/,非常详细了,我这里就不重复了。
1 A6 @! J2 C( F9 F3 Q
4 ^" F8 ]) |1 L7 t* y& ]7 F* n

& T) r; X; Y3 {# r4 a6 c# D$ j7 ~3 q确保开发板有网络连接后,我们就可以在 rosserial 里面配置为使用 tcp 连接:
7 w9 Z5 ]" w  h! X5 F& a
微信图片_20200514172634.jpg

. S0 W' k& ]' {4 a, g
我们只需要在上一部分的 main.cpp 里添加一行代码:+ w7 Y0 U$ E3 v
1// 设置 ROS 的 IP 端口号
! v/ ]7 A% W+ T* u2nh.getHardware()->setConnection("192.168.1.210", 11411);
" U3 e2 M% c) c& L! G3
# B6 K5 W! A' ~4// 添加在节点初始化之前
3 z! P: x: \. j5 w5nh.initNode();

# G, _- P: s) t0 h! Y; q$ k3 s0 B9 g+ L! D$ W

3 N0 c, W# ^  N" D8 r* x7 B9 ]& H8 z开发板就能通过 tcp 连接和 ROS 通信了,非常方便。
% m4 x; W; j% R8 o1 h) `# V7 q( q- ^

% X4 G1 }* x' ]5 T8 T# D6 x3.2 ROS 配置% A' F% p( E" D

4 a1 L$ x* L+ x4 k2 U7 B$ v
/ v% ]4 S. @" W# x# L! c
由于我们使用了 tcp 连接,所以 ROS 上自然也要开启一个服务器了,之前是使用的串口建立连接,现在就是使用 tcp 了:
% y- ^% s) J& |) n9 k# ]1 $ rosrun rosserial_python serial_node.py tcp( |% V8 q/ j- q- Y. O% G/ m

, F9 Y# l8 s" |2 `1 @2 ]! @

! k1 y: n# C5 c$ {9 h; ^( b
其他的代码完全不需要改变,这样我们就实现了一个 ROS 无线控制的小车了。
' o* w: E/ l8 o
3.3 参考文献
  • RT-Thread 使用 ESP8266 上网:
    http://www.rt-thread.org/document/site/application-note/components/at/an0014-at-client/

    2 Z% R- {# M3 u5 |2 o
4 总结
  n2 B! ], a0 d" n' k( c% D8 H0 T# X0 U( A
这里再总结一下,其实 RT-Thread 使用 rosserial 软件包和 ROS 建立连接非常简单,只需要在自己小车原有代码的基础上发布一些消息,告诉 ROS 小车当前的状态,以及订阅来自 ROS 的控制指令就可以了。
收藏 1 评论2 发布时间:2020-5-14 17:29

举报

2个回答
xiaonihao444-20 回答时间:2020-5-14 19:08:18
厉害
李康1202 回答时间:2020-5-15 08:42:03
大佬门路挺广的哈1 P' k) t, k+ F

所属标签

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