382 lines
10 KiB
C
382 lines
10 KiB
C
/*
|
|
* VMC虚拟模型控制器实现
|
|
*
|
|
* 本文件实现了轮腿机器人的VMC (Virtual Model Control) 虚拟模型控制算法
|
|
* 主要功能包括:
|
|
* 1. 五连杆机构的正逆运动学解算
|
|
* 2. 虚拟力到关节力矩的映射
|
|
* 3. 地面接触检测
|
|
* 4. 等效摆动杆模型转换
|
|
*
|
|
* 参考文献:
|
|
* - 韭菜的菜 知乎: 平衡步兵控制系统设计
|
|
* - 上交轮腿电控开源方案 (2023)
|
|
*/
|
|
|
|
#include "vmc.h"
|
|
#include <string.h>
|
|
|
|
/* Private typedef ---------------------------------------------------------- */
|
|
/* Private define ----------------------------------------------------------- */
|
|
|
|
#define VMC_EPSILON (1e-6f) // 数值计算精度
|
|
#define VMC_MAX_ITER (10) // 最大迭代次数
|
|
|
|
/* Private macro ------------------------------------------------------------ */
|
|
|
|
/**
|
|
* @brief 限制数值范围
|
|
*/
|
|
#define VMC_CLAMP(val, min, max) ((val) < (min) ? (min) : ((val) > (max) ? (max) : (val)))
|
|
|
|
/**
|
|
* @brief 安全开方
|
|
*/
|
|
#define VMC_SAFE_SQRT(x) (((x) > 0) ? sqrtf(x) : 0.0f)
|
|
|
|
/* Private variables -------------------------------------------------------- */
|
|
/* Private function prototypes ---------------------------------------------- */
|
|
|
|
static int8_t VMC_ValidateParams(const VMC_Param_t *param);
|
|
static void VMC_UpdateKinematics(VMC_t *vmc, float phi1, float phi4);
|
|
static int8_t VMC_SolveClosedLoop(VMC_t *vmc);
|
|
static float VMC_ComputeNumericDerivative(float current, float previous, float dt);
|
|
|
|
/* Exported functions ------------------------------------------------------- */
|
|
|
|
/**
|
|
* @brief 初始化VMC控制器
|
|
*/
|
|
int8_t VMC_Init(VMC_t *vmc, const VMC_Param_t *param, float sample_freq) {
|
|
if (vmc == NULL || param == NULL || sample_freq <= 0) {
|
|
return -1;
|
|
}
|
|
|
|
// 复制参数
|
|
memcpy(&vmc->param, param, sizeof(VMC_Param_t));
|
|
|
|
// 设置控制周期
|
|
vmc->dt = 1.0f / sample_freq;
|
|
|
|
// 重置状态
|
|
VMC_Reset(vmc);
|
|
|
|
vmc->initialized = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief VMC五连杆正解算
|
|
*
|
|
* 通过髋关节角度和机体姿态,计算足端位置和等效摆动杆参数
|
|
*
|
|
* 坐标系定义:
|
|
* - x轴: 机体前进方向为正
|
|
* - y轴: 竖直向下为正
|
|
* - 角度: 顺时针为正
|
|
*/
|
|
int8_t VMC_ForwardSolve(VMC_t *vmc, float phi1, float phi4, float body_pitch, float body_pitch_rate) {
|
|
if (vmc == NULL || !vmc->initialized) {
|
|
return -1;
|
|
}
|
|
|
|
VMC_Leg_t *leg = &vmc->leg;
|
|
|
|
// 保存历史值
|
|
leg->last_phi0 = leg->phi0;
|
|
leg->last_L0 = leg->L0;
|
|
leg->last_d_L0 = leg->d_L0;
|
|
leg->last_d_theta = leg->d_theta;
|
|
|
|
// 更新关节角度
|
|
leg->phi1 = phi1;
|
|
leg->phi4 = phi4;
|
|
|
|
// 更新运动学状态
|
|
VMC_UpdateKinematics(vmc, phi1, phi4);
|
|
|
|
// 求解闭环运动学
|
|
if (VMC_SolveClosedLoop(vmc) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
// 计算足端坐标
|
|
leg->foot_x = leg->XC - vmc->param.hip_length / 2.0f;
|
|
leg->foot_y = leg->YC;
|
|
|
|
// 计算等效摆动杆参数
|
|
leg->L0 = VMC_SAFE_SQRT(leg->foot_x * leg->foot_x + leg->foot_y * leg->foot_y);
|
|
leg->phi0 = atan2f(leg->foot_y, leg->foot_x);
|
|
|
|
// 计算等效摆动杆角度(相对于机体坐标系)
|
|
leg->alpha = VMC_PI_2 - leg->phi0;
|
|
leg->theta = -(VMC_PI_2 + body_pitch - leg->phi0);
|
|
|
|
// 角度归一化
|
|
VMC_ANGLE_NORMALIZE(leg->theta);
|
|
VMC_ANGLE_NORMALIZE(leg->phi0);
|
|
|
|
// 计算角速度和长度变化率
|
|
leg->d_phi0 = VMC_ComputeNumericDerivative(leg->phi0, leg->last_phi0, vmc->dt);
|
|
leg->d_alpha = -leg->d_phi0;
|
|
leg->d_theta = body_pitch_rate + leg->d_phi0;
|
|
leg->d_L0 = VMC_ComputeNumericDerivative(leg->L0, leg->last_L0, vmc->dt);
|
|
|
|
// 计算角加速度
|
|
leg->dd_theta = VMC_ComputeNumericDerivative(leg->d_theta, leg->last_d_theta, vmc->dt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief VMC五连杆逆解算(力矩分配)
|
|
*
|
|
* 根据期望的虚拟力和力矩,通过雅可比矩阵计算关节力矩
|
|
*/
|
|
int8_t VMC_InverseSolve(VMC_t *vmc, float F_virtual, float T_virtual) {
|
|
if (vmc == NULL || !vmc->initialized) {
|
|
return -1;
|
|
}
|
|
|
|
// 保存虚拟力和力矩
|
|
vmc->leg.F_virtual = -F_virtual;
|
|
vmc->leg.T_virtual = T_virtual;
|
|
|
|
// 计算雅可比矩阵
|
|
if (VMC_ComputeJacobian(vmc) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
VMC_Leg_t *leg = &vmc->leg;
|
|
|
|
// 通过雅可比转置计算关节力矩
|
|
// tau = J^T * F_virtual
|
|
leg->tau_hip_rear = leg->J11 * vmc->leg.F_virtual + leg->J12 * vmc->leg.T_virtual;
|
|
leg->tau_hip_front = leg->J21 * vmc->leg.F_virtual + leg->J22 * vmc->leg.T_virtual;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief 地面接触检测
|
|
*
|
|
* 基于虚拟力和腿部状态估计地面法向力
|
|
*/
|
|
float VMC_GroundContactDetection(VMC_t *vmc) {
|
|
if (vmc == NULL || !vmc->initialized) {
|
|
return 0.0f;
|
|
}
|
|
|
|
VMC_Leg_t *leg = &vmc->leg;
|
|
|
|
// 计算地面法向力
|
|
// Fn = F0*cos(theta) + Tp*sin(theta)/L0 + mg
|
|
leg->Fn = leg->F_virtual * cosf(leg->theta) +
|
|
leg->T_virtual * sinf(leg->theta) / leg->L0 +
|
|
vmc->param.wheel_mass * 9.8f; // 添加轮子重力
|
|
|
|
// 地面接触判断
|
|
leg->is_ground_contact = (leg->Fn > 10.0f); // 10N阈值
|
|
|
|
return leg->Fn;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取足端位置
|
|
*/
|
|
int8_t VMC_GetFootPosition(const VMC_t *vmc, float *x, float *y) {
|
|
if (vmc == NULL || !vmc->initialized || x == NULL || y == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
*x = vmc->leg.foot_x;
|
|
*y = vmc->leg.foot_y;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取等效摆动杆参数
|
|
*/
|
|
int8_t VMC_GetVirtualLegState(const VMC_t *vmc, float *length, float *angle, float *d_length, float *d_angle) {
|
|
if (vmc == NULL || !vmc->initialized) {
|
|
return -1;
|
|
}
|
|
|
|
if (length) *length = vmc->leg.L0;
|
|
if (angle) *angle = vmc->leg.theta;
|
|
if (d_length) *d_length = vmc->leg.d_L0;
|
|
if (d_angle) *d_angle = vmc->leg.d_theta;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief 获取关节输出力矩
|
|
*/
|
|
int8_t VMC_GetJointTorques(const VMC_t *vmc, float *tau_front, float *tau_rear) {
|
|
if (vmc == NULL || !vmc->initialized || tau_front == NULL || tau_rear == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
*tau_front = vmc->leg.tau_hip_front;
|
|
*tau_rear = vmc->leg.tau_hip_rear;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief 重置VMC控制器状态
|
|
*/
|
|
void VMC_Reset(VMC_t *vmc) {
|
|
if (vmc == NULL) {
|
|
return;
|
|
}
|
|
|
|
// 清零腿部状态
|
|
memset(&vmc->leg, 0, sizeof(VMC_Leg_t));
|
|
|
|
// 设置初始值
|
|
vmc->leg.L0 = 0.15f; // 默认腿长15cm
|
|
vmc->leg.theta = 0.0f;
|
|
}
|
|
|
|
/**
|
|
* @brief 设置虚拟力和力矩
|
|
*/
|
|
void VMC_SetVirtualForces(VMC_t *vmc, float F_virtual, float T_virtual) {
|
|
if (vmc == NULL || !vmc->initialized) {
|
|
return;
|
|
}
|
|
|
|
vmc->leg.F_virtual = F_virtual;
|
|
vmc->leg.T_virtual = T_virtual;
|
|
}
|
|
|
|
/**
|
|
* @brief 计算雅可比矩阵
|
|
*
|
|
* 根据当前关节配置计算从虚拟力到关节力矩的雅可比矩阵
|
|
*/
|
|
int8_t VMC_ComputeJacobian(VMC_t *vmc) {
|
|
if (vmc == NULL || !vmc->initialized) {
|
|
return -1;
|
|
}
|
|
|
|
VMC_Leg_t *leg = &vmc->leg;
|
|
|
|
// 检查分母不为零
|
|
float sin_diff = sinf(leg->phi3 - leg->phi2);
|
|
if (fabsf(sin_diff) < VMC_EPSILON) {
|
|
return -1; // 奇异配置
|
|
}
|
|
|
|
// 计算雅可比矩阵元素
|
|
// J11: 后髋关节到支撑力的雅可比
|
|
leg->J11 = (vmc->param.leg_1 * sinf(leg->phi0 - leg->phi3) *
|
|
sinf(leg->phi1 - leg->phi2)) / sin_diff;
|
|
|
|
// J12: 后髋关节到摆动力矩的雅可比
|
|
leg->J12 = (vmc->param.leg_1 * cosf(leg->phi0 - leg->phi3) *
|
|
sinf(leg->phi1 - leg->phi2)) / (leg->L0 * sin_diff);
|
|
|
|
// J21: 前髋关节到支撑力的雅可比
|
|
leg->J21 = (vmc->param.leg_4 * sinf(leg->phi0 - leg->phi2) *
|
|
sinf(leg->phi3 - leg->phi4)) / sin_diff;
|
|
|
|
// J22: 前髋关节到摆动力矩的雅可比
|
|
leg->J22 = (vmc->param.leg_4 * cosf(leg->phi0 - leg->phi2) *
|
|
sinf(leg->phi3 - leg->phi4)) / (leg->L0 * sin_diff);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Private functions -------------------------------------------------------- */
|
|
|
|
/**
|
|
* @brief 验证VMC参数有效性
|
|
*/
|
|
static int8_t VMC_ValidateParams(const VMC_Param_t *param) {
|
|
if (param->hip_length <= 0 || param->leg_1 <= 0 || param->leg_2 <= 0 ||
|
|
param->leg_3 <= 0 || param->leg_4 <= 0 || param->wheel_radius <= 0) {
|
|
return -1;
|
|
}
|
|
|
|
// 检查腿部几何约束
|
|
if (param->leg_2 + param->leg_3 <= param->leg_1 + param->leg_4) {
|
|
return -1; // 不满足闭环几何约束
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief 更新基本运动学参数
|
|
*/
|
|
static void VMC_UpdateKinematics(VMC_t *vmc, float phi1, float phi4) {
|
|
VMC_Leg_t *leg = &vmc->leg;
|
|
|
|
// 计算关键点坐标
|
|
// 点B (后髋关节末端)
|
|
leg->XB = vmc->param.leg_1 * cosf(phi1);
|
|
leg->YB = vmc->param.leg_1 * sinf(phi1);
|
|
|
|
// 点D (前髋关节末端)
|
|
leg->XD = vmc->param.hip_length + vmc->param.leg_4 * cosf(phi4);
|
|
leg->YD = vmc->param.leg_4 * sinf(phi4);
|
|
|
|
// 计算BD连杆长度
|
|
float dx = leg->XD - leg->XB;
|
|
float dy = leg->YD - leg->YB;
|
|
leg->lBD = VMC_SAFE_SQRT(dx * dx + dy * dy);
|
|
}
|
|
|
|
/**
|
|
* @brief 求解闭环运动学方程
|
|
*
|
|
* 根据两个髋关节角度,求解中间关节角度
|
|
*/
|
|
static int8_t VMC_SolveClosedLoop(VMC_t *vmc) {
|
|
VMC_Leg_t *leg = &vmc->leg;
|
|
|
|
// 使用余弦定理求解phi2
|
|
leg->A0 = 2 * vmc->param.leg_2 * (leg->XD - leg->XB);
|
|
leg->B0 = 2 * vmc->param.leg_2 * (leg->YD - leg->YB);
|
|
leg->C0 = vmc->param.leg_2 * vmc->param.leg_2 +
|
|
leg->lBD * leg->lBD -
|
|
vmc->param.leg_3 * vmc->param.leg_3;
|
|
|
|
// 检查判别式
|
|
float discriminant = leg->A0 * leg->A0 + leg->B0 * leg->B0 - leg->C0 * leg->C0;
|
|
if (discriminant < 0) {
|
|
return -1; // 无解
|
|
}
|
|
|
|
float sqrt_discriminant = VMC_SAFE_SQRT(discriminant);
|
|
|
|
// 计算phi2 (选择合适的解)
|
|
leg->phi2 = 2 * atan2f(leg->B0 + sqrt_discriminant, leg->A0 + leg->C0);
|
|
|
|
// 计算phi3
|
|
leg->phi3 = atan2f(leg->YB - leg->YD + vmc->param.leg_2 * sinf(leg->phi2),
|
|
leg->XB - leg->XD + vmc->param.leg_2 * cosf(leg->phi2));
|
|
|
|
// 计算足端坐标点C
|
|
leg->XC = leg->XB + vmc->param.leg_2 * cosf(leg->phi2);
|
|
leg->YC = leg->YB + vmc->param.leg_2 * sinf(leg->phi2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief 计算数值微分
|
|
*/
|
|
static float VMC_ComputeNumericDerivative(float current, float previous, float dt) {
|
|
if (dt <= 0) {
|
|
return 0.0f;
|
|
}
|
|
|
|
return (current - previous) / dt;
|
|
}
|