☰
网页按钮控制ROS2小车:从WebSocket到电机驱动的完整链路
2026/10/20 1:05:44 网站建设 项目流程

动手做之前,我其实犹豫了很久。ROS2 小车并不是新东西,网上教程一大把,但绝大多数不是停在仿真阶段,就是教你“按几个命令让机器人原地转圈”。我想做的不是这种:我希望在手机或电脑浏览器上放几个按钮,点一下,放在地上的小车真能前进、后退、转弯;点另一个按钮,它能立刻停车。听起来很像 demo,但真正要打通“网页 → WebSocket → ROS2 话题 → 电机驱动 → 车轮”这条完整链路时,涉及的知识点远比想象中多。这篇文章整理的是我一个人的改造记录,也可以看作是系列第 1 期:把最小可用闭环跑通,让车先“听人话”。

1. 项目背景与整体思路:为什么坚持“网页按钮”这个入口

1.1 项目初衷:从最小闭环开始,而不是一上来就堆功能

很多人刚开始接触 ROS2 小车时,第一反应是去看 SLAM、导航、机械臂这些看起来很酷的功能。我也一样,差点一上来就买一堆传感器。但冷静下来想了一下,如果连一个最简单的“前进”指令都无法从外部可靠地发到电机上,后面那些高级功能全都是空中楼阁。所以我给自己定了一个极简目标:网页上点击按钮,小车能立刻响应,并且有一个状态反馈让我确定它“真的动了”。

这个目标听起来简单,实际上需要四层配合:前端界面负责交互,通信桥负责协议转换,ROS2 节点负责话题收发,底盘驱动负责把消息变成电机的 PWM 信号。每一层单独看都不难,难的是把它们串起来,并且处理好延迟、安全、异常断开这些真实场景里躲不开的问题。

我特意选“网页按钮”作为入口,而不是直接用命令行发消息,是因为它最接近普通用户的使用直觉,也最容易验证整条链路是否真的通畅。命令行输入ros2 topic pub能发消息,但你没法直观感受“点击反馈”和“持续按压”这种交互逻辑。网页端天然支持鼠标、触摸屏和键盘事件,后续想加手柄控制、遥控器、甚至手机重力感应,都可以在同一个前端框架上扩展。

1.2 技术选型:为什么是 ROS2 + rosbridge_suite + 简易前端

确定目标之后,技术选型反而没有花太多时间。ROS2 是机器人领域的事实标准,社区生态成熟,尤其是 Humble 版本在 Ubuntu 22.04 上非常稳定,教程也多,出了问题搜一下基本都有答案。

网页和 ROS2 之间的桥接,我选了 rosbridge_suite 而不是自己写协议转换。原因很简单:rosbridge_suite 提供了一种标准化的 WebSocket 通信机制,前端可以用 JSON 消息直接发布/订阅 ROS2 话题,省去自己维护 DDS 协议栈的麻烦。DDS 是 ROS2 底层的实时通信中间件,它设计得非常强大,但它的客户端库在大前端环境里很难直接运行,这才需要 rosbridge 作为“翻译官”。

前端则故意做得非常简单,纯 HTML + JavaScript,没有引入 Vue 或 React。这个决定是故意的:第一期最重要的目标是验证链路,而不是构建一个复杂的管理后台。一个页面、几个按钮、一个状态指示灯,就能说明全部问题。等后面需要加入视频流、地图显示、参数配置面板时,再引入前端框架也不迟。

整体链路可以这样理解:浏览器里的按钮动作,变成 JSON 指令,通过 WebSocket 发送给 rosbridge_server;rosbridge_server 再把 JSON 转成 ROS2 消息,发布到名为/cmd_vel的话题上;底层驱动节点订阅这个话题,解析出速度值,写入串口或引脚,最终控制电机转动。听起来很绕,但每一环都有现成工具,难点只在于把细节做扎实。

2. 环境准备与硬件清单:这一期我到底用了哪些东西

2.1 小车本体与主控选型:兼容性比性能更重要

