欢迎光临
我们一直在努力

【花雕学编程】Arduino BLDC 之上肢外骨骼机器人感知-调度-执行柔顺助力闭环

在这里插入图片描述
“Arduino BLDC 之上肢外骨骼机器人感知-调度-执行柔顺助力闭环”可以理解为:上肢外骨骼通过 IMU、编码器、力/力矩传感器、EMG 等传感器感知人体运动意图和交互状态,由调度层判断当前处于自由运动、助力、支撑、康复训练还是零力模式,再经阻抗/导纳或力矩控制律计算目标力矩,最后由 BLDC FOC 电流环低抖动地输出助力,使人机交互既“跟手”又安全。它的典型控制链路是:传感器采集 → 意图识别/状态估计 → 助力策略/阻抗参数调度 → 力矩合成 → BLDC FOC 电流闭环 → 交互力/姿态反馈。

一、主要特点

  • 感知层:多模态意图识别,而非单点位置跟踪
    上肢外骨骼不能只靠编码器判断“手臂在哪”,还需要判断“人想怎么动、用多大力”。常见信号包括:
    IMU:获取肩、肘、上臂、前臂的姿态和角速度;
    编码器:获取关节角度、角速度,用于闭环控制;
    力/力矩传感器或交互力估计:判断人机接触力大小和方向;
    EMG:提前捕捉肌肉发力意图,但信号处理难度较高;
    电流反馈:通过 BLDC q 轴电流估算关节输出力矩,辅助判断负载和接触状态。
    单一传感器容易误判,例如 EMG 受皮肤阻抗和电极位置影响,IMU 受动态加速度干扰,力传感器受绑带预紧力影响。工程上通常采用多传感器融合,并用状态机、阈值判断或轻量滤波提高鲁棒性。
  • 调度层:根据任务阶段动态切换助力策略
    调度层负责把“感知到的状态”转成“该给多少助力”。常见策略包括:
    自由运动阶段:降低刚度,减少外骨骼对人体的阻碍;
    助力搬运阶段:根据负载或发力强度提高助力增益;
    康复训练阶段:提供适度阻力或轨迹引导,避免完全代偿;
    零力示教阶段:接近零刚度,让人轻松拖动外骨骼;
    异常阶段:检测到碰撞、打滑、超限或传感器异常时立即降级。
    调度层的关键不是固定 PID,而是让 Kp、Kd、助力增益、力矩上限随状态变化。例如用户发力明显时降低阻抗,让用户主导;用户乏力时提高支撑;检测到异常时迅速进入安全模式。
  • 执行层:BLDC FOC 实现低脉动力矩输出
    上肢外骨骼与人体直接耦合,力矩输出必须平滑、低噪声、可限幅。BLDC 配合 FOC 电流环可以把 q 轴电流作为力矩控制量,实现较线性的力矩输出。
    如果使用普通开环 ESC,只能做速度或电压控制,力矩响应不线性,容易出现“顿挫感”“拉扯感”,不适合柔顺助力。更合适的方案是采用支持电流闭环的 FOC 驱动器,并在底层保持稳定的控制周期。
  • 柔顺助力通常采用阻抗/导纳或力矩闭环
    阻抗控制把关节建模为虚拟弹簧-阻尼系统,使外骨骼对外力表现出可控柔顺性;导纳控制则根据交互力生成期望运动。上肢外骨骼常用形式为:
    τ = Kp·(q_d − q) + Kd·(q̇_d − q̇) + τ_ff
    其中 τ_ff 可包含重力补偿、摩擦补偿、前馈助力或 EMG 映射力矩。这样外骨骼既能跟随人体运动,又不会在接触瞬间产生刚性冲击。
  • 分层实时架构更适合嵌入式实现
    完整系统可分为:
    感知层:读取 EMG、IMU、编码器、力传感器、电流;
    估计层:滤波、姿态解算、交互力估计、意图识别;
    调度层:模式切换、阻抗参数调度、助力增益计算;
    执行层:FOC 电流环、关节力矩限幅、安全保护;
    监控层:看门狗、急停、通信超时、故障日志。
    8 位 Arduino 适合做轻量原型,但若同时运行多关节 FOC、阻抗调度和多传感器滤波,实时压力较大。更稳妥的方案是用 ESP32、Teensy、STM32 或“上位机 + 底层 MCU”架构。
  • 二、应用场景

  • 上肢康复训练
    用于脑卒中、肩肘功能障碍患者的重复性训练。外骨骼可根据患者主动发力程度调整辅助量,避免完全替代患者运动,促进主动参与。
  • 工业搬运与装配助力
    在举升、托举、 overhead 作业中,外骨骼对肩肘关节提供重力补偿和助力,降低肌肉疲劳。调度层可根据负载重量、手臂角度和发力强度动态调整助力。
  • 远程操作与力反馈主手
    外骨骼作为遥操作主手时,需要把远端环境力或虚拟环境力反馈到人体手臂。此时 BLDC FOC 可提供较精细的力觉渲染,阻抗控制则保证交互安全。
  • VR/AR 交互与力觉渲染
    在虚拟训练中,外骨骼可模拟物体重量、碰撞阻力、弹簧回弹等触感。感知层判断用户动作,执行层输出对应阻力,使虚拟交互更真实。
  • 教学科研平台
    适合验证 EMG 意图识别、交互力估计、阻抗/导纳控制、重力补偿、FOC 力矩闭环和人机安全降级策略。
  • 三、需要注意的事项

  • 必须使用支持电流/力矩闭环的 FOC 驱动
    普通 ESC 无法稳定实现柔顺助力。应选择支持电流采样、FOC 算法和力矩限幅的驱动器,并确保编码器分辨率足够。
  • 控制周期和延迟要严格控制
    上肢运动频率较高,助力环路延迟过大会产生“滞后拉扯感”。建议底层力矩/电流环保持较高更新频率,感知和调度层也应在毫秒级完成,避免在主循环中执行耗时打印或动态内存分配。
  • 力矩限幅、角度限幅和急停必须独立存在
    外骨骼直接作用于人体,安全优先级高于控制性能。应设置:
    最大输出力矩限幅;
    关节活动范围软/硬限位;
    电流、电压、温度保护;
    通信超时停机;
    硬件急停回路;
    看门狗防止程序卡死。
  • 重力补偿和摩擦补偿需要标定
    上肢外骨骼在肩肘不同角度下重力矩变化明显。如果不做重力补偿,用户会感觉外骨骼“沉重”;如果摩擦补偿不足,低速运动会产生卡顿。需在静止和多角度位置下标定零偏、摩擦和重力矩。
  • EMG/力传感器信号必须滤波和置信度评估
    EMG 易受运动伪影、电极接触、汗液和工频干扰影响;力传感器受绑带松紧、安装预载和冲击影响。应加入带通滤波、滑动平均、异常值剔除,并设置置信度阈值,避免误触发助力。
  • 助力增益不能只随单一信号线性放大
    如果仅根据 EMG 幅值或交互力直接放大输出,可能出现正反馈:用户越用力,外骨骼越推,外骨骼越推,用户越紧张。应加入增益限幅、死区、迟滞和模式切换,保证助力“适度、可退让、可中断”。
  • 人机机械对齐和穿戴适配很关键
    外骨骼关节轴与人体关节轴不重合时,会产生附加剪切力和不适感。肩关节尤其复杂,涉及肩胛骨运动补偿。结构上应尽量保证旋转中心接近人体关节,并通过可调绑带减少相对滑动。
  • Arduino 平台要控制算法复杂度
    单关节轻量原型可在 Arduino Due、ESP32 或 Teensy 上实现;多关节、EMG 融合、阻抗调度和 FOC 同时运行时,8 位 Arduino 通常不够。建议把 FOC 电流环、安全保护和阻抗执行放在底层 MCU,把 EMG 分类、轨迹预测或参数自适应放在上位机或高性能 MCU。
  • 电源隔离和信号抗干扰不可忽视
    BLDC 换相电流会干扰 EMG、力传感器和编码器信号。控制电源与动力电源应隔离,模拟信号使用屏蔽线,ADC 采样注意地线布局和滤波电容,避免力矩输出抖动。
    整体来看,这条链路的核心是:感知层解决“人想怎么动、用了多大力”,调度层解决“此刻该给多少助力、用多软/多硬的响应”,执行层解决“力矩能否平滑、安全、低延迟地输出”。做原型时,建议先从单肘关节开始,跑通“编码器 + 电流反馈 → 助力增益调度 → FOC 力矩限幅 → 安全停机”的最小闭环,再加入肩关节、EMG 和自适应阻抗。
  • 在这里插入图片描述
    1、基于sEMG阈值触发的助力开关(感知→执行直接映射)
    此案例展示最基础的上肢外骨骼助力逻辑:sEMG信号超过用户特定阈值时触发助力,低于阈值时停止。感知与执行之间是简单的阈值判断,适合康复初期的被动触发训练。

    #include <SimpleFOC.h>

    // ==================== BLDC 肘关节 ====================
    BLDCMotor elbowMotor = BLDCMotor(7);
    BLDCDriver3PWM elbowDriver = BLDCDriver3PWM(9, 10, 11, 8);
    Encoder elbowEncoder = Encoder(2, 3, 500);

    // ==================== sEMG 传感器 ====================
    const int EMG_PIN = A0;
    int emgThreshold = 300; // 用户特定阈值,需标定
    int emgValue = 0;
    bool assistActive = false;

    // ==================== 助力参数 ====================
    float assistTorque = 2.0f; // 助力力矩 (Nm)
    float targetAngle = 0.0f; // 目标角度(简化:保持当前角度)

    void setup() {
    Serial.begin(115200);

    elbowMotor.linkDriver(&elbowDriver);
    elbowMotor.linkSensor(&elbowEncoder);
    elbowMotor.controller = MotionControlType::torque;
    elbowMotor.torque_controller = TorqueControlType::foc_current;
    elbowMotor.current_limit = 3.0f;
    elbowMotor.init();
    elbowMotor.initFOC();

    Serial.println("sEMG阈值触发助力就绪");
    Serial.println("请放松肌肉,3秒后开始标定静息阈值…");
    delay(3000);
    calibrateThreshold();
    }

    void loop() {
    elbowMotor.loopFOC();

    // 1. 感知:读取sEMG
    emgValue = analogRead(EMG_PIN);

    // 2. 调度:阈值判断
    if (emgValue > emgThreshold) {
    if (!assistActive) {
    assistActive = true;
    Serial.print("助力启动, EMG: ");
    Serial.println(emgValue);
    }
    } else {
    if (assistActive) {
    assistActive = false;
    Serial.println("助力停止");
    }
    }

    // 3. 执行:力矩输出
    if (assistActive) {
    elbowMotor.move(assistTorque / 10.0f);
    } else {
    elbowMotor.move(0);
    }

    delay(5);
    }

    // 标定:静息状态下采集10次,取均值+余量作为阈值
    void calibrateThreshold() {
    long sum = 0;
    for (int i = 0; i < 50; i++) {
    sum += analogRead(EMG_PIN);
    delay(10);
    }
    int baseline = sum / 50;
    emgThreshold = baseline + 80; // 余量需根据实际信号调整
    Serial.print("标定完成,静息基线: ");
    Serial.print(baseline);
    Serial.print(",触发阈值: ");
    Serial.println(emgThreshold);
    }

    要点:sEMG阈值标定是外骨骼个性化的第一步。不同用户的肌肉电信号幅值差异巨大,固定阈值不可行。calibrateThreshold() 在静息状态下采集基线,加上固定余量作为触发条件,是最简洁的个性化适配方式。感知到执行的映射是二值化的——要么助力,要么不助力。

    2、力闭环自适应阻抗助力(感知→调度→执行完整闭环)
    此案例引入自适应阻抗调度:根据交互力大小动态调整阻抗系数,实现“用户发力大时降低阻抗(让用户主导),发力小时提高阻抗(提供支撑)”。这是“减法式康复”的核心逻辑。

    #include <SimpleFOC.h>
    #include <HX711.h>

    // ==================== BLDC 肘关节 ====================
    BLDCMotor elbowMotor = BLDCMotor(7);
    BLDCDriver3PWM elbowDriver = BLDCDriver3PWM(9, 10, 11, 8);
    Encoder elbowEncoder = Encoder(2, 3, 500);

    // ==================== 力传感器 ====================
    HX711 forceSensor;
    const int LOADCELL_DOUT = 4;
    const int LOADCELL_SCK = 5;

    // ==================== 自适应阻抗参数 ====================
    float targetImpedance = 0.5f; // 当前阻抗系数
    float minImpedance = 0.2f; // 最小阻抗(用户主导)
    float maxImpedance = 1.0f; // 最大阻抗(强支撑)
    float forceThreshold = 50.0f; // 力阈值 (N)
    float lastPosition = 0;

    // ==================== 执行参数 ====================
    float kp_torque = 0.8f, ki_torque = 0.1f, kd_torque = 0.02f;
    float integral = 0, lastError = 0;

    void setup() {
    Serial.begin(115200);

    // 力传感器初始化
    forceSensor.begin(LOADCELL_DOUT, LOADCELL_SCK);
    forceSensor.set_scale(2280.f);
    forceSensor.tare();

    // 电机初始化
    elbowMotor.linkDriver(&elbowDriver);
    elbowMotor.linkSensor(&elbowEncoder);
    elbowMotor.controller = MotionControlType::torque;
    elbowMotor.torque_controller = TorqueControlType::foc_current;
    elbowMotor.current_limit = 3.0f;
    elbowMotor.init();
    elbowMotor.initFOC();

    lastPosition = elbowEncoder.getAngle();
    Serial.println("力闭环自适应阻抗助力就绪");
    }

    void loop() {
    elbowMotor.loopFOC();

    // ===== 1. 感知 =====
    float currentPos = elbowEncoder.getAngle();
    float currentVel = (currentPos – lastPosition) / 0.01f;
    lastPosition = currentPos;
    float force = forceSensor.get_units(10);

    // ===== 2. 调度:自适应阻抗系数调整 =====
    if (force > forceThreshold) {
    // 用户发力大 → 降低阻抗,让用户主导
    targetImpedance = minImpedance;
    } else if (force < forceThreshold * 0.5f) {
    // 用户发力小 → 提高阻抗,提供支撑
    targetImpedance = maxImpedance;
    } else {
    // 中间区域线性过渡
    targetImpedance = maxImpedance –
    (maxImpedance – minImpedance) *
    (force – forceThreshold * 0.5f) / (forceThreshold * 0.5f);
    }

    // ===== 3. 执行:阻抗控制律 + 力矩闭环 =====
    // 阻抗律:期望力矩 = 交互力 × 阻抗系数
    float desiredTorque = force * targetImpedance;

    // 力矩PID跟踪
    float torqueError = desiredTorque – elbowMotor.shaft_torque;
    integral += torqueError * 0.01f;
    integral = constrain(integral, –5.0f, 5.0f);
    float derivative = torqueError – lastError;
    lastError = torqueError;

    float outputTorque = kp_torque * torqueError + ki_torque * integral + kd_torque * derivative;
    outputTorque = constrain(outputTorque, –elbowMotor.current_limit * 0.5f, elbowMotor.current_limit * 0.5f);

    elbowMotor.move(outputTorque / 10.0f);

    delay(10);
    }

    要点:调度层的核心是阻抗系数的连续映射。force > forceThreshold 时降至最小阻抗,force < 0.5×threshold 时升至最大阻抗,中间线性过渡。这种“力大则软、力小则硬”的策略,让康复训练从“机器拖着走”逐渐过渡到“患者自己发力”,实现减法式康复。执行层的力矩PID确保BLDC实际输出跟踪期望力矩,SimpleFOC的q轴电流直接映射为力矩。

    3、多模式调度 + 安全限幅的完整闭环
    此案例在案例二基础上增加训练模式切换与多层安全保护。根据sEMG疲劳特征和交互力,在“主动助力”和“阻抗训练”模式间切换,同时实现位置/速度/力矩三重限幅。

    #include <SimpleFOC.h>
    #include <HX711.h>

    // ==================== BLDC ====================
    BLDCMotor elbowMotor = BLDCMotor(7);
    BLDCDriver3PWM elbowDriver = BLDCDriver3PWM(9, 10, 11, 8);
    Encoder elbowEncoder = Encoder(2, 3, 500);

    // ==================== 传感器 ====================
    HX711 forceSensor;
    const int LOADCELL_DOUT = 4;
    const int LOADCELL_SCK = 5;
    const int EMG_PIN = A0;

    // ==================== 训练模式 ====================
    enum TrainingMode { MODE_ASSIST, MODE_RESIST };
    TrainingMode currentMode = MODE_ASSIST;

    // ==================== 阻抗参数 ====================
    float adaptiveKp = 50.0f; // 刚度
    float adaptiveKd = 20.0f; // 阻尼
    float targetAngle = 0.0f; // 目标角度
    float forceThreshold = 40.0f;

    // ==================== 安全限幅 ====================
    const float MAX_POSITION_ERROR = 45.0f; // 度
    const float MAX_TORQUE = 3.0f; // Nm

    // ==================== 状态 ====================
    float integral = 0, lastError = 0;
    unsigned long lastControlTime = 0;

    void setup() {
    Serial.begin(115200);

    forceSensor.begin(LOADCELL_DOUT, LOADCELL_SCK);
    forceSensor.set_scale(2280.f);
    forceSensor.tare();

    elbowMotor.linkDriver(&elbowDriver);
    elbowMotor.linkSensor(&elbowEncoder);
    elbowMotor.controller = MotionControlType::torque;
    elbowMotor.torque_controller = TorqueControlType::foc_current;
    elbowMotor.current_limit = 4.0f;
    elbowMotor.init();
    elbowMotor.initFOC();

    lastControlTime = millis();
    Serial.println("多模式调度 + 安全限幅外骨骼就绪");
    }

    void loop() {
    elbowMotor.loopFOC();

    unsigned long now = millis();
    if (now – lastControlTime < 10) return; // 100Hz 控制
    lastControlTime = now;

    // ===== 1. 感知 =====
    float currentAngle = elbowEncoder.getAngle();
    float currentVel = elbowEncoder.getVelocity();
    float force = forceSensor.get_units(5);
    int emg = analogRead(EMG_PIN);

    // ===== 2. 调度:模式判断 =====
    // 简化逻辑:力持续偏低且EMG活跃 → 切换阻抗训练模式
    static int lowForceCount = 0;
    if (force < forceThreshold * 0.3f && emg > 250) {
    lowForceCount++;
    if (lowForceCount > 50) { // 持续约0.5秒
    currentMode = MODE_RESIST;
    lowForceCount = 0;
    Serial.println("切换至阻抗训练模式");
    }
    } else {
    lowForceCount = 0;
    if (currentMode == MODE_RESIST && force > forceThreshold * 0.8f) {
    currentMode = MODE_ASSIST;
    Serial.println("切换至主动助力模式");
    }
    }

    // ===== 3. 调度:自适应参数 =====
    if (force > forceThreshold) {
    adaptiveKp = 30.0f;
    adaptiveKd = 35.0f;
    } else {
    adaptiveKp = 80.0f;
    adaptiveKd = 15.0f;
    }
    adaptiveKp = constrain(adaptiveKp, 20.0f, 130.0f);
    adaptiveKd = constrain(adaptiveKd, 8.0f, 40.0f);

    // ===== 4. 执行:阻抗控制 =====
    float angleError = targetAngle – currentAngle;

    // 安全限幅1:位置误差
    if (fabs(angleError) > MAX_POSITION_ERROR) {
    Serial.println("安全停机:位置误差超限");
    elbowMotor.move(0);
    return;
    }

    float outputTorque;
    if (currentMode == MODE_ASSIST) {
    // 助力模式:角度误差驱动 + 力前馈
    outputTorque = adaptiveKp * angleError / 100.0f – adaptiveKd * currentVel / 100.0f
    + force * 0.3f;
    } else {
    // 阻抗训练模式:反向阻抗(患者发力越大,阻力越大)
    outputTorque = –adaptiveKp * (currentAngle – targetAngle) / 100.0f
    – adaptiveKd * currentVel / 100.0f;
    }

    // 安全限幅2:力矩限幅
    outputTorque = constrain(outputTorque, –MAX_TORQUE, MAX_TORQUE);

    // 安全限幅3:电流限幅(SimpleFOC内置)
    elbowMotor.move(outputTorque / 10.0f);

    delay(5);
    }

    要点:调度层承担三重决策:模式切换(助力 vs 阻抗训练)、参数自适应(刚度/阻尼随力调整)、安全仲裁(位置/力矩双重限幅)。MAX_POSITION_ERROR 的检查在输出前执行,一旦超限立即停机,这是外骨骼安全的底线。阻抗训练模式的反向输出逻辑(-adaptiveKp * error)模拟“患者越用力、机器越抵抗”,用于肌力增强训练。

    要点解读

  • 感知层的核心是“意图提取”,而非“信号读取”
    sEMG的原始幅值受电极位置、皮肤阻抗、汗液影响极大,直接用于控制必然失败。必须做用户特定标定(静息基线 + 触发余量)或归一化处理(最大自主收缩百分比)。力传感器同样需要tare()去皮。感知层的输出应该是“用户想用多大力”或“用户当前发力等级”,而非原始ADC值。

  • 调度层的本质是“阻抗参数随交互状态迁移”
    固定阻抗无法兼顾安全与效果:高刚度支撑力强但对抗性强,低刚度柔顺但支撑不足。自适应调度的策略是:力大→降刚度(让用户主导,避免“机器和人打架”),力小→升刚度(提供足够支撑)。案例二的线性过渡是工程上最简洁有效的实现,案例三的模式切换则针对康复不同阶段的需求。

  • 执行层必须用FOC力矩闭环,而非电压/PWM开环
    阻抗控制的数学表达是“力矩 = 刚度×位置误差 + 阻尼×速度误差”。如果执行层用PWM开环驱动,实际输出力矩随电池电压、电机温度、负载变化而漂移,阻抗模型完全失效。SimpleFOC的torque_controller = TorqueControlType::foc_current将q轴电流映射为力矩,配合编码器反馈,才能让“虚拟弹簧-阻尼”行为在物理上成立。

  • 安全限幅是外骨骼的“不可协商项”
    外骨骼直接与人接触,任何软件bug都可能导致关节对人施加危险力矩。必须实现至少三重保护:位置限幅(关节不超出生理范围)、力矩限幅(不超过人体耐受阈值)、电流限幅(电机硬件保护)。案例三中MAX_POSITION_ERROR超限立即停机,constrain(outputTorque, -MAX_TORQUE, MAX_TORQUE)在输出前裁剪,SimpleFOC的current_limit作为最后一道防线。

  • 控制周期与信号噪声的权衡决定系统可用性
    sEMG信号需要滤波(移动平均或带通),力传感器需要多次采样平均(get_units(10)),这些处理引入延迟。但外骨骼的柔顺交互要求控制环路延迟尽可能低(建议≤10ms)。工程折中方案:感知层用滑动窗口低延迟滤波(而非长窗口平均),调度层参数更新降频(如20Hz即可,无需与控制环同频),执行层保持高频FOC(1kHz+)。案例三中控制循环以100Hz运行,参数更新在每次循环中执行,但传感器读取使用短窗口滤波,在噪声抑制和响应速度之间取得平衡。

  • 在这里插入图片描述
    4、EMG+交互力意图感知 + BLDC 力矩环柔顺助力
    适用硬件:Arduino Due / ESP32 + SimpleFOC BLDC(带减速器)+ 编码器 + MyoWare 肌电 + 薄膜压力传感器。
    核心思路:电流环即力矩环(τ = Kt × I),感知层给"意图力矩",调度层状态机仲裁,执行层只消费力矩并硬限幅。

    /*
    * 案例4:EMG+交互力意图感知,状态机调度,电流环(力矩)柔顺执行
    */

    #include <SimpleFOC.h>

    BLDCMotor motor = BLDCMotor(7); // 7对极,带减速器
    BLDCDriver3PWM driver = BLDCDriver3PWM(9, 10, 11, 8);
    Encoder encoder = Encoder(2, 3, 2048);
    void onEncoder() { encoder.handleA(); }

    const int emgPin = A0; // 肌电模拟输入
    const int forcePin = A1; // 人机交互力(0~1 归一化)

    enum State { STANDBY, ASSIST, HOLD, SAFE };
    State state = STANDBY;

    // 感知:EMG 取 RMS + 8点滑动平均,得到活动度 0~1023
    float readEMG() {
    static float buf[8]; static int i = 0;
    float s = 0;
    for (int k = 0; k < 4; k++) { float v = analogRead(emgPin); s += v * v; delayMicroseconds(300); }
    buf[i] = sqrt(s / 4.0f); i = (i + 1) % 8;
    float m = 0; for (int k = 0; k < 8; k++) m += buf[k];
    return m / 8.0f;
    }

    void setup() {
    encoder.init(); encoder.enableInterrupts(onEncoder);
    motor.linkSensor(&encoder);
    motor.controller = MotionControlType::torque; // 力矩模式 = 电流环
    motor.PID_current.P = 0.5f; motor.PID_current.I = 10.0f;
    motor.voltage_limit = 12.0f;
    motor.current_limit = 2.0f; // 电流=力矩上限(柔顺安全的关键)
    motor.init();
    driver.voltage_power_supply = 12.0f; driver.init();
    motor.linkDriver(&driver);
    Serial.begin(115200);
    }

    void loop() {
    float emg = readEMG();
    float f = analogRead(forcePin) / 1023.0f;
    float tau = 0;

    // —- 调度:安全超限最优先,其次按意图切换(带迟滞防抖) —-
    if (f > 0.95f) state = SAFE; // 交互力超限 -> 立刻撤力
    else if (emg > 700 || f > 0.4f) state = ASSIST; // 意图明确 -> 助力
    else if (state == ASSIST && emg > 350) state = HOLD; // 迟滞保持,防抖
    else state = STANDBY;

    switch (state) {
    case ASSIST: // 融合:肌电主导助力幅值 + 交互力微调
    tau = constrain((emg – 350) / 650.0f * 1.2f + f * 0.3f, –1.2f, 1.2f);
    break;
    case HOLD: tau = 0.15f * f; break; // 小幅重力补偿保持
    default: tau = 0; break; // STANDBY / SAFE 恒为 0
    }

    motor.move(tau); // 执行:只消费意图力矩,内部限幅兜底
    Serial.print(state); Serial.print(" emg="); Serial.print(emg);
    Serial.print(" f="); Serial.print(f);
    Serial.print(" tau="); Serial.println(tau);
    delay(10); // 调度周期 10ms;电流环建议移入 1kHz 定时器中断
    }

    工程提示:把 motor.loopFOC() 与 motor.move(tau) 挂进硬件定时器 1kHz 中断,loop() 只跑感知与调度,防止 analogRead/delay 阻塞力矩环。

    5、FreeRTOS 感知-调度-执行三层任务 + 导纳柔顺
    适用硬件:ESP32(双核 + FreeRTOS)。
    核心思路:三个独立 Task 强制分层——感知层只采集滤波(1kHz)、调度层做意图识别与导纳计算(100Hz)、执行层做速度环跟踪(1kHz),共享数据用互斥锁保护。柔顺通过"导纳外环"实现:交互力 ÷ 阻尼 = 期望速度,人拉多少就走多快。

    /*
    * 案例5:ESP32 FreeRTOS 感知-调度-执行三层分离 + 导纳柔顺助力
    */

    #include <SimpleFOC.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"

    BLDCMotor motor = BLDCMotor(7);
    BLDCDriver3PWM driver = BLDCDriver3PWM(9, 10, 11, 8);
    Encoder encoder = Encoder(2, 3, 2048);
    void onEncoder() { encoder.handleA(); }

    SemaphoreHandle_t mux;
    volatile float s_force = 0, s_angle = 0, s_vel = 0; // 感知输出
    volatile float c_targetVel = 0; // 调度输出
    volatile bool c_estop = false;

    // —- 感知层:纯采集 + 一阶低通,不决策 —-
    void taskSense(void*) {
    TickType_t last = xTaskGetTickCount();
    for (;;) {
    float f = analogRead(A1) / 1023.0f;
    xSemaphoreTake(mux, portMAX_DELAY);
    s_force = 0.7f * s_force + 0.3f * f; // 低通去抖
    s_angle = motor.shaft_angle;
    s_vel = motor.shaft_velocity;
    xSemaphoreGive(mux);
    vTaskDelayUntil(&last, pdMS_TO_TICKS(1)); // 1kHz
    }
    }

    // —- 调度层:导纳计算(力->期望速度)+ 软限位 + 安全仲裁 —-
    void taskPlanner(void*) {
    TickType_t last = xTaskGetTickCount();
    for (;;) {
    float f, a;
    xSemaphoreTake(mux, portMAX_DELAY);
    f = s_force; a = s_angle;
    xSemaphoreGive(mux);

    float target = f * 0.8f; // 导纳:期望速度 = 力/阻尼(1/0.8)
    if (a < 0.2f) target = constrain(target, –0.5f, 0.5f); // 软限位
    if (a > 2.6f) target = constrain(target, –0.5f, 0.5f);
    target = constrain(target, –1.0f, 1.0f); // 速度硬限

    xSemaphoreTake(mux, portMAX_DELAY);
    c_targetVel = target;
    c_estop = (f > 0.95f); // 力超限 -> 执行层急停
    xSemaphoreGive(mux);
    vTaskDelayUntil(&last, pdMS_TO_TICKS(10)); // 100Hz
    }
    }

    // —- 执行层:速度环跟踪,仅消费调度输出 —-
    void taskExec(void*) {
    TickType_t last = xTaskGetTickCount();
    for (;;) {
    float t;
    xSemaphoreTake(mux, portMAX_DELAY);
    t = c_estop ? 0 : c_targetVel;
    xSemaphoreGive(mux);
    motor.loopFOC(); // 电流环
    motor.move(t); // 速度环
    vTaskDelayUntil(&last, pdMS_TO_TICKS(1)); // 1kHz
    }
    }

    void setup() {
    encoder.init(); encoder.enableInterrupts(onEncoder);
    motor.linkSensor(&encoder);
    motor.controller = MotionControlType::velocity;
    motor.PID_velocity.P = 0.2f; motor.PID_velocity.I = 2.0f;
    motor.voltage_limit = 12.0f; motor.current_limit = 2.0f;
    motor.init();
    driver.voltage_power_supply = 12.0f; driver.init();
    motor.linkDriver(&driver);

    mux = xSemaphoreCreateMutex();
    xTaskCreatePinnedToCore(taskSense, "sense", 4096, NULL, 3, NULL, 0);
    xTaskCreatePinnedToCore(taskPlanner, "planner", 4096, NULL, 2, NULL, 1);
    xTaskCreatePinnedToCore(taskExec, "exec", 4096, NULL, 3, NULL, 1);
    }
    void loop() { vTaskDelay(portMAX_DELAY); }

    6、重力补偿前馈 + 可变阻抗助力闭环(肘部举升典型)
    适用硬件:Arduino + SimpleFOC BLDC + 编码器 + MPU6050(前臂姿态)+ 交互力传感器。
    核心思路:感知 IMU 算重力矩方向,调度按关节角分段切换阻抗参数(启动段大助力、中段平稳、末端软限位+高阻尼),执行层把"重力前馈 + 阻抗力矩 + 意图力矩"合成后经电流环输出——这是最接近真实上肢外骨骼的工程构型。

    /*
    * 案例6:重力补偿前馈 + 可变阻抗助力(肘关节外骨骼)
    * τ_out = τ_grav(q) + Kp(qd-q) + Kd(0-q̇) + τ_intent
    */

    #include <SimpleFOC.h>
    #include <Wire.h>
    #include <MPU6050.h>

    BLDCMotor motor = BLDCMotor(7);
    BLDCDriver3PWM driver = BLDCDriver3PWM(9, 10, 11, 8);
    Encoder encoder = Encoder(2, 3, 2048);
    void onEncoder() { encoder.handleA(); }

    MPU6050 imu;
    const float m = 2.5f, g = 9.81f, L = 0.28f; // 前臂质量/重力/质心距

    // 调度:按关节角返回阻抗参数(启动段大助力,末端渐软+高阻尼)
    void getImpedance(float q, float &kp, float &kd, float &tauGain) {
    if (q < 1.0f) { kp = 3.0f; kd = 0.3f; tauGain = 1.0f; } // 低位:大助力
    else if (q < 2.2f) { kp = 1.5f; kd = 0.5f; tauGain = 0.8f; } // 中段:平稳
    else { kp = 0.5f; kd = 1.2f; tauGain = 0.3f; } // 高位:软限位+阻尼
    }

    void setup() {
    encoder.init(); encoder.enableInterrupts(onEncoder);
    motor.linkSensor(&encoder);
    motor.controller = MotionControlType::torque; // 力矩模式
    motor.PID_current.P = 0.5f; motor.PID_current.I = 10.0f;
    motor.voltage_limit = 12.0f; motor.current_limit = 2.0f;
    motor.init();
    driver.voltage_power_supply = 12.0f; driver.init();
    motor.linkDriver(&driver);
    Wire.begin(); imu.initialize();
    Serial.begin(115200);
    }

    void loop() {
    float q = motor.shaft_angle; // 关节角
    float qd = motor.shaft_velocity; // 关节速度
    imu.update();
    float theta = imu.getPitch() * DEG_TO_RAD; // 前臂与水平夹角

    // —- 感知融合:重力矩前馈(需知道前臂绝对姿态) —-
    float tauGrav = m * g * L * cos(theta);

    // —- 调度:分段阻抗参数 —-
    float kp, kd, tauGain;
    getImpedance(q, kp, kd, tauGain);

    // —- 意图:交互力经低通进入助力 —-
    float f = analogRead(A1) / 1023.0f;
    float tauIntent = f * 0.6f * tauGain;

    // —- 执行合成:前馈 + 阻抗(柔顺回中)+ 意图,硬限幅 —-
    float tau = tauGrav
    + kp * (1.6f – q) // 期望位 1.6rad 弱回中,人可自由对抗
    – kd * qd
    + tauIntent;
    tau = constrain(tau, –1.5f, 1.5f); // 力矩硬限(柔顺安全底线)

    motor.move(tau);
    Serial.print("q="); Serial.print(q);
    Serial.print(" tauGrav="); Serial.print(tauGrav);
    Serial.print(" tau="); Serial.println(tau);
    delay(5);
    }

    要点解读
    一、柔顺助力的本质是"力矩-运动可交互关系",不是位置伺服
    外骨骼不能像工业机械臂那样硬性跟踪目标位置,否则人一对抗就会产生危险内力。柔顺闭环的核心是把控制目标从"位置/速度"换成"力矩"(案例一、三的电流环模式),或把交互力经导纳映射成期望速度(案例二)——机械阻抗(刚度 Kp、阻尼 Kd)就是"人机协商"的谈判参数:Kp 大则跟随感强但对抗感强,Kd 大则越稳但越"拖沓",必须按工况在线调。

    二、意图感知是闭环的入口,误判 = 安全事故
    感知层(EMG 活动度、交互力、IMU 姿态)决定了"该不该助力、助多大力"。单传感器噪声大,所以案例一用 RMS+滑动平均,案例二用低通滤波,案例三把重力方向做成前馈而非猜测。更关键的是调度层的迟滞状态机(案例一 ASSIST→HOLD→STANDBY 用阈值区间防抖)——防止肌肉颤抖或传感器毛刺造成助力忽大忽小。意图估计错误比电机性能不足危险得多,这是上肢外骨骼的第一设计约束。

    三、调度层是"仲裁者 + 安全守门人",但绝不能碰实时环
    状态机(待机/助力/保持/安全)、软限位、阻抗参数切换都应由调度层完成,它只输出一个被约束好的目标量(目标力矩或目标速度),更新率 100Hz 级别即可。案例二用三个独立 Task 把感知-调度-执行物理分开,案例一用主循环与定时器中断分开——调度的任何逻辑错误、甚至被阻塞,都不应传导到执行层。反过来,执行层越简单,就越容易证明它"在极端情况下也能安全"。

    四、执行层必须纯实时、可降级,力矩/速度双限幅是底线
    电流环(力矩环)至少要 1kHz 且绝不阻塞,任何 delay、Serial.print、传感器读取都不允许进入(案例二把它独立成最高优先级 Task)。同时执行层要有自己的硬限幅:current_limit(电流=力矩)、速度硬限、关节软限位,不能只依赖调度层自觉。这样即使感知全部失效,执行层也不会输出危险力矩——"分层"的价值就在于此:每一层都有独立的自我保护。

    五、失效安全(Fail-Safe)要贯彻到断电那一刻
    上肢外骨骼是直接贴在人身上的执行器,必须按"最坏情况"设计:交互力超限 → 立刻撤力回零(案例一 SAFE 态、案例二 c_estop);传感器掉线 → 降级为纯重力补偿或零力矩跟随(人还能自己动,只是不助力);断电 → 电机零力矩(SimpleFOC 默认安全)或抱闸,绝不锁死关节。永远保留一条"不管感知和调度发生了什么,执行层都能把力矩归零"的物理通道——这是柔顺助力闭环与普通伺服系统的分水岭。

    请注意:以上案例仅作为思路拓展的参考示例,不保证完全正确、适配所有场景或可直接编译运行。由于硬件平台、实际使用场景、Arduino 版本的差异,均可能影响代码的适配性与使用方法的选择。在实际编程开发时,请务必根据自身硬件配置、使用场景及具体功能需求进行针对性调整,并通过多次实测验证效果;同时需确保硬件接线正确,充分了解所用传感器、执行器等设备的技术规范与核心特性。对于涉及硬件操作的代码,使用前务必核对引脚定义、电平参数等关键信息的准确性与安全性,避免因参数错误导致硬件损坏或运行异常。

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » 【花雕学编程】Arduino BLDC 之上肢外骨骼机器人感知-调度-执行柔顺助力闭环
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址