docs: add localization system documentation

Comprehensive reference for the LiDAR + AMCL + teach-in localization stack. Covers TF tree, architecture, phased implementation plan, configuration, and teach-in/navigation workflow for the CRAIC desktop competition.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-19 06:30:09 +08:00
parent 87a0e26079
commit 44cf290a6d

253
docs/localization.md Normal file
View File

@@ -0,0 +1,253 @@
# CRAIC 定位系统文档
激光 SLAM 建图 + AMCL 精准定位 + 示教记录点位。移植自 `lzu_robot` / `move_try`
(同一赛事的参考实现),适配 CRAIC 麦轮底盘 + ESP32 + 机械臂平台。
---
## 1. 概述
- **目标**:在已知(预先测量)的竞赛场地上对底盘做绝对、无累积漂移的精准定位,并提供示教记录关键点位的工具。
- **方案**YDLiDAR → `rf2o` 激光里程计提供连续 `odom``slam_gmapping` 建一次场地地图;`nav2 AMCL` 用激光匹配已知地图做全局校正。
- **运行环境**ROS 2 Humbleconda/robostack `ros2_humble`)。机器人端工作区在 `~/Desktop/ros2`(下文命令默认在此目录、且已 `source install/setup.bash`)。
> 设计上原打算用**轮式里程计**做运动来源,但实测 CRAIC 底盘板上电后只短时上报里程计(见 §8
> 故改用 `rf2o`(激光里程计)提供 `odom→base`,与 `lzu_robot`/`move_try` 一致。轮式里程计节点保留,
> 待底盘板问题解决后可用 EKF 融合提精度。
---
## 2. 系统架构
### TF 树
```
map ──(amcl激光匹配已知地图)──> odom
odom ──(rf2o来自连续 /scan)──────> base_footprint
base_footprint ──(静态外参)────────> laser_frame # 激光安装位lidar_yaw 等参数
base_footprint ──(静态恒等)────────> base_link # 兼容 gmapping/Nav2 默认基准帧
```
### 数据流
```
YDLiDAR ──/dev/ttylzulaser──> ydlidar 驱动 ──> /scan(10Hz)
/scan ──> rf2o ──> /odom + TF(odom→base_footprint)
/scan + odom ──> slam_gmapping ──> /map + TF(map→odom) # 建图阶段
/scan + 已知地图 ──> amcl ──> TF(map→odom) = 绝对位姿 # 定位阶段
底盘速度命令: keyboard_control ──UDP──> ESP32 ──UART──> 底盘板 # 与定位互不干扰
轮式里程计(可选): 底盘板 ──/dev/ttylzucar──> chassis_odometry ──> /odom(独立测试用)
```
---
## 3. 软件清单
### 自建包 `ros2/src/craic_localization`
| 文件 | 作用 |
|---|---|
| `craic_localization/chassis_odometry.py` | 轮式里程计节点(读 `/dev/ttylzucar`,发 `/odom`+TF。**默认不参与建图/定位**,独立测试用 |
| `craic_localization/teach_points.py` | 示教节点:记录 map 系下位姿存 YAML |
| `craic_localization/navigate_to_point.py` | 导航节点:自动驱动到示教点(读 taught_points.yaml + AMCL 闭环 + UDP 发 XYW |
| `craic_localization/show_points.py` | 在 rviz 显示所有示教点位MarkerArray 箭头+文字标签) |
| `launch/lidar.launch.py` | YDLiDAR 驱动 + 静态外参(base_footprint→laser_frame、→base_link) |
| `launch/bringup.launch.py` | 轮式里程计 + 激光 + rviz传感器自检 |
| `launch/mapping.launch.py` | 激光 + rf2o + slam_gmapping建图 |
| `launch/localization.launch.py` | 激光 + rf2o + map_server + amcl + lifecycle_managerAMCL 定位) |
| `config/lidar.yaml` | YDLiDAR TminiPro 参数(端口 `/dev/ttylzulaser` |
| `config/gmapping.yaml` | gmapping 建图参数(`/**` 通配键base_footprint/odom/map |
| `config/amcl.yaml` | map_server + amcl 参数omni 运动模型) |
| `rviz/localization.rviz` | rviz 配置TF/Map/LaserScan/Odometry |
| `maps/` | 建图存盘输出(`craic.pgm` + `craic.yaml` |
| `config/taught_points.yaml` | 示教输出(运行时生成) |
### 移植包
- `ros2/src/ydlidar_ros2_driver` —— 来自 `move_try`,依赖系统 `ydlidar_sdk`(已装在 conda env
- `ros2/src/rf2o_laser_odometry` —— 来自 `lzu_robot`激光里程计ICRA'16
### 外部依赖Nav2已装入 conda env
`nav2_amcl`(amcl)、`nav2_map_server`(map_server, map_saver_cli)、`nav2_lifecycle_manager`(lifecycle_manager)、`slam_gmapping`/`openslam_gmapping`
---
## 4. 环境准备与编译
一次性安装 Nav2 定位组件(若环境无):
```bash
conda install -n ros2_humble -c robostack-staging -c conda-forge \
ros-humble-nav2-amcl ros-humble-nav2-map-server ros-humble-nav2-lifecycle-manager
```
编译(在工作区根目录):
```bash
conda activate ros2_humble
cd ~/Desktop/ros2
colcon build --symlink-install --packages-select \
ydlidar_ros2_driver rf2o_laser_odometry craic_localization
source install/setup.bash
```
> **开发机 ↔ 机器人同步**:代码在开发机维护,需同步到机器人 `~/Desktop/ros2` 后**重新 colcon build**。
> 新增/改动包时务必确认源码已同步(尤其新包 `ydlidar_ros2_driver`、`rf2o_laser_odometry`)。
---
## 5. 硬件接口
| 设备 | 接口 | 说明 |
|---|---|---|
| 底盘 MCU | `/dev/ttylzucar` @115200 | 上报 32 字节里程计包;命令通过 ESP32 转发,非直连 |
| 激光雷达 | `/dev/ttylzulaser` @230400 | YDLiDAR TminiPro`/scan` 10Hz |
| 底盘命令 | UDP `192.168.4.1:8888` → ESP32 → UART | `XYW:<x>:<y>:<w>:XZHY`(麦轮速度) |
**底盘里程计协议**(移植自 lzu seriallzucar32 字节,帧尾 `'L''Z''U'``packet[0]`=校验和=`sum(packet[1:30])%256`
`packet[1:29]`=`<fffffff>`=`x,y,yaw,4轮位置`(x/y 单位 mm)`packet[29]`=模式字节,`'D'`=有效。
打开串口后下发 `UP0LZU` 使能上报(参数 `enable_cmd`)。
---
## 6. 使用流程
> 前置:每个终端都先 `cd ~/Desktop/ros2 && source install/setup.bash`。
### 6.1 传感器自检(可选)
```bash
ros2 launch craic_localization bringup.launch.py
```
rviz 看 `/scan`、TF。用于核对激光朝向`lidar_yaw` 等,见 §7
### 6.2 建图
```bash
# 终端1建图栈激光 + rf2o + gmapping + rviz
ros2 launch craic_localization mapping.launch.py
# 终端2遥控慢速走遍场地见 6.6
# 终端3地图满意后存盘gmapping 保持运行)
mkdir -p src/craic_localization/maps
ros2 run nav2_map_server map_saver_cli \
-f src/craic_localization/maps/craic \
--ros-args -p save_map_timeout:=10000.0
```
**走图要点**慢、贴墙走遍每条边界、最后绕回起点rviz Fixed Frame 设 `map`,墙应是单条清晰线、无重影。
**起点即原点**:建图启动时机器人所在位置=map(0,0)、正前方=map +X。建议从固定起点开始。
存盘得到 `craic.pgm` + `craic.yaml`。**确认 `craic.yaml``free_thresh`**(缺则 map_server 加载失败):
```bash
tail -1 src/craic_localization/maps/craic.yaml # 应为 free_thresh: 0.25
# 若缺: printf '\nfree_thresh: 0.25\n' >> src/craic_localization/maps/craic.yaml
```
存盘后重编一次让默认地图安装:`colcon build --symlink-install --packages-select craic_localization`
> 地图四周大片灰色是 gmapping 预留画布(`xmin/xmax ±10` → 20m×20m属正常不影响 AMCL。
### 6.3 定位AMCL
```bash
ros2 launch craic_localization localization.launch.py \
map:=$HOME/Desktop/ros2/src/craic_localization/maps/craic.yaml
# 起点不在地图原点时给初值:加 init_x:= init_y:= init_yaw:=,或 rviz 工具栏 "2D Pose Estimate" 点
```
正常表现rviz(Fixed Frame=`map`) 红色激光点贴合墙体,移动时始终咬住墙、位姿稳定不漂。
### 6.4 定位精度测试
```bash
ros2 run tf2_ros tf2_echo map base_footprint # 实时读 x/y/yaw
ros2 topic echo /amcl_pose # 位姿 + 协方差
```
- **重复定位**:标记一物理点记位姿 → 开一圈回同点再记 → 差值 < 3cm 为佳。
- **定长移动**:精确前进 1.00mAMCL 报的位移误差应在几 cm 内。
- (可选) rviz Add → PoseArray → `/particlecloud`,粒子收紧=收敛。
### 6.5 示教记录点位
```bash
# 终端1定位栈(6.3) 终端2遥控(6.6) 终端3示教
q
```
交互命令:**回车/`r`** 记录当前预设点并前进;`p` 查看当前位姿;`name <X>` 记自定义点;
`del <X>` 删除;`list` 列出;`skip`/`back` 跳过/回退;`save` 存盘;`q` 退出。
预设顺序 `B_1..B_6, C_1, D_1, E_1, F_1`(每记一点自动存盘)。记 A 区用 `name A_1`…,或改 `points` 参数。
输出map 系,米/弧度):
```yaml
points:
B_1: {x: 1.234, y: 0.456, yaw: 1.571}
...
```
**在 rviz 看示教点**`localization.launch.py` 已自动启动 `show_points`(青色箭头=位置+朝向,白字=点名,话题 `/taught_points`rviz 内置 MarkerArray 显示)。
想在示教**过程中实时**看到新点,给定位 launch 传 `points_file:=<你的 taught_points.yaml 绝对路径>`,或单独运行:
```bash
ros2 run craic_localization show_points --ros-args \
-p points_file:=$PWD/src/craic_localization/config/taught_points.yaml
```
### 6.6 自动导航到示教点
读取 `taught_points.yaml`,用 AMCL 闭环把底盘开到指定点(麦轮 holonomic P 控制,经 UDP 发 XYW
**首次务必先 dry_run 验证方向,再低速实测,手放 Ctrl+C退出自动停车** 前置:定位栈(6.3) 在运行。
```bash
# ① 干跑:只打印命令不发,确认逻辑与方向符号
ros2 run craic_localization navigate_to_point --ros-args \
-p points_file:=$PWD/src/craic_localization/config/taught_points.yaml \
-p dry_run:=true -p goal:=B_1
# ② 交互选点(低速实测)
ros2 run craic_localization navigate_to_point --ros-args \
-p points_file:=$PWD/src/craic_localization/config/taught_points.yaml \
-p max_linear:=40.0 -p max_angular:=20.0
# ③ 一次性去某点 / 供上层任务程序用话题指定
ros2 run craic_localization navigate_to_point --ros-args -p points_file:=... -p goal:=C_1
ros2 topic pub -1 /goto std_msgs/String "{data: 'C_1'}"
```
交互命令:输入点名→导航;`s` 停车;`q` 退出。到达 `pos_tolerance`(5cm)/`yaw_tolerance`(~3°) 容差内停车。
**若实车方向相反**:翻转 `sign_x`/`sign_y`/`sign_w`(默认 -1按 keyboard_control 推导)。如左右反了 `-p sign_x:=1`
### 6.7 遥控
```bash
ros2 run udp_teleop keyboard_control --ros-args \
--params-file src/udp_teleop/config/params.yaml \
-p chassis_linear_speed:=40 -p chassis_angular_speed:=20 # 建图建议降速
```
按键:`W/S` 前后、`A/D` 左右平移、`Q/E` 左右转;`Ctrl+C` 退出。须 `ros2 run`、交互终端。
---
## 7. 配置 / 关键参数
**`lidar.launch.py`**`lidar_x/y/z``lidar_yaw`(默认 -3.14159=-180°使机体 X 轴对准底盘实际前进方向;若前后颠倒改 0.0)、
`intensity`(true基础 Tmini 无强度时改 false)、`sample_rate`(4)、`baudrate`(230400)、`base_frame``laser_frame`
**`localization.launch.py`**`map`(地图 yaml)、`init_x/init_y/init_yaw`(初始位姿)、`use_rviz`
**`mapping.launch.py`**`use_rviz`。范围 `xmin/xmax/ymin/ymax`(±10) 在 `config/gmapping.yaml`,可收小到 ±3 得更紧凑地图(需重新建图)。
**`amcl.yaml`**`odom_model_type: omni`(麦轮)、`base_frame_id: base_footprint``laser_max_range: 12.0``alpha1..5`(里程计噪声)。
**`chassis_odometry`**(独立测试):`port``enable_cmd`(UP0LZU)、`enable_cmd_period`(0=仅打开时发一次)、`require_mode_d`(true)。
**`navigate_to_point`**`points_file``goal`(一次性目标)、`max_linear`(60)/`max_angular`(30)、`kp_linear`(150)/`kp_angular`(40)、`pos_tolerance`(0.05)/`yaw_tolerance`(0.05)、`sign_x/sign_y/sign_w`(默认 -1方向修正)、`goal_timeout`(30s)、`dry_run``udp_ip/udp_port`
---
## 8. 故障排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 激光狂刷 `Check Sum X != Y` | 有第二个进程在读 `/dev/ttylzulaser`,字节流被撕裂 | `pgrep -af ydlidar``pkill -f ydlidar_ros2_driver_node`;保证单实例。若仍报:试 `intensity:=false`Tmini 无强度) |
| gmapping `"base_link" ... does not exist` | gmapping 硬编码基准帧 `base_link`,而 TF 树只有 base_footprint | 已修:`lidar.launch.py` 发 base_footprint→base_link 恒等 TF |
| gmapping `Message Filter ... queue is full`、地图/位置不动 | `odom→base` TF 冻结:轮式里程计板上电后只短时上报 | 已修:改用 `rf2o` 从 /scan 连续提供 odom→base见 §1 |
| map_server 加载地图失败 | `craic.yaml``free_thresh` | 补 `free_thresh: 0.25`(见 6.2 |
| rviz 地图上一串绿箭头 | Odometry 显示保留历史(Keep) | 已改默认 `Keep:1`;或 rviz 里取消勾选 Odometry不影响地图 |
| 轮式里程计只在上电后几帧有数据 | 底盘板上电后停止上报固有行为UP0LZU 未能维持) | 建图/定位已不依赖它;如需启用试 `enable_cmd_period:=1.0` 保活,或确认主机串口 TX 已接、或经 ESP32 通道使能 |
| 时间戳显示 ~2000 年 | 机器人系统时钟未对时 | 单机不影响;多机协同前用 NTP/RTC 对时 |
| 导航时往错误方向开 / 原地打转 | XYW 速度符号与实车不一致 | 翻转 `sign_x/sign_y/sign_w`(默认 -1);先 `dry_run:=true` 核对,再低速实测 |
| rviz 里机体朝向/前进方向与遥控差 90° | 激光外参 `lidar_yaw` 未校准 | 设 `lidar_yaw=-180°`(前后颠倒则 0.0);改后 **yaw 变了需重新示教**,建议重建图 |
通用排查:`ros2 run tf2_ros tf2_echo map base_footprint``ros2 topic hz /scan /odom /map`
`ros2 run tf2_tools view_frames`(看 TF 树连通)。
---
## 9. 已知限制与后续
- **激光外参标定**`lidar_yaw` 已校准为 -180°使机体 X 轴对准底盘实际前进方向rviz 显示与遥控一致,导航默认符号 `sign_x/y/w=-1` 也随之正确)。平移 `lidar_x/y` 仍为 0 占位,需要更高位置精度可再标。
**改动 `lidar_yaw` 后**:机体朝向变了 —— 旧地图仍可定位AMCL 自动补偿),但**示教点的 yaw 需重新记录**,建议重新建图让地图朝向也一致。
- **轮式里程计**:底盘板“持续上报”问题待解;解决后可用 `robot_localization` EKF 融合 轮速 + rf2o 再喂 AMCL 提精度。
- **地图坐标系**`map(0,0)` = 建图起点。比赛复现时让机器人从同一物理起点开机,或用 “2D Pose Estimate” 给初值。
---
## 10. 参考来源
- `lzu_robot/src/cgbot/cgbot/seriallzucar.py`(里程计协议)、`maps/amcl_config.yaml``src/slam_gmapping``src/rf2o_laser_odometry``src/ydlidar_ros2_driver-humble`
- `move_try/src/ydlidar_ros2_driver/params/TminiPro.yaml``src/move_try`(同赛事场地/规划参考)