在这里插入图片描述
基于反向动力学(Inverse Kinematics, IK)的关节角度闭环控制,是机器人控制领域从“简单自动化”迈向“智能协作”的关键一步。它不再仅仅是让关节机械地转动到指定角度,而是让机器人具备了根据末端执行器的任务目标,自主计算并协调各关节运动的能力。

🛠️ 主要特点
模型驱动的前瞻性控制
与传统的反应式控制(如 PID 在出现误差后才纠正)不同,反向动力学控制是前瞻性的。
核心逻辑: 它基于一个精确的机器人动力学模型。当给定一个期望的末端轨迹(例如机械臂末端要画一条直线),该模型能提前计算出各个关节需要产生的精确力矩,以克服惯性、重力和关节间的耦合效应。
优势: 这种“预判”能力使得机器人的运动极其平滑、精确且高效,避免了传统控制方法在处理复杂轨迹时容易产生的滞后和振荡。
力矩作为核心控制变量
这是反向动力学控制与传统位置/速度控制的根本区别。
直接力矩输出: 系统的核心输出是转矩指令,而非简单的 PWM 占空比或目标转速。这使得机器人能够直接控制与环境交互的力。例如,在进行抛光作业时,可以精确控制末端对工件保持恒定的压力,而不是一个固定的位置。
柔顺性实现: 通过力矩控制,机器人可以模拟出类似人体肌肉的“柔顺性”,在意外碰撞时顺应外力,保护自身和环境。
系统解耦与线性化
多关节机器人是一个高度耦合和非线性的系统,一个关节的运动会直接影响其他关节。
解耦作用: 反向动力学控制器作为一个高阶的“内环”,其核心作用之一就是实时计算并补偿这些复杂的耦合项和非线性项(如科里奥利力、离心力)。
简化设计: 经过补偿后,从外层的控制环(如位置环)来看,整个复杂的多关节系统表现得就像一个简单的、解耦的线性系统,从而极大地简化了高级运动规划算法的设计难度。

🏭 应用场景
该技术主要应用于对运动精度、平滑性和人机交互安全性有高要求的场景:
协作机器人(Cobot): 在工业生产线上,协作机器人需要与人类工人近距离协同工作。基于反向动力学的控制使其能够安全地响应外部的推拉力,实现拖拽示教或柔性装配。
精密加工与打磨: 在对曲面工件进行打磨、抛光或去毛刺时,工具末端必须始终垂直于表面并保持恒定的压力。力矩控制是实现这一工艺要求的关键。
仿生机器人与假肢: 无论是双足行走的仿人机器人,还是智能假肢,都需要模拟生物体自然、节能且柔顺的运动方式,反向动力学是实现这一目标的核心算法。
科研与高级原型开发: 作为研究先进控制理论、人机交互和复杂运动规划的理想平台。

⚠️ 注意事项
实施该系统是一项复杂的系统工程,需重点关注以下挑战:
计算资源的高门槛
算力瓶颈: 反向动力学的实时计算非常消耗算力。经典的 Arduino Uno/Nano 等 8 位单片机完全无法胜任。必须采用高性能的 ARM 架构开发板,如 Arduino Portenta H7、Teensy 4.1 或结合 Raspberry Pi/Jetson Nano 等上位机协同工作。
算法优化: 必须对动力学方程进行极致的代码优化,利用查表法、近似计算等手段减少浮点运算量,确保控制周期的稳定性(通常要求 1ms 以内)。
精确的动力学建模
模型准确性: 控制效果高度依赖于动力学模型的准确性。必须精确测量或辨识每个连杆的质量、质心、转动惯量等参数。模型失配会导致补偿不足或过度,引发系统振荡。
摩擦力补偿: 实际机械结构中的摩擦力(库仑摩擦、粘滞摩擦)是非线性的,且难以精确建模,这是影响低速控制精度的主要因素之一。
底层硬件的协同
传感器精度: 高精度的关节角度反馈(如高分辨率磁编码器)是闭环控制的基础。传感器的噪声和延迟会直接影响力矩控制的精度。
驱动器性能: 底层的 BLDC 驱动器必须具备快速的电流环响应能力,以准确执行上层控制器下发的力矩指令。通信延迟必须极低且确定。
安全与保护机制
物理限位: 必须设置硬件限位开关,防止算法计算错误导致关节超程损坏机械结构。
力矩限制: 在软件中必须设置力矩输出的上下限,防止电机堵转或过载烧毁。

在这里插入图片描述
1、单关节位置闭环控制(使用编码器反馈)

#include <SimpleFOC.h>

// 电机与编码器配置
BLDCMotor motor(7);                // 电机PWM引脚
BLDCDriver3PWM driver(3, 5, 6, 11); // 驱动器PWM引脚
Encoder encoder(2, 4);             // 编码器A/B相引脚

// PID参数
float target_angle = 90.0;         // 目标角度(度)
float Kp = 0.5, Ki = 0.1, Kd = 0.01;
PIDController pid({Kp, Ki, Kd});

void setup() {
  Serial.begin(9600);
  
  // 初始化编码器(4倍频)
  encoder.init();
  encoder.enableInterrupts(doA, doB);
  
  // 链接电机与驱动器
  motor.linkDriver(&driver);
  motor.linkSensor(&encoder);
  motor.controller = MotionControlType::torque; // 扭矩控制模式
  
  // 初始化FOC
  motor.init();
  motor.initFOC();
}

