- 清晰的章节结构:几何参数、正运动学、逆运动学 - 完整的数学推导(带 LaTeX 公式) - Python 代码实现示例 - 工作空间分析和奇异点说明 - 运动插值算法 - 实用示例和测试命令 替换了原有的混乱结构,现在更易理解和参考
425 lines
8.6 KiB
Markdown
425 lines
8.6 KiB
Markdown
# 机械臂运动学推导
|
||
|
||
6-DOF 机械臂逆运动学和正运动学完整推导。
|
||
|
||
## 机械臂结构
|
||
|
||
### 关节配置
|
||
|
||
```
|
||
J1: 线性滑轨(垂直运动,高度 d1)
|
||
J2: 基座旋转(绕 Z 轴,XY 平面)
|
||
J3: 肘关节(绕 Z 轴,XY 平面)
|
||
J4: 腕关节(绕 Z 轴,XY 平面)
|
||
J5: 末端俯仰(0° = 水平)
|
||
J6: 夹爪旋转
|
||
```
|
||
|
||
### 几何参数
|
||
|
||
| 参数 | 值 (mm) | 说明 |
|
||
|------|---------|------|
|
||
| L1 | 125 | J2-J3 连杆长度 |
|
||
| L2 | 125 | J3-J4 连杆长度 |
|
||
| x4 | 110 | J4-TCP 水平偏移 |
|
||
| z4 | 80 | J4-TCP 垂直偏移(夹爪状态相关:张开 55mm,闭合 -100mm) |
|
||
|
||
### 坐标系
|
||
|
||
**基坐标系**(右手系,Z 轴朝上):
|
||
```
|
||
Z↑ (高度)
|
||
|
|
||
|
|
||
o----→ X (前)
|
||
/
|
||
/
|
||
Y (左)
|
||
```
|
||
|
||
- 原点:J1 滑轨底部
|
||
- d1 范围:[-290, 0] mm(负值表示在基准面以下)
|
||
|
||
## 正运动学(FK)
|
||
|
||
已知关节状态 → 计算 TCP 位姿
|
||
|
||
### 输入
|
||
|
||
- `d1`: 高度(J1 线性位移)
|
||
- `θ2, θ3, θ4`: J2/J3/J4 角度(度)
|
||
|
||
### 输出
|
||
|
||
- `(x, y, z)`: TCP 位置(mm)
|
||
- `φ`: TCP 偏航角(度)
|
||
|
||
### 推导
|
||
|
||
**步骤 1**:计算 J4 中心位置(XY 平面)
|
||
|
||
J2 和 J3 形成二连杆机构:
|
||
|
||
$$
|
||
x_{j4} = L_1 \cos\theta_2 + L_2 \cos(\theta_2 + \theta_3)
|
||
$$
|
||
|
||
$$
|
||
y_{j4} = L_1 \sin\theta_2 + L_2 \sin(\theta_2 + \theta_3)
|
||
$$
|
||
|
||
**步骤 2**:计算 TCP 偏航角
|
||
|
||
$$
|
||
\phi = \theta_2 + \theta_3 + \theta_4
|
||
$$
|
||
|
||
**步骤 3**:计算 TCP 位置
|
||
|
||
从 J4 中心沿 φ 方向偏移 x4,垂直偏移 z4:
|
||
|
||
$$
|
||
x = x_{j4} + x_4 \cos\phi
|
||
$$
|
||
|
||
$$
|
||
y = y_{j4} + x_4 \sin\phi
|
||
$$
|
||
|
||
$$
|
||
z = d_1 - z_4
|
||
$$
|
||
|
||
### Python 实现
|
||
|
||
```python
|
||
def forward_kinematics(d1, theta2_deg, theta3_deg, theta4_deg, L1=125, L2=125, x4=110, z4=80):
|
||
"""正运动学"""
|
||
theta2 = math.radians(theta2_deg)
|
||
theta3 = math.radians(theta3_deg)
|
||
theta4 = math.radians(theta4_deg)
|
||
phi = theta2 + theta3 + theta4
|
||
|
||
# J4 中心
|
||
x_j4 = L1 * math.cos(theta2) + L2 * math.cos(theta2 + theta3)
|
||
y_j4 = L1 * math.sin(theta2) + L2 * math.sin(theta2 + theta3)
|
||
|
||
# TCP 位置
|
||
x = x_j4 + x4 * math.cos(phi)
|
||
y = y_j4 + x4 * math.sin(phi)
|
||
z = d1 - z4
|
||
|
||
return x, y, z, math.degrees(phi)
|
||
```
|
||
|
||
## 逆运动学(IK)
|
||
|
||
已知 TCP 目标位姿 → 计算关节角度
|
||
|
||
### 输入
|
||
|
||
- `(x, y, z)`: 目标位置(mm)
|
||
- `φ`: 目标偏航角(度)
|
||
|
||
### 输出
|
||
|
||
- `d1`: 高度
|
||
- `θ2, θ3, θ4`: 关节角度(度)
|
||
|
||
### 推导
|
||
|
||
**步骤 1**:计算 J4 中心目标位置
|
||
|
||
从 TCP 位置反向计算 J4 位置:
|
||
|
||
$$
|
||
x_{j4} = x - x_4 \cos\phi
|
||
$$
|
||
|
||
$$
|
||
y_{j4} = y - x_4 \sin\phi
|
||
$$
|
||
|
||
$$
|
||
z_{j4} = z + z_4
|
||
$$
|
||
|
||
$$
|
||
d_1 = z_{j4}
|
||
$$
|
||
|
||
**步骤 2**:计算平面距离
|
||
|
||
$$
|
||
r = \sqrt{x_{j4}^2 + y_{j4}^2}
|
||
$$
|
||
|
||
**步骤 3**:求解 θ3(余弦定理)
|
||
|
||
二连杆机构的标准解法:
|
||
|
||
$$
|
||
\cos\theta_3 = \frac{r^2 - L_1^2 - L_2^2}{2L_1L_2}
|
||
$$
|
||
|
||
检查工作空间:
|
||
|
||
$$
|
||
-1 \leq \cos\theta_3 \leq 1 \quad \Rightarrow \quad |L_1 - L_2| \leq r \leq L_1 + L_2
|
||
$$
|
||
|
||
否则目标超出工作空间。
|
||
|
||
有两个解(肘部朝上 / 朝下):
|
||
|
||
$$
|
||
\theta_3 = \pm \arccos\left(\frac{r^2 - L_1^2 - L_2^2}{2L_1L_2}\right)
|
||
$$
|
||
|
||
**步骤 4**:求解 θ2
|
||
|
||
$$
|
||
\theta_2 = \arctan2(y_{j4}, x_{j4}) - \arctan2\left(L_2\sin\theta_3, L_1 + L_2\cos\theta_3\right)
|
||
$$
|
||
|
||
**步骤 5**:求解 θ4
|
||
|
||
$$
|
||
\theta_4 = \phi - \theta_2 - \theta_3
|
||
$$
|
||
|
||
### Python 实现
|
||
|
||
```python
|
||
def inverse_kinematics(x, y, z, phi_deg, elbow_up=False, L1=125, L2=125, x4=110, z4=80):
|
||
"""逆运动学"""
|
||
phi = math.radians(phi_deg)
|
||
|
||
# 步骤 1: 计算 J4 中心
|
||
x_j4 = x - x4 * math.cos(phi)
|
||
y_j4 = y - x4 * math.sin(phi)
|
||
z_j4 = z + z4
|
||
d1 = z_j4
|
||
|
||
# 步骤 2: 平面距离
|
||
r2 = x_j4**2 + y_j4**2
|
||
r = math.sqrt(r2)
|
||
|
||
# 步骤 3: 求解 theta3
|
||
cos_theta3 = (r2 - L1**2 - L2**2) / (2 * L1 * L2)
|
||
|
||
if cos_theta3 < -1 or cos_theta3 > 1:
|
||
raise ValueError(f"目标超出工作空间,r={r:.1f}mm")
|
||
|
||
# 肘部朝上:负角度,肘部朝下:正角度
|
||
sin_theta3 = -math.sqrt(1 - cos_theta3**2) if elbow_up else math.sqrt(1 - cos_theta3**2)
|
||
theta3 = math.atan2(sin_theta3, cos_theta3)
|
||
|
||
# 步骤 4: 求解 theta2
|
||
theta2 = math.atan2(y_j4, x_j4) - math.atan2(
|
||
L2 * sin_theta3,
|
||
L1 + L2 * cos_theta3
|
||
)
|
||
|
||
# 步骤 5: 求解 theta4
|
||
theta4 = phi - theta2 - theta3
|
||
|
||
# 角度归一化到 [-180, 180)
|
||
def normalize(angle):
|
||
a = (angle + math.pi) % (2 * math.pi) - math.pi
|
||
return a if a != -math.pi or angle <= 0 else math.pi
|
||
|
||
return (
|
||
d1,
|
||
math.degrees(normalize(theta2)),
|
||
math.degrees(normalize(theta3)),
|
||
math.degrees(normalize(theta4))
|
||
)
|
||
```
|
||
|
||
## 零点偏移
|
||
|
||
物理零点(机械对齐)与数学零点(直线构型)存在偏差:
|
||
|
||
| 关节 | 零点偏移 |
|
||
|------|----------|
|
||
| J2 | +3° |
|
||
| J3 | +7° |
|
||
| J4 | +25° |
|
||
|
||
### 转换关系
|
||
|
||
**命令角度 ↔ 数学角度**:
|
||
|
||
$$
|
||
\theta_{\text{command}} = \theta_{\text{math}} + \text{offset}
|
||
$$
|
||
|
||
$$
|
||
\theta_{\text{math}} = \theta_{\text{command}} - \text{offset}
|
||
$$
|
||
|
||
**使用**:
|
||
- 正运动学:先减去偏移(命令 → 数学),再计算
|
||
- 逆运动学:先计算(数学),再加上偏移(数学 → 命令)
|
||
|
||
## 工作空间
|
||
|
||
### 高度范围
|
||
|
||
$$
|
||
z \in [-290 - z_4, 0 - z_4] = [-370, -80] \text{ mm}
|
||
$$
|
||
|
||
### 水平范围
|
||
|
||
$$
|
||
r_{\min} = |L_1 - L_2| + x_4 = 0 + 110 = 110 \text{ mm}
|
||
$$
|
||
|
||
$$
|
||
r_{\max} = L_1 + L_2 + x_4 = 125 + 125 + 110 = 360 \text{ mm}
|
||
$$
|
||
|
||
**可达圆环**:半径 110mm 到 360mm
|
||
|
||
### 关节限位
|
||
|
||
| 关节 | 最小值 | 最大值 | 单位 |
|
||
|------|--------|--------|------|
|
||
| height | -290 | 0 | mm |
|
||
| J2 | -110 | 115 | ° |
|
||
| J3 | -120 | 145 | ° |
|
||
| J4 | -90 | 130 | ° |
|
||
| J5 | -180 | 180 | ° |
|
||
| J6 | -180 | 180 | ° |
|
||
|
||
## 奇异点
|
||
|
||
### 1. 肩部奇异点
|
||
|
||
当 J4 中心在原点正上方:
|
||
|
||
$$
|
||
x_{j4} = y_{j4} = 0 \quad \Rightarrow \quad r = 0
|
||
$$
|
||
|
||
此时 θ2 无定义(可取任意值)。
|
||
|
||
**避免**:保持 `r > 10mm`
|
||
|
||
### 2. 肘部奇异点
|
||
|
||
当机械臂完全伸直或完全折叠:
|
||
|
||
$$
|
||
\theta_3 = 0° \text{ 或 } \pm 180°
|
||
$$
|
||
|
||
此时运动控制不稳定。
|
||
|
||
**避免**:保持 `|θ3| > 5°`
|
||
|
||
## 运动插值
|
||
|
||
关节空间线性插值,避免笛卡尔空间的复杂路径规划。
|
||
|
||
### 算法
|
||
|
||
给定起点 `q_start` 和终点 `q_end`,生成 N 个中间点:
|
||
|
||
$$
|
||
q_i = q_{\text{start}} + \frac{i}{N}(q_{\text{end}} - q_{\text{start}}), \quad i = 1, 2, \ldots, N
|
||
$$
|
||
|
||
### 参数
|
||
|
||
- `duration`: 运动时长(秒)
|
||
- `rate`: 插值频率(Hz)
|
||
- `steps = ceil(duration × rate)`: 插值点数
|
||
|
||
**示例**:`duration=2.0s`, `rate=20Hz` → `steps=40`
|
||
|
||
### Python 实现
|
||
|
||
```python
|
||
def interpolate_joints(start, end, duration=2.0, rate=20.0):
|
||
"""关节空间插值"""
|
||
steps = max(1, int(math.ceil(duration * rate)))
|
||
trajectory = []
|
||
|
||
for i in range(1, steps + 1):
|
||
t = i / steps
|
||
state = {
|
||
key: int(round(start[key] + t * (end[key] - start[key])))
|
||
for key in start.keys()
|
||
}
|
||
trajectory.append(state)
|
||
|
||
return trajectory
|
||
```
|
||
|
||
## 完整示例
|
||
|
||
### 示例 1:前方抓取
|
||
|
||
**目标**:抓取前方 250mm,右侧 50mm,高度 -150mm 的物体
|
||
|
||
```python
|
||
# 逆运动学
|
||
d1, theta2, theta3, theta4 = inverse_kinematics(
|
||
x=250, y=50, z=-150, phi_deg=0
|
||
)
|
||
# 输出: d1=-70, theta2=11.3°, theta3=-48.6°, theta4=37.3°
|
||
|
||
# 验证:正运动学
|
||
x, y, z, phi = forward_kinematics(d1, theta2, theta3, theta4)
|
||
# 输出: (250.0, 50.0, -150.0, 0.0°) ✓
|
||
```
|
||
|
||
### 示例 2:多段轨迹
|
||
|
||
```python
|
||
# 初始位置
|
||
start = {'height': 0, 'j2': 0, 'j3': 0, 'j4': 0, 'j5': 81, 'j6': 30}
|
||
|
||
# 目标 1: 上方
|
||
_, theta2, theta3, theta4 = inverse_kinematics(200, 100, -50, 45)
|
||
waypoint1 = {'height': -50, 'j2': theta2, 'j3': theta3, 'j4': theta4, 'j5': 81, 'j6': 30}
|
||
|
||
# 目标 2: 抓取位置
|
||
_, theta2, theta3, theta4 = inverse_kinematics(200, 100, -150, 45)
|
||
waypoint2 = {'height': -150, 'j2': theta2, 'j3': theta3, 'j4': theta4, 'j5': -100, 'j6': -5}
|
||
|
||
# 生成轨迹
|
||
traj1 = interpolate_joints(start, waypoint1, duration=2.0) # 2秒到上方
|
||
traj2 = interpolate_joints(waypoint1, waypoint2, duration=1.0) # 1秒下降抓取
|
||
```
|
||
|
||
## 参考
|
||
|
||
### UDP 命令格式
|
||
|
||
```
|
||
JXB:<height>:<J2>:<J3>:<J4>:<J5>:<J6>:0:0:EZHY\n
|
||
```
|
||
|
||
- 所有角度值已包含零点偏移
|
||
- 直接发送到 ESP32 的 UDP 端口 8888
|
||
|
||
### 相关工具
|
||
|
||
- `tools/udp_control.py` - 命令行控制(支持 `joints` 和 `pose` 模式)
|
||
- `ros2/src/udp_teleop/udp_teleop/arm_control.py` - ROS 节点
|
||
- `docs/vision_calibration_horizontal.md` - 相机坐标变换
|
||
|
||
### 测试
|
||
|
||
```bash
|
||
# 测试逆运动学
|
||
python tools/udp_control.py pose --x 200 --y 100 --z -100 --phi 45 --dry-run
|
||
|
||
# 发送命令
|
||
python tools/udp_control.py pose --x 200 --y 100 --z -100 --phi 45 --duration 2.0
|
||
```
|