1. 为什么人形机器人开发绕不开 Jetson Thor 这套环境
人形机器人这个赛道,这两年从实验室一路卷到了工程落地。我前后接触过不少平台,从早期拿 NUC 凑合跑感知,到后来用 Xavier、Orin 一路迭代,再到最近上手 Jetson Thor,最大的感受就是:算力上来了,但环境配置的复杂度也跟着上来了。Thor 这颗芯片定位很明确,就是冲着具身智能、人形机器人这种需要本地跑大模型推理加实时控制回路的场景去的,它的 GPU 算力、内存带宽、以及对多路传感器同步的支持,都不是前几代能比的。
但问题也恰恰出在这里。你拿到一块 Thor 的开发套件,开机之后会发现它预装的是 NVIDIA 自己的 JetPack 体系,底层是 Ubuntu 加 L4T 内核,跟你在普通 x86 机器上装的 Ubuntu 完全不是一回事。ARM64 架构、特定的 CUDA 版本、TensorRT 的绑定关系、还有那个让人又爱又恨的 JetPack 版本管理,任何一环没对齐,后面编译 Unitree SDK 或者跑 ROS2 节点的时候就会各种报错。我见过太多人卡在第一步——环境都没配通,就开始怀疑是不是硬件有问题。
这篇内容我打算把「人形 + Jetson Thor + 环境配置」这条链路完整拆一遍。核心围绕几个东西:JetPack 基础系统、ROS2 的选型和安装、Unitree SDK 的编译接入、CycloneDDS 的调优,以及中间那些官方文档不会告诉你的坑。适合谁看?如果你手上已经有一台 Thor 设备,或者正准备入手做人形机器人的本体开发、运控对接、感知部署,那这篇基本能帮你把环境这关一次性趟平。如果你还在用 Orin 或者 Xavier,里面的 ROS2 和 DDS 部分同样适用,只是算力相关的参数要按你的平台调整。
我尽量不写成说明书,而是按我实际配置的顺序和踩坑的顺序来讲,你能直接抄作业,也能理解每一步为什么要这么做。
2. 整体方案设计与选型思路拆解
2.1 为什么是 JetPack 而不是自己刷 Ubuntu
很多人第一反应是:我能不能直接给 Thor 刷一个干净的 Ubuntu 22.04,然后自己装 CUDA 和驱动?理论上可以,实际上极其痛苦。Jetson 系列的 GPU 驱动、CUDA、cuDNN、TensorRT 是深度绑定在 L4T(Linux for Tegra)内核上的,你单独装官方 CUDA 包大概率版本对不上,就算勉强装上,GPU 也用不起来。JetPack 本质上是 NVIDIA 把这一整套东西打包好了,包括 bootloader、内核、驱动、CUDA、多媒体 API、以及一堆 samples。
所以正确的做法是:以 JetPack 为底座,在上面叠加你的机器人软件栈。Thor 对应的 JetPack 版本目前是 7.x 系列,底层 Ubuntu 版本是 22.04(Jammy),这个信息很关键,因为它直接决定了你后面 ROS2 该选 Humble 还是 Jazzy。Ubuntu 22.04 对应 ROS2 Humble,这是官方长期支持组合,社区资料最全,遇到问题最好查。
注意:不要看到网上有人用 Jazzy 就跟着装,Jazzy 对应的是 Ubuntu 24.04,在 Thor 的 JetPack 7 上强行装会有大量依赖冲突,尤其是 ros-humble 系列的包和系统库版本对不上。
2.2 ROS2 版本与 DDS 中间件的取舍
ROS2 的通信层默认用的是 DDS,而 DDS 的实现有好几种:Fast DDS、CycloneDDS、RTI Connext 等。为什么这次要特别提 CycloneDDS?因为人形机器人对通信的实时性和确定性要求很高,尤其是关节控制回路,几十个电机同时收发指令,如果 DDS 的发现机制和序列化效率不行,就会出现延迟抖动甚至丢包。
Fast DDS 是 ROS2 Humble 的默认实现,开箱即用,但在多网卡、大流量场景下配置起来比较绕。CycloneDDS 的优势在于它的配置更透明,XML 配置文件清晰,对多播和单播的控制更细,而且在 ARM 平台上的性能表现稳定。Unitree 的官方 SDK 和很多运控示例里,推荐的就是 CycloneDDS。所以我的方案是:ROS2 Humble + CycloneDDS 作为 RMW 实现,这样跟 Unitree SDK 的对接最顺。
2.3 Unitree SDK 的接入方式选择
Unitree 提供了好几套 SDK:unitree_sdk2(C++)、unitree_sdk2_python、还有基于 ROS2 的 unitree_ros2 包。这里要分清楚你的用途。如果你是要做底层运控、直接发关节指令,那用 C++ 的 unitree_sdk2 性能最好,延迟最低。如果你是要做上层算法验证、快速原型,那 unitree_sdk2_python 更顺手。而 unitree_ros2 是把 SDK 封装成了 ROS2 的 topic 和 service,方便跟其他 ROS2 节点集成。
我的建议是三套都装上,因为它们不冲突,而且实际开发中你会混着用。底层控制用 C++ SDK,算法验证用 Python SDK,系统集成用 ROS2 包。下面我会分别讲怎么配。
2.4 整体环境分层结构
把整个环境想成四层,从下往上:
| 层级 | 内容 | 作用 |
|---|---|---|
| 第一层 | JetPack 7 / L4T / Ubuntu 22.04 | 基础系统与驱动 |
| 第二层 | CUDA / TensorRT / cuDNN | GPU 算力支撑 |
| 第三层 | ROS2 Humble + CycloneDDS | 通信与节点框架 |
| 第四层 | Unitree SDK / 应用代码 | 机器人业务逻辑 |
这个分层很重要,因为排查问题的时候你要能判断是哪一层出的问题。比如编译 SDK 报错,可能是第三层的 ROS2 环境变量没 source,也可能是第二层的 CUDA 路径不对。分层清晰,排查就有方向。
3. 核心细节解析与实操要点
3.1 JetPack 基础系统的确认与更新
拿到 Thor 设备后,第一件事不是急着装东西,而是确认当前系统状态。打开终端,先看几个关键信息:
# 查看 L4T 版本 cat /etc/nv_tegra_release # 查看 JetPack 版本 cat /etc/nv_jetpack_release 2>/dev/null || dpkg -l | grep jetpack # 查看 Ubuntu 版本 lsb_release -a # 查看 CUDA 版本 nvcc --version # 查看 GPU 信息 sudo tegrastatstegrastats这个命令特别有用,它能实时显示 GPU 占用、内存带宽、各模块功耗和温度。人形机器人跑起来之后,你靠它来判断算力瓶颈在哪。我一般会开着它跑一整天,观察峰值。
确认完版本后,做一次系统更新:
sudo apt update sudo apt upgrade -y sudo apt install -y python3-pip python3-dev build-essential cmake git注意:
apt upgrade在 Jetson 上有时候会升级 L4T 内核相关包,如果 JetPack 版本比较新,建议先sudo apt-mark hold住关键的内核包,避免升级后驱动不匹配。我踩过一次,升级完 GPU 直接不认了,只能重刷。
3.2 CUDA 与 TensorRT 环境变量配置
JetPack 装好之后 CUDA 其实已经在系统里了,但环境变量不一定配全。检查~/.bashrc里有没有这些:
export CUDA_HOME=/usr/local/cuda export PATH=$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH export TENSORRT_DIR=/usr/lib/aarch64-linux-gnu如果没有就加上,然后source ~/.bashrc。验证一下:
nvcc --version python3 -c "import tensorrt; print(tensorrt.__version__)"TensorRT 的 Python 包在 JetPack 里通常是预装的,但如果你用 pip 装过别的版本,可能会冲突。这时候用pip show tensorrt看一下路径,确保它指向系统自带的那个。
3.3 ROS2 Humble 在 ARM64 上的安装要点
ROS2 Humble 在 ARM64 上的安装跟 x86 略有不同,主要是 apt 源的问题。官方有提供 Ubuntu 22.04 的 ARM64 包,直接按标准流程走就行:
# 添加 ROS2 源 sudo apt install -y software-properties-common sudo add-apt-repository universe sudo apt update && sudo apt install -y curl 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 $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null # 安装 ROS2 Humble sudo apt update sudo apt install -y ros-humble-desktop ros-dev-tools装完之后 source 环境:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc source ~/.bashrc验证:
ros2 topic list如果能看到/parameter_events和/rosout,说明 ROS2 基础环境通了。
实操心得:
ros-humble-desktop包含了 RViz2、demo 节点等一大堆东西,在 Thor 上装完大概占 2-3GB。如果你存储紧张,可以只装ros-humble-ros-base,然后按需装 RViz2。但人形机器人开发基本离不开 RViz2 做可视化,所以我还是建议直接装 desktop。
3.4 CycloneDDS 的安装与切换
ROS2 Humble 默认用 Fast DDS,切换到 CycloneDDS 需要装对应的 RMW 包:
sudo apt install -y ros-humble-rmw-cyclonedds-cpp然后设置环境变量:
echo "export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp" >> ~/.bashrc source ~/.bashrc验证当前用的是哪个 RMW:
ros2 doctor --report | grep rmw应该能看到rmw_cyclonedds_cpp。
3.5 CycloneDDS 配置文件调优
光切换还不够,CycloneDDS 的默认配置在多网卡环境下会有问题。人形机器人通常有多个网络接口:一个连内部电机总线,一个连外部调试网络,可能还有 WiFi。如果不指定用哪个网卡,DDS 的发现机制会到处广播,导致通信不稳定。
创建一个配置文件~/cyclonedds_config.xml:
<?xml version="1.0" encoding="UTF-8"?> <CycloneDDS xmlns="https://cdds.io/config"> <Domain id="any"> <General> <Interfaces> <NetworkInterface name="eth0" priority="default" multicast="default"/> </Interfaces> <AllowMulticast>default</AllowMulticast> </General> <Internal> <Watermarks> <WhcHigh>500kB</WhcHigh> </Watermarks> </Internal> </Domain> </CycloneDDS>把eth0换成你实际用的网卡名,用ip addr查。然后设置:
echo "export CYCLONEDDS_URI=file://$HOME/cyclonedds_config.xml" >> ~/.bashrc source ~/.bashrc注意:
WhcHigh这个参数控制的是发送缓冲区的高水位线。默认值在高频小包场景下会导致频繁的阻塞,调到 500kB 能明显改善关节指令的抖动。这个是我实测出来的,官方文档里没写。
3.6 Unitree SDK 的编译与依赖处理
Unitree SDK 的 C++ 版本依赖 CMake、Eigen3、以及一些网络库。先装依赖:
sudo apt install -y libeigen3-dev libboost-all-dev libyaml-cpp-dev然后克隆编译:
git clone https://github.com/unitreerobotics/unitree_sdk2.git cd unitree_sdk2 mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) sudo make install编译过程中最常见的报错是找不到 Eigen3,这时候检查/usr/include/eigen3是否存在,如果不存在说明libeigen3-dev没装好。另一个常见问题是 CMake 版本太低,Thor 自带的 CMake 一般是 3.22 以上,够用。
Python 版本更简单:
pip3 install unitree_sdk2py但注意,pip 装的版本可能跟你的固件版本不匹配,如果通信报错,建议从源码装:
git clone https://github.com/unitreerobotics/unitree_sdk2_python.git cd unitree_sdk2_python pip3 install -e .3.7 ROS2 与 Unitree SDK 的桥接
如果你要用 ROS2 跟 Unitree 机器人通信,需要装unitree_ros2包:
mkdir -p ~/unitree_ws/src cd ~/unitree_ws/src git clone https://github.com/unitreerobotics/unitree_ros2.git cd ~/unitree_ws colcon build source install/setup.bash编译的时候如果报找不到unitree_sdk2,说明前面 C++ SDK 的make install没成功,或者CMAKE_PREFIX_PATH没包含安装路径。检查/usr/local/lib/cmake/unitree_sdk2是否存在。
4. 实操过程与核心环节实现
4.1 从零到能跑通的第一条关节指令
环境配好之后,怎么验证整条链路是通的?我的做法是写一个最小的测试程序,直接给某个关节发一个位置指令,看机器人有没有反应。这一步能同时验证 SDK 编译、网络通信、DDS 配置、以及硬件连接。
先确认网络。Unitree 机器人的控制接口通常走以太网,默认 IP 段是192.168.123.x。把你的 Thor 网口配成同网段:
sudo ip addr add 192.168.123.100/24 dev eth0 sudo ip link set eth0 up ping 192.168.123.161192.168.123.161通常是机器人的主控地址,能 ping 通说明物理链路没问题。
然后写一个 C++ 测试程序:
#include <unitree/robot/channel/channel_publisher.hpp> #include <unitree/idl/hg/LowCmd_.hpp> #include <unitree/idl/hg/LowState_.hpp> #include <thread> #include <chrono> using namespace unitree::robot; using namespace unitree::common; int main() { ChannelFactory::Instance()->Init(0, "eth0"); ChannelPublisher<unitree_hg::msg::dds_::LowCmd_> publisher("rt/lowcmd"); publisher.InitChannel(); unitree_hg::msg::dds_::LowCmd_ cmd; // 初始化电机数量等参数 // ... while (true) { // 填充关节指令 publisher.Write(cmd); std::this_thread::sleep_for(std::chrono::milliseconds(2)); } return 0; }编译:
g++ -o test_joint test_joint.cpp -lunitree_sdk2 -lddscxx -lddsc -lpthread注意:
ChannelFactory::Instance()->Init(0, "eth0")里的第二个参数是网卡名,必须跟你实际连机器人的网卡一致。如果写错了,程序不会报错,但指令发不出去,你会以为是 SDK 的问题,其实是网卡选错了。这个坑我踩过,排查了半天。
4.2 CycloneDDS 与 Unitree 通信的兼容性验证
Unitree 的 SDK 内部其实也用了 DDS,但它用的是自己封装的unitree_sdk2里的 DDS 层,跟 ROS2 的 CycloneDDS 是两套东西。如果你要让 ROS2 节点和 Unitree SDK 直接通信,需要确保它们用的是同一个 DDS Domain。
检查方法:
# 查看当前 DDS domain echo $ROS_DOMAIN_ID # Unitree SDK 默认用 domain 0如果 ROS2 的 domain 不是 0,改成 0:
export ROS_DOMAIN_ID=0然后在 ROS2 里订阅 Unitree 的 topic:
ros2 topic list ros2 topic echo /rt/lowstate如果能看到关节状态数据,说明 ROS2 和 Unitree SDK 的 DDS 层打通了。
4.3 实时性调优:CPU 亲和性与调度策略
人形机器人的控制回路对实时性要求很高,Thor 虽然算力强,但默认的 Linux 调度策略是给通用场景设计的,控制线程可能会被其他任务抢占。我的做法是给控制线程绑核,并设置实时优先级。
# 查看 CPU 核心 nproc lscpu | grep "Model name"Thor 一般是 12 核或 14 核的 ARM 架构,我会把控制线程绑到单独的核心上,比如核心 6:
#include <pthread.h> #include <sched.h> void set_realtime_priority() { struct sched_param param; param.sched_priority = 80; pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(6, &cpuset); pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); }注意:设置
SCHED_FIFO需要 root 权限,或者给可执行文件加CAP_SYS_NICE能力。另外,绑核之后要确保那个核心上没有其他重负载任务,否则适得其反。我一般会把 GPU 相关的进程赶到其他核心上。
4.4 感知与控制的算力分配
Thor 的 GPU 算力很强,但人形机器人上 GPU 要同时跑感知模型(比如视觉、点云)和控制策略(比如强化学习策略网络)。如果不做分配,两者会互相抢资源。
我的做法是用 CUDA 的 stream 机制做隔离:
cudaStream_t perception_stream, control_stream; cudaStreamCreate(&perception_stream); cudaStreamCreate(&control_stream); // 感知任务用 perception_stream // 控制任务用 control_stream同时用tegrastats监控 GPU 占用,如果感知任务把 GPU 占满了,控制任务的推理延迟就会飙升。这时候要么降低感知模型的频率,要么用 TensorRT 做量化加速。
4.5 完整启动流程的脚本化
环境配好之后,每次开机都要 source 一堆东西、设置一堆环境变量,很容易漏。我写了一个启动脚本setup_robot_env.sh:
#!/bin/bash # ROS2 环境 source /opt/ros/humble/setup.bash source ~/unitree_ws/install/setup.bash # DDS 配置 export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp export CYCLONEDDS_URI=file://$HOME/cyclonedds_config.xml export ROS_DOMAIN_ID=0 # CUDA 环境 export CUDA_HOME=/usr/local/cuda export PATH=$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH # 网络配置 sudo ip addr add 192.168.123.100/24 dev eth0 2>/dev/null sudo ip link set eth0 up echo "Robot environment ready."每次开终端先source setup_robot_env.sh,省得漏配置。
5. 常见问题与排查技巧实录
5.1 编译类问题速查
| 报错信息 | 可能原因 | 解决方法 |
|---|---|---|
fatal error: Eigen/Core: No such file | Eigen3 未安装或路径不对 | sudo apt install libeigen3-dev,检查/usr/include/eigen3 |
undefined reference to dds_create_participant | DDS 库链接顺序问题 | 确保-lddscxx -lddsc在-lunitree_sdk2之后 |
CMake Error: Could not find unitree_sdk2 | SDK 未 install 或 CMAKE_PREFIX_PATH 缺失 | 重新sudo make install,设置export CMAKE_PREFIX_PATH=/usr/local:$CMAKE_PREFIX_PATH |
ImportError: libunitree_sdk2.so not found | Python 包找不到 C++ 库 | export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH |
5.2 通信类问题排查
通信问题是最难查的,因为往往不报错,只是没反应。我的排查顺序是:
- 物理层:
ping机器人地址,确认网线、网口、IP 配置没问题。 - 网卡选择:确认程序里指定的网卡名跟实际一致,用
ip addr查。 - DDS Domain:确认 ROS2 和 SDK 的 domain 一致,都是 0。
- 防火墙:
sudo ufw status,如果开着,关掉或者放行 DDS 端口。 - 多播:有些交换机默认禁多播,CycloneDDS 配置里可以改成单播。
实操心得:我遇到过一次 ping 得通但 DDS 就是不通的情况,最后发现是网卡开了节能模式,导致小包延迟抖动大。用
sudo ethtool -K eth0 gro off gso off tso off关掉 offload 就好了。这个坑很隐蔽,网上资料也少。
5.3 实时性问题的表现与定位
实时性问题通常表现为:关节指令延迟、机器人动作抖动、控制频率上不去。定位方法:
# 查看线程调度情况 ps -eLo pid,tid,class,rtprio,pri,psr,comm | grep your_program # 查看中断情况 cat /proc/interrupts | grep eth0如果发现控制线程的psr(运行核心)一直在变,说明没绑核成功。如果rtprio是-,说明实时优先级没设上。
5.4 内存与散热问题
Thor 的性能强,功耗也高。人形机器人本体上散热空间有限,如果散热做不好,Thor 会降频,算力直接打对折。监控方法:
sudo tegrastats --interval 1000关注tj(结温)和gpu频率。如果结温超过 85 度,就要考虑加散热片或者风扇。我实测下来,Thor 在满载跑感知加控制的时候,功耗能到 40W 以上,散热必须认真对待。
5.5 版本兼容性避坑清单
- JetPack 版本和 CUDA 版本必须匹配,不要单独升级 CUDA。
- ROS2 Humble 对应 Ubuntu 22.04,不要混用 Jazzy 的包。
- Unitree SDK 的版本要跟机器人固件版本匹配,升级固件后 SDK 也要更新。
- CycloneDDS 的版本跟 ROS2 Humble 绑定,不要用 pip 装别的版本。
6. 一些实际配置中的经验补充
6.1 关于 VSCode 远程开发
在 Thor 上直接写代码体验一般,我习惯用 VSCode 的 Remote-SSH 连上去开发。配置 C++ 环境的时候,c_cpp_properties.json里要把 ROS2 和 Unitree SDK 的头文件路径都加进去:
{ "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**", "/opt/ros/humble/include/**", "/usr/local/include/**", "/usr/include/eigen3/**" ], "defines": [], "compilerPath": "/usr/bin/gcc", "cStandard": "c17", "cppStandard": "c++17", "intelliSenseMode": "linux-gcc-arm64" } ], "version": 4 }Python 环境的话,在 VSCode 里选/usr/bin/python3作为解释器,因为 pip 装的包默认装到系统 Python 里。
6.2 关于 Docker 容器化
如果你不想污染 Thor 的主系统,可以用 Docker 跑 ROS2。NVIDIA 提供了l4t-ros的基础镜像,直接拉下来就能用:
docker pull nvcr.io/nvidia/l4t-ros:humble docker run -it --runtime nvidia --network host nvcr.io/nvidia/l4t-ros:humble但注意,Docker 里的 DDS 通信要跟宿主机打通,需要--network host,否则 DDS 发现机制会失效。另外 GPU 访问需要--runtime nvidia,这个在 Jetson 上要提前配好nvidia-container-runtime。
6.3 关于零拷贝通信
ROS2 在 Humble 里支持零拷贝(loaned messages),对于大点云、大图像这种数据,零拷贝能显著降低延迟。启用方法是在发布者和订阅者都用rclcpp::LoanedMessage,并且确保 RMW 支持。CycloneDDS 对零拷贝的支持还在完善中,Fast DDS 支持更好。如果你对零拷贝有强需求,可能要在 DDS 选型上重新权衡。
6.4 关于固件升级的注意事项
Unitree 机器人的固件升级有风险,升级过程中断电会导致机器人变砖。升级前务必:
- 确认电池电量在 50% 以上。
- 用有线网络升级,不要用 WiFi。
- 升级完先做一次完整的关节自检,确认所有电机正常。
我个人的习惯是,固件升级和 SDK 升级同步做,避免版本错配。升级前把当前能跑通的代码和环境做个备份,出问题能快速回滚。
6.5 一个容易被忽略的点:时间同步
人形机器人上多个传感器和控制器之间需要时间同步,否则融合出来的数据会有偏差。Thor 上可以用 PTP 或者 NTP。如果机器人内部有交换机支持 PTP,优先用 PTP,精度能到微秒级。配置方法:
sudo apt install linuxptp sudo ptp4l -i eth0 -m -s如果只是做算法验证,NTP 也够用,但控制回路对时间敏感,还是建议上 PTP。
这套环境我从头配到尾大概花了三天,其中大部分时间花在排查 DDS 通信和实时性调优上。真正装软件的时间其实不多,难的是让各个组件协同工作。如果你刚开始上手,建议按我这个顺序一步步来,每步都验证通过再往下走,不要跳步。尤其是 DDS 那块,配置不对的话后面所有通信都会有问题,而且很难查。
最后分享一个小技巧:把tegrastats和ros2 topic hz同时开着跑,能直观看到算力占用和通信频率的关系。如果发现某个 topic 的频率上不去,先看 CPU 和 GPU 占用,再看 DDS 配置,基本能定位到瓶颈。这个组合我用了很久,比任何 profiling 工具都直接。