
“Arduino BLDC 之上肢外骨骼机器人感知-调度-执行柔顺助力闭环”可以理解为:上肢外骨骼通过 IMU、编码器、力/力矩传感器、EMG 等传感器感知人体运动意图和交互状态,由调度层判断当前处于自由运动、助力、支撑、康复训练还是零力模式,再经阻抗/导纳或力矩控制律计算目标力矩,最后由 BLDC FOC 电流环低抖动地输出助力,使人机交互既“跟手”又安全。它的典型控制链路是:传感器采集 → 意图识别/状态估计 → 助力策略/阻抗参数调度 → 力矩合成 → BLDC FOC 电流闭环 → 交互力/姿态反馈。
一、主要特点
上肢外骨骼不能只靠编码器判断“手臂在哪”,还需要判断“人想怎么动、用多大力”。常见信号包括:
IMU:获取肩、肘、上臂、前臂的姿态和角速度;
编码器:获取关节角度、角速度,用于闭环控制;
力/力矩传感器或交互力估计:判断人机接触力大小和方向;
EMG:提前捕捉肌肉发力意图,但信号处理难度较高;
电流反馈:通过 BLDC q 轴电流估算关节输出力矩,辅助判断负载和接触状态。
单一传感器容易误判,例如 EMG 受皮肤阻抗和电极位置影响,IMU 受动态加速度干扰,力传感器受绑带预紧力影响。工程上通常采用多传感器融合,并用状态机、阈值判断或轻量滤波提高鲁棒性。
调度层负责把“感知到的状态”转成“该给多少助力”。常见策略包括:
自由运动阶段:降低刚度,减少外骨骼对人体的阻碍;
助力搬运阶段:根据负载或发力强度提高助力增益;
康复训练阶段:提供适度阻力或轨迹引导,避免完全代偿;
零力示教阶段:接近零刚度,让人轻松拖动外骨骼;
异常阶段:检测到碰撞、打滑、超限或传感器异常时立即降级。
调度层的关键不是固定 PID,而是让 Kp、Kd、助力增益、力矩上限随状态变化。例如用户发力明显时降低阻抗,让用户主导;用户乏力时提高支撑;检测到异常时迅速进入安全模式。
上肢外骨骼与人体直接耦合,力矩输出必须平滑、低噪声、可限幅。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 可提供较精细的力觉渲染,阻抗控制则保证交互安全。
在虚拟训练中,外骨骼可模拟物体重量、碰撞阻力、弹簧回弹等触感。感知层判断用户动作,执行层输出对应阻力,使虚拟交互更真实。
适合验证 EMG 意图识别、交互力估计、阻抗/导纳控制、重力补偿、FOC 力矩闭环和人机安全降级策略。
三、需要注意的事项
普通 ESC 无法稳定实现柔顺助力。应选择支持电流采样、FOC 算法和力矩限幅的驱动器,并确保编码器分辨率足够。
上肢运动频率较高,助力环路延迟过大会产生“滞后拉扯感”。建议底层力矩/电流环保持较高更新频率,感知和调度层也应在毫秒级完成,避免在主循环中执行耗时打印或动态内存分配。
外骨骼直接作用于人体,安全优先级高于控制性能。应设置:
最大输出力矩限幅;
关节活动范围软/硬限位;
电流、电压、温度保护;
通信超时停机;
硬件急停回路;
看门狗防止程序卡死。
上肢外骨骼在肩肘不同角度下重力矩变化明显。如果不做重力补偿,用户会感觉外骨骼“沉重”;如果摩擦补偿不足,低速运动会产生卡顿。需在静止和多角度位置下标定零偏、摩擦和重力矩。
EMG 易受运动伪影、电极接触、汗液和工频干扰影响;力传感器受绑带松紧、安装预载和冲击影响。应加入带通滤波、滑动平均、异常值剔除,并设置置信度阈值,避免误触发助力。
如果仅根据 EMG 幅值或交互力直接放大输出,可能出现正反馈:用户越用力,外骨骼越推,外骨骼越推,用户越紧张。应加入增益限幅、死区、迟滞和模式切换,保证助力“适度、可退让、可中断”。
外骨骼关节轴与人体关节轴不重合时,会产生附加剪切力和不适感。肩关节尤其复杂,涉及肩胛骨运动补偿。结构上应尽量保证旋转中心接近人体关节,并通过可调绑带减少相对滑动。
单关节轻量原型可在 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 版本的差异,均可能影响代码的适配性与使用方法的选择。在实际编程开发时,请务必根据自身硬件配置、使用场景及具体功能需求进行针对性调整,并通过多次实测验证效果;同时需确保硬件接线正确,充分了解所用传感器、执行器等设备的技术规范与核心特性。对于涉及硬件操作的代码,使用前务必核对引脚定义、电平参数等关键信息的准确性与安全性,避免因参数错误导致硬件损坏或运行异常。

实现100%通过率-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261026130223-6adf4f5f34184-220x80.gif)
![打卡信奥刷题(3652)用C++实现信奥题 P12076 [OOI 2025] Cute Subsequences-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261026055333-6adeeaddd0c48-220x150.png)