小车本体我用的是一台常见的差速驱动底盘,两个主动轮加一个万向轮,车架是铝合金的,方便后续加装传感器。这种底盘在淘宝上很常见,关键是电机自带编码器,后面想做里程计时不用换硬件。电机驱动板是集成了 TB6612 或者类似芯片的模块,支持双路 PWM 输出,控制正反转和调速都很直接。

主控分两层:上层是运行 ROS2 的开发板,我用了支持 Ubuntu 22.04 的 ARM 开发板;下层是驱动电机的最小系统板,负责读取编码器、输出 PWM。下方控制板直接接收上层通过串口发送的速度指令,这样的分工清晰,上层坏了不会烧电机,电机的高速中断也不会拖垮 ROS2 节点。

选型时的核心原则是兼容性优先。ROS2 官方支持 Ubuntu 22.04 + Humble 这个组合,所以主控板只要能跑这个系统,并且有稳定的电源管理,就算合格。ARM 开发板性能不用太强,但一定要有 USB 和串口同时可用,因为调试时你会经常需要插着线看日志。

2.2 环境搭建要点:rosbridge_suite 的安装和测试

环境安装本身不复杂,但有几个细节容易踩坑。首先是系统版本,我强烈建议不要随意升级到最新版 Ubuntu,ROS2 版本和系统版本是严格对应的。Humble 对应 Ubuntu 22.04,如果你换成 24.04,就需要使用对应 ROS2 版本,很多教程命令就不适用了。

rosbridge_suite 的安装只需要一行命令:

sudo apt install ros-humble-rosbridge-server

安装完成之后,启动 rosbridge 也很简单:

ros2 launch rosbridge_server rosbridge_websocket_launch.xml

启动后默认监听 9090 端口。你可以先用浏览器或者在线 WebSocket 测试工具连接ws://localhost:9090,如果能连上,说明桥接服务正常。

光能连上还不够,我习惯先做一次“空转测试”:在命令行发布一个话题,再从网页订阅回来,确认数据能双向流动。这个测试非常重要,很多问题其实出在rosbridge服务器没有正确发布话题,而不是底盘没反应。

ros2 topic pub /test_chatter std_msgs/msg/String '{data: "hello"}' --once

如果网页能收到这条消息,说明整条路已经通了 80%。剩下要做的是让前端发出控制指令。

2.3 前端资源准备:一个能显示状态的简单页面

前端页面我用的是纯静态文件,放在本机就行,不需要服务器。因为 websocket 连接是浏览器直接发起的,只要页面能打开,JS 就能连到 rosbridge。这个特性非常方便,开发时直接用浏览器打开index.html,不需要额外部署。

页面里我放了三组元素:方向控制按钮(前进、后退、左转、右转、停止)、速度滑块、状态显示区。按钮事件会调用发送函数,状态区通过订阅/battery_voltage和/motor_status这类反馈话题来更新内容。第一期没有做复杂的 UI,但状态反馈必须有,否则你会分不清是链路没通还是小车真的没动。

3. 关键链路拆解:网页按钮是如何变成小车前进的

3.1 说人话的通信原理解释:WebSocket 里到底发了什么

很多做机器人的人对 Web 技术不熟,一听 WebSocket 就头大。其实可以打一个比方:WebSocket 就是一条电话专线,一旦建立连接,双方随时可以通话,不用每次拨号。前端按钮点击时,就是拿起电话说了一句“我要往前进”;rosbridge 听到后,把它翻译成 ROS2 能听懂的话题消息。

具体格式遵循 rosbridge v2 协议,前端发送的消息类似这样:

{ "op": "publish", "topic": "/cmd_vel", "msg": { "linear": {"x": 0.2, "y": 0.0, "z": 0.0}, "angular": {"x": 0.0, "y": 0.0, "z": 0.0} } }

这段 JSON 的含义非常直观:op是操作类型,publish代表发布;topic是目标话题;msg是话题数据。linear.x是线速度,单位是米每秒,0.2表示每秒钟前进 0.2 米;angular.z是角速度,单位是弧度每秒,正值表示逆时针旋转。

前端 JS 代码也很简短,点击按钮时调用发送函数即可:

let ws = new WebSocket('ws://localhost:9090'); function sendCommand(x, z) { let cmd = { op: 'publish', topic: '/cmd_vel', msg: { linear: { x: x, y: 0.0, z: 0.0 }, angular: { x: 0.0, y: 0.0, z: z } } }; ws.send(JSON.stringify(cmd)); }

