概述
项目介绍和功能特性
项目介绍和功能特性 Desktop-Wanderer 是一个基于视觉引导的小型机器人系统,实现了自主目标检测、导航和抓取功能。系统采用模块化设计,支持多种硬件加速方案,具备以下核心功能特性:
- 视觉感知:基于YOLO的目标检测系统,支持ONNX CPU推理和Atlas 310B、RK3588 NPU硬件加速 process.py
- 智能导航:视觉引导的移动控制,能够自动导航至目标位置 move_controller.py
- 精确操作:支持两种机械臂控制模式 - ACT学习策略和逆运动学控制 setup.py
- 状态管理:基于状态机的行为控制,实现搜索-抓取的自动化流程 setup.py
系统架构概览
Desktop-Wanderer 采用四层分层架构设计,从应用层到硬件层逐级抽象:

系统核心是位于 src/main.py 的主控制循环 main.py ,负责协调各个子系统:
- 初始化阶段:加载配置、初始化机器人连接、读取初始关节角度
- 感知阶段:获取机器人观测数据,执行YOLO目标检测
- 决策阶段:根据当前状态选择相应控制器(移动控制或机械臂控制)
- 执行阶段:发送控制指令到机器人,维持指定FPS控制频率
快速开始指南
环境准备
- 硬件要求:
- LeKiwi机器人平台
- USB串口连接舵机控制板
- 12V8A电源连接舵机控制板
- 软件依赖
pip install -r requirements.txt
- lerobot 平台安装
python 3.10环境准备
wget "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
bash Miniforge3-$(uname)-$(uname -m).sh
conda create -y -n lerobot python=3.10
lerobot 安装
conda activate lerobot
conda install ffmpeg -c conda-forge
git clone https://github.com/huggingface/lerobot.git
cd lerobot
pip install -e .
# 舵机驱动安装
pip install -e ".[feetech]"
详细说明可看安装与配置部分
配置设置
修改 config.yaml 配置文件:
port: /dev/tty.usbmodem5AE60581751 # 串口号
fps: 20 # 帧率
log_level: INFO # 日志级别
hardware_mode: normal # normal, 310b, rk3588
control_mode: inverse # inverse, act
安装与配置 (Installation & Configuration)
环境要求和依赖
在PC上进行调试需要安装lerobot平台,具体安装方法可以参考 lerobot_Installation,下文也会给出。
安装miniforge
wget "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
bash Miniforge3-$(uname)-$(uname -m).sh
创建python3.10环境
conda create -y -n lerobot python=3.10
激活环境
conda activate lerobot
conda安装ffmpeg
conda install ffmpeg -c conda-forge
从源码下载安装lerobot
克隆仓库
git clone https://github.com/huggingface/lerobot.git
cd lerobot
安装
pip install -e .
安装舵机控制SDK
pip install -e ".[feetech]"
基础环境准备完毕,在代码根目录运行
python -m src.main
配置文件说明
配置文件内容
port: /dev/ttyACM0 # 在控制板上的默认端口号
fps: 20 # 每秒识别20帧
log_level: INFO # 日志级别
hardware_mode: normal # normal, rk3588 支持的硬件
control_mode: inverse # inverse, act 行动模式
其中
- port:在安装lerobot后,处于lerobot环境下,将设备连接到电脑运行
lerobot-find-port,之后会出现一堆设备号,根据指示,拔掉连接线后按回车,即可获得串口号,控制板上的环境已经安装完毕,端口号为固定的 '/dev/ttyACM0' - hardware_mode: 在PC上调试的时候选择normal模式,在控制板上运行的时候,为rk3588
硬件连接设置
核心架构 (Core Architecture)
四层架构设计
模块依赖关系
初始化流程
状态机 (State Machine)
RobotStatus 枚举定义
文件: src/setup.py
class RobotStatus(Enum):
SEARCH = "search" # 找球模式
PICK = "pick" # 捡球模式
FIND_BUCKET = "find_bucket" # 找桶模式
PUT_BALL = "put_ball" # 放球模式
状态转换图
┌─────────────────────────────────────────────────────┐
│ │
▼ │
┌─────────┐ ┌─────────┐ ┌──────────────┐ ┌──────────┐ ┌─────────┐
│ SEARCH │───▶│ PICK │───▶│ FIND_BUCKET │───▶│ PUT_BALL │───▶│ SEARCH │
└─────────┘ └─────────┘ └──────────────┘ └──────────┘ └─────────┘
▲ │ │ │ │
│ │ │ │ │
└──────────────┴─────────────────┴────────────────┴──────────────┘
(reset_robot)
状态详细说明
1. SEARCH(找球模式)
进入条件:
- 系统启动后初始状态
- PUT_BALL 完成后返回
行为:
- 使用
move_controller()控制底盘移动 - 调用
yolo_infer()进行目标检测(找球) - 检测到球并在视野中央稳定 10 帧后进入 PICK 状态
目标检测区域:
┌─────────────────────────────────┐
│ │
│ ┌───────────────────┐ │
│ │ │ │
│ │ TARGET ZONE │ │ ← 球需要在这个区域内
│ │ │ │
│ └───────────────────┘ │
│ │
└─────────────────────────────────┘
2. PICK(捡球模式)
进入条件:
- SEARCH 状态下检测到球并稳定 10 帧
行为:
- 使用
p_control_loop()执行CATCH_ACTION动作序列 - 逆运动学控制机械臂移动到球的位置
- 执行夹爪动作抓住球
- 动作序列完成后进入 FIND_BUCKET 状态
动作序列:
CATCH_ACTION = [
("move_to", (0.0989, 0.125)), # 移动到初始位置
("shoulder_pan", -12), # 肩部旋转
("gripper", 60), # 张开夹爪
("wrist_flex", 80), # 腕部弯曲
("move_to", (0.140, 0.1211)), # 移动到球上方
("move_to", (0.140, -0.05)), # 下降到球位置
("gap", 0), # 停顿
("gripper", -60), # 闭合夹爪(夹住球)
("gap", 0), # 停顿
("shoulder_pan", 12), # 肩部归位
("move_to", (-0.1, 0.2)), # 举起球
("wrist_flex", -20) # 腕部配合
]
3. FIND_BUCKET(找桶模式)
进入条件:
- PICK 状态动作序列执行完成
行为:
- 检查夹爪是否仍然夹住球(
gripper_pos > 25) - 如果球丢失,返回 SEARCH 状态重新寻找
- 使用
move_controller_for_bucket()控制底盘移动 - 调用
get_black_bucket_local()或get_red_bucket_local()寻找桶 - 检测到桶并在视野中央稳定 10 帧后进入 PUT_BALL 状态
找桶策略:
- 优先检测黑色桶
- 如果找不到黑色桶,检测红色桶
4. PUT_BALL(放球模式)
进入条件:
- FIND_BUCKET 状态下检测到桶并稳定 10 帧
行为:
- 使用
p_control_loop()执行PUT_ACTION动作序列 - 逆运动学控制机械臂移动到桶上方
- 执行夹爪动作释放球
- 动作序列完成后返回 SEARCH 状态
动作序列:
PUT_ACTION = [
("shoulder_lift", 50), # 抬起机械臂
("gap", 0), # 停顿
("gripper", 60), # 张开夹爪(放球)
("gap", 0), # 停顿
("move_to", (-0.1, 0.2)), # 收回机械臂
("gripper", -60), # 闭合夹爪
]
状态转换逻辑
文件: src/main.py
SEARCH → PICK
# move_controller.py:53-56
if position > (TARGET_POSITION + 10): # 球太大(太近)
action = direction.get_action("backward", 1)
elif center_x < left or center_x > right: # 球偏离中心
action = direction.get_action("rotate_left/right")
else:
_cycle_time += 1
if _cycle_time > 10: # 稳定10帧
set_robot_status(RobotStatus.PICK)
PICK → FIND_BUCKET
# main.py:121-123
if CATCH_ACTION[command_step][0] == "move_to":
if abs(current_x - target_x) < 0.002 and abs(current_y - target_y) < 0.002:
command_step += 1
if command_step == len(CATCH_ACTION):
set_robot_status(RobotStatus.FIND_BUCKET)
FIND_BUCKET → PUT_BALL
# move_controller.py:98-102
else:
_cycle_time += 1
if _cycle_time > 10:
set_robot_status(RobotStatus.PUT_BALL)
FIND_BUCKET → SEARCH(球丢失)
# main.py:80-83
gripper_pos = current_obs.get('arm_gripper.pos', 5)
is_gripper_holding = gripper_pos > 25
if not is_gripper_holding:
set_robot_status(RobotStatus.SEARCH)
reset_robot()
PUT_BALL → SEARCH
# main.py:136-146
if command_step == len(PUT_ACTION):
set_robot_status(RobotStatus.SEARCH)
reset_robot()
command_step = 0
稳定性计数器机制
文件: src/move_controller.py
_cycle_time = 0 # 全局计数器
def move_controller(...):
if result: # 检测到目标
if 目标在视野中央:
_cycle_time += 1
if _cycle_time > 10: # 稳定10帧
触发状态转换
else:
_cycle_time = 0 # 目标偏离,重置计数器
else: # 未检测到目标
_cycle_time = 0
作用:
- 防止误触发(目标一闪而过)
- 确保机器人已稳定对准目标
- 10 帧约等于 0.5 秒(20 FPS)
控制模式
文件: src/setup.py
class RobotControlModel(Enum):
ACT = "act" # ACT 学习策略控制
INVERSE = "inverse" # 逆运动学控制
| 模式 | 说明 | 使用场景 |
|---|---|---|
inverse | 逆运动学控制机械臂 | 默认模式,精确位置控制 |
act | ACT 策略控制 | 模仿学习模式 |
目标检测区域参数
文件: src/main.py:46-53
height, width = 480, 640
_target_w = (min(height, width) // 3) - 10 # 约 150
_target_h = min(height, width) // 3 # 约 160
_left = max(0, (width - _target_w) // 2) # 约 245
_top = max(0, (height - _target_h) // 2) # 约 160
_right = min(width, _left + _target_w) # 约 395
_bottom = min(height, _top + _target_h) # 约 320
这些参数定义了摄像头画面中央的目标区域,用于判断球/桶是否在视野中央。
控制系统 (Control Systems)
移动控制器
机械臂控制器
ACT策略控制器
逆运动学控制器
控制模式选择
视觉系统 (Vision System)
YOLO推理实现
硬件加速支持 (ONNX/Atlas 310B)
目标检测和跟踪
硬件抽象层 (Hardware Abstraction)
LeKiwi机器人接口
方向控制
键盘遥操作
主控制循环 (Main Control Loop)
控制循环实现
动作-观察循环
时序控制机制
开发指南 (Development Guide)
代码结构和规范
添加新功能
调试和测试
模型优化
参看1的doc/03_Rockchip_RKNPU_API_Reference_RKNN_Toolkit2_V2.3.2_CN.pdf,这是官方的API使用文档。
把onnx模型转为rknn模型的极简脚本:
# file name: onnx2rknn.py
from rknn.api import RKNN
rknn = RKNN()
rknn.config(target_platform='rk3588')
rknn.load_onnx(model='tennis.onnx')
rknn.build(do_quantization=False)
rknn.export_rknn(export_path='tennis.rknn')
rknn.release()
连接主机
目前有adb, ssh, 调试串口三种连接方式。日常调试推荐用ssh连接。
用户名:orangepi 密码:orangepi
# 调试串口连接
minicom -D /dev/ttyUSB0 -b 1500000
# usb线adb连接
adb devices
adb shell
# ssh连接
sudo ip a add 192.168.1.2/24 dev <ethN> # 设置主机的ip地址
ssh orangepi@192.168.1.20 # 开发板的两个网口分别设置为10和20,哪个能连上就用哪个
手动运行
系统启动后本程序会自动运行,可以手动运行观察输出。如下命令都是在开发板的系统上执行。
sudo systemctl stop my-car # 停止本程序的运行,注意下次重启后本程序依然会自动运行
conda activate rknn # 切换到本程序的运行环境
cd ~/Code/Desktop-Wanderer # 切换到本程序所在的目录
python -m src.main # 手动运行本程序
附录
https://github.com/airockchip/rknn-toolkit2.git
故障排除 (Troubleshooting)
常见问题
调试技巧
性能优化
LeKiwi 概述与代码架构
硬件组成
| 组件 | 数量 | 说明 |
|---|---|---|
| 机械臂 | 1 | 6自由度,含夹爪 |
| 移动底盘 | 1 | 三全向轮结构 |
| 摄像头 | 1 | 前置 OpenCV 相机 |
模块关系
main.py (入口)
│
├── setup.py # 全局配置管理
│ └── config.yaml
│
├── robot_setup.py # 机器人初始化
│ │
│ └── lekiwi/lekiwi.py # LeKiwi 机器人类
│ ├── connect() # 连接并校准
│ ├── calibrate() # 校准流程
│ ├── configure() # 配置电机参数
│ ├── get_observation() # 获取传感器数据
│ └── send_action() # 发送控制指令
│
├── arm_inverse_controller.py # 逆运动学控制
│
├── arm_act_controller.py # ACT 模式控制
│
└── move_controller.py # 底盘移动控制
核心类说明
| 类/模块 | 文件 | 职责 |
|---|---|---|
LeKiwi | lekiwi/lekiwi.py | 机器人主类,管理连接、校准、控制 |
LeKiwiConfig | lekiwi/lekiwi_config.py | 机器人配置(端口、摄像头等) |
DirectionControl | lekiwi/direction_control.py | 底盘速度方向控制 |
KeyboardTeleop | lekiwi/key_board_teleop.py | 键盘遥控输入 |
Robot | lekiwi/robot.py | 抽象基类,定义机器人接口 |
电机列表
机械臂电机(位置模式)
| 电机名称 | ID | 控制模式 | 标准范围 |
|---|---|---|---|
arm_shoulder_pan | 1 | 位置 | -100~100 |
arm_shoulder_lift | 2 | 位置 | -100~100 |
arm_elbow_flex | 3 | 位置 | -100~100 |
arm_wrist_flex | 4 | 位置 | -100~100 |
arm_wrist_roll | 5 | 位置 | -100~100 |
arm_gripper | 6 | 位置 | 0~100 |
底盘电机(速度模式)
| 电机名称 | ID | 控制模式 | 标准范围 |
|---|---|---|---|
base_left_wheel | 7 | 速度 | -100~100 |
base_back_wheel | 8 | 速度 | -100~100 |
base_right_wheel | 9 | 速度 | -100~100 |
键盘控制映射
文件: lekiwi/key_board_teleop.py
| 按键 | 动作 | 速度调节 |
|---|---|---|
| W | 前进 | |
| S | 后退 | |
| A | 左移 | |
| D | 右移 | |
| Q | 左转 | |
| E | 右转 | |
| ] | 加速 | 切换到下一档 |
| [ | 减速 | 切换到上一档 |
| Esc | 退出 |
底盘速度档位
文件: lekiwi/direction_control.py
| 档位 | xy速度 (m/s) | 角速度 (deg/s) | 说明 |
|---|---|---|---|
| 0 | 0.02 | 15 | 特慢 |
| 1 | 0.05 | 30 | 慢 |
| 2 | 0.25 | 50 | 中(默认) |
| 3 | 0.40 | 80 | 快 |
LeKiwi 快速开始
环境要求
安装 Miniforge
wget "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
bash Miniforge3-$(uname)-$(uname -m).sh
创建 Python 3.10 环境
conda create -y -n lerobot python=3.10
conda activate lerobot
安装依赖
conda install ffmpeg -c conda-forge
git clone https://github.com/huggingface/lerobot.git
cd lerobot
pip install -e .
pip install -e ".[feetech]"
硬件连接
- 使用 USB 线将控制板连接到 PC
- 连接 12V 8A 电源适配器
- 检查串口端口
lerobot-find-port
启动命令
conda activate lerobot
cd /path/to/Desktop-Wanderer
python -m src.main
初始化流程
代码层面初始化顺序
# 1. 加载配置 (main.py:48)
init_app() # 从 config.yaml 读取配置到全局变量
# 2. 初始化机器人对象 (robot_setup.py:29)
init_robot()
# └─ cfg = LeKiwiConfig(port=get_port())
# └─ _robot = LeKiwi(cfg)
# 3. 连接硬件 (main.py:52)
robot.connect()
# ├─ bus.connect() # 连接串口总线
# ├─ calibrate() # 如未校准则校准
# ├─ cam.connect() # 连接摄像头
# └─ configure() # 配置电机参数
# 4. 获取初始状态 (main.py:54)
start_obs = robot.get_observation()
首次校准
校准触发条件
满足以下任一条件时进入校准:
- 首次使用设备(无校准文件)
- 校准文件与电机实际值不匹配
- 启动时输入
c手动选择重新校准
校准步骤
第一步:居中机械臂
代码位置: lekiwi.py:152
Move robot to the middle of its range of motion and press ENTER....
将所有机械臂关节手动转动到行程中点位置,按回车确认。
第二步:记录关节行程
代码位置: lekiwi.py:162-169
Move all arm joints except '[full_turn_motor]' sequentially through their entire ranges of motion.
依次缓慢转动每个关节,确保经过上下限位,按回车停止。
注意:轮子电机和腕部滚动无需手动转动,系统自动跳过。
第三步:保存校准
校准文件保存至:
~/.cache/huggingface/lerobot/calibration/robots/lekiwi/None.json
后续启动
已有校准文件时:
Press ENTER to use provided calibration file associated with the id None, or type 'c' and press ENTER to run calibration:
| 操作 | 效果 |
|---|---|
| 按 Enter | 使用现有校准文件继续 |
输入 c + Enter | 重新校准 |
| 等待 3 秒 | 自动使用现有校准文件 |
强制重新校准
rm ~/.cache/huggingface/lerobot/calibration/robots/lekiwi/None.json
python -m src.main
机械臂预设位置
文件: robot_setup.py
_start_positions = {
'arm_shoulder_pan': 0.0,
'arm_shoulder_lift': -31.70,
'arm_elbow_flex': 27.69,
'arm_wrist_flex': 80.00,
'arm_wrist_roll': 0.0,
'arm_gripper': 10.0
}
LeKiwi 工作流程
完整工作循环
┌─────────────────────────────────────────────────────────────────┐
│ 系统启动 │
│ python -m src.main │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. 初始化 │
│ - init_app() 加载 config.yaml │
│ - init_robot() 创建 LeKiwi 实例 │
│ - robot.connect() 连接硬件,执行校准(如需要) │
│ - get_observation() 获取初始关节角度 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. 机械臂归位 │
│ - return_to_start_position() │
│ - 移动到预设位置 (robot_setup.py:5-12) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────┐
│ 主循环 while │
│ True │
└───────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ SEARCH │ │ PICK │ │FIND_BUCKET│
└───────────┘ └───────────┘ └───────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────┐
│ 目标检测 (YOLO) │
│ - yolo_infer() 找球 │
│ - get_black_bucket() 找黑桶 │
│ - get_red_bucket() 找红桶(备选) │
└───────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────┐
│ 底盘移动控制 │
│ - move_controller() 搜索目标 │
│ - move_controller_for_bucket() 跟随桶 │
└───────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────┐
│ 发送控制指令 │
│ - robot.send_action({**arm_action, **move}) │
└───────────────────────────────────────────────┘
状态详解
SEARCH(搜索状态)
职责:寻找球
SEARCH:
├─ 检测: yolo_infer(frame) → 找球
├─ 控制: move_controller(direction, result)
│ ├─ 球在左侧 → 旋转向左
│ ├─ 球在右侧 → 旋转向右
│ ├─ 球太小 → 前进
│ ├─ 球太大 → 后退
│ └─ 球居中且大小合适 → 稳定计数
└─ 稳定10帧 → 切换到 PICK
PICK(捡球状态)
职责:执行捡球动作序列
PICK:
├─ 控制器: p_control_loop(CATCH_ACTION[step])
├─ 动作序列执行:
│ 1. move_to(0.0989, 0.125) # 移动到初始位置
│ 2. shoulder_pan(-12) # 调整肩部角度
│ 3. gripper(60) # 张开夹爪
│ 4. wrist_flex(80) # 弯曲腕部
│ 5. move_to(0.140, 0.1211) # 移动到球上方
│ 6. move_to(0.140, -0.05) # 下降到球位置
│ 7. gap() # 停顿
│ 8. gripper(-60) # 闭合夹爪(夹球)
│ 9. gap() # 停顿
│ 10. shoulder_pan(12) # 肩部归位
│ 11. move_to(-0.1, 0.2) # 举起球
│ 12. wrist_flex(-20) # 调整腕部
└─ 序列完成 → 切换到 FIND_BUCKET
FIND_BUCKET(寻找桶状态)
职责:导航到桶的位置
FIND_BUCKET:
├─ 检测: get_black_bucket_local(frame)
│ └─ 找不到 → get_red_bucket_local(frame)
├─ 检查: gripper_pos > 25 ?
│ ├─ 否(球丢失)→ reset_robot() → SEARCH
│ └─ 是(球还在)→ 继续
├─ 控制: move_controller_for_bucket(direction, result)
│ ├─ 桶在左侧 → 旋转向左
│ ├─ 桶在右侧 → 旋转向右
│ ├─ 桶太小 → 前进
│ ├─ 桶太大 → 后退
│ └─ 桶居中且大小合适 → 稳定计数
└─ 稳定10帧 → 切换到 PUT_BALL
PUT_BALL(放球状态)
职责:执行放球动作序列
PUT_BALL:
├─ 控制器: p_control_loop(PUT_ACTION[step])
├─ 动作序列执行:
│ 1. shoulder_lift(50) # 抬起机械臂
│ 2. gap() # 停顿
│ 3. gripper(60) # 张开夹爪(放球)
│ 4. gap() # 停顿
│ 5. move_to(-0.1, 0.2) # 收回机械臂
│ 6. gripper(-60) # 闭合夹爪
└─ 序列完成 → reset_robot() → SEARCH
捡球动作详解
坐标系统
机械臂工作空间坐标(单位:米):
y (0.22)
│
│
│
(-0.22)──┼───────────────── x
│
│
│
y (-0.15)
注意:x 范围 -0.22 ~ 0.22,y 范围 -0.15 ~ 0.22
逆运动学控制
文件: arm_inverse_controller.py
def inverse_kinematics(x, y, l1=0.1159, l2=0.1350):
"""
计算二维机械臂逆运动学
x, y: 末端执行器目标坐标
l1: 上臂长度 0.1159m
l2: 下臂长度 0.1350m
返回: (肩部角度, 肘部角度)
"""
P控制参数
MOTION_PARAMS = {
'max_step': 0.01, # 最大步长 1cm/帧
'min_step': 0.001, # 最小步长 1mm/帧
'slow_dist': 0.05, # 5cm 内开始减速
'dead_zone': 0.0005 # 0.5mm 内认为到达
}
夹爪控制
| 动作 | 值 | 说明 |
|---|---|---|
| 张开 | +60 | 夹爪打开到 60° |
| 闭合 | -60 | 夹爪闭合,夹住物体 |
| 检测 | >25 | 夹爪角度大于 25° 认为夹住物体 |
视觉目标检测
检测区域
摄像头画面中央的目标区域:
┌────────────────────────────────────────┐
│ (0,0) │
│ ┌───────────────────────────────┐ │
│ │ │ │
│ │ ┌───────────────┐ │ │
│ │ │ TARGET ZONE │ │ │ ← 球/桶需要在这个区域
│ │ │ │ │ │
│ │ └───────────────┘ │ │
│ │ │ │
│ └───────────────────────────────┘ │
│ (640, 480) │
└────────────────────────────────────────┘
检测参数
TARGET_POSITION = max(target_w, target_h) # 约 160 像素
TARGET_CX = left + target_w // 2 # 约 245 + 75 = 320
TARGET_CY = top + target_h // 2 # 约 160 + 80 = 240
目标大小判断
| 目标在画面中的大小 | 判断 | 机器人动作 |
|---|---|---|
max(w,h) < TARGET_POSITION - 10 | 太远 | 前进 |
max(w,h) > TARGET_POSITION + 10 | 太近 | 后退 |
| 居中且大小合适 | 到位 | 等待稳定 |
异常处理
球丢失检测
# main.py:76-83
if get_robot_status() == RobotStatus.FIND_BUCKET:
gripper_pos = current_obs.get('arm_gripper.pos', 5)
is_gripper_holding = gripper_pos > 25
if not is_gripper_holding:
set_robot_status(RobotStatus.SEARCH)
reset_robot()
当检测到夹爪角度小于 25° 时,认为球已丢失,返回 SEARCH 状态重新寻找。
找不到目标
# move_controller.py:58-66
if not result: # 没有检测到目标
if _last_ball_center_x is not None:
# 向最后看到目标的方向旋转
action = direction.get_action("rotate_left/right")
else:
action = direction.get_action(None) # 停止
机器人会记住最后看到目标的位置,并朝那个方向旋转继续搜索。
完整流程时序图
时间轴
│
▼
SEARCH ────────────────────────────────────────────────────────
│ │
│ [检测到球] │
│ │
▼ │
PICK │ │
│ │
├─ move_to(0.0989, 0.125) [机械臂移动] │
├─ shoulder_pan(-12) [调整角度] │
├─ gripper(60) [张开夹爪] │
├─ move_to(0.140, 0.1211) [下降到球上方] │
├─ move_to(0.140, -0.05) [接触球] │
├─ gripper(-60) [夹住球] │
├─ shoulder_pan(12) [抬起] │
├─ move_to(-0.1, 0.2) [举起球] │
│ │
▼ │
FIND_BUCKET │ │
│ │
│ [找桶导航] │
│ │
▼ │
PUT_BALL │ │
│ │
├─ shoulder_lift(50) [抬起臂] │
├─ gripper(60) [张开放球] │
├─ move_to(-0.1, 0.2) [收回] │
├─ gripper(-60) [闭合夹爪] │
│ │
▼ │
SEARCH ────────────────────────────────────────────────────────
LeKiwi 配置参数详解
config.yaml
项目根目录的配置文件:
port: /dev/ttyACM0 # 串口设备路径
fps: 20 # 控制频率 (Hz)
log_level: INFO # 日志级别:DEBUG, INFO, WARNING, ERROR
hardware_mode: normal # 硬件模式:normal (PC), rk3588 (控制板)
control_mode: inverse # 控制模式:inverse (逆运动学), act (ACT策略)
配置项说明
| 配置项 | 可选值 | 说明 |
|---|---|---|
port | /dev/ttyACM0, /dev/ttyUSB0 等 | 串口设备路径 |
fps | 正整数 | 控制频率,建议 20Hz |
log_level | DEBUG, INFO, WARNING, ERROR | 日志详细程度 |
hardware_mode | normal, rk3588 | normal=PC调试,rk3588=控制板运行 |
control_mode | inverse, act | inverse=逆运动学,act=ACT策略 |
LeKiwiConfig 配置类
文件: lekiwi/lekiwi_config.py
@dataclass
class LeKiwiConfig(RobotConfig):
port: str = "/dev/ttyACM0" # 串口端口
disable_torque_on_disconnect: bool = True # 断开时禁用扭矩
max_relative_target: float | dict = None # 相对目标限制
cameras: dict = lekiwi_cameras_config() # 摄像头配置
use_degrees: bool = False # 是否使用角度制
摄像头配置
文件: lekiwi/lekiwi_config.py
def lekiwi_cameras_config() -> dict[str, CameraConfig]:
return {
"front": OpenCVCameraConfig(
index_or_path=0, # 摄像头索引(0, 1, 2...)
fps=30, # 帧率
width=640, # 图像宽度
height=480, # 图像高度
color_mode=ColorMode.BGR
),
}
电机参数配置
文件: lekiwi/lekiwi.py
机械臂电机(位置模式)
for name in self.arm_motors:
self.bus.write("Operating_Mode", name, OperatingMode.POSITION.value)
self.bus.write("P_Coefficient", name, 16) # P控制系数
self.bus.write("I_Coefficient", name, 0) # I控制系数
self.bus.write("D_Coefficient", name, 32) # D控制系数
| 参数 | 默认值 | 说明 |
|---|---|---|
| P_Coefficient | 16 | 比例系数,调低可减少抖动 |
| I_Coefficient | 0 | 积分系数 |
| D_Coefficient | 32 | 微分系数 |
底盘电机(速度模式)
for name in self.base_motors:
self.bus.write("Operating_Mode", name, OperatingMode.VELOCITY.value)
关节校准参数
文件: arm_inverse_controller.py
JOINT_CALIBRATION = [
['arm_shoulder_pan', 6.0, 1.0], # [电机名, 零位偏移, 缩放因子]
['arm_shoulder_lift', 2.0, 0.97],
['arm_elbow_flex', 0.0, 1.05],
['arm_wrist_flex', 0.0, 0.94],
['arm_wrist_roll', 0.0, 0.5],
['arm_gripper', 0.0, 1.0],
]
| 参数 | 说明 |
|---|---|
| 零位偏移 | 校正电机零点误差 |
| 缩放因子 | 校正角度缩放比例 |
运动控制参数
文件: arm_inverse_controller.py
MOTION_PARAMS = {
'max_step': 0.01, # 最大步长 (m/帧)
'min_step': 0.001, # 最小步长 (m/帧)
'slow_dist': 0.05, # 减速距离 (m)
'dead_zone': 0.0005 # 死区 (m)
}
| 参数 | 默认值 | 说明 |
|---|---|---|
| max_step | 0.01 | 远距离时每帧移动 2cm |
| min_step | 0.001 | 精细调整时每帧移动 1mm |
| slow_dist | 0.05 | 距离目标 5cm 以内开始减速 |
| dead_zone | 0.0005 | 误差小于 0.5mm 认为到达 |
逆运动学参数
文件: arm_inverse_controller.py
def inverse_kinematics(x, y, l1=0.1159, l2=0.1350):
# l1: 上臂长度 (m),肩部到肘部
# l2: 下臂长度 (m),肘部到腕部
| 参数 | 值 | 说明 |
|---|---|---|
| l1 | 0.1159 m | 上臂长度 |
| l2 | 0.1350 m | 下臂长度 |
校准文件格式
路径: ~/.cache/huggingface/lerobot/calibration/robots/lekiwi/None.json
{
"arm_shoulder_pan": {
"id": 1,
"drive_mode": 0,
"homing_offset": 1234,
"range_min": 0,
"range_max": 4095
},
"arm_shoulder_lift": {
"id": 2,
"drive_mode": 0,
"homing_offset": -567,
"range_min": 0,
"range_max": 4095
}
}
| 字段 | 说明 |
|---|---|
| id | 电机 ID(在总线上的标识) |
| drive_mode | 驱动模式(0=电机模式) |
| homing_offset | 零位偏移量 |
| range_min | 关节最小位置值 |
| range_max | 关节最大位置值 |
LeKiwi 调试方法
日志调试
修改 config.yaml 中的 log_level:
log_level: DEBUG # 获取最详细的调试信息
日志级别说明
| 级别 | 输出内容 |
|---|---|
| DEBUG | 电机状态、观测数据、控制输出等详细信息 |
| INFO | 连接成功、校准完成等关键事件 |
| WARNING | 潜在问题但不影响运行 |
| ERROR | 连接失败、控制异常等错误 |
摄像头调试
文件: main.py:91-105
在 hardware_mode: normal 模式下,会显示实时摄像头画面:
if get_hardware_mode() == 'normal':
cv2.imshow("frame", frame) # 显示画面
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break # 按 q 退出
摄像头相关调试操作
- 显示画面: 设置
hardware_mode: normal - 隐藏画面: 设置
hardware_mode: rk3588 - 按 q 退出: 在摄像头窗口按下 q 键
串口连接调试
查找端口
# 方法1:使用 lerobot 工具
lerobot-find-port
# 方法2:手动查看
ls -l /dev/ttyACM* # Linux
ls -l /dev/ttyUSB* # Linux
ls /dev/cu.* # macOS
串口权限问题
# 添加读写权限
sudo chmod 666 /dev/ttyACM0
# 或将用户添加到 dialout 组
sudo usermod -a -G dialout $USER
常见串口问题
- 权限不足:
sudo chmod 666 /dev/ttyACM0 - 端口被占用: 检查是否有其他程序占用串口
- 端口名称错误: 确认
config.yaml中port与实际一致
电机状态调试
获取当前电机状态
# 获取当前电机观测数据
obs = robot.get_observation()
print(obs)
输出示例:
{
'arm_shoulder_pan.pos': 0.5,
'arm_shoulder_lift.pos': -31.7,
'arm_elbow_flex.pos': 27.69,
'arm_wrist_flex.pos': 80.0,
'arm_wrist_roll.pos': 0.0,
'arm_gripper.pos': 10.0,
'x.vel': 0.0,
'y.vel': 0.0,
'theta.vel': 0.0,
'front': <numpy.ndarray shape=(480, 640, 3)>
}
手动发送控制指令
发送机械臂位置控制
robot.send_action({
'arm_shoulder_pan.pos': 10.0,
'arm_shoulder_lift.pos': -20.0,
'arm_elbow_flex.pos': 15.0,
'arm_wrist_flex.pos': 80.0,
'arm_wrist_roll.pos': 0.0,
'arm_gripper.pos': 50.0,
'x.vel': 0.0,
'y.vel': 0.0,
'theta.vel': 0.0,
})
发送底盘速度控制
robot.send_action({
'x.vel': 0.1, # 前进 (m/s)
'y.vel': 0.0, # 侧移 (m/s)
'theta.vel': 30.0, # 旋转 (deg/s)
})
P 控制调试
文件: arm_inverse_controller.py
def p_control_loop(cmd, current_x, current_y, current_obs, kp=0.5):
# kp: 比例增益
# 增大 kp:响应更快但可能振荡
# 减小 kp:响应慢但更平稳
调参建议
| 问题 | 调整方法 |
|---|---|
| 响应太慢 | 增大 kp(建议 0.5~1.0) |
| 出现振荡 | 减小 kp(建议 0.2~0.4) |
| 运动不平滑 | 减小 max_step 参数 |
校准数据调试
查看校准文件
cat ~/.cache/huggingface/lerobot/calibration/robots/lekiwi/None.json
验证校准是否成功
- 启动后观察机械臂是否平滑移动到中点位置
- 检查日志中是否显示 "Calibration saved"
- 验证校准文件存在且内容完整
独立调试脚本
创建 debug_robot.py:
#!/usr/bin/env python
import sys
sys.path.insert(0, 'src')
from lekiwi import LeKiwi, LeKiwiConfig
# 创建机器人实例
config = LeKiwiConfig(port="/dev/ttyACM0")
robot = LeKiwi(config)
# 连接
print("Connecting...")
robot.connect()
# 打印观测数据
print("\n=== Robot Observation ===")
obs = robot.get_observation()
for key, value in obs.items():
if key != 'front':
print(f" {key}: {value}")
# 测试控制
print("\n=== Testing Control ===")
action = {
'arm_shoulder_lift.pos': -10.0,
'arm_elbow_flex.pos': 10.0,
'x.vel': 0.0,
'y.vel': 0.0,
'theta.vel': 0.0,
}
robot.send_action(action)
# 断开
robot.disconnect()
print("Done")
运行调试脚本
python debug_robot.py
打印初始化信息
文件: main.py:54-64
启动时会打印初始关节角度:
Reading initial joint angles...
Initial joint angles:
arm_shoulder_pan: 0°
arm_shoulder_lift: -31.7°
arm_elbow_flex: 27.69°
arm_wrist_flex: 80°
arm_wrist_roll: 0°
arm_gripper: 10°
快速调试检查清单
-
串口连接正常 (
ls /dev/ttyACM*) -
摄像头可访问 (
cv2.VideoCapture(0).isOpened()) - 校准文件存在
- 日志级别设置为 DEBUG
- 电源电压稳定 (12V 8A)
LeKiwi 故障排除
Q1: 提示 "Device not connected" 或找不到设备
可能原因:
- USB 线连接不良
- 串口端口号错误
- 串口权限不足
- 串口被其他程序占用
解决方案:
# 1. 检查 USB 连接
ls -l /dev/ttyACM*
# 2. 重新检测端口
lerobot-find-port
# 3. 更新 config.yaml 中的 port
port: /dev/ttyACM0 # 确认端口号正确
# 4. 添加串口权限
sudo chmod 666 /dev/ttyACM0
Q2: 机械臂运动不顺畅或抖动
可能原因:
- P 控制系数过高
- 电源电压不稳定
- 电机线缆接触不良
解决方案:
检查电机线缆:确保所有电机连接牢固
Q3: 摄像头无法打开
可能原因:
- 摄像头索引不正确
- 摄像头被其他程序占用
- 摄像头驱动问题
解决方案:
# Linux: 查看可用摄像头
v4l2-ctl --list-devices
# macOS: 查看摄像头
system_profiler SPCameraDataType
修改 lekiwi_config.py 中的摄像头索引:
index_or_path=0, # 尝试 0, 1, 2...
Q4: 底盘轮子不转动但机械臂正常
可能原因:
- 底盘电机线缆问题
- 控制模式配置错误
- 轮子被卡住
解决方案:
-
检查电机控制模式(
lekiwi.py:199-200):self.bus.write("Operating_Mode", name, OperatingMode.VELOCITY.value) -
查看日志错误(设置
log_level: DEBUG) -
手动测试底盘:
robot.send_action({ 'x.vel': 0.1, 'y.vel': 0.0, 'theta.vel': 0.0, })
Q5: 如何确认校准是否成功?
检查方法:
-
启动后观察机械臂是否平滑移动到中点位置
-
检查日志中是否显示:
Calibration saved to ~/.cache/huggingface/lerobot/calibration/robots/lekiwi/None.json -
验证校准文件存在且内容完整:
cat ~/.cache/huggingface/lerobot/calibration/robots/lekiwi/None.json | head -20
Q6: 关节角度与预期不符
可能原因:
- 零位偏移不正确
- 缩放因子不准确
解决方案:
调整 arm_inverse_controller.py 中的校准参数:
JOINT_CALIBRATION = [
['arm_shoulder_pan', 6.0, 1.0], # 调整偏移和缩放
['arm_shoulder_lift', 2.0, 0.97],
...
]
或重新校准:
rm ~/.cache/huggingface/lerobot/calibration/robots/lekiwi/None.json
python -m src.main
Q7: 运动时末端执行器轨迹不平滑
可能原因:
- 步长设置过大
- P 控制增益过高
解决方案:
-
调整运动控制参数(
arm_inverse_controller.py):MOTION_PARAMS = { 'max_step': 0.005, # 减小可提高平滑度 'slow_dist': 0.08, # 增大减速距离 ... } -
调整 P 控制增益:
p_control_loop(..., kp=0.3) # 降低 kp 提高稳定性
Q8: 启动时卡在校准界面
可能原因:
- 机械臂不在中点位置
- 关节行程记录不完整
解决方案:
- 手动将机械臂所有关节置于中点位置
- 按回车继续
- 缓慢转动每个关节经过完整行程
Q9: 校准文件丢失或损坏
解决方案:
删除损坏的校准文件并重新启动:
rm ~/.cache/huggingface/lerobot/calibration/robots/lekiwi/None.json
python -m src.main
快速恢复
如需完全恢复出厂设置:
# 删除所有校准缓存
rm -rf ~/.cache/huggingface/lerobot/
# 重置配置
# 编辑 config.yaml 确认参数正确
# 重新启动
python -m src.main
诊断命令汇总
| 命令 | 用途 |
|---|---|
ls -l /dev/ttyACM* | 检查串口设备 |
lerobot-find-port | 自动查找端口 |
cat ~/.cache/huggingface/lerobot/calibration/robots/lekiwi/None.json | 查看校准文件 |
python -m src.main | 启动程序 |
sudo chmod 666 /dev/ttyACM0 | 修复串口权限 |