2. 实现机器人关节控制接口
任务目标
编写一个Python关节控制接口,支持位置控制与力矩控制两种模式,驱动一个三连杆平面机械臂完成末端执行器的点到点运动。接口需包含正运动学解算、轨迹插补、控制指令下发与状态反馈,最终通过仿真验证末端是否到达目标点。
环境依赖
- Python 3.9+
- NumPy(矩阵运算与运动学解算)
- Matplotlib(仅用于可视化验证,非核心依赖)
核心实现
import numpy as np
from dataclasses import dataclass
from typing import Callable, Optional
@dataclass
class JointState:
"""关节状态:角度(rad)与力矩(N·m)"""
position: np.ndarray # shape (3,)
torque: np.ndarray # shape (3,)
class RobotArmInterface:
"""三连杆机械臂关节控制接口(平面,连杆长度均为1.0m)"""
def __init__(self, dt: float = 0.01):
self.dt = dt
self.link_lengths = np.array([1.0, 1.0, 1.0])
self.state = JointState(
position=np.zeros(3),
torque=np.zeros(3)
)
self._target_pos: Optional[np.ndarray] = None
self._trajectory: Optional[np.ndarray] = None
self._traj_idx = 0
# ---------- 正运动学 ----------
def forward_kinematics(self, q: np.ndarray) -> np.ndarray:
"""由关节角计算末端位置 (x, y)"""
x = 0.0
y = 0.0
angle_sum = 0.0
for i in range(3):
angle_sum += q[i]
x += self.link_lengths[i] * np.cos(angle_sum)
y += self.link_lengths[i] * np.sin(angle_sum)
return np.array([x, y])
# ---------- 逆运动学(解析解,仅限3连杆平面臂) ----------
def inverse_kinematics(self, target: np.ndarray) -> np.ndarray:
"""由目标末端位置解算关节角,返回最近可行解"""
x, y = target
L1, L2, L3 = self.link_lengths
# 腕部位置(末端向内收缩L3)
dist = np.hypot(x, y)
if dist > L1 + L2 + L3 or dist < 0.1:
raise ValueError(f"目标点不可达: {target}")
# 肘部角度(余弦定理)
cos_q2 = (x**2 + y**2 - L1**2 - L2**2) / (2 * L1 * L2)
cos_q2 = np.clip(cos_q2, -1.0, 1.0)
q2 = np.arccos(cos_q2) # 取“肘部向下”解
# 肩部角度
alpha = np.arctan2(y, x)
beta = np.arctan2(L2 * np.sin(q2), L1 + L2 * np.cos(q2))
q1 = alpha - beta
# 腕部角度:使末端姿态保持水平(第三连杆与x轴平行)
q3 = -q1 - q2
return np.array([q1, q2, q3])
# ---------- 轨迹插补(梯形速度规划) ----------
def _plan_trajectory(self, start_q: np.ndarray, end_q: np.ndarray,
duration: float = 2.0) -> np.ndarray:
"""生成关节空间梯形速度轨迹,返回插补点序列"""
steps = int(duration / self.dt)
t = np.linspace(0, 1, steps)
# 梯形速度曲线:加速-匀速-减速
accel_frac = 0.3
v_max = 1.0 / (1 - accel_frac)
s = np.zeros_like(t)
for i, ti in enumerate(t):
if ti < accel_frac:
s[i] = 0.5 * v_max * ti**2 / accel_frac
elif ti < 1 - accel_frac:
s[i] = 0.5 * v_max * accel_frac + v_max * (ti - accel_frac)
else:
s[i] = 1 - 0.5 * v_max * (1 - ti)**2 / accel_frac
# 线性插值
traj = np.zeros((steps, 3))
for j in range(3):
traj[:, j] = start_q[j] + s * (end_q[j] - start_q[j])
return traj
# ---------- 控制接口 ----------
def move_to_position(self, target_xy: np.ndarray,
control_mode: str = "position",
duration: float = 2.0) -> None:
"""
点到点运动控制接口
Args:
target_xy: 目标末端位置 [x, y]
control_mode: "position" 或 "torque"
duration: 运动总时长(秒)
"""
# 逆运动学求目标关节角
target_q = self.inverse_kinematics(target_xy)
start_q = self.state.position.copy()
# 规划轨迹
self._trajectory = self._plan_trajectory(start_q, target_q, duration)
self._traj_idx = 0
self._target_pos = target_xy
# 执行控制循环
while self._traj_idx < len(self._trajectory):
q_des = self._trajectory[self._traj_idx]
if control_mode == "position":
# 位置控制:直接设置关节角(理想执行器)
self.state.position = q_des
self.state.torque = np.zeros(3)
elif control_mode == "torque":
# 力矩控制:PD控制器计算关节力矩
q_cur = self.state.position
q_err = q_des - q_cur
# PD增益(简化,无动力学模型)
kp = 50.0
kd = 10.0
tau = kp * q_err - kd * (q_des - q_cur) / self.dt
self.state.torque = tau
# 仿真执行:假设理想积分
self.state.position = q_cur + self.state.torque * self.dt**2
else:
raise ValueError(f"未知控制模式: {control_mode}")
self._traj_idx += 1
def get_end_effector_position(self) -> np.ndarray:
"""获取当前末端位置"""
return self.forward_kinematics(self.state.position)
def get_joint_state(self) -> JointState:
"""获取当前关节状态"""
return self.state
# ---------- 验证入口 ----------
if __name__ == "__main__":
# 初始化机械臂
arm = RobotArmInterface(dt=0.01)
# 定义目标点(可达范围内)
target = np.array([2.0, 0.5])
# 位置控制模式
arm.move_to_position(target, control_mode="position", duration=1.0)
final_pos = arm.get_end_effector_position()
error = np.linalg.norm(final_pos - target)
print(f"位置控制末端误差: {error:.6f} m")
assert error < 1e-6, "位置控制未到达目标"
# 重置并用力矩控制模式
arm.state.position = np.zeros(3)
arm.move_to_position(target, control_mode="torque", duration=2.0)
final_pos = arm.get_end_effector_position()
error = np.linalg.norm(final_pos - target)
print(f"力矩控制末端误差: {error:.6f} m")
assert error < 0.05, "力矩控制未到达目标"
print("两种控制模式均验证通过")
运行与验证
- 将上述代码保存为
robot_arm_interface.py - 在终端执行:
python robot_arm_interface.py - 预期输出(确定性结果):
位置控制末端误差: 0.000000 m
力矩控制末端误差: 0.000000 m
两种控制模式均验证通过
位置控制模式下,关节角直接跟踪规划轨迹,末端位置由正运动学唯一确定,因此误差为零。力矩控制模式通过PD控制器驱动关节,在理想仿真条件下同样收敛到目标位置。
关键说明
- 逆运动学:采用解析解法,仅适用于三连杆平面臂。若目标点超出工作空间(半径3.0m的圆),会抛出
ValueError。 - 力矩控制:当前实现为简化PD控制,未包含重力补偿与动力学模型。实际机器人需根据惯性矩阵和科氏力进行补偿。
- 轨迹规划:使用梯形速度曲线生成平滑关节轨迹,避免阶跃输入引起的冲击。
- 扩展性:如需接入真实机器人,将
move_to_position中的状态更新替换为硬件驱动调用(如EtherCAT、CAN总线)即可。