前进按钮绑定sendCommand(0.2, 0),左转按钮绑定sendCommand(0, 0.5),停止按钮绑定sendCommand(0, 0)。注意停止不是发送一个“停”的魔法值,而是把线速度和角速度都置零,让底盘自然减速停下。

3.2 为什么话题名是/cmd_vel:ROS2 里最通用的“油门踏板”

刚接触 ROS2 的人会问:为什么大家都用/cmd_vel,能不能换个名字?能,但最好不要。/cmd_vel几乎是所有移动机器人底盘的“标准接口”,导航栈、遥控节点、SLAM 建图工具都默认往这个话题发速度指令。如果你改了话题名,后面的生态工具全都要跟着改,得不偿失。

/cmd_vel的消息类型是geometry_msgs/msg/Twist,这个消息的结构非常符合直觉:linear表示线速度,angular表示角速度,每个字段又分 x、y、z 三个方向。对于差速小车,我们只用linear.x和angular.z就够了:linear.x控制直行,angular.z控制旋转。

底盘驱动节点订阅/cmd_vel后,会根据这两个值做运动学解算。差速底盘的解算公式并不复杂:左右轮速度等于线速度加减一个与角速度相关的修正项。如果线速度是 0.2,角速度是 0.5,那么左轮和右轮的速度就会不同,小车自然走出一条弧线。这个细节在底盘节点里处理,前端只需要把用户意图发出去就行。

3.3 状态订阅是怎么做的:让网页知道“车真的动了”

只有发出指令还不够,我希望网页上有反馈,告诉我车已经动了。于是我还订阅了两个话题:一个是/motor_status,由底盘驱动节点周期性发布,内容是车轮当前速度;另一个是/battery_voltage,输出电池电压,用来判断电量是否够用。

前端订阅的代码逻辑是这样:先告诉 rosbridge 我要监听某个话题,之后每当该话题有数据发布,rosbridge 就会把数据推送给网页。为了减少实时数据量,可以设置节流参数,比如每秒最多推送 10 次。

{ "op": "subscribe", "topic": "/motor_status", "type": "std_msgs/msg/String", "throttle_rate": 10 }

订阅之后,页面上的状态区会显示类似“左轮:0.21 m/s,右轮:0.19 m/s”的信息。当看到网页显示的速度值和小车实际运动状态一致时,才能说链路真正闭环。这一期我把状态显示做得很朴素,但它在调试时帮了大忙。

4. 底盘执行链路:从订阅到电机转起来

4.1 底盘节点的工作流程:数据进来之后做了什么

网页消息到达/cmd_vel只是第一步,真正让电机转起来靠的是底盘驱动节点。这个节点通常运行在 ROS2 环境里,订阅/cmd_vel,然后把速度值转成串口指令发给底层控制板。

我写的底盘驱动节点逻辑大致分三步:第一步,接收 Twist 消息并做合法性校验,速度值超过预设上限就拒绝执行,这是安全底线;第二步,换算成左右轮实际速度,再根据电机转速系数转成 PWM 占空比;第三步,通过串口把数据帧发给下位机,下位机控制电机转动。

这里有一个容易忽略的细节:ROS2 环境和底盘控制板之间是串口通信,数据格式必须自己定义。我使用了一种简单但可靠的帧格式:帧头、数据长度、指令类型、数据字段、校验位。每个字节都有固定含义,防止数据错位。如果你用现成的底盘模块,可以跳过这一步,但自己写一次会让你对整套系统理解更深。

串口的数据帧大致长这样:

0xAA 0x55 [长度] [0x01 前进] [速度高字节] [速度低字节] [CRC]

下位机收到后先校验,校验通过才执行,否则丢弃并返回错误帧。这套机制在电机控制中是必要的,因为串口偶尔会出现干扰,没有校验就直接控制电机是很危险的事情。

4.2 下位机控制逻辑:把速度值变成 PWM 信号

