
2026 年 8 月的 BBC NEWS 里一条关于“中国人形机器人竞技”的短讯让我印象很深新闻画面的重点已经不再是实验室里展示一段演示视频而是机器人在相对复杂的场景中完成跑动、对抗和任务协作。这类画面放在五年前还只能出现在研究机构的宣传片里如今却越来越多出现在公开报道中。很多人看到的第一反应是“电机真强、算法真猛”但如果真的从事机器人开发就会知道一个残酷的事实人形机器人的门槛从来不只是硬件而是让几十个关节、几十种传感器、多套算法在一个统一的软件架构里稳定跑起来。这篇文章不讨论新闻报道中的具体事件也不评价任何政策或赛事组织方式只从技术视角回答一个更值得开发者关心的问题一台人形机器人从芯片选型到软件架构再到 ROS 2 节点如何协作到底该怎么落地读完你至少能获得三样东西第一对人形机器人软件架构有一个完整的认知地图第二掌握端侧机器人主控芯片的选型思路包括国产 SoC 方案在其中的定位第三拿到一套可运行的 ROS 2 最小示例理解感知、决策、运动控制三个节点如何通信、如何配置、如何排查问题。1. 人形机器人为什么“看起来简单做起来难”先说一个真实痛点。很多人第一次接触人形机器人项目最容易被“看起来能走、能跑、能握手”的画面带偏以为只要买一台高性能电机驱动的机器人平台再把强化学习模型调一调就能复现新闻里的效果。等真正动手才发现问题根本不在单关节能不能转而在几十个关节同时动起来时系统能不能在同一毫秒内完成状态同步、指令下发和反馈采集。人形机器人是一个典型的多传感器、多执行器、多算法并发系统。它的身体里有 IMU、关节编码器、力矩传感器、相机、激光雷达、麦克风阵列头部的视觉算法要识别目标躯干的姿态算法要维持平衡双腿的步态算法要规划落脚点双臂的规划算法要避障抓取。这些算法不是独立运行的孤岛它们共享同一份机器人状态并且必须以极低延迟完成数据交换。如果只靠“写一个 while 循环读传感器再写一个 if 判断发指令”这种单片机的思路结果一定是灾难视觉线程卡顿一下姿态控制线程可能已经发散控制线程频繁抢占 CPU感知线程的帧率就会掉通信管道没有优先级设计关键的命令可能被日志数据挤到队尾。人形机器人真正的复杂度不是某一个算法的难度而是如何把这些算法组织成一个可靠、低延迟、可调试的系统。这正是“软件架构”这个看似空泛的词在实际项目中如此重要的原因。它决定了传感器数据从采集到被算法消费延迟是 1 毫秒还是 50 毫秒增加一个新技能模块时是改一个文件还是改十几个节点的接口机器人摔倒时日志系统能不能在 10 秒内还原出“哪条指令、哪个关节、哪个时刻”出了问题。新闻里看到的是机器人在竞技场上完成任务技术人应该看到的是一套软件系统如何调度几十个并行任务、如何处理异常、如何在资源受限的端侧设备上保证实时性。2. 人形机器人软件架构全景从芯片到应用在进入代码之前先把全景图建立起来。人形机器人的软件架构大致可以分成五层每一层都有自己清晰的目标层次核心职责典型技术组件关键约束硬件驱动层读写关节数据、采集传感器、下发电流指令EtherCAT、CANopen、Linux 驱动、RTOS微秒级确定性协议隔离实时控制层关节伺服、姿态平衡、力控/阻抗控制实时线程、状态估计、QP 求解器1kHz 或更高控制频率感知与状态层视觉识别、激光建图、状态估计、运动预测ROS 2、OpenCV、PCL、深度学习推理毫秒级延迟算力需求高决策规划层任务调度、行为树、运动规划、避障BehaviorTree.CPP、MoveIt、状态机事件驱动逻辑可读应用与人机交互层语音交互、任务编排、远程监控、日志分析WebSocket、语音引擎、可视化工具产品体验扩展能力这里需要特别理解实时控制层和感知决策层是两套不同的技术体系。很多初学者把 ROS 2 当作“机器人操作系统”后就试图用 ROS 2 的话题通信去跑 1kHz 的关节控制这是典型的架构误用。关节控制需要的是确定性的、微秒级抖动的执行环境而 ROS 2 的话题通信是事件驱动、基于 DDS 的适合感知和决策这种对绝对时限要求没那么苛刻的任务。人形机器人的主流架构是“实时核 应用核”双系统实时核跑关节伺服和姿态控制使用 RTOS 或者带 PREEMPT_RT 补丁的 Linux应用核跑感知、规划、交互使用 ROS 2 作为通信框架。两个核之间通过共享内存、EtherCAT 或者自定义的轻量协议桥接。在整机架构中芯片的作用不是单纯“算得快”而是能否同时满足三种需求实时性能否保证控制线程不被其他任务抢占算力密度感知和决策需要 NPU、GPU 或足够的 CPU 算力接口丰富度能否同时接电机总线、相机、激光雷达、麦克风以及外部调试网络。这也是为什么人形机器人主控芯片的选型不能只看 CPU 跑分要看整个 SoC 的“组合能力”。3. 端侧机器人主控芯片选型为什么 SoC 比想象中重要在“中国人形机器人竞技”类新闻发酵的同时芯片侧的讨论往往被忽略但它恰恰决定了产品能否量产、成本能否下降。这里以国产端侧 SoC 厂商全志科技作为说明对象聊聊机器人主控芯片的真实选型逻辑。从公开产品线看全志科技过去更多出现在平板、智能音箱、车载中控这类消费与工业场景但在机器人热潮里它的端侧 SoC 之所以被开发者关注核心原因是机器人主控芯片的需求和智能硬件主控高度重合需要多核 CPU 处理复杂逻辑需要 NPU 跑视觉模型需要丰富的外设接口接电机和传感器还需要相对可控的功耗和成本。基于材料可以做一个保守判断全志这类国产 SoC 在人形机器人项目中的机会主要不是“大脑”而是“小脑”和“感知协处理器”。人形机器人通常会有多块计算板头部/视觉计算板跑 YOLO、语义分割、视觉 SLAM需要较强的 NPU躯干主控板跑状态机、运动规划、通信网关需要稳定 CPU 和丰富接口实时控制板跑关节伺服和平衡算法需要高实时性 MCU 或带实时核的 SoC语音交互板跑麦克风阵列和唤醒词需要低功耗、低成本的 AI 芯片。在这个分工里全志面向边缘视觉和智能硬件的 SoC 产品线如内置 AI 加速单元的型号比较适合承担“视觉感知任务”和“躯干业务主控”的角色。它们不像英伟达 Jetson 那样把算力堆到极致但在功耗、成本、供货稳定性和工业接口适配上有自己的优势。机器人初创公司做产品定义时如果每个模块都用最高规格的芯片整机 BOM 成本会直接失控而合理的方案往往是“高端算力板 高性价比控制板 MCU 实时板”组合。从开发者视角选型时应该用下面这张表做初步筛选选型维度需要关注的问题验证方法CPU 多核能力能否同时跑感知、规划、通信中间件开启全核负载后测试调度抖动NPU 兼容性团队使用的模型能否转换到该 NPU 工具链跑一个 YOLO 或关键点检测 demo电机总线接口是否支持 EtherCAT / CAN FD / UART查看参考设计原理图实时性内核是否支持 PREEMPT_RT是否有独立实时核cyclictest 测试最大延迟成本与供货在机器人生命周期内能否稳定供货评估原厂产品路线图软件生态BSP、驱动、ROS 2 适配、NPU 工具链是否完善实际编译一个 ROS 2 包并跑话题通信这里特别提醒不要为了“国产替代”而替代也不要为了“生态成熟”而盲目选 NVIDIA。人形机器人项目选芯片的唯一标准是在目标成本下能否满足实时性、算力、接口和功耗四个维度的最低要求。4. 环境准备与前置条件看完架构和选型下面进入实操环节。本文用一个最小的人形机器人软件框架作为示例一台运行 Ubuntu 22.04 的 x86 主机安装了 ROS 2 Humble另外准备一块 ARM 开发板例如基于全志 T527 的评估板或树莓派作为端侧主控如果没有真实机器人可以用 Gazebo 中的差速或双足简化模型代替重点是跑通 ROS 2 的节点通信和状态流转。环境要求如下组件推荐版本说明操作系统Ubuntu 22.04 / Debian 12ROS 2 Humble 官方支持 Ubuntu 22.04ROS 2Humble Hawksbill长期支持版本LTS 到 2027 年中间件DDS默认 Fast DDS无需额外安装Python3.10ROS 2 Python 客户端依赖仿真器Gazebo Classic 11 / Gazebo Fortress可选用于无硬件验证版本控制git、colcon构建 ROS 2 工作空间需要说明的是各组件版本请以实际项目为准本文重点演示通用思路不绑定特定板卡。如果你的开发板是 ARM 架构编译流程稍微不同但 ROS 2 的接口不变。安装 ROS 2 Humble 时如果网络环境访问官方源比较慢建议配置国内镜像源。这里给出一个最小安装命令序列# 添加 ROS 2 官方源以 Ubuntu 22.04 为例 sudo apt update sudo apt install -y curl gnupg lsb-release sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main | sudo tee /etc/apt/sources.list.d/ros2.list /dev/null sudo apt update sudo apt install -y ros-humble-desktop python3-colcon-common-extensions安装完成后初始化环境变量source /opt/ros/humble/setup.bash然后创建工作空间和功能包mkdir -p ~/humanoid_ws/src cd ~/humanoid_ws/src ros2 pkg create --build-type ament_python humanoid_bringup这一步会把humanoid_bringup作为我们的示例功能包后续的代码都放在这个包里。5. 人形机器人 ROS 2 完整示例代码实现下面实现一个简化但完整的人形机器人控制链路一个感知节点发布目标位置一个决策节点根据目标位置选择动作状态一个运动控制节点接收动作指令并通过自定义消息类型传递。为了避免示例依赖特定硬件运动控制节点只输出日志和关节目标角度。5.1 自定义消息类型实际项目中感知、决策、控制之间传递的数据结构往往比标准消息更贴合业务。这里创建一个自定义消息TargetPose.msg用于表示感知到的目标物体在机器人坐标系下的位置。在src下创建消息包cd ~/humanoid_ws/src ros2 pkg create --build-type ament_cmake humanoid_msgs mkdir -p humanoid_msgs/msg创建humanoid_msgs/msg/TargetPose.msg# 目标物体在机器人坐标系下的位置 float32 x float32 y float32 z float32 confidence编辑humanoid_msgs/CMakeLists.txt加入消息定义find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} msg/TargetPose.msg )然后编译消息包cd ~/humanoid_ws colcon build --packages-select humanoid_msgs source install/setup.bash5.2 感知节点发布目标位置感知节点模拟一个视觉检测结果。实际项目中它会订阅相机话题运行模型推理这里用定时器模拟检测到前方 1.2 米处有一个目标。文件路径~/humanoid_ws/src/humanoid_bringup/humanoid_bringup/perception_node.pyimport rclpy from rclpy.node import Node from humanoid_msgs.msg import TargetPose class PerceptionNode(Node): def __init__(self): super().__init__(perception_node) self.publisher self.create_publisher(TargetPose, target_pose, 10) self.timer self.create_timer(0.1, self.on_timer) self.get_logger().info(Perception node started.) def on_timer(self): msg TargetPose() msg.x 1.2 msg.y 0.0 msg.z 0.3 msg.confidence 0.95 self.publisher.publish(msg) self.get_logger().debug(fPublish target: x{msg.x}, y{msg.y}, z{msg.z}) def main(argsNone): rclpy.init(argsargs) node PerceptionNode() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ __main__: main()这个节点的核心逻辑是以 10Hz 的频率发布目标位姿。confidence字段可以传入置信度决策节点可以用它过滤不可靠检测结果。5.3 决策节点有限状态机决策节点订阅target_pose根据目标距离切换机器人状态远距离时approach接近近距离时reach抓取丢失目标时idle。文件路径~/humanoid_ws/src/humanoid_bringup/humanoid_bringup/decision_node.pyimport rclpy from rclpy.node import Node from std_msgs.msg import String from humanoid_msgs.msg import TargetPose class DecisionNode(Node): def __init__(self): super().__init__(decision_node) self.subscription self.create_subscription( TargetPose, target_pose, self.on_target, 10) self.cmd_pub self.create_publisher(String, robot_cmd, 10) self.state idle self.approach_threshold 0.5 self.get_logger().info(Decision node started with state: idle) def on_target(self, msg): if msg.confidence 0.6: self.set_state(idle) return distance (msg.x ** 2 msg.y ** 2 msg.z ** 2) ** 0.5 if distance self.approach_threshold: self.set_state(approach) else: self.set_state(reach) def set_state(self, new_state): if new_state ! self.state: old_state self.state self.state new_state cmd_msg String() cmd_msg.data new_state self.cmd_pub.publish(cmd_msg) self.get_logger().info(fState transition: {old_state} - {new_state}) def main(argsNone): rclpy.init(argsargs) node DecisionNode() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ __main__: main()决策节点不直接控制电机而是发布类型为String的robot_cmd主题。这样做的好处是控制策略和业务决策解耦未来如果从状态机升级到行为树只需要替换这个节点。5.4 运动控制节点接收指令并输出关节目标运动控制节点订阅robot_cmd用一张简单的映射表把动作名称映射为关节目标角度然后输出到日志。真实项目中这里会调用 ros2_control 硬件接口把目标角度写入伺服驱动器。文件路径~/humanoid_ws/src/humanoid_bringup/humanoid_bringup/motion_control_node.pyimport math import rclpy from rclpy.node import Node from std_msgs.msg import String class MotionControlNode(Node): def __init__(self): super().__init__(motion_control_node) self.subscription self.create_subscription( String, robot_cmd, self.on_cmd, 10) self.joint_targets { idle: {hip_pitch: 0.0, knee_pitch: 0.0, arm_shoulder: 0.0}, approach: {hip_pitch: 0.3, knee_pitch: -0.2, arm_shoulder: 0.1}, reach: {hip_pitch: 0.0, knee_pitch: -0.1, arm_shoulder: 0.8}, } self.get_logger().info(Motion control node started.) def on_cmd(self, msg): cmd msg.data if cmd not in self.joint_targets: self.get_logger().warn(fUnknown command: {cmd}) return targets self.joint_targets[cmd] for joint, angle in targets.items(): self.get_logger().info(fSet joint {joint} {math.degrees(angle):.1f} deg) self.get_logger().info(fExecute command: {cmd}) def main(argsNone): rclpy.init(argsargs) node MotionControlNode() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ __main__: main()以上三个节点已经构成了一条完整的数据流perception_node→target_pose→decision_node→robot_cmd→motion_control_node。这个模式扩展到真实机器人时只需要在运动控制节点里把日志替换成 ros2_control 的指令下发其余结构都可以保留。5.5 Launch 文件一键启动为了让三个节点可以一键启动编写 launch 文件。文件路径~/humanoid_ws/src/humanoid_bringup/launch/humanoid_bringup.launch.pyimport launch from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): perception_node Node( packagehumanoid_bringup, executableperception_node, nameperception_node, outputscreen, parameters[{use_sim_time: False}], ) decision_node Node( packagehumanoid_bringup, executabledecision_node, namedecision_node, outputscreen, ) motion_control_node Node( packagehumanoid_bringup, executablemotion_control_node, namemotion_control_node, outputscreen, ) return LaunchDescription([ perception_node, decision_node, motion_control_node, ])还需要在setup.py中加入 entry point否则ros2 run找不到可执行文件。编辑~/humanoid_ws/src/humanoid_bringup/setup.py在entry_points中补充entry_points{ console_scripts: [ perception_node humanoid_bringup.perception_node:main, decision_node humanoid_bringup.decision_node:main, motion_control_node humanoid_bringup.motion_control_node:main, ], },5.6 参数配置与主题重映射在实际机器人上感知阈值、控制频率、关节限位这些参数不应该写死在代码里。ROS 2 的推荐做法是把参数写入 YAML 文件。这里给出一个示例配置文件路径~/humanoid_ws/src/humanoid_bringup/config/robot_config.yamlperception_node: ros__parameters: publish_hz: 10.0 confidence_threshold: 0.6 decision_node: ros__parameters: approach_threshold: 0.5 idle_timeout: 3.0 motion_control_node: ros__parameters: control_period_ms: 10 joint_limits: hip_pitch: [-0.5, 0.5] knee_pitch: [-0.8, 0.0] arm_shoulder: [-1.2, 1.2]在 launch 文件中通过parameters[...]把 YAML 传入对应节点就可以在不重新编译的前提下调整行为。6. 运行结果与效果验证代码写完之后进入验证环节。先编译整个工作空间cd ~/humanoid_ws colcon build source install/setup.bash启动三个节点ros2 launch humanoid_bringup humanoid_bringup.launch.py正常启动后终端会依次打印三个节点的启动日志。因为感知节点发布频率是 10Hz决策节点会和距离阈值比较后切换状态运动控制节点会输出关节目标角度。预期输出类似[perception_node]: Perception node started. [decision_node]: Decision node started with state: idle [motion_control_node]: Motion control node started. [decision_node]: State transition: idle - approach [motion_control_node]: Set joint hip_pitch 17.2 deg [motion_control_node]: Set joint knee_pitch -11.5 deg [motion_control_node]: Set joint arm_shoulder 5.7 deg [decision_node]: State transition: approach - reach [motion_control_node]: Set joint hip_pitch 0.0 deg [motion_control_node]: Set joint knee_pitch -5.7 deg [motion_control_node]: Set joint arm_shoulder 45.8 deg如果运行失败第一步不是改代码而是检查节点是否都活着。用另外两个终端查看ros2 node list ros2 topic list ros2 topic echo /robot_cmdnode list应该看到perception_node、decision_node、motion_control_node三个名字。topic echo应该能看到approach和reach字符串交替输出。这里特别提醒如果三个节点在同一台机器上运行DDS 的发现机制通常没问题如果感知节点跑在 ARM 开发板上决策和控制节点跑在 x86 主机上则需要确认两端处于同一网段并且防火墙没有屏蔽 7400-7500 附近的 DDS 端口。7. 人形机器人常见问题与排查方法实际开发中你大概率会碰到下面这些问题问题现象可能原因排查方式解决方案colcon build报找不到消息类型没有先编译依赖包或没有source install/setup.bash检查编译顺序和功能包依赖先编译humanoid_msgs再编译humanoid_bringup节点启动后互相看不到话题DDS 域 ID 不一致或网络隔离运行ros2 domain list检查多机网络统一ROS_DOMAIN_ID开放 DDS 端口决策节点频繁状态切换感知抖动或阈值不合适ros2 topic echo /target_pose观察数据增加置信度过滤或使用状态机最小持续时间运动控制节点收不到指令决策节点没有发布检查robot_cmd话题是否有数据用ros2 topic info /robot_cmd -v查看发布者和订阅者实时控制频率上不去ROS 2 话题通信不适合高频控制查看 CPU 占用和 DDS 队列延迟把关节伺服下放到实时核/RTOSROS 2 只做上层调度机器人运行一段时间后卡顿大量日志写磁盘、共享内存不足检查/tmp占用查看 dmesg启用 log 轮转使用executor参数优化线程模型NPU 模型推理精度低模型转换参数不对对比推理输出与原始模型输出重新量化使用原厂工具链校准数据集在这些问题中最容易被忽略的是日志系统。人形机器人一旦在真实环境中摔倒如果没有可靠的日志回溯调试会变成一场灾难。建议从第一天起就给每个节点加上时间戳、关节 ID 和命令来源日志输出统一走ros2 bag record或者轻量级日志服务而不是随手print。ros2 bag是 ROS 2 自带的录制工具可以把话题数据完整记录下来事后用于回放分析ros2 bag record /target_pose /robot_cmd -o robot_log这个命令会把感知和决策数据记录到robot_log目录之后用ros2 bag play robot_log就可以离线重放相同的数据流方便定位是感知问题还是控制问题。8. 最佳实践与工程建议基于前文的架构和示例这里给出几条对真实项目更有价值的工程建议。第一硬实时任务绝不放在 ROS 2 线程池里。ROS 2 的默认 executor 对事件驱动任务很友好但关节伺服、PID 计算、状态估计这些需要 1kHz 稳定周期的任务必须放在独立实时线程或 RTOS 核心里。常见做法是底层用 EtherCAT 主站跑实时循环通过共享内存与 ROS 2 节点交换目标位置和反馈状态。这个边界一旦模糊轻则控制抖动重则机器人摔倒。第二模块拆分以“故障隔离”为第一原则。感知挂了机器人应该停在原地而不是乱走决策挂了运动控制应该进入安全急停而不是执行最后一条指令。示例代码里每个节点独立进程恰恰实现了这个隔离能力。如果你图省事把感知、决策、控制写进一个 Python 脚本某一行代码异常会直接拖垮整个系统。第三仿真先行但仿真不能替代真机验证。Gazebo 或者 MuJoCo 这类仿真环境能帮你先把软件架构和数据流跑通也能批量测试极端参数。但人形机器人的接触动力学、电机发热、通信延迟在仿真里很难完全还原。更稳妥的节奏是仿真验证逻辑、半实物平台验证接口、真机小步快跑验证稳定性和安全策略。第四关注芯片的“软件工具链”而非单纯算力。在前面选型讨论里很多开发者只盯着 NPU 的 TOPS 数值却忽略了工具链是否支持当前模型、BSP 是否能稳定跑 ROS 2、驱动是否容易适配电机总线。全志这类国产 SoC 的优势场景恰恰是文档相对开放、硬件接口丰富、参考设计完整开发者能更快把整机板卡做出来。但在考虑这类芯片时建议先在评估板上完成三个测试编译 ROS 2 功能包、跑一个目标检测模型、用cyclictest测实时延迟合格后再进入整机设计。第五把参数系统做成“可观测、可回滚”。用 YAML 管理参数之后所有改动都要进入 git 历史。真机调试时团队经常遇到“昨天还能走今天怎么不行了”的情况如果没有参数版本管理很难定位是谁改了哪个阈值。ROS 2 本身有ros2 param dump工具可以把节点的当前参数保存下来建议每次调试前先 dump 一份基准参数。第六安全机制要从第一天设计不能最后补。人形机器人的关节功率和活动范围对人是危险的。软件上要有看门狗、急停话题、关节软限位硬件上要有独立的急停回路不依赖主控 CPU。这些安全逻辑的优先级要高于任何功能逻辑而且不能只在调试时开启。9. 总结与后续学习方向回到开头的新闻画面。人形机器人从实验室到竞技场再到未来的家庭和工厂真正决定上限的不是某一个漂亮的 demo而是软件架构能否在长期运行中保持稳定、在故障时能否快速定位、在增加新技能时能否低成本扩展。这篇文章讲清楚了几件事人形机器人软件架构的分层逻辑实时控制与上层决策的边界端侧主控芯片的选型维度以及全志这类国产 SoC 在机器人系统中更可能承担的角色最后用 ROS 2 跑通了一条“感知 → 决策 → 控制”的最小链路。建议你把这套代码保存下来作为后续学习行为树、ros2_control、运动规划的前置骨架。下一步可以沿着三个方向深入一是把决策节点从状态机升级为行为树用 BehaviorTree.CPP 管理复杂任务二是接入 ros2_control 和真实关节驱动把日志输出替换为真实指令三是引入仿真器在 Gazebo 中搭一个简化双足模型测试姿态平衡和步态规划。无论哪个方向核心都是同一件事让人形机器人这个复杂系统变得可构建、可调试、可演进。