【ROS实战】03-从零实现小车运动控制的 ROS 功能包

本教程将从 零基础 带你创建一个包含两个节点的 ROS 包:
一个用键盘控制小车方向,另一个接收控制信息并解析动作。

本章使用ROS Noetic版本,如果对ROS背景,ROS主题,ROS架构和工作空间概念不了解的读者,可以先阅读我的专栏中的前两篇文章《01-ROS安装详细指南》和《02-ROS架构介绍》


🧱 第一步:创建工作空间与包

✅ 创建工作空间

这里我们给工作空间起名为catkin_ws

你可以根据需要命名工作空间。catkin_ws 是”catkin workspace”(catkin工作空间)的缩写,其中,catkin 是ROS中的构建系统的名称,ws 代表工作空间workspace。

1
2
3
4
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws
catkin_make
source devel/setup.bash

✅ 创建 ROS 包

1
2
cd ~/catkin_ws/src
catkin_create_pkg my_robot_control rospy std_msgs geometry_msgs

解释:

  • my_robot_control:你的包名;
  • rospy:使用 Python 写 ROS 节点;
  • std_msgsgeometry_msgs:标准消息类型,geometry_msgs/Twist 是控制速度的关键消息类型。

✍️ 第二步:编写 Python 节点

1️⃣ keyboard_control_node.py:读取键盘输入,发布 /cmd_vel

路径~/catkin_ws/src/my_robot_control/scripts/keyboard_control_node.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python3
import rospy
from geometry_msgs.msg import Twist

def main():
# 创建一个Publisher,发布到 /cmd_vel,消息类型为Twist
pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10)
# 初始化节点,名称为 keyboard_control_node
rospy.init_node('keyboard_control_node')
rate = rospy.Rate(10) # 发布频率 10Hz

print("=== 使用键盘控制小车 ===")
print("w: 前进 | s: 后退 | a: 左转 | d: 右转 | q: 退出")

while not rospy.is_shutdown():
key = input("请输入方向键:")
msg = Twist() # 初始化速度消息
if key == 'w':
msg.linear.x = 0.5 # 前进
elif key == 's':
msg.linear.x = -0.5 # 后退
elif key == 'a':
msg.angular.z = 0.5 # 左转
elif key == 'd':
msg.angular.z = -0.5 # 右转
elif key == 'q':
print("退出控制程序")
break
else:
print("无效输入,请输入 w/s/a/d/q")
continue

pub.publish(msg) # 发布消息
rate.sleep()

if __name__ == '__main__':
main()

2️⃣ motor_controller_node.py:接收 /cmd_vel,输出控制信息

路径~/catkin_ws/src/my_robot_control/scripts/motor_controller_node.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python3
import rospy
from geometry_msgs.msg import Twist

# 回调函数,当接收到 /cmd_vel 消息时触发
def callback(msg):
linear = msg.linear.x
angular = msg.angular.z

# 判断动作类型
if linear > 0:
action = "前进"
elif linear < 0:
action = "后退"
elif angular > 0:
action = "左转"
elif angular < 0:
action = "右转"
else:
action = "停止"

# 输出动作和速度信息
print(f"[MotorController] 动作: {action} | 线速度: {linear:.2f} m/s | 角速度: {angular:.2f} rad/s")

def main():
rospy.init_node('motor_controller_node') # 初始化节点
rospy.Subscriber('/cmd_vel', Twist, callback) # 订阅 /cmd_vel
rospy.spin() # 等待回调

if __name__ == '__main__':
main()

🔑 第三步:配置可执行权限与 CMakeLists.txt

✅ 设置可执行权限