下位机我用的是常见的单片机,通过串口接收指令,用定时器产生 PWM 信号控制电机驱动芯片。为了防止两个轮子转速不一致导致小车跑偏,还需要在驱动节点里加入闭环控制:读取编码器脉冲,计算实际转速,再调整 PWM 输出。

编码器的原理很朴素:电机后面拖着一圈码盘,当轮子转动时,霍尔传感器会输出脉冲信号。单位时间内脉冲数越多,说明转速越快。把期望速度和实际速度的差值输入到 PID 控制器,就能自动修正 PWM 占空比。这种闭环控制在差速小车里几乎是必须的,否则电池电量变化、地面摩擦力不同都会让小车跑偏。

PID 参数调试是这一期最耗时的工作之一。我先从 P 项开始调,也就是比例系数:期望速度乘以一个系数得到 PWM 修正值。如果车在低速时抖动厉害,说明 P 太大;如果速度稳定后还差一截,则需要加 I 项消除稳态误差。调参的原则是“小步快跑”:每次只改一个参数,观察车轮空转时的响应,再上真车测试。

4.3 安全机制:为什么我固执地把急停按钮做了三次

真实的小车不像仿真,撞墙就是撞墙,不会自动回退。所以在做控制之前,我把安全机制放在了优先级最高的位置。

第一层安全是上位机限速。底盘节点里有一个速度上限表,线速度最大 0.5 米每秒,角速度最大 1.0 弧度每秒,超过这条线的一律拒绝执行。这个值可以根据场地环境调整,但绝对不能没有。

第二层安全是前端超时。我在页面里加了定时器:如果超过 500 毫秒没有收到新的控制指令,就自动发送停止指令。这样即使浏览器断线或者用户忘记松按钮,小车也会停下来。这层保护是因为 WebSocket 连接可能突然断开,但底盘节点并不知道,如果没有超时机制,小车可能会一直执行最后一条指令。

第三层安全是物理急停开关。我在电池回路里串了一个手动开关,在任何情况下都能直接切断电机电源。这层保护不需要任何代码,纯粹是提高容错率。改车这类项目动手时总会遇到意想不到的软件 bug,但物理开关能保证最坏的情况下也不会伤人伤车。

5. 实测记录与问题排查:第一批踩过的坑都在这里

5.1 先在仿真里跑通,不要直接上真车

没有仿真就上真车,是很多新手会犯的错误。我在 Gazebo 里先搭了一个简单的小车模型,配置好差速驱动插件,然后把 rosbridge_suite 也跑起来。这样前端页面可以直接连接本地 rosbridge,发布速度指令给仿真小车,观察它在虚拟世界里是否前进、转弯。

仿真测试的主要目的不是验证电机,而是验证消息链路。Gazebo 里的小车模型对所有话题的响应是确定性的,如果前端发出前进指令后仿真车没有动,问题一定出在消息层,不会牵扯到底盘硬件。排查起来会轻松很多。

值得一提的是,Gazebo 的模型定义里要特别注意<publishWheelJointState>这类插件配置,否则无法从/odom话题获取里程信息,也就看不到小车实际位置。第一次配置时我也踩了这个坑,仿真小车发布速度指令有反应,但里程计一直是零,后来才发现是插件参数漏了。

5.2 真车测试记录:从点到动,我遇到的 4 个典型问题

仿真通过之后,我把前端页面搬到电脑上,连接实际小车进行测试。第一次点击“前进”按钮时,车轮只是轻微抖动了一下,并没有真正转起来。下面这 4 个问题是这一期里最典型的,也是后续同类项目最容易遇到的。

第一个问题是 rosbridge 服务器没有正确找到 ROS2 框架的主节点,导致前端连接到了默认端口,但话题列表里看不到/cmd_vel。解决方法是在启动 rosbridge 之前,先确认环境变量已经正确加载,也就是已经执行过source /opt/ros/humble/setup.bash和source ~/your_ws/install/setup.bash。这个细节在终端里很自然,但如果在 systemd 服务或脚本里启动 rosbridge,就容易漏掉。

第二个问题是前端 WebSocket 连接经常掉线,排查后发现是端口代理配置问题。如果浏览器和 rosbridge 服务器不在同一台机器上,需要确保 9090 端口没被防火墙拦截,并且连接的 IP 地址写的是开发板实际 IP,而不是localhost。这一点在接手机控制时尤其重要。

第三个问题是小车前进时严重跑偏,检查后发现左右轮编码器数据不一致。原因是其中一个电机的齿轮箱存在轻微机械阻力,导致在相同 PWM 下转速不同。解决办法是先做一次编码器标定,记录每个轮子的脉冲频率与实际速度的关系表,然后在驱动节点里补偿。

第四个问题是最隐蔽的:前端按钮一直发送重复指令,导致小车的速度指令队列堆积,松开按钮之后还在继续走。我的解决方式是在按钮的mousedown事件里发送速度指令、在mouseup事件里发送停止指令,同时用节流函数限制指令发送频率。按钮响应很快,但也不至于让底盘“疯狂鞭策”。

5.3 常见问题速查表:建议直接收藏

现象可能原因排查方向
网页连接失败rosbridge 未启动或端口错误检查 9090 端口、ros2 node list
网页连接正常,但小车无反应话题名写错或类型不匹配用ros2 topic list和ros2 topic info /cmd_vel检查
小车突进或抖动消息频率过高或 PID 参数过大降低前端发送频率、减小 P 项
小车跑偏严重左右轮编码器标定不准重新标定轮速,检查机械阻力
网页反馈数据不更新订阅时没指定消息类型在订阅指令里补全type字段
电池供电不足导致自动重启电机启动电流过大换更大容量电池或增加电容滤波

这里我强烈建议,无论出现什么问题,都要先在终端里用ros2 topic echo /cmd_vel确认消息是否到达。只要终端里能实时看到速度值变化,问题就肯定出在底层驱动而不是前端链路。

6. 这一期的工程总结和第 2 期计划

6.1 我会如何做第二次迭代:从手动控制走向自动控制

第一期跑通之后,这个项目其实已经具备了一个很实用的基础平台。接下来第 2 期我会优先加两个东西:一个是摄像头视频回传,另一个是简单的自动巡线功能。

视频回传技术上并不复杂,ROS2 里可以用usb_cam或v4l2_camera_node获取图像,再通过 MJPEG 或者 WebRTC 推到网页。难点在于延迟和带宽管理,尤其是开发板通过 Wi-Fi 传输时,图像压缩质量需要动态调整。

自动巡线则是在底盘控制之上加一层感知逻辑。比如用 OpenCV 处理摄像头图像,识别地面上的引导线,计算偏离角度,再生成对应的/cmd_vel指令。这一层逻辑在仿真里很好测,但真车的环境光照、镜头畸变、地面反光都会严重影响识别效果,需要大量实机调试。

另外我计划把前端从纯静态页面升级成一个小型 Web 应用,加入手动模式/自动模式切换、速度曲线显示、日志记录等功能。不过功能越多,调试成本越高,所以每一步我会尽量小步推进。

6.2 给想复刻这个项目的朋友几条实在建议

如果你也打算做类似的东西,我给三条建议。第一条,不要追求一步到位,先把“网页按钮 → 小车转动”这个最小链路做完再做其他功能。这个闭环一旦通了,后面加传感器、加算法都只是增量工作。

第二条,准备一块可靠的电池和多根数据线。很多奇怪问题都来自供电不足或者接触不良,排查半天发现是线松了,非常浪费时间。记得尽量用带屏蔽的串口线,并且用扎带把线缆固定好,避免车辆震动时松动。

第三条,学会看日志,而不是盲猜。ROS2 的日志系统会告诉你每个节点是否正常启动、消息是否超时。多花一点时间读懂日志,比反复改代码更有效。有一条我常用的经验:遇到异常时,先把所有终端日志保存下来,再根据时间戳逐帧分析,往往比盯着代码看更快定位问题。

这一期做完,我最直观的体会是:看似简单的“点一下按钮”,真正落地时涉及的环节比想象中多得多,但也正因如此,跑通闭环带给人的满足感才特别强。我第一次看到网页上的前进按钮按下、车轮同时转动的那一刻,就知道这个方向值得继续做下去。下一期我打算把摄像头上车,把“能遥控的车”升级成“会看路的车”,到时候再继续记录。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询