Files
ROS2_learn/doc/100-embedded-deployment.md
T
2026-08-05 18:17:25 +08:00

932 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 100 · 嵌入式部署:RDK X5 + RK3506 + ROS2(完全实操指南)
> **本篇目标**:从"硬件到手"到"三机 ROS2 互通、RK3506 控制真实电机"全流程跑通。
> 假设你手上有:1 台 PC + 1 台 RDK X5 + 2 台 RK3506。本指南按 **step-by-step** 走,每步给出**预期输出 + 排错**。
---
## 目录
- [第 0 章: 准备与检查清单](#0-准备与检查清单)
- [第 1 章: 三层架构详解](#1-三层架构详解)
- [第 2 章: PC 主控完整配置](#2-pc-主控完整配置)
- [第 3 章: RDK X5 边缘 AI 配置](#3-rdk-x5-边缘-ai-配置)
- [第 4 章: RK3506 精简 ROS2 配置(核心)](#4-rk3506-精简-ros2-配置核心)
- [第 5 章: 三机 LAN DDS 组网](#5-三机-lan-dds-组网)
- [第 6 章: 跨机 Demo 联调](#6-跨机-demo-联调)
- [第 7 章: RK3506 接真实电机 / 编码器](#7-rk3506-接真实电机--编码器)
- [第 8 章: ros2_control 闭环](#8-ros2_control-闭环)
- [第 9 章: 故障排查大全](#9-故障排查大全)
- [第 10 章: 性能调优 Checklist](#10-性能调优-checklist)
- [附录 A: 采购清单](#附录-a-采购清单)
- [附录 B: 关键命令速查](#附录-b-关键命令速查)
---
## 0. 准备与检查清单
### 0.1 你需要的硬件
- [ ] PC 一台(Ubuntu 22.04 / Windows + Docker)
- [ ] RDK X5 一台(已烧 Ubuntu 22.04 官方镜像)
- [ ] RK3506 两台(已烧 Linux 镜像,默认账户 `root``user`)
- [ ] 网线 + 路由器 / 交换机(三个设备同网段)
- [ ] RK3506 ↔ PC / RDK X5 的 USB-TTL 串口线(debug 用)
### 0.2 网络规划(提前固定)
```
PC 192.168.1.10
RDK X5 192.168.1.20
RK3506-1 192.168.1.31
RK3506-2 192.168.1.32
子网掩码 255.255.255.0
ROS_DOMAIN_ID = 0 (三层共用)
```
> 💡 **强烈建议固定 IP**(路由器 DHCP 静态分配 或 各板 `/etc/network/interfaces` 写死),避免重启后地址变了连不上。
### 0.3 先确认每台设备能 SSH / 访问
```bash
# PC
ssh user@192.168.1.10 # 应该能登
# RDK X5
ssh user@192.168.1.20
# RK3506
ssh user@192.168.1.31
ssh user@192.168.1.32
```
每台板子**先 ping 互通**:
```bash
ping -c 3 192.168.1.20 # 在 PC 上 ping RDK X5
ping -c 3 192.168.1.31 # 在 PC 上 ping RK3506 #1
```
**预期**: `0% packet loss`
**若不通**: 检查网线、IP、子网掩码、路由器是否拦 IGMP。
---
## 1. 三层架构详解
### 1.1 为什么分层?
| 层 | 设备 | 跑什么 | 不跑什么 | 为什么 |
|---|---|---|---|---|
| **PC (主控)** | x86 8GB+ | MoveIt2 / Nav2 / RViz / 仿真 / 编译 | 真电机驱动 | 算力大但不便实时控制硬件 |
| **RDK X5 (边缘 AI)** | ARM + 5 TOPS NPU | YOLO / SAM / Whisper / SLAM | MoveIt2 大规划 | NPU 加速推理,分担 PC 算力 |
| **RK3506 (实时控制)** | ARM 3 核 512MB | JointState / PID 闭环 / 编码器读取 | AI 推理 / RViz | 资源紧,但本地控制实时性好 |
### 1.2 数据流(典型抓取场景)
```
┌─────────────────────────────────────────┐
│ PC │
│ RViz 可视化 + 决策 + MoveIt2 规划 │
└─────┬───────────────────────┬───────────┘
│ /grasp_pose │ /joint_trajectory
│ (DDS) │ (DDS)
┌───────────────────────▼─────────┐ ┌─────────▼───────────┐
│ RDK X5 │ │ RK3506 #1 │
│ vision_node: │ │ joint_state_publisher│
│ YOLO 检测 → publish /grasp │ │ 编码器 → /joint_states│
│ (可选) VLA 推理(NPU 加速) │ │ 订阅 /cmd_vel 控制电机│
└─────────────────────────────────┘ └─────────────────────┘
┌─────────────────────┐
│ RK3506 #2 │
│ NPU 推理 / 备用控制│
│ (YOLO-Lite / SAM) │
└─────────────────────┘
```
### 1.3 通信方式(三选一)
| 方式 | 优点 | 缺点 | 用法 |
|---|---|---|---|
| **Multicast 默认** | 零配置,自动发现 | 路由器/防火墙可能拦 | 同网段 + 默认 |
| **Unicast discovery server** | 跨网段可用 | 要起 discovery server | 多 VLAN / 跨子网 |
| **Unicast `ROS_STATIC_PEERS`** | 简单、稳 | 节点列表要预先列 | 小规模 LAN |
**推荐先试 multicast,不通再切 unicast**。下面 Step 5 详解。
---
## 2. PC 主控完整配置
### 2.1 你的工作环境(选项 A: Linux)
假设你用 **Ubuntu 22.04** 真机(不是 Docker)。
```bash
# 2.1.1 装 ROS2 Humble
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt install curl -y
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
sudo apt update
sudo apt install ros-humble-desktop python3-colcon-common-extensions -y
# 验证
source /opt/ros/humble/setup.bash
ros2 --help # 应该输出 ros2 命令帮助
```
### 2.2 你的工作环境(选项 B: Windows + Docker)——本仓库主路径
如果你用的是本仓库的 Docker 工作流,环境已就绪:
```powershell
docker exec -it ros2_dev bash -lc "source /opt/ros/humble/setup.bash && source /root/ros2_ws/install/setup.bash && exec bash"
```
后续章节都假设你**在 Docker 容器内**操作。**PC 的"ROS_DOMAIN_ID=0"和"DDS 自动发现"对 LAN 上其他设备是开放的**——因为 docker-compose 用 `network_mode: host`,容器用宿主机网络栈。
### 2.3 PC 上启动本仓库的 bringup 全套
```bash
docker exec ros2_dev bash -lc "cd /root/ros2_ws && source install/setup.bash && ros2 launch bringup full_demo_launch.py"
```
预期输出(日志截取):
```
[INFO] [launch]: All log files can be found below /root/.ros/log/2026-08-03-...
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [talker-1]: process started with pid [56]
[INFO] [listener-2]: process started with pid [58]
[INFO] [talker-3]: process started with pid [60]
[INFO] [listener-4]: process started with pid [62]
[talker-3] [INFO] [...] talker_cpp started -> topic=chatter, period=500ms
[listener-4] [INFO] [...] listener_cpp subscribed <- chatter
```
11 个节点跑起来。如果 LAN 上有 RDK X5 / RK3506 节点,它们会自动被发现。
### 2.4 PC 端跨机网络验证
```bash
docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && ros2 node list --no-daemon"
```
预期(假设 RDK X5 还没装,只有 PC 自己):
```
/joint_state_publisher_cpp
/talker_py
/talker_cpp
/listener_py
/listener_cpp
/tf2_listener_cpp
/fake_camera_py
/image_processor_py
/fibonacci_action_server_py
/add_two_ints_server_py
```
如果 RDK X5 装好 ROS2 后,**它的节点也会出现在这里**。
---
## 3. RDK X5 边缘 AI 配置
### 3.1 准备工作
```bash
# SSH 到 RDK X5
ssh user@192.168.1.20
# 检查系统
uname -a
# Linux rdk-x5 5.10.xxx ...
cat /etc/os-release
# Ubuntu 22.04.4 LTS ... (地瓜官方镜像)
# 网络
ip addr show | grep inet
# 应该看到 192.168.1.20/24
```
### 3.2 装 ROS2(完整版)
```bash
# 同 PC 步骤,装桌面版(包含 RViz 远程)
sudo apt install ros-humble-desktop -y
# 加装本仓库相关包
sudo apt install -y \
ros-humble-cv-bridge \
ros-humble-tf2-ros \
ros-humble-tf2-tools \
ros-humble-ros-gz \
ros-humble-nav2-bringup \
ros-humble-moveit \
ros-humble-image-transport \
ros-humble-vision-msgs \
ros-humble-sensor-msgs \
ros-humble-geometry-msgs \
ros-humble-rmw-fastrtps-cpp \
python3-colcon-common-extensions
```
预计下载 ~500MB,装 5-10 分钟。
### 3.3 拷本仓库源码 + colcon build
```bash
# 从 PC 拷(在 PC 上)
scp -r D:\xs\ros2 user@192.168.1.20:~/ros2_ws
# 注意:D:\xs\ros2 拷到 RDK X5 后变成 ~/ros2_ws
# 在 RDK X5 上(SSH 进)
cd ~/ros2_ws
source /opt/ros/humble/setup.bash
# ARM 编译,首次 5-10 分钟
colcon build --symlink-install \
--packages-select py_pubsub cpp_pubsub py_srv py_action_demo cpp_robot_tf2 py_vision_demo bringup
# 预期输出(末尾)
Summary: 7 packages finished [8m 32s]
```
> ⚠️ **arm64 兼容性**: 全部 7 个包都用纯 Python + 标准 C++,**没有架构专属代码**,ARM 上 build 应该一次过。如果遇到 `Could NOT find X`,检查 apt 包名。
### 3.4 RDK X5 上启动 vision demo
```bash
# SSH 到 RDK X5
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
# 设 ROS_DOMAIN_ID(虽然默认是 0,但显式更稳)
export ROS_DOMAIN_ID=0
# 启动 fake_camera (sensor_msgs/Image 发布者)
ros2 run py_vision_demo fake_camera
# 预期
[INFO] [fake_camera_py]: fake_camera_py started: 320x240 @ 10fps -> /image_raw
```
### 3.5 PC 上验证 RDK X5 节点可见
```bash
# 在 PC / 容器内
docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && ros2 node list --no-daemon"
# 应该看到:
# /fake_camera_py (来自 RDK X5)
# /talker_py /listener_py ... (本地)
# 看 RDK X5 上 fake_camera 发的 /image_raw 频率
ros2 topic hz /image_raw --no-daemon
# 预期:average rate: 10.000
```
### 3.6 RDK X5 跑 YOLO 推理(扩展)
如果 RDK X5 装了 ultralytics + YOLO 模型:
```bash
# 安装
pip install ultralytics --break-system-packages # 如果是 Buildroot 系统不带 pip,先 apt install python3-pip
# 准备模型
# 把 yolov8n.pt 拷到 ~/models/
# 写一个 yolo_detector 节点(参考 py_vision_demo 写法):
# 订阅 /camera/color/image_raw (或 fake_camera 的 /image_raw)
# 推理 → publish /detections (vision_msgs/Detection2DArray)
```
这部分**需要你写新包**,参考 [`src/py_vision_demo/`](../src/py_vision_demo/)。
---
## 4. RK3506 精简 ROS2 配置(核心)
### 4.1 为什么 RK3506 要"精简"
| 配置 | 占用 RAM |
|---|---|
| Linux 基础系统 | ~80 MB |
| ROS2 rclpy 客户端 | ~50-80 MB |
| FastDDS 参与者(单节点) | ~30-50 MB |
| 默认 daemon (`ros2 daemon`) | ~50 MB ← **必须关** |
| RViz / rqt | ~300 MB ← **绝对不装** |
| MoveIt2 / Nav2 | 200-500 MB ← **不装** |
不精简 = 直接 OOM。
### 4.2 准备 RK3506
```bash
ssh user@192.168.1.31
uname -a
# Linux rk3506 5.10.xxx ...
free -h
# total used free shared buff/cache available
# Mem: 484Mi 95Mi 240Mi 1.0Mi 148Mi 380Mi
df -h /
# /dev/root 7.4G 1.2G 5.8G /
# 看是不是 aarch64(必须)
uname -m
# aarch64
```
### 4.3 装精简 ROS2
```bash
# 4.3.1 装 ROS2 apt 源(同 PC)
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=arm64 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
sudo apt update
# 4.3.2 装精简 ROS2 包(注意:没有 desktop)
sudo apt install -y \
ros-humble-ros-base \
ros-humble-cv-bridge \
ros-humble-tf2-ros \
ros-humble-sensor-msgs \
ros-humble-geometry-msgs \
ros-humble-rmw-fastrtps-cpp \
ros-humble-ros2-control \
python3-colcon-common-extensions
# 检查内存
free -h
# 装完后大约用掉 ~250-280 MB(系统+ROS2+daemon)
```
### 4.4 关闭 ROS2 daemon(省 ~50MB)
ROS2 默认启动 `ros2 daemon` 加速 `ros2 ...` 命令调用,但每节点起一个 daemon 浪费内存。
```bash
# 停 + 禁启 daemon
pkill -9 -f ros2_daemon 2>/dev/null
# 注:`alias ros2='ros2 --no-daemon'` 写到 ~/.bashrc **不会传给子脚本**(脚本里 `ros2` 不展开 alias)。
# Humble 里 `ros2 topic ... --no-daemon` 这个 flag 已被 deprecated,改用下面的方案:
# 设 CYCLONE_DDS_URI / FASTRTPS_DEFAULT_PROFILES_FILE 走静态发现,从根本上不用 daemon
# 验证: 不再起 daemon
ps aux | grep -v grep | grep -i daemon | head -3
# 应该空
```
### 4.5 加 FastDDS 单播发现配置
RK3506 默认走 multicast,但某些网络环境 multicast 不通(如路由器拦了)。**改用 unicast 显式指定 PC + RDK X5** 更稳。
```bash
mkdir -p ~/.ros
cat > ~/.ros/fastdds.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<transport_descriptors>
<transport_descriptor>
<transport_id>udp_transport</transport_id>
<type>UDPv4</type>
</transport_descriptor>
</transport_descriptors>
<participant profile_name="rk3506_participant" is_default_profile="true">
<rtps>
<builtin>
<initialPeersList>
<initialPeer>
<address>192.168.1.10</address>
<port>7400</port>
</initialPeer>
<initialPeer>
<address>192.168.1.20</address>
<port>7400</port>
</initialPeer>
</initialPeersList>
<discovery_config>
<discoveryProtocol>SIMPLE</discoveryProtocol>
<!-- discoveryStrategy 决定 SIMPLE 的语义:STATIC(只连 initialPeersList)/ NONE(全不主动连)/ MULTICAST(默认)
跟 <initialPeersList> 配合才能实现"单播静态发现" -->
<discoveryStrategy>STATIC</discoveryStrategy>
<leaseDuration>30</leaseDuration>
</discovery_config>
<metatrafficUnicastLocatorList>
<locator>
<udpv4>
<address>0.0.0.0</address>
<port>7400</port>
</udpv4>
</locator>
</metatrafficUnicastLocatorList>
</builtin>
</rtps>
</participant>
</profiles>
EOF
# 加进 .bashrc
cat >> ~/.bashrc <<'EOF'
export ROS_DOMAIN_ID=0
export ROS_LOCALHOST_ONLY=0
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export FASTRTPS_DEFAULT_PROFILES_FILE=~/.ros/fastdds.xml
EOF
source ~/.bashrc
```
> 💡 `discoveryStrategy=STATIC` 让 RK3506 只找列表里的两个 IP,**不会到处广播**,省网络+省电。
### 4.6 把本仓库拷到 RK3506 + build
```bash
# 在 PC 上(假设在 D:\xs\ros2)
scp -r D:\xs\ros2 user@192.168.1.31:~/ros2_ws
# SSH 到 RK3506
ssh user@192.168.1.31
cd ~/ros2_ws
source /opt/ros/humble/setup.bash
# 编译(ARM,首次 5-10 分钟)
colcon build --symlink-install \
--packages-select py_pubsub cpp_pubsub cpp_robot_tf2 bringup
# 预期
Summary: 4 packages finished [6m 12s]
```
> ⚠️ **如果 RK3506 资源太紧编译失败**,只 build 1-2 个必要的包:
> ```bash
> colcon build --packages-select cpp_robot_tf2
> ```
### 4.7 RK3506 上启动 joint_state_publisher
```bash
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
export ROS_DOMAIN_ID=0
# 跑 cpp_robot_tf2 的关节发布者(executable 名是 joint_state_publisher_cpp,不是 joint_state_publisher)
ros2 run cpp_robot_tf2 joint_state_publisher_cpp
# 预期
[joint_state_publisher_cpp]: joint_state_publisher_cpp started, joints: 3
```
> 注: 这是**仿真关节**(3 关节 sin 运动),真实电机时改成读编码器(见 [第 7 章](#7-rk3506-接真实电机--编码器))。
### 4.8 PC 上验证 RK3506 节点可见
```bash
# 在 PC / Docker 内
docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && ros2 node list --no-daemon"
# 应该看到:
# /joint_state_publisher_cpp ← 来自 RK3506 (192.168.1.31)
# /talker_py /listener_py ... ← 本地 PC 节点
```
**看不到时**: 看下面 [第 9 章 故障排查](#9-故障排查大全)。
---
## 5. 三机 LAN DDS 组网
### 5.1 验证 multicast 通(默认方式)
```bash
# 在 RK3506 上
sudo tcpdump -i eth0 -n udp port 7400 -c 20
# 应该看到来自 PC (192.168.1.10) 和 RDK X5 (192.168.1.20) 的 UDP 包
# Ctrl+C 停止
```
**如果 multicast 不通**: 走 [4.5 单播配置](#45-加-fastdds-单播发现配置)。
### 5.2 三机互相"看见"节点
| 操作 | PC | RDK X5 | RK3506 #1 |
|---|---|---|---|
| `ros2 node list --no-daemon` | 见 PC + RDK + RK3506 节点 | 见 PC + RDK + RK3506 节点 | 见 PC + RDK + RK3506 节点 |
**预期**: 三机每台的 `node list` 输出**完全一致**(都看到 3 台的节点)。
### 5.3 跨机 topic 频率
```bash
# 在 RK3506 上看 /chatter 频率(由 PC 的 talker 发布)
ros2 topic hz /chatter --no-daemon
# average rate: 4.000 (4Hz = 2 talker × 2Hz)
```
**延迟**: 跨机 topic echo 一般 < 5ms(LAN);> 50ms 说明网络出问题。
---
## 6. 跨机 Demo 联调
### 6.1 场景 A:PC 视觉 → RK3506 控制(简化版)
```bash
# === 在 PC (Docker) ===
docker exec ros2_dev bash -lc "cd /root/ros2_ws && source install/setup.bash && ros2 run py_vision_demo fake_camera"
# === 在 RK3506 ===
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 topic echo /image_raw --no-daemon # 看到 PC 发的图像
# === 在 PC ===
docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && ros2 run py_vision_demo image_processor"
# 看到 PC 自己处理 + (如果有 RDK X5 / RK3506 也跑 image_processor,都能收到)
```
### 6.2 场景 B:RDK X5 视觉 + RK3506 电机(推荐起手)
```bash
# === 在 RDK X5 (SSH) ===
ssh user@192.168.1.20
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 run py_vision_demo fake_camera # 或你写的 YOLO 节点
# === 在 RK3506 #1 (SSH) ===
ssh user@192.168.1.31
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 run cpp_robot_tf2 joint_state_publisher_cpp
# (后续:接真实电机时改读编码器)
# === 在 PC 上监控全部 ===
docker exec ros2_dev bash -lc "ros2 node list"
# 看到 /fake_camera (RDK X5), /joint_state_publisher (RK3506,launch 重命名去掉了 _cpp 后缀)
docker exec ros2_dev bash -lc "ros2 topic hz /image_raw /joint_states"
# /image_raw: ~10Hz
# /joint_states: ~20Hz
```
### 6.3 场景 C:三机跑本仓库 full_demo
```bash
# === PC ===
docker exec ros2_dev bash -lc "cd /root/ros2_ws && source install/setup.bash && ros2 launch bringup full_demo_launch.py"
# 11 个 PC 节点
# === RDK X5 ===
ssh user@192.168.1.20
ros2 run py_vision_demo fake_camera # 1 节点
# === RK3506 #1 ===
ssh user@192.168.1.31
ros2 run cpp_robot_tf2 joint_state_publisher_cpp # 1 节点
# === RK3506 #2 ===
ssh user@192.168.1.32
ros2 run cpp_robot_tf2 tf2_listener_cpp # 1 节点
# === 在任一设备看 ===
ros2 node list --no-daemon
# 看到 PC 的 11 + RDK X5 的 1 + RK3506 的 2 = 14 节点!
```
---
## 7. RK3506 接真实电机 / 编码器
### 7.1 硬件接线(典型 6-DoF 机械臂)
```
总控舵机 (例如 LewanSoul/Hiwonder LX-16):
- 1 根总线(Bus Servo): 6 个舵机串联,数据线 Tx/Rx 共用
- 电源 7-12V(单独供电,不要从 RK3506 取)
- 接线到 RK3506 串口(UART): TXD/RXD/GND
编码器(可选,闭环舵机内部已有):
- SPI 接口(Magnetic encoder AS5048A)
- 或通过总线舵机协议反馈
RK3506:
- /dev/ttyS0 / /dev/ttyS1 (硬件串口)
- /dev/ttyUSB0 (USB-TTL 调试线)
```
### 7.2 写一个电机驱动节点(C++)
参考本仓库 `cpp_robot_tf2/src/joint_state_publisher.cpp`(模板)。新建 `motor_driver` 包:
```cpp
// ~/ros2_ws/src/motor_driver/src/motor_driver.cpp
#include <chrono>
#include <cstdio>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/joint_state.hpp"
class MotorDriver : public rclcpp::Node {
public:
MotorDriver() : rclcpp::Node("motor_driver") {
pub_ = this->create_publisher<sensor_msgs::msg::JointState>("/joint_states", 10);
// 打开串口(假设 /dev/ttyS0 是总线舵机)
fd_ = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd_ < 0) {
RCLCPP_FATAL(this->get_logger(), "Cannot open /dev/ttyS0");
return;
}
struct termios opts;
tcgetattr(fd_, &opts);
cfsetispeed(&opts, B115200);
cfsetospeed(&opts, B115200);
opts.c_cflag |= (CLOCAL | CREAD);
opts.c_cflag &= ~PARENB;
opts.c_cflag &= ~CSTOPB;
opts.c_cflag &= ~CSIZE;
opts.c_cflag |= CS8;
tcsetattr(fd_, TCSANOW, &opts);
timer_ = this->create_wall_timer(50ms, std::bind(&MotorDriver::tick, this));
// 订阅 cmd
sub_ = this->create_subscription<sensor_msgs::msg::JointState>(
"/cmd_joint_states", 10,
[this](const sensor_msgs::msg::JointState::SharedPtr msg) {
// 发送舵机命令(总线舵机协议)
send_servo_cmd(msg);
});
}
private:
void tick() {
// 1) 读舵机当前角度(总线舵机协议 INSTRUCTION 0x02 read position)
// 2) 构造 JointState 消息
auto msg = sensor_msgs::msg::JointState();
msg.header.stamp = this->now();
msg.name = {"joint1", "joint2", "joint3", "joint4", "joint5", "joint6"};
msg.position.resize(6);
// 读 6 个舵机当前角(伪代码)
for (int i = 0; i < 6; i++) msg.position[i] = read_servo_angle(i);
pub_->publish(msg);
}
void send_servo_cmd(const sensor_msgs::msg::JointState::SharedPtr& msg) {
// 总线舵机协议: 0x55 0x55 len id cmd param...
// 这里写 6 个舵机的目标角度
for (size_t i = 0; i < msg->position.size() && i < 6; i++) {
uint8_t id = i + 1; // 舵机 ID 1-6
uint16_t angle = static_cast<uint16_t>((msg->position[i] + 3.14159) / 6.28318 * 1000);
uint8_t buf[8] = {0x55, 0x55, 0x08, id, 0x03, 0x1E, (uint8_t)(angle & 0xFF), (uint8_t)(angle >> 8)};
write(fd_, buf, 8);
}
}
double read_servo_angle(int id) {
// 实际读位置:发送读指令 → 等待响应 → 解析
return 0.0; // 简化
}
rclcpp::Publisher<sensor_msgs::msg::JointState>::SharedPtr pub_;
rclcpp::Subscription<sensor_msgs::msg::JointState>::SharedPtr sub_;
rclcpp::TimerBase::SharedPtr timer_;
int fd_ = -1;
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MotorDriver>());
rclcpp::shutdown();
return 0;
}
```
### 7.3 上电测试(务必小心!)
```
1. 舵机先**单独供电**,不上机械臂(用编程器/数据线模式调零)
2. RK3506 接 USB-TTL 调试线,登录 ssh
3. 启动 motor_driver 节点(默认发布 0° 关节角)
4. 手动旋转舵机,看 /joint_states 是否变化
5. 测试 `/cmd_joint_states` 订阅(在 PC 上 ros2 topic pub)
ros2 topic pub /cmd_joint_states sensor_msgs/msg/JointState "{name: ['j1'], position: [1.57]}"
6. 确认舵机收到命令并转动
7. 装上机械臂,运行 MoveIt2 demo
```
---
## 8. ros2_control 闭环
### 8.1 ros2_control 是什么
`ros2_control` 是电机驱动的**抽象层**。你写一个 `HardwareInterface`,RK3506 的真驱动接进去,
高层节点(PC 的 MoveIt2)只看到标准接口(`position_cmd` / `position_state`),不关心底层是舵机 / 步进电机 / 谐波减速。
```
┌──────────────┐ ┌──────────────────────────┐ ┌─────────────┐
│ MoveIt2 │ ──→ │ ros2_control │ ──→ │ 真电机驱动 │
│ /joint_ │ std │ ControllerManager │ │ (Bus Servo)│
│ trajectory │ if │ (JointTrajectoryController)│ │ │
└──────────────┘ └──────────────────────────┘ └─────────────┘
```
### 8.2 在 RK3506 上跑 controller_manager
```bash
sudo apt install ros-humble-ros2-control ros-humble-ros2-controllers -y
```
写一个 `motor_hw_interface` 包(本仓库后续可加),声明 6 个关节的 hardware interface。
### 8.3 PC 上跑 MoveIt2(规划)
```bash
sudo apt install ros-humble-moveit -y
# 启动 MoveIt(配 6-DoF SRDF)
ros2 launch moveit2_tutorials demo.launch.py
```
PC 算轨迹 → 通过 `FollowJointTrajectory` Action 发给 RK3506 → RK3506 controller_manager 执行。
---
## 9. 故障排查大全
### 9.1 看不到其他机器的节点
```bash
# 步骤 1: 网络通?
ping -c 3 192.168.1.31
# 步骤 2: 端口 7400 通?
nc -zv 192.168.1.31 7400
# 步骤 3: multicast 通?
tcpdump -i eth0 -n udp port 7400 -c 5
# 步骤 4: ROS_DOMAIN_ID 一致?
echo $ROS_DOMAIN_ID # 三机都要一样
```
### 9.2 multicast 不通
切单播 (4.5 节):
```bash
# 分隔符是英文逗号 `,`,不是分号 `;`(FastDDS 默认)
export ROS_STATIC_PEERS="192.168.1.10:7400,192.168.1.20:7400"
```
### 9.3 RK3506 内存不足
```bash
free -h
# 看哪些进程吃内存
ps aux --sort=-%mem | head -10
# 杀掉
pkill -9 -f ros2_daemon
pkill -9 -f ros2 # 注意:会杀掉所有 ROS2 进程,先确认其他节点不跑
```
### 9.4 编译失败
```bash
# 列报错包名,查 apt 是否装齐
dpkg -l | grep ros-humble-rclcpp
# 装缺失依赖
sudo rosdep init && rosdep update
sudo rosdep install -i --from-paths src/
# 清理重 build
cd ~/ros2_ws && rm -rf build install log
colcon build --symlink-install
```
### 9.5 节点启动后立刻死
```bash
ros2 run <pkg> <node> --log-level debug
# 看具体 traceback
```
常见原因:
- 参数缺失(`use_sim_time` 未设)
- 缺少文件(URDF 路径错)
- 网络端口冲突
---
## 10. 性能调优 Checklist
| 项 | 目标 | 命令 |
|---|---|---|
| RAM 占用 | < 350MB | `free -h` |
| CPU 空闲 | > 60% | `top -bn1 \| head -20` |
| TF lookup 延迟 | < 5ms | `ros2 topic delay /tf --no-daemon` |
| DDS heartbeat | < 100ms | `ros2 daemon stop; ros2 --no-daemon ...` |
| 电机响应延迟 | < 50ms | 录 topic 时间戳,看从 PC 发出到 RK3506 转动 |
---
## 附录 A: 采购清单
| 项 | 数量 | 备注 |
|---|---|---|
| RDK X5 | 1 | 地瓜机器人官方 |
| RK3506 开发板 | 2 | 注意要 aarch64 Linux 镜像 |
| 6-DoF 机械臂套件 | 1 | 推荐 LewanSoul/Hiwonder 铝架 |
| 总线舵机(6 个) | 1 套 | 型号 LX-16 / LX-224 |
| 舵机电源(7-12V 5A+) | 1 | **必须独立供电** |
| USB-TTL 串口线 | 2 | RK3506 debug 用 |
| 网线 + 路由器 | 1 套 | 三机同网段 |
| 急停按钮 | 1 | 接 RK3506 GPIO |
---
## 附录 B: 关键命令速查
```bash
# 三机通用
source /opt/ros/humble/setup.bash
ros2 node list --no-daemon
ros2 topic list --no-daemon
ros2 topic hz /topic --no-daemon
ros2 topic echo /topic --no-daemon
# RK3506 内存
free -h
ps aux --sort=-%mem | head
# 网络
ip addr show
ping -c 3 <ip>
nc -zv <ip> 7400
# DDS 配置
cat ~/.ros/fastdds.xml
echo $ROS_STATIC_PEERS
# colcon build
cd ~/ros2_ws && colcon build --packages-select <pkg>
cd ~/ros2_ws && rm -rf build install log && colcon build
# ros2_control
ros2 control list_controllers
ros2 control list_hardware_interfaces
# MoveIt2
ros2 launch moveit2_tutorials demo.launch.py
```
---
## 下一步建议
读完本篇你能做到:
✅ 三机 ROS2 互通
✅ RK3506 接真实电机
✅ PC + RDK X5 + RK3506 完整工作流
**接着学习**:
- ros2_control 完整配置(`doc/99-embodied-ai.md` 路径)
- MoveIt2 SRDF 生成 + 路径规划
- Gazebo 物理仿真验证(在 PC 上)
- NPU 推理在 RDK X5 / RK3506 #2 上跑(YOLO-Lite)
如需在本仓库加 `ros2_control_demo` / `motor_driver` 包,后续 PR 即可。
---
---
## 📖 阅读路径导航
> 💡 这是仓库 `doc/` 下所有文档的推荐阅读顺序。[返回 README 总导航](../README.md#-23-篇文档怎么读)
>
> ⏱ **本文预计阅读时间**: 90 分钟
> 📍 **当前位置**: 第 23 / 24 篇
-**上一篇**: [VLA / 机器人开发路线图](99-embodied-ai.md)