void loop() {
  // 反向动力学:将目标角度转换为扭矩指令
  float current_angle = encoder.getAngle() * 180 / PI; // 编码器角度(度)
  float error = target_angle - current_angle;
  float torque = pid(error); // PID输出扭矩
  
  // 设置电机扭矩
  motor.move(torque);
  
  // 调试输出
  Serial.print("Target: "); Serial.print(target_angle);
  Serial.print(" Current: "); Serial.print(current_angle);
  Serial.print(" Torque: "); Serial.println(torque);
  delay(10);
}

// 编码器中断回调
void doA() { encoder.handleA(); }
void doB() { encoder.handleB(); }

2、双关节协同控制(机械臂模拟)

#include <SimpleFOC.h>

// 关节1配置
BLDCMotor motor1(7);
BLDCDriver3PWM driver1(3, 5, 6, 11);
Encoder encoder1(2, 4);
PIDController pid1({0.5, 0.1, 0.01});

// 关节2配置
BLDCMotor motor2(8);
BLDCDriver3PWM driver2(9, 10, 12, 13);
Encoder encoder2(A0, A1);
PIDController pid2({0.4, 0.08, 0.01});

// 目标点(笛卡尔坐标转关节角度)
float target_x = 10.0, target_y = 5.0; // 单位:cm
float L1 = 15.0, L2 = 10.0;           // 连杆长度

void setup() {
  Serial.begin(9600);
  
  // 初始化关节1
  encoder1.init(); encoder1.enableInterrupts(doA1, doB1);
  motor1.linkDriver(&driver1); motor1.linkSensor(&encoder1);
  motor1.init(); motor1.initFOC();
  
  // 初始化关节2
  encoder2.init(); encoder2.enableInterrupts(doA2, doB2);
  motor2.linkDriver(&driver2); motor2.linkSensor(&encoder2);
  motor2.init(); motor2.initFOC();
}

void loop() {
  // 反向运动学:笛卡尔坐标转关节角度
  float theta2 = acos((sq(target_x) + sq(target_y) - sq(L1) - sq(L2)) / (2 * L1 * L2));
  float theta1 = atan2(target_y, target_x) - atan2(L2 * sin(theta2), L1 + L2 * cos(theta2));
  
  // 转换为度并控制关节1
  float angle1 = theta1 * 180 / PI;
  float error1 = angle1 - encoder1.getAngle() * 180 / PI;
  motor1.move(pid1(error1));
  
  // 控制关节2
  float angle2 = theta2 * 180 / PI;
  float error2 = angle2 - encoder2.getAngle() * 180 / PI;
  motor2.move(pid2(error2));
  
  delay(20);
}

// 中断回调
void doA1() { encoder1.handleA(); } void doB1() { encoder1.handleB(); }
void doA2() { encoder2.handleA(); } void doB2() { encoder2.handleB(); }

3、带重力补偿的关节控制(模拟负载)

#include <SimpleFOC.h>

// 电机与编码器配置
BLDCMotor motor(7);
BLDCDriver3PWM driver(3, 5, 6, 11);
Encoder encoder(2, 4);

// 参数
float target_angle = 45.0; // 目标角度
float Kp = 0.6, Ki = 0.05, Kd = 0.02;
float gravity_comp = 0.3;  // 重力补偿系数(需根据实际负载调整)

void setup() {
  Serial.begin(9600);
  encoder.init(); encoder.enableInterrupts(doA, doB);
  motor.linkDriver(&driver); motor.linkSensor(&encoder);
  motor.init(); motor.initFOC();
}

void loop() {
  // 当前角度(弧度)
  float current_angle = encoder.getAngle();
  
  // 反向动力学:PID + 重力补偿
  float error = target_angle * PI / 180 - current_angle;
  float pid_output = Kp * error + Ki * integral(error) + Kd * derivative(error);
  float torque = pid_output + gravity_comp * sin(current_angle); // 补偿重力
  
  motor.move(torque);
  
  Serial.print("Angle: "); Serial.print(current_angle * 180 / PI);
  Serial.print(" Torque: "); Serial.println(torque);
  delay(10);
}

// 简化的积分与微分(实际建议用库函数)
float integral(float error) {
  static float sum = 0;
  sum += error;
  return sum * 0.01; // 假设循环周期10ms
}

float derivative(float error) {
  static float last_error = 0;
  float d = (error - last_error) / 0.01;
  last_error = error;
  return d;
}

void doA() { encoder.handleA(); } void doB() { encoder.handleB(); }

要点解读
反向动力学与正向动力学的区别
正向动力学:已知关节角度/扭矩 → 计算末端位置(如机械臂仿真)。
反向动力学:已知目标位置 → 计算所需关节角度/扭矩(如实际控制)。
案例2通过几何关系将笛卡尔坐标转换为关节角度,体现反向动力学核心思想。
传感器反馈的精度要求
编码器分辨率直接影响控制精度。例如:
6.5寸轮毂电机若需1°精度,需≥360CPR编码器(案例1)。
机械臂关节可能需要更高分辨率(如案例2使用绝对编码器)。
重力补偿的必要性
水平关节无需补偿,但垂直关节(如案例3)需额外扭矩平衡负载重力。
补偿系数需通过实验标定(如悬挂砝码测试)。
多关节协同的复杂性
双关节系统(案例2)需解耦控制,避免关节间干扰。
高级方案可引入雅可比矩阵或计算力矩法(CMC)处理非线性。
实际工程中的挑战
摩擦补偿:机械关节的库仑摩擦需额外建模(如添加前馈项)。
安全限制:需软件限位(如if (angle > MAX_ANGLE) torque = 0;)。
通信延迟:远程控制时需预测补偿(如卡尔曼滤波)。