1
chmod +x ~/catkin_ws/src/my_robot_control/scripts/*.py

✅ 修改 CMakeLists.txt

确保添加如下内容来安装你的 Python 脚本,修改~/catkin_ws/src/my_robot_control/CMakeLists.txt,在find_package之后添加如下语句

1
2
3
4
5
6
7
catkin_package()

catkin_install_python(PROGRAMS
scripts/keyboard_control_node.py
scripts/motor_controller_node.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

🔨 第四步:编译项目

1
2
3
cd ~/catkin_ws
catkin_make
source devel/setup.bash

🧪 第五步:单独运行每个节点

✅ 先启动 roscore

1
roscore

如果没有安装或者安装后命令无法识别,请参考我专栏中的《01-ROS安装详细指南》

✅ 运行 keyboard 控制节点

1
2
source ~/catkin_ws/devel/setup.bash
rosrun my_robot_control keyboard_control_node.py

含义解释:

  • rosrun:运行单个 ROS 节点;
  • my_robot_control:你的包名;
  • keyboard_control_node.py:脚本名称,必须在 scripts/ 下并有可执行权限。

✅ 在另一个终端运行 motor controller 节点

1
2
source ~/catkin_ws/devel/setup.bash
rosrun my_robot_control motor_controller_node.py

此时,在keyboard控制节点终端输入方向按键(wsad)并输入回车后,就可以看到另一个终端的输出信息。


🚀 第六步:使用 Launch 文件同时启动两个节点

目前每个节点都需要独立运行,当节点数量多了后,尤其是生产环境,可能有几十个甚至数百个节点,如果还是这样一个一个运行,就会非常麻烦,且启动的参数也不容易文档化管理。

这时,我们就可以使用Launch文件同时启动多个节点。

✅ 创建 launch 文件夹

1
mkdir -p ~/catkin_ws/src/my_robot_control/launch

✅ 编写 my_robot_launch.launch

路径~/catkin_ws/src/my_robot_control/launch/my_robot_launch.launch

1
2
3
4
5
6
7
<launch>
<!-- 启动键盘控制节点 -->
<node pkg="my_robot_control" type="keyboard_control_node.py" name="keyboard_control" output="screen"/>

<!-- 启动电机控制节点 -->
<node pkg="my_robot_control" type="motor_controller_node.py" name="motor_controller" output="screen"/>
</launch>

参数说明:

  • pkg:节点所在的包名;
  • type:节点的可执行文件(脚本)名称;
  • name:运行时给节点起的名字(可用于调试);
  • output="screen":将日志输出显示在终端。

✅ 启动 launch 文件

1
roslaunch my_robot_control my_robot_launch.launch

含义解释:

  • roslaunch:用于运行 launch 文件,支持同时启动多个节点;
  • my_robot_control:包名;
  • my_robot_launch.launch:启动文件名称,必须放在 launch/ 文件夹下。

此时,已经在一个窗口中通过roslaunch命令同时启动了两个节点,我们在窗口中同时输入控制指令并看到另一个节点的输出。


📂 最终项目结构

1
2
3
4
5
6
7
8
my_robot_control/
├── CMakeLists.txt
├── package.xml
├── launch/
│ └── my_robot_launch.launch
├── scripts/
│ ├── keyboard_control_node.py
│ └── motor_controller_node.py

常用调试命令

通过结合以下 ROS 相关命令,可以从另一个角度观察程序的运行状态。

在启动两个节点后:

  1. 查看当前话题:
    使用 rostopic list 命令可以列出当前系统中所有的话题,确以看到我们两个节点使用的 /cmd_vel 话题。

    1
    2
    $:~/catkin_ws$ rostopic list
    /cmd_vel
  2. 查看当前节点:
    使用 rosnode list 命令可以列出当前运行的所有节点,可以看到我们启动的两个节点:keyboard_controlmotor_controller

    1
    2
    3
    $:~/catkin_ws$ rosnode list
    /keyboard_control
    /motor_controller
  3. 实时监听话题数据:
    使用 rostopic echo /cmd_vel 可以在终端中持续监听 /cmd_vel 话题的数据。如果该话题有数据发布,命令行中会实时打印出来。比如在执行后,在之前启动的 keyboard_control 终端中,输入 wsad 控制时,便能同步看到输出数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $:~/catkin_ws$ rostopic echo /cmd_vel
    linear:
    x: 0.5
    y: 0.0
    z: 0.0
    angular:
    x: 0.0
    y: 0.0
    z: 0.0

这样,你就可以通过这些命令更直观地调试和监控 ROS 系统的运行状态。


✅ 小结

你现在拥有了一个完整的 ROS 包:

  • ✅ 可用键盘控制小车;
  • ✅ 发布 /cmd_vel 控制速度;
  • ✅ 模拟电机接收速度并输出动作信息;
  • ✅ 支持手动运行和 launch 一键启动!
  • ✅ 使用ros的常用命令进行调试。

如果想继续扩展,可以加入:

  • 🔋 控制真实电机(通过串口或 GPIO);
  • 🖥️ 用 GUI 或者接受远端遥控信号替代命令行键盘输入;
  • 🧠 使用导航算法,实现自动规划。