双足机器人 2-RSS-1U 并联踝关节设计与运动学解析
在人形机器人的行走控制中,脚踝往往是被低估的关键环节。相比于复杂的躯干平衡算法,踝关节的机械性能直接决定了机器人在复杂地形下的动态表现与能耗效率。
技术路线:串联与并联的抉择
在双自由度踝关节设计中,串联(Series)与并联(Parallel)是两种主流构型。虽然串联结构控制直观,但在高性能需求下,并联方案正逐渐成为趋势。
串联构型的局限
串联设计将 Pitch(俯仰)和 Roll(侧滚)两个电机依次堆叠。这种方式的致命弱点在于转动惯量过大。处于运动链末端的电机及其减速器成为了上一级关节的额外负载,导致腿部末端惯量显著增加,限制了摆腿速度和动态响应能力。此外,悬臂梁式的受力结构使得整体刚度较低,承载能力有限。
并联构型的优势
并联构型将驱动器布置在固定的基座(小腿)上,通过连杆机构共同驱动脚掌平台。其核心优势在于:
- 低转动惯量:质量最大的电机部件上移至小腿,极大降低了脚部惯量,利于快速、节能的动态行走。
- 高刚度与承载力:闭环结构天然比开环结构更稳固,能抵抗更大的外部冲击。
尽管并联机构的运动学耦合增加了控制难度,但其在物理性能上的巨大优势使其成为实现高动态运动的必然选择。
机构设计:2-RSS-1U 方案
综合考量后,我们选择了 2-RSS-1U 这一并联构型。该名称精确描述了其机械结构:
- 2-RSS:代表系统包含两条相同的 R-S-S 运动链。
- R (Revolute):位于小腿上的主动旋转关节,由电机驱动。
- S (Spherical):两个球形关节,分别位于连杆中段和连接脚掌处,提供空间运动自由度。
- 1-U:代表一个位于中心的被动万向节(Universal Joint)。它是整个机构的运动学核心,其交叉转轴定义了脚掌的 Pitch 和 Roll 运动。
相比 UPU 或 UPS 等其他构型,RSS 结构在力矩传递上具有显著优势,力矩传递比在整个运动空间内变化更小,有利于保持力控制的稳定性。
在设计阶段,我们重点关注了关键几何参数:l_bar(驱动臂长度)、l_rod(连杆长度)和 l_spacing(链间距)。合理的参数配置在保证结构紧凑的同时,实现了 Roll 方向 [-20, 20] 度和 Pitch 方向 [-58, 42] 度的宽阔运动范围。
运动学解算:从姿态到指令
建立精确的数学模型是实现有效控制的前提,主要包括逆运动学、正运动学和雅可比矩阵求解。
1. 逆运动学 (IK)
这是控制中最基础的一步:给定脚掌的目标姿态(q_roll, q_pitch),计算驱动电机的角度(θ1, θ2)。
求解逻辑可转化为几何约束问题:根据目标姿态确定脚掌平台的旋转矩阵,计算连杆连接点的三维坐标,进而利用已知连杆长度约束求解电机角度。该问题存在封闭形式的解析解,计算速度快且无奇异性,为实时位置控制提供了坚实基础。
2. 雅可比矩阵 (Jacobian)
雅可比矩阵建立了关节空间速度与任务空间速度之间的线性关系。对于本机构:
- 关节空间:由电机角度
θ = [θ₁, θ₂]ᵀ构成。 - 任务空间:由脚掌姿态角
q = [q_roll, q_pitch]ᵀ构成。
逆运动学函数表示为 θ = f(q)。对其求导得到速度关系:
θ̇ = J * q̇
其中 J 为雅可比矩阵。它不仅是牛顿 - 拉夫逊法迭代中的关键部分,更是实现柔顺控制和力矩伺服的核心,通过转置矩阵 J^T 可建立末端力矩与电机驱动力矩的关系。
3. 正运动学 (FK)
这是 IK 的逆问题:根据编码器读取的实际角度反算脚掌当前姿态。由于无法直接求得解析解,通常采用牛顿 - 拉夫逊(Newton-Raphson)迭代法。通过定义误差函数并利用雅可比矩阵的逆进行迭代更新,该方法收敛速度极快,通常在几次迭代内即可达到高精度,完全满足实时性要求。
核心算法 C++ 实现
为了便于工程落地,我们将上述逆运动学算法封装为 C++ 类。以下代码基于 Eigen 库实现,可直接用于测试和优化。
//g++ -std=c++17 -O2 -I/usr/include/eigen3 pm_ankle_example.cpp -o pm_ankle_example
#include <Eigen/Dense>
#include <iostream>
#include <cmath>
#include <stdexcept>
// 并联机构几何参数结构体
struct ExternalParameters {
double pm_ankle_a_1[3] = {0.0, 53, 180}; // 执行器 1 的 A 点坐标 [mm]
double pm_ankle_c_1[3] = {-70.0, 53, 0.0}; // 执行器 1 的 C 点初始坐标 [mm]
double pm_ankle_bar_rod_1[2] = {70, 180}; // 执行器 1 连杆长度 [法兰杆,连杆] [mm]
double pm_ankle_a_2[3] = {0.0, -53, 100.0}; // 执行器 2 的 A 点坐标 [mm]
double pm_ankle_c_2[3] = {-70.0, -53, 0.0}; // 执行器 2 的 C 点初始坐标 [mm]
double pm_ankle_bar_rod_2[2] = {70, 100}; // 执行器 2 连杆长度 [法兰杆,连杆] [mm]
};
// 单个执行器的几何参数
struct AnkleActuatorParams {
Eigen::Vector3d r_Ai_0; // A 点在基坐标系下的位置向量 [mm]
Eigen::Vector3d r_Ci_0_initial; // C 点在初始姿态下的位置向量 [mm]
double l_bar; // 法兰杆长度 [mm]
double l_rod; // 连杆长度 [mm]
double alpha_offset_rad; // 电机零位偏移 [rad]
double l_bar_sq; // l_bar 的平方,用于加速计算 [mm^2]
double l_rod_sq; // l_rod 的平方,用于加速计算 [mm^2]
AnkleActuatorParams() : l_bar(0), l_rod(0), alpha_offset_rad(0), l_bar_sq(0), l_rod_sq(0) {}
AnkleActuatorParams(const Eigen::Vector3d& r_ai_0, const Eigen::Vector3d& r_ci_0_initial,
double lbar, double lrod, double alpha_rad = 0.0)
: r_Ai_0(r_ai_0), r_Ci_0_initial(r_ci_0_initial), l_bar(lbar), l_rod(lrod), alpha_offset_rad(alpha_rad) {
if (l_bar <= 1e-9) {
throw std::runtime_error("l_bar 必须大于零");
}
l_bar_sq = l_bar * l_bar;
l_rod_sq = l_rod * l_rod;
}
};
// 绕 X 轴旋转的旋转矩阵
Eigen::Matrix3d Rx(double angle_rad) {
return Eigen::AngleAxisd(angle_rad, Eigen::Vector3d::UnitX()).toRotationMatrix();
}
// 绕 Y 轴旋转的旋转矩阵
Eigen::Matrix3d Ry(double angle_rad) {
return Eigen::AngleAxisd(angle_rad, Eigen::Vector3d::UnitY()).toRotationMatrix();
}
// 计算单个执行器的逆运动学
// 输入:roll 和 pitch 角度,执行器几何参数
// 输出:pair<电机角度 [rad], 计算是否成功>
std::pair<double, bool> calculate_single_theta_i_internal(double q_roll_rad, double q_pitch_rad,
const AnkleActuatorParams& params) {
const Eigen::Vector3d& r_Ai = params.r_Ai_0;
// 计算旋转后的 C 点位置
Eigen::Matrix3d X_rot = Ry(q_pitch_rad) * Rx(q_roll_rad);
Eigen::Vector3d r_Ci = X_rot * params.r_Ci_0_initial;
Eigen::Vector3d r_Ci_minus_r_Ai = r_Ci - r_Ai;
// 计算中间变量 a, b, c
double a_val = r_Ci_minus_r_Ai.x();
double b_val = (r_Ai - r_Ci).z();
double norm_sq_r_Ci_minus_r_Ai = r_Ci_minus_r_Ai.squaredNorm();
if (params.l_bar < 1e-9) {
std::cerr << "错误:l_bar 接近零" << std::endl;
return {0.0, false};
}
double c_val = (params.l_rod_sq - params.l_bar_sq - norm_sq_r_Ci_minus_r_Ai) / (2.0 * params.l_bar);
// 检查 arcsin 参数的有效性
double den_asin = a_val * a_val + b_val * b_val;
double inner_sqrt_term = a_val * a_val + b_val * b_val - c_val * c_val;
if (inner_sqrt_term < -1e-9) {
std::cerr << "错误:配置不可达,平方根项为负 (" << inner_sqrt_term << ")" << std::endl;
return {0.0, false};
}
if (inner_sqrt_term < 0) inner_sqrt_term = 0;
double num_asin = b_val * c_val + std::abs(a_val) * std::sqrt(inner_sqrt_term);
if (std::abs(den_asin) < 1e-12) {
std::cerr << "错误:arcsin 分母接近零" << std::endl;
if (std::abs(num_asin) < 1e-9) {
std::cout << "警告:奇异构型,theta 未定义" << std::endl;
}
return {0.0, false};
}
double arg_asin = num_asin / den_asin;
if (std::abs(arg_asin) > 1.0 + 1e-9) {
std::cerr << "错误:arcsin 参数超出定义域 [-1, 1]: " << arg_asin << std::endl;
return {0.0, false};
}
arg_asin = std::max(-1.0, std::min(1.0, arg_asin));
// 计算电机角度
double theta_i_rad = std::asin(arg_asin);
theta_i_rad += params.alpha_offset_rad;
return {theta_i_rad, true};
}
// 并联机构逆解结果
struct AnkleIKSolution {
double theta_1_rad; // 执行器 1 的电机角度 [rad]
bool success_1; // 执行器 1 计算是否成功
double theta_2_rad; // 执行器 2 的电机角度 [rad]
bool success_2; // 执行器 2 计算是否成功
AnkleIKSolution() : theta_1_rad(0), success_1(false), theta_2_rad(0), success_2(false) {}
};
// 计算并联机构的逆运动学(两个执行器)
AnkleIKSolution calculate_ankle_ik_both_actuators(double q_roll_rad, double q_pitch_rad,
const AnkleActuatorParams& params_1,
const AnkleActuatorParams& params_2) {
AnkleIKSolution solution;
auto result_1 = calculate_single_theta_i_internal(q_roll_rad, q_pitch_rad, params_1);
solution.theta_1_rad = result_1.first;
solution.success_1 = result_1.second;
auto result_2 = calculate_single_theta_i_internal(q_roll_rad, q_pitch_rad, params_2);
solution.theta_2_rad = result_2.first;
solution.success_2 = result_2.second;
return solution;
}
// 并联机构控制类
class PMAnkle {
public:
ExternalParameters ext_params;
AnkleActuatorParams params_1; // 执行器 1 的参数
AnkleActuatorParams params_2; // 执行器 2 的参数
PMAnkle() {}
// 初始化并联机构的几何参数
void PMankleInit() {
// 初始化执行器 1
Eigen::Vector3d r_A_1_vec(ext_params.pm_ankle_a_1[0], ext_params.pm_ankle_a_1[1], ext_params.pm_ankle_a_1[2]);
Eigen::Vector3d r_C_1_vec(ext_params.pm_ankle_c_1[0], ext_params.pm_ankle_c_1[1], ext_params.pm_ankle_c_1[2]);
double L_bar_1 = ext_params.pm_ankle_bar_rod_1[0];
double L_rod_1 = ext_params.pm_ankle_bar_rod_1[1];
double alpha_1_rad = 0.0;
params_1 = AnkleActuatorParams(r_A_1_vec, r_C_1_vec, L_bar_1, L_rod_1, alpha_1_rad);
// 初始化执行器 2
Eigen::Vector3d r_A_2_vec(ext_params.pm_ankle_a_2[0], ext_params.pm_ankle_a_2[1], ext_params.pm_ankle_a_2[2]);
Eigen::Vector3d r_C_2_vec(ext_params.pm_ankle_c_2[0], ext_params.pm_ankle_c_2[1], ext_params.pm_ankle_c_2[2]);
double L_bar_2 = ext_params.pm_ankle_bar_rod_2[0];
double L_rod_2 = ext_params.pm_ankle_bar_rod_2[1];
double alpha_2_rad = 0.0;
params_2 = AnkleActuatorParams(r_A_2_vec, r_C_2_vec, L_bar_2, L_rod_2, alpha_2_rad);
std::cout << "并联机构初始化完成" << std::endl;
std::cout << " 执行器 1 - L_bar: " << params_1.l_bar << " mm, L_rod: " << params_1.l_rod << " mm" << std::endl;
std::cout << " 执行器 2 - L_bar: " << params_2.l_bar << " mm, L_rod: " << params_2.l_rod << " mm" << std::endl;
}
// 计算给定姿态角度下的电机角度
AnkleIKSolution getAnkleMotorAngles(double q_roll_rad, double q_pitch_rad) {
return calculate_ankle_ik_both_actuators(q_roll_rad, q_pitch_rad, params_1, params_2);
}
};
int main() {
PMAnkle ankle_ankle;
std::cout << "=== 踝关节并联机构逆运动学求解 ===" << std::endl;
ankle_ankle.PMankleInit();
// 设置目标姿态角
double q_roll_deg = 10.0;
double q_pitch_deg = 10.0;
double q_roll_rad = q_roll_deg * M_PI / 180.0;
double q_pitch_rad = q_pitch_deg * M_PI / 180.0;
std::cout << "\n目标姿态角:" << std::endl;
std::cout << " Roll: " << q_roll_deg << " 度 (" << q_roll_rad << " rad)" << std::endl;
std::cout << " Pitch: " << q_pitch_deg << " 度 (" << q_pitch_rad << " rad)" << std::endl;
// 求解逆运动学
AnkleIKSolution solution = ankle_ankle.getAnkleMotorAngles(q_roll_rad, q_pitch_rad);
std::cout << "\n电机角度:" << std::endl;
if (solution.success_1) {
std::cout << " 电机 1: " << solution.theta_1_rad * 180.0 / M_PI << " 度 (" << solution.theta_1_rad << " rad)" << std::endl;
} else {
std::cout << " 电机 1: 计算失败" << std::endl;
}
if (solution.success_2) {
std::cout << " 电机 2: " << solution.theta_2_rad * 180.0 / M_PI << " 度 (" << solution.theta_2_rad << " rad)" << std::endl;
} else {
std::cout << " 电机 2: 计算失败" << std::endl;
}
return 0;
}
以上代码展示了如何根据目标姿态解算电机角度。实际应用中,建议结合具体的传感器反馈对参数进行微调,并加入异常处理机制以确保运行安全。