在这里插入图片描述
4、平面2自由度BLDC关节机械臂(物料抓取定位)
场景定位
工业生产线上的物料精准抓取与定位,机械臂通过2个BLDC驱动的关节,实现平面内末端位置的高精度控制,比如从传送带抓取零件并放置到指定工位。核心需求是根据目标坐标,快速求解关节角度,并通过闭环控制修正角度误差,确保末端定位误差≤0.1cm。
核心控制逻辑
IK求解:平面2连杆(长度l1=20cm,l2=15cm),给定末端坐标(x,y),通过余弦定理+反三角函数求解关节角度θ1(底座关节)、θ2(肘部关节);
闭环控制:Arduino向VESC驱动板发送目标角度指令(CAN协议),VESC读取编码器反馈的实际角度,执行内部PID算法,修正角度误差;
反馈补偿:Arduino读取VESC返回的关节实际角度,计算角度误差,叠加辅助PID补偿(增强精度,避免驱动板内置PID的误差),确保末端定位精度。

// 平面2自由度BLDC关节机械臂控制代码(物料抓取)
#include <CAN.h>            // Arduino CAN通信库(需适配VESC的CAN协议)
#include <Arduino.h>

// ========== 机械臂结构参数(可根据实际情况修改) ==========
const float l1 = 20.0; // 关节1到关节2的连杆长度(cm)
const float l2 = 15.0; // 关节2到末端的连杆长度(cm)
const float PI = 3.1415926;

// ========== CAN通信配置(VESC默认CAN参数) ==========
#define CAN_BAUD_RATE 500000
#define JOINT1_ID 0x01  // 关节1的CAN节点ID(VESC配置的ID)
#define JOINT2_ID 0x02  // 关节2的CAN节点ID

// ========== 全局变量(存储目标与实际角度) ==========
float targetX = 0, targetY = 0;          // 末端目标坐标(cm)
float targetTheta1 = 0, targetTheta2 = 0; // IK求解的目标关节角度(弧度)
float actualTheta1 = 0, actualTheta2 = 0; // 反馈的实际关节角度(弧度)
float pidError1 = 0, pidError2 = 0;       // 角度误差
float lastError1 = 0, lastError2 = 0;     // 上一时刻误差(用于PID微分)

// ========== PID参数(辅助补偿,提升闭环精度) ==========
const float Kp1 = 0.8, Ki1 = 0.01, Kd1 = 0.1; // 关节1 PID参数
const float Kp2 = 0.7, Ki2 = 0.008, Kd2 = 0.08;// 关节2 PID参数

// ========== 硬件引脚定义 ==========
#define ULTRA_TRIG 10  // 超声波Trig引脚
#define ULTRA_ECHO 11  // 超声波Echo引脚
#define GRIPPER_PIN 9  // 舵机控制引脚(末端抓取)

// ========== 函数声明 ==========
void setupCAN();
void setupHardware();
void readTargetCoordinate(); // 读取超声波检测的目标坐标
void calculateIK();          // 反向动力学求解关节角度
void sendJointTarget();      // 向VESC发送关节目标角度(CAN协议)
void readJointActual();      // 读取关节实际角度(CAN反馈)
void pidCompensation();      // 辅助PID补偿
void controlGripper(bool open);// 控制末端抓取器

void setup() {
  Serial.begin(115200);
  Serial.println("2自由度BLDC机械臂初始化...");

  setupCAN();       // 初始化CAN通信(连接VESC驱动板)
  setupHardware(); // 初始化硬件引脚(超声波、舵机)

  // 初始化VESC CAN(根据VESC CAN协议,配置节点ID与波特率)
  // 注:VESC默认支持CANopen,需提前配置电机ID和闭环参数
  Serial.println("初始化完成,开始检测目标坐标...");
}

void loop() {
  static unsigned long lastUpdate = 0;
  if (millis() - lastUpdate < 50) return; // 20Hz更新频率,避免通信过载
  lastUpdate = millis();

  1. 检测目标坐标(物料位置)
  readTargetCoordinate();
  if (targetX == 0 && targetY == 0) {
    Serial.println("未检测到目标物料");
    return;
  }

  2. 反向动力学求解目标关节角度
  calculateIK();

  3. 向VESC发送关节目标角度(闭环控制指令)
  sendJointTarget();

  4. 读取关节实际反馈角度
  readJointActual();

  5. 辅助PID补偿(叠加到VESC目标角度,修正误差)
  pidCompensation();

  6. 末端抓取控制(到达目标后闭合)
  if (sqrt(pow(targetX, 2) + pow(targetY, 2)) < 0.5) { // 末端到位(误差<0.5cm)
    controlGripper(true); // 闭合抓取
    Serial.println("末端到位,执行抓取");
    delay(2000);
    controlGripper(false);// 张开释放
  }
}

// ========== 初始化CAN通信(适配VESC协议) ==========
void setupCAN() {
  CAN.begin(CAN_BAUD_RATE);
  Serial.println("CAN通信初始化完成,波特率500k");
}

// ========== 初始化硬件引脚 ==========
void setupHardware() {
  pinMode(ULTRA_TRIG, OUTPUT);
  pinMode(ULTRA_ECHO, INPUT);
  pinMode(GRIPPER_PIN, OUTPUT);
  // 舵机初始化
  Serial.println("硬件引脚初始化完成");
}

// ========== 读取超声波检测的目标坐标(x:水平距离,y:垂直高度) ==========
void readTargetCoordinate() {
  digitalWrite(ULTRA_TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(ULTRA_TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(ULTRA_TRIG, LOW);

  long duration = pulseIn(ULTRA_ECHO, HIGH);
  // 距离转换(cm),假设物料在机械臂前方,y=10cm(固定高度)
  float distance = duration * 0.0343 / 2;
  targetX = distance; // 水平距离作为x
  targetY = 10.0;     // 固定高度作为y(可扩展为多超声波检测)

  // 坐标有效性校验
  if (targetX < 0 || targetX > (l1 + l2) * 0.9) {
    targetX = 0; // 超出工作空间,清零等待重新检测
  }
}

// ========== 反向动力学求解关节角度(平面2连杆模型) ==========
void calculateIK() {
  float dx = targetX;
  float dy = targetY;
  float r = sqrt(dx * dx + dy * dy); // 原点到末端的距离

  // 校验:目标点是否在工作空间内(l1+l2为最大距离,l1-l2为最小距离)
  if (r > l1 + l2 || r < fabs(l1 - l2)) {
    Serial.println("目标点超出工作空间!");
    targetTheta1 = 0;
    targetTheta2 = 0;
    return;
  }

  // 求解θ2(肘部关节角度,用余弦定理)
  float cosTheta2 = (dx*dx + dy*dy - l1*l1 - l2*l2) / (2 * l1 * l2);
  cosTheta2 = constrain(cosTheta2, -1.0, 1.0); // 防止浮点误差导致超出acos范围
  targetTheta2 = acos(cosTheta2);

  // 求解θ1(底座关节角度,用反正切)
  float beta = atan2(dy, dx);
  float alpha = acos((l1*l1 + r*r - l2*l2) / (2 * l1 * r));
  targetTheta1 = beta - alpha;

  // 转换弧度为度(VESC角度指令通常用度,需按需转换)
  targetTheta1 = targetTheta1 * 180 / PI;
  targetTheta2 = targetTheta2 * 180 / PI;

  Serial.print("目标角度:θ1="); Serial.print(targetTheta1);
  Serial.print("° θ2="); Serial.println(targetTheta2);
}

// ========== 向VESC发送关节目标角度(CANopen协议,简化版) ==========
void sendJointTarget() {
  // CANopen协议中,目标角度通常以「目标位置」数据帧发送,格式为:
  // ID:节点ID + 0x600(PDO发送ID),数据:6字节(32位浮点数,单位:度)
  // 此处简化为发送3字节整数(示例,实际需按VESC CANopen手册配置)

  // 关节1目标角度(角度值转换为0-360,避免溢出)
  int theta1Target = (int)constrain(targetTheta1, 0, 360);
  // 关节2目标角度
  int theta2Target = (int)constrain(targetTheta2, 0, 360);

  // 发送关节1目标(CAN数据帧:ID=JOINT1_ID,数据=theta1Target的字节)
  byte data1[4] = {(byte)(theta1Target >> 8), (byte)theta1Target, 0, 0};
  CAN.send(JOINT1_ID, data1, 4);

  // 发送关节2目标
  byte data2[4] = {(byte)(theta2Target >> 8), (byte)theta2Target, 0, 0};
  CAN.send(JOINT2_ID, data2, 4);

  Serial.println("已发送关节目标角度指令");
}

// ========== 读取关节实际角度(CAN反馈) ==========
void readJointActual() {
  // VESC通过CAN反馈实际位置,格式为:ID=节点ID+0x580(PDO接收ID),数据:6字节浮点数
  // 此处简化为接收数据并解析(示例,实际需按VESC反馈格式解析)
  if (CAN.available()) {
    byte id = CAN.getReceivedId();
    byte len = CAN.getReceivedLength();
    byte* data = CAN.getReceivedData();

    if (id == JOINT1_ID) {
      // 解析关节1实际角度(假设前2字节为整数角度)
      actualTheta1 = (data[0] << 8) | data[1];
    } else if (id == JOINT2_ID) {
      actualTheta2 = (data[0] << 8) | data[1];
    }

    Serial.print("实际角度:θ1="); Serial.print(actualTheta1);
    Serial.print("° θ2="); Serial.println(actualTheta2);
  }
}

// ========== 辅助PID补偿(叠加角度修正) ==========
void pidCompensation() {
  // 计算角度误差
  pidError1 = targetTheta1 - actualTheta1;
  pidError2 = targetTheta2 - actualTheta2;

  // 防止误差过大导致超调
  pidError1 = constrain(pidError1, -10, 10);
  pidError2 = constrain(pidError2, -10, 10);

  // PID计算(P:比例,I:积分,D:微分)
  // 注:此处为简化PID,实际可优化为增量式PID,且积分项需限幅
  float delta1 = Kp1 * pidError1 + Kd1 * (pidError1 - lastError1);
  float delta2 = Kp2 * pidError2 + Kd2 * (pidError2 - lastError2);

  // 将补偿量叠加到目标角度(发送给VESC)
  // 实际应用中,可将delta转换为PWM信号或CAN指令的微调量
  targetTheta1 += delta1 * 0.1; // 小幅度补偿,避免震荡
  targetTheta2 += delta2 * 0.1;

  lastError1 = pidError1;
  lastError2 = pidError2;
}

// ========== 控制末端抓取器(舵机) ==========
void controlGripper(bool open) {
  int angle = open ? 0 : 90; // 张开0°,闭合90°
  analogWrite(GRIPPER_PIN, angle); // 简化PWM控制(实际用Servo库更准确)
  Serial.print("抓取器状态:"); Serial.println(open ? "张开" : "闭合");
}

5、四足机器人关节腿(BLDC闭环控制-步态规划)
场景定位
小型四足机器人的关节腿闭环控制,通过反向动力学规划腿部关节角度,实现行走步态,核心需求是腿部关节根据步态时序动态调整目标角度,并通过BLDC闭环控制保证关节角度误差≤0.5°,避免行走晃动或关节失步。
核心控制逻辑
步态规划与IK求解:四足机器人采用三足步态,每个步态周期内,腿部需完成摆腿→落地→支撑→收腿四个动作,通过目标足端坐标,用平面单腿IK求解髋关节角度θ1、膝关节角度θ2;
多关节同步闭环:Arduino通过CAN总线向4个关节的VESC发送目标角度,VESC读取编码器反馈,执行PID控制,同时Arduino接收所有关节的反馈数据,实时监控步态状态;
步态时序控制:通过状态机实现步态切换,当足端压力传感器检测到落地时,切换为支撑相,调整关节角度,确保行走稳定。

// 四足机器人关节腿步态控制代码(BLDC闭环)
#include <CAN.h>
#include <Arduino.h>

// ========== 单腿结构参数(四足每条腿相同) ==========
const float legL1 = 12.0; // 髋关节到膝关节长度(cm)
const float legL2 = 10.0; // 膝关节到足端长度(cm)
const float PI = 3.1415926;

// ========== CAN通信配置 ==========
#define CAN_BAUD_RATE 500000
#define NUM_JOINTS 4       // 4个关节(2腿×2关节/腿)
// 关节ID映射:腿1髋关节=0x01,腿1膝关节=0x02,腿2髋关节=0x03,腿2膝关节=0x04
const byte jointIDs[NUM_JOINTS] = {0x01, 0x02, 0x03, 0x04};

// ========== 步态参数 ==========
const int stepFrequency = 1; // 步态频率(步/秒)
const float stepLength = 15.0; // 步长(cm)
const float stepHeight = 8.0;  // 摆腿最大高度(cm)

// ========== 全局变量(关节目标与实际角度) ==========
float targetTheta[NUM_JOINTS] = {0};    // 4个关节的目标角度(度)
float actualTheta[NUM_JOINTS] = {0};    // 4个关节的实际反馈角度(度)
int gaitState = 0; // 步态状态:0=摆腿相,1=支撑相
unsigned long gaitTimer = 0;
const long stepPeriod = 1000 / stepFrequency; // 步态周期(ms)

// ========== 函数声明 ==========
void setupCAN();
void setupGait();
void gaitPlanning(); // 步态规划(生成目标足端坐标)
void singleLegIK(float x, float y, float& theta1, float& theta2); // 单腿IK求解
void sendAllJointTargets(); // 向所有关节发送目标角度
void readAllJointActual(); // 读取所有关节实际角度
void gaitStateMachine(); // 步态状态机切换

void setup() {
  Serial.begin(115200);
  Serial.println("四足关节腿初始化...");

  setupCAN();
  setupGait();
  Serial.println("初始化完成,启动步态规划");
}

void loop() {
  unsigned long now = millis();

  1. 步态时序控制(按步态周期更新目标)
  if (now - gaitTimer >= stepPeriod) {
    gaitTimer = now;
    gaitStateMachine(); // 切换步态状态
    gaitPlanning();     // 生成新的目标足端坐标
  }

  2. 反向动力学求解关节角度
  // 假设步态规划输出:腿1足端(x1,y1),腿2足端(x2,y2)
  float x1 = 7.5, y1 = 15.0; // 示例:腿1支撑相足端坐标
  float x2 = 12.5, y2 = 15.0; // 腿2摆腿相足端坐标

  singleLegIK(x1, y1, targetTheta[0], targetTheta[1]); // 腿1关节1、2
  singleLegIK(x2, y2, targetTheta[2], targetTheta[3]); // 腿2关节1、2

  3. 发送目标角度到VESC
  sendAllJointTargets();

  4. 读取关节反馈
  readAllJointActual();

  5. 打印调试信息(步态周期内输出一次)
  if (now - gaitTimer >= stepPeriod / 2) {
    Serial.print("步态状态:"); Serial.println(gaitState == 0 ? "摆腿相" : "支撑相");
  }
}

// ========== 初始化CAN(级联4个VESC驱动板) ==========
void setupCAN() {
  CAN.begin(CAN_BAUD_RATE);
  Serial.println("CAN初始化完成,支持4个关节级联");
}

// ========== 步态初始化(设置初始关节角度) ==========
void setupGait() {
  // 初始姿态:所有关节角度为0(直立)
  for (int i = 0; i < NUM_JOINTS; i++) {
    targetTheta[i] = 0;
  }
  gaitState = 0;
  gaitTimer = millis();
}

// ========== 步态状态机(切换摆腿相/支撑相) ==========
void gaitStateMachine() {
  gaitState = (gaitState + 1) % 2; // 0和1交替切换
  // 可根据足端压力传感器进一步确认状态(此处简化为时序切换)
  // 若接入压力传感器,可在落地后检测到压力,触发状态切换
  Serial.print("步态切换至:"); Serial.println(gaitState == 0 ? "摆腿相" : "支撑相");
}

// ========== 步态规划(生成目标足端坐标) ==========
void gaitPlanning() {
  // 简化步态:摆腿相时,足端前伸+抬腿;支撑相时,足端后缩+落地
  static float footPos1 = 0; // 腿1足端x坐标(周期性变化)
  static float footPos2 = 0; // 腿2足端x坐标

  // 按步态状态生成目标坐标
  if (gaitState == 0) { // 摆腿相:前伸+抬腿
    footPos1 += stepLength / (stepPeriod / 50); // 50ms更新一次,平滑过渡
    footPos2 = footPos1 + stepLength;
  } else { // 支撑相:后缩+落地
    footPos1 -= stepLength / (stepPeriod / 50);
    footPos2 = footPos1;
  }

  // 计算y坐标(摆腿相时抬腿,支撑相时落地)
  float y1 = gaitState == 0 ? stepHeight + 10.0 : 10.0; // 落地高度
  float y2 = gaitState == 0 ? 10.0 : stepHeight + 10.0;

  // 此处仅作示例,实际需同步4条腿的坐标(四足步态为三足着地)
  // 简化为两腿交替摆腿,实际可扩展为4腿步态规划
}

// ========== 单腿反向动力学(平面2连杆,足端(x,y)求关节角度) ==========
void singleLegIK(float x, float y, float& theta1, float& theta2) {
  float r = sqrt(x*x + y*y);

  // 工作空间校验
  if (r > legL1 + legL2 || r < fabs(legL1 - legL2)) {
    theta1 = 0;
    theta2 = 0;
    return;
  }

  // 求解θ2(膝关节角度)
  float cosTheta2 = (x*x + y*y - legL1*legL1 - legL2*legL2) / (2 * legL1 * legL2);
  cosTheta2 = constrain(cosTheta2, -1.0, 1.0);
  theta2 = acos(cosTheta2) * 180 / PI;

  // 求解θ1(髋关节角度)
  float beta = atan2(y, x);
  float alpha = acos((legL1*legL1 + r*r - legL2*legL2) / (2 * legL1 * r));
  theta1 = (beta - alpha) * 180 / PI;

  // 限制关节角度范围(髋关节:-45~45°,膝关节:0~135°)
  theta1 = constrain(theta1, -45.0, 45.0);
  theta2 = constrain(theta2, 0.0, 135.0);
}

// ========== 向所有关节发送目标角度(CAN级联发送) ==========
void sendAllJointTargets() {
  for (int i = 0; i < NUM_JOINTS; i++) {
    // 转换目标角度为整数(0-360)
    int thetaTarget = (int)constrain(targetTheta[i], 0, 360);
    byte data[4] = {(byte)(thetaTarget >> 8), (byte)thetaTarget, 0, 0};
    CAN.send(jointIDs[i], data, 4);
  }
  Serial.println("已发送所有关节目标角度");
}

// ========== 读取所有关节实际角度(CAN级联接收) ==========
void readAllJointActual() {
  while (CAN.available()) {
    byte id = CAN.getReceivedId();
    byte len = CAN.getReceivedLength();
    byte* data = CAN.getReceivedData();

    // 查找对应关节ID
    for (int i = 0; i < NUM_JOINTS; i++) {
      if (id == jointIDs[i]) {
        actualTheta[i] = (data[0] << 8) | data[1];
        Serial.print("关节"); Serial.print(i+1);
        Serial.print("实际角度:"); Serial.println(actualTheta[i]);
        break;
      }
    }
  }
}

6、舵机+BLDC混合关节(舵机粗定位+BLDC闭环精调)
场景定位
需要大范围转动+高精度角度锁定的场景,比如云台跟踪、协作机械臂的关节,采用舵机实现大角度粗定位,BLDC驱动核心负载并实现高精度闭环控制,核心是舵机快速定位到目标范围,BLDC通过反向动力学计算精调角度,实现误差≤0.1°的高精度闭环。
核心控制逻辑
粗定位+精调分工:舵机负责大范围快速转动,使目标落入工作范围(误差≤10°),BLDC通过反向动力学计算精调角度,修正剩余误差;
目标角度计算:红外传感器检测目标方位,结合传感器安装位置,计算目标相对于关节的角度;
混合闭环控制:舵机开环控制(快速响应),BLDC位置闭环控制(高精度),Arduino实时读取编码器反馈,计算误差,输出BLDC控制指令。

// 舵机+BLDC混合关节控制代码(高精度角度闭环)
#include <Servo.h>
#include <CAN.h>
#include <Arduino.h>

// ========== 系统参数 ==========
const int SERVO_PIN = 9;       // 舵机PWM引脚
#define CAN_BAUD_RATE 500000
#define BLDC_JOINT_ID 0x01     // BLDC关节的CAN ID
const float TARGET_ERROR = 0.1; // 目标跟踪误差≤0.1°

// ========== 全局变量 ==========
Servo servoMotor;              // 舵机对象
float targetAngle = 0;          // 目标总角度(舵机+BLDC共同指向)
float servoAngle = 0;           // 舵机粗定位角度(0-180°)
float bldcTargetAngle = 0;      // BLDC精调目标角度(剩余误差)
float bldcActualAngle = 0;      // BLDC实际反馈角度
float angleError = 0;           // 角度误差

// ========== 函数声明 ==========
void setupHardware();
void detectTarget();           // 检测目标并计算目标角度
void servoCoarsePosition();     // 舵机粗定位
void calculateBLDCTarget();     // 计算BLDC精调目标(反向动力学修正)
void bldcClosedLoop();          // BLDC闭环控制
void sendBLDCTarget();          // 发送BLDC目标角度
void readBLDCActual();          // 读取BLDC实际角度

void setup() {
  Serial.begin(115200);
  Serial.println("舵机+BLDC混合关节初始化...");

  setupHardware();
  servoMotor.attach(SERVO_PIN);
  servoMotor.write(90); // 初始位置90°

  Serial.println("初始化完成,开始检测目标");
}

void loop() {
  static unsigned long lastUpdate = 0;
  if (millis() - lastUpdate < 30) return; // 30ms更新一次,平衡响应与精度
  lastUpdate = millis();

  1. 检测目标,计算目标总角度
  detectTarget();
  if (targetAngle == 0) {
    Serial.println("未检测到目标");
    return;
  }

  2. 舵机粗定位(快速转到目标范围,误差≤10°)
  servoCoarsePosition();

  3. 计算BLDC精调目标(反向动力学修正剩余误差)
  calculateBLDCTarget();

  4. BLDC闭环控制(高精度跟踪)
  bldcClosedLoop();

  5. 打印调试信息
  Serial.print("目标角度:"); Serial.print(targetAngle);
  Serial.print("° 舵机角度:"); Serial.print(servoAngle);
  Serial.print("° BLDC角度:"); Serial.print(bldcActualAngle);
  Serial.print("° 误差:"); Serial.println(angleError);

  // 达标判断:误差≤0.1°时停止调整
  if (abs(angleError) <= TARGET_ERROR) {
    Serial.println("角度闭环达标,跟踪稳定");
    delay(100);
  }
}

// ========== 初始化硬件(红外传感器、CAN) ==========
void setupHardware() {
  pinMode(10, INPUT); // 红外传感器引脚
  CAN.begin(CAN_BAUD_RATE);
  Serial.println("硬件初始化完成");
}

// ========== 检测目标,计算目标角度(红外传感器示例) ==========
void detectTarget() {
  int sensorState = digitalRead(10);
  if (sensorState == HIGH) {
    // 假设红外传感器安装位置固定,目标在正前方时角度为0°,左偏1°/像素,简化计算
    // 实际可扩展为多个红外传感器,计算目标方位角
    targetAngle = 45.0; // 示例:目标在左前方45°
  } else {
    targetAngle = 0;
  }
}

// ========== 舵机粗定位(快速转到目标范围附近) ==========
void servoCoarsePosition() {
  // 舵机粗定位逻辑:将目标角度映射到0-180°(舵机范围)
  servoAngle = constrain(targetAngle, 0, 180);
  // 舵机转动(开环控制,快速响应)
  servoMotor.write((int)servoAngle);
  delay(100); // 等待舵机到位(SG90转动时间约0.2s,简化处理)
}

// ========== 计算BLDC精调目标(反向动力学修正剩余误差) ==========
void calculateBLDCTarget() {
  // 粗定位后,剩余误差为:目标角度 - 舵机角度
  float remainingError = targetAngle - servoAngle;

  // 反向动力学核心:通过剩余误差计算BLDC需要修正的角度
  // 假设BLDC驱动的关节负责微调,修正范围0-10°(可根据实际结构调整)
  // 简化为:剩余误差全部由BLDC修正,且修正角度不超过10°
  bldcTargetAngle = constrain(remainingError, -10, 10);

  // 若舵机已精准到位,BLDC目标为0
  if (abs(remainingError) <= 0.5) {
    bldcTargetAngle = 0;
  }

  Serial.print("剩余误差:"); Serial.print(remainingError);
  Serial.print("° BLDC精调目标:"); Serial.println(bldcTargetAngle);
}

// ========== BLDC闭环控制(PID精调) ==========
void bldcClosedLoop() {
  1. 发送BLDC目标角度
  sendBLDCTarget();

  2. 读取BLDC实际角度
  readBLDCActual();

  3. 计算角度误差
  angleError = bldcTargetAngle - bldcActualAngle;

  4. 增量式PID控制(高精度修正)
  static float integral = 0;
  static float lastError = 0;
  const float Kp = 1.2, Ki = 0.05, Kd = 0.8; // PID参数(调试后确定)

  float delta = Kp * angleError + Ki * integral + Kd * (angleError - lastError);
  delta = constrain(delta, -5, 5); // 限制输出幅度,避免震荡

  // 叠加修正到目标角度(实际通过CAN发送微调指令,此处简化为直接更新目标)
  bldcTargetAngle += delta;
  bldcTargetAngle = constrain(bldcTargetAngle, -10, 10);

  // 积分限幅(避免积分饱和)
  integral += angleError;
  integral = constrain(integral, -10, 10);

  lastError = angleError;

  5. 发送修正后的目标角度
  sendBLDCTarget();
}

// ========== 向BLDC发送目标角度(CAN协议) ==========
void sendBLDCTarget() {
  int thetaTarget = (int)constrain(bldcTargetAngle, 0, 360);
  byte data[4] = {(byte)(thetaTarget >> 8), (byte)thetaTarget, 0, 0};
  CAN.send(BLDC_JOINT_ID, data, 4);
}

// ========== 读取BLDC实际角度(CAN反馈) ==========
void readBLDCActual() {
  if (CAN.available()) {
    byte id = CAN.getReceivedId();
    byte len = CAN.getReceivedLength();
    byte* data = CAN.getReceivedData();

    if (id == BLDC_JOINT_ID && len >= 2) {
      bldcActualAngle = (data[0] << 8) | data[1];
      // 转换为有符号角度(-180~180)
      if (bldcActualAngle > 180) bldcActualAngle -= 360;
    }
  }
}

要点解读

  1. 反向动力学求解的“场景适配性”:不同结构用不同算法,精度与效率平衡
    反向动力学是关节控制的“大脑”,需根据关节结构选择算法,避免盲目套用公式,否则会导致计算误差或超时:
    简单平面结构(案例4、5):2自由度连杆优先用解析法(余弦定理+反三角函数),计算速度快(微秒级),适合Arduino实时求解,精度依赖结构参数标定(如连杆长度需实际测量,而非理论值),案例中通过constrain函数避免浮点误差导致的acos参数超范围;
    复杂空间结构:若为6自由度空间臂、球面关节,解析法公式复杂,需用数值迭代法(如雅可比迭代、Newton-Raphson),虽然计算耗时略长,但Arduino Mega等大内存芯片可胜任,需设置迭代次数和误差阈值,避免无限循环;
    精度前置校验:求解前必须做工作空间校验,案例1中通过判断目标点是否在连杆可达范围内,避免无效解,防止关节角度输出为NaN,导致电机失控,这是工业场景的必备安全逻辑。
  2. BLDC关节的“闭环控制层设计”:驱动板与Arduino分工,避免资源冲突
    BLDC的闭环控制需明确Arduino与驱动板的分工,避免两者重复做闭环导致震荡,同时保证控制精度:
    驱动板的核心角色:VESC等专业驱动板内置电流环、速度环,支持位置环,Arduino无需实现底层PID,只需发送目标角度指令,驱动板自动完成“目标角度→电机转动→编码器反馈→PID修正”的闭环,案例1、2中通过CAN协议发送目标角度,依赖驱动板的位置闭环,简化Arduino代码;
    Arduino的辅助闭环:案例4中的辅助PID补偿,是叠加在驱动板目标角度上的“上层修正”,用于补偿驱动板内置PID的稳态误差,两者是“主从关系”,而非重复闭环,避免输出指令冲突,确保最终角度误差控制在0.1°以内;
    反馈精度的核心保障:编码器分辨率直接决定闭环精度,案例5中选用14位AS5048编码器,分辨率0.022°,配合VESC的高频率闭环响应,才能实现0.5°的关节误差控制,普通3线霍尔传感器分辨率低,无法满足高精度需求。
  3. 多关节/多任务的“同步与通信设计”:CAN总线级联+时序调度,避免数据阻塞
    多关节系统的核心痛点是通信冲突和任务不同步,需从通信方式和时序两方面优化:
    通信协议选择:多关节优先用CAN总线,而非串口,CAN支持多节点级联,每个关节对应独立ID,发送/接收互不干扰,案例5中4个关节通过1个CAN口级联,通信延迟低,且可通过ID快速识别数据来源,串口为点对点通信,多关节需切换引脚,效率极低;
    时序调度逻辑:代码中通过millis()计算时间间隔,设置合理的更新频率,比如电机控制10-20Hz,传感器采集1-2Hz,避免高频发送CAN指令导致总线阻塞,同时防止Arduino CPU过载,多关节同步发送时,按ID顺序依次发送,而非同时发送,CAN总线仲裁机制可自动处理冲突;
    数据格式标准化:所有关节的目标角度、反馈角度需采用统一的数据格式,比如32位浮点数或16位整数,避免字节解析错误,案例中简化为2字节整数,实际需按VESC CANopen协议配置标准PDO格式,确保数据兼容性。
  4. 闭环系统的“误差补偿机制”:从参数标定到PID优化,解决稳态误差
    反向动力学与闭环控制的精度瓶颈是误差,需从参数、算法、反馈三方面建立补偿机制:
    结构参数标定:连杆长度、关节零点是基础参数,标定误差会直接传递到末端,比如案例4中连杆长度理论20cm,实际测量可能19.8cm,需通过“末端触碰已知点,反向计算参数误差”的方式标定,代码中可将标定参数设为全局变量,方便调整;
    关节零点校准:每次上电后,需控制关节转动到机械零点,读取编码器零点值,将后续角度统一以零点为基准,避免零点漂移,案例5中可在初始化时发送零点指令,驱动板自动记录零点,无需手动校准;
    PID参数调优:闭环控制的核心是PID参数,比例增益负责响应速度,积分增益消除稳态误差,微分增益抑制超调,初始参数可设为经验值,然后通过逐步增大比例增益,观察阶跃响应,避免震荡,再调整积分增益,微分增益,案例3中采用增量式PID,叠加微量修正,避免PWM突变,确保平滑跟踪。
  5. 系统可靠性的“安全与容错设计”:从硬件保护到软件逻辑,防止失控
    关节控制涉及机械运动,可靠性是底线,需从硬件和软件双重防护:
    硬件层面防护:BLDC电机需搭配过流保护的驱动板,防止堵转过流烧毁电机,编码器线缆采用屏蔽线,避免工业环境中的电磁干扰导致反馈数据错误,电源端加滤波电容,防止电压波动导致驱动板重启;
    软件层面限幅与校验:对目标角度、控制输出做限幅,比如案例4中关节角度限制在0-360°,避免角度溢出导致电机超限转动,CAN数据接收后做长度校验,防止解析错误数据,目标点超出工作空间时,输出默认安全角度,电机停止,避免机械碰撞;
    故障自恢复逻辑:当编码器反馈失效或通信中断时,软件需检测到错误,自动切换为开环控制,输出低PWM让关节缓慢回零,同时通过串口报警,案例5中若CAN通信超时,可设置标志位,停止发送目标指令,驱动板内置的过流保护会触发电机停止,防止失控。

注意,以上案例只是为了拓展思路,仅供参考。它们可能有错误、不适用或者无法编译。您的硬件平台、使用场景和Arduino版本可能影响使用方法的选择。实际编程时,您要根据自己的硬件配置、使用场景和具体需求进行调整,并多次实际测试。您还要正确连接硬件,了解所用传感器和设备的规范和特性。涉及硬件操作的代码,您要在使用前确认引脚和电平等参数的正确性和安全性。

在这里插入图片描述

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