跳到主要内容基于 Arduino 的 BLDC 自适应阻抗控制外骨骼机器人 | 极客日志C++算法
基于 Arduino 的 BLDC 自适应阻抗控制外骨骼机器人
介绍基于 Arduino 无刷直流电机(BLDC)实现自适应阻抗控制的外骨骼机器人系统。通过模拟生物肌肉特性,根据人体意图和环境交互力实时调整刚度与阻尼。涵盖下肢步态跟随、上肢康复训练及负重搬运等场景,提供多模态传感器融合方案(力传感器、EMG、IMU 等)。包含完整的 C++ 代码示例,涉及卡尔曼滤波、PID 控制及动态参数调整。强调硬件安全、实时性优化及人机协同设计,为康复工程与智能控制提供参考。
Kubernet23 浏览 基于 Arduino 的无刷直流电机(BLDC)实现自适应阻抗控制的外骨骼机器人
基于 Arduino 的无刷直流电机(BLDC)实现自适应阻抗控制的外骨骼机器人,代表了康复工程与智能控制领域的前沿方向。该系统旨在让机器人的运动特性(如刚度、阻尼)不再是固定的,而是能根据人体意图和环境交互力实时调整,从而实现如'肌肉'般柔顺、自然的协同运动。
1. 主要特点
类肌肉的柔顺驱动特性
这是阻抗控制的核心优势,旨在模拟生物系统的运动特性。
- 力 - 位置耦合关系:传统的刚性位置控制容易导致人机交互中的'动力对抗'。自适应阻抗控制将外骨骼关节建模为一个虚拟的弹簧 - 阻尼系统。这使得外骨骼在受到外部推力时能产生顺应性位移,而非硬性抵抗,极大提升了穿戴舒适度与安全性。
- 无感交互:通过 BLDC 配合 FOC(磁场定向控制),可以实现高精度的力矩控制,精确复现阻抗模型所需的输出力,让人感觉像是在自然行走,而非被机器'拖着走'。
基于生理信号的自适应机制
'自适应'是该系统的进阶特征,它解决了固定参数无法适应复杂人体需求的问题。
- 意图识别:系统通过传感器(如表面肌电 sEMG 传感器、IMU 惯性测量单元或足底压力传感器)实时采集穿戴者的运动意图和生理状态。
- 参数在线调整:Arduino(或协同的高性能处理器)根据采集到的信号,利用特定算法(如查表法、简单的神经网络或强化学习策略)实时调整阻抗模型中的刚度和阻尼参数。例如,当检测到用户肌肉发力增强时,系统自动降低外骨骼的刚度,让用户主导运动;当用户疲劳时,增加刚度辅助支撑。
能量回馈与高效运行
BLDC 电机的特性完美契合了外骨骼对能效的要求。
- 再生制动:在步态的摆动相或下坡行走时,电机可切换至发电模式,将机械能转化为电能回馈给电池,模拟肌肉的'离心收缩',这不仅延长了续航,也使得制动过程更加平滑,符合人体生物力学习惯。

2. 应用场景
该技术主要应用于医疗康复与人体机能增强领域:
- 神经康复训练:针对中风、脊髓损伤或脑瘫患者,自适应阻抗控制的外骨骼能提供'恰到好处'的辅助。在康复初期,提供高刚度支撑帮助患者行走;随着患者肌力恢复,逐渐降低刚度,鼓励患者主动发力,实现'减法式'康复训练,促进神经重塑。
- 老年助行与护理:为行动不便的老年人或术后卧床患者提供日常行走辅助。柔顺的控制特性可以防止因突然的绊倒或失衡导致的二次伤害,增强老人的独立生活能力,同时减轻护理人员的负担。
- 工业外骨骼:在物流搬运或汽车装配线上,下肢外骨骼通过自适应阻抗控制,辅助工人完成长时间的蹲起或负重行走任务,有效缓解腰部和膝关节的肌肉疲劳,预防职业损伤。
3. 注意事项
开发此类系统涉及多学科交叉,面临极高的技术挑战:
- 生理信号采集与处理的可靠性:噪声干扰(sEMG 信号极其微弱,极易受到电磁干扰)、个体差异(需具备在线校准流程)。
- 控制系统的实时性与安全性:硬实时要求(通常要求 <1ms,建议采用高性能 ARM 架构开发板)、安全急停(设计独立硬件安全回路)。
- 机械结构与人机耦合:穿戴适配性(模块化设计)、串联弹性驱动(SEA)提升力控精度。
- 能源管理:峰值功率需求,电池高倍率放电能力及过流保护。
4. 代码示例
4.1 基于力传感器的自适应助力控制(下肢外骨骼)
BLDCMotor motor = ();
BLDCDriver3PWM driver = (,,,);
Encoder encoder = (,,);
HX711 forceSensor;
LOADCELL_DOUT_PIN = ;
LOADCELL_SCK_PIN = ;
targetImpedance = ;
lastPosition = ;
{
Serial.();
forceSensor.(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
forceSensor.();
forceSensor.();
motor.(&driver);
motor.(&encoder);
motor.();
motor.();
lastPosition = encoder.();
}
{
currentPos = encoder.();
currentVel = (currentPos - lastPosition)/;
lastPosition = currentPos;
force = forceSensor.();
desiredTorque = force * targetImpedance;
pidOutput = ;
PIDController pid{,,};
pidOutput = (currentPos, desiredTorque);
motor.(pidOutput);
(force > ) targetImpedance = ;
targetImpedance = ;
Serial.(); Serial.(force);
Serial.(); Serial.(desiredTorque);
Serial.(); Serial.(targetImpedance);
();
}
#include<SimpleFOC.h>
#include<HX711.h>
BLDCMotor
7
BLDCDriver3PWM
9
10
11
8
Encoder
2
3
500
const
int
4
const
int
5
float
0.5
float
0
void setup()
begin
115200
begin
set_scale
2280.f
tare
linkDriver
linkEncoder
init
initFOC
getAngle
void loop()
float
getAngle
float
0.01
float
get_units
10
float
float
0
static
0.5
0.1
0.01
pid
move
if
50
0.3
else
0.5
print
"Force: "
print
print
" Torque: "
print
print
" Impedance: "
println
delay
10
4.2 基于 EMG 信号的肌电协同控制(上肢外骨骼)
#include<SimpleFOC.h>
#include<EMGFilters.h>
BLDCMotor motor(7);
BLDCDriver3PWM driver(9,10,11,8);
Encoder encoder(2,3,500);
EMGFilters myFilter;
const int emgPin = A0;
float muscleActivation = 0;
float adaptiveGain = 1.0;
void setup(){
Serial.begin(115200);
myFilter.init(500);
motor.linkDriver(&driver);
motor.linkEncoder(&encoder);
motor.init();
motor.initFOC();
}
void loop(){
int rawEMG = analogRead(emgPin);
float filteredEMG = myFilter.update(rawEMG);
muscleActivation = constrain(map(filteredEMG,-1000,1000,0,1),0,1);
float targetAngle = muscleActivation * 90.0;
float currentAngle = encoder.getAngle();
float error = targetAngle - currentAngle;
adaptiveGain = 1.0 + 2.0 * muscleActivation;
static float integral = 0;
float derivative = error - (targetAngle - encoder.getPreviousAngle());
integral += error * 0.01;
float output = 1.5 * error + 0.1 * integral + 0.05 * derivative;
output *= adaptiveGain;
motor.move(output);
if(abs(error)>45) motor.setPhaseVoltage(0,0);
Serial.print("EMG: "); Serial.print(muscleActivation);
Serial.print(" Angle: "); Serial.print(currentAngle);
Serial.print(" Gain: "); Serial.println(adaptiveGain);
delay(10);
}
4.3 基于运动意图预测的自适应控制(步态辅助外骨骼)
#include<SimpleFOC.h>
#include<KalmanFilter.h>
BLDCMotor motor(7);
BLDCDriver3PWM driver(9,10,11,8);
Encoder encoder(2,3,500);
KalmanFilter kf;
float stiffness = 0.8;
float damping = 0.3;
float lastPrediction = 0;
void setup(){
Serial.begin(115200);
kf.setState(0,0);
kf.setProcessNoise(0.01);
kf.setMeasurementNoise(0.1);
motor.linkDriver(&driver);
motor.linkEncoder(&encoder);
motor.init();
motor.initFOC();
}
void loop(){
float currentPos = encoder.getAngle();
float currentVel = encoder.getVelocity();
kf.update(currentPos, currentVel);
float predictedPos = kf.getState(0);
float predictedVel = kf.getState(1);
float predictionError = abs(predictedVel - lastPrediction);
if(predictionError > 0.5){
stiffness = 0.5;
damping = 0.6;
}else{
stiffness = 0.8;
damping = 0.3;
}
lastPrediction = predictedVel;
float desiredForce = stiffness *(predictedPos - currentPos)+ damping *(predictedVel - currentVel);
float currentRef = desiredForce * 0.5;
motor.move(currentRef);
Serial.print("Predicted Vel: "); Serial.print(predictedVel);
Serial.print(" Stiffness: "); Serial.print(stiffness);
Serial.print(" Output: "); Serial.println(currentRef);
delay(10);
}
4.4 下肢步态自适应跟随外骨骼(楼梯/平路切换辅助)
场景说明:面向下肢行动障碍者,外骨骼需跟随人体自然步态,自适应切换平路、楼梯等不同运动模式,根据人体运动意图动态调节阻抗参数,实现平稳跟随不卡滞、跨越障碍不脱钩,核心解决'不同运动场景下人体意图与外骨骼阻抗的动态匹配'问题,适用于日常行走辅助。
硬件配置:Arduino Mega 2560、TB6612FNG 双路 BLDC 驱动模块、足底压力传感器阵列、关节角度编码器、IMU(MPU6050)、应变式力传感器。
#include<Wire.h>
#include<MPU6050.h>
#include<Encoder.h>
#define HIP_LEFT_PWM 9
#define HIP_LEFT_DIR 8
#define KNEE_LEFT_PWM 10
#define KNEE_LEFT_DIR 7
#define HIP_RIGHT_PWM 11
#define HIP_RIGHT_DIR 12
#define KNEE_RIGHT_PWM 13
#define KNEE_RIGHT_DIR 14
#define FOOT_PRESSURE_LEFT "A0-A3"
#define FOOT_PRESSURE_RIGHT "A4-A7"
#define HIP_ENCODER_LEFT "2,3"
#define KNEE_ENCODER_LEFT "4,5"
#define HIP_ENCODER_RIGHT "6,7"
#define KNEE_ENCODER_RIGHT "8,9"
#define FORCE_SENSOR_LEFT "A8"
#define FORCE_SENSOR_RIGHT "A9"
MPU6050 imu;
Encoder hipEncLeft(HIP_ENCODER_LEFT_A, HIP_ENCODER_LEFT_B);
Encoder kneeEncLeft(KNEE_ENCODER_LEFT_A, KNEE_ENCODER_LEFT_B);
Encoder hipEncRight(HIP_ENCODER_RIGHT_A, HIP_ENCODER_RIGHT_B);
Encoder kneeEncRight(KNEE_ENCODER_RIGHT_A, KNEE_ENCODER_RIGHT_B);
float adaptiveImpedanceKp = 80;
float adaptiveImpedanceKd = 25;
float targetForce = 0;
float forceError = 0;
float angleTarget = 0;
float impedanceOutput = 0;
int currentScene = 0;
int footPressureThreshold = 600;
int stairPressureThreshold = 800;
float hipAngleLeft = 0, kneeAngleLeft = 0;
float hipAngleRight = 0, kneeAngleRight = 0;
float footPressureLeft = 0, footPressureRight = 0;
float humanForceLeft = 0, humanForceRight = 0;
float imuTilt = 0;
void setup(){
Serial.begin(115200);
Wire.begin();
imu.initialize();
pinMode(HIP_LEFT_PWM, OUTPUT);
pinMode(HIP_LEFT_DIR, OUTPUT);
pinMode(KNEE_LEFT_PWM, OUTPUT);
pinMode(KNEE_LEFT_DIR, OUTPUT);
pinMode(HIP_RIGHT_PWM, OUTPUT);
pinMode(HIP_RIGHT_DIR, OUTPUT);
pinMode(KNEE_RIGHT_PWM, OUTPUT);
pinMode(KNEE_RIGHT_DIR, OUTPUT);
stopAllMotors();
}
void loop(){
static unsigned long lastLoopTime = 0;
if(millis()- lastLoopTime < 20) return;
lastLoopTime = millis();
readSensorData();
identifyMovementScene();
adjustImpedanceParameters();
calculateImpedanceControl();
executeMotorOutput();
Serial.print("场景:"); Serial.print(currentScene);
Serial.print(" 左髋角度:"); Serial.print(hipAngleLeft);
Serial.print(" 左腿力:"); Serial.print(humanForceLeft);
Serial.print(" 阻抗输出:"); Serial.println(impedanceOutput);
}
void readSensorData(){
hipAngleLeft = hipEncLeft.read()/1000.0*360;
kneeAngleLeft = kneeEncLeft.read()/1000.0*360;
hipAngleRight = hipEncRight.read()/1000.0*360;
kneeAngleRight = kneeEncRight.read()/1000.0*360;
footPressureLeft = (analogRead(FOOT_PRESSURE_LEFT_0)+analogRead(FOOT_PRESSURE_LEFT_1)+analogRead(FOOT_PRESSURE_LEFT_2)+analogRead(FOOT_PRESSURE_LEFT_3))/4.0;
footPressureRight = (analogRead(FOOT_PRESSURE_RIGHT_0)+analogRead(FOOT_PRESSURE_RIGHT_1)+analogRead(FOOT_PRESSURE_RIGHT_2)+analogRead(FOOT_PRESSURE_RIGHT_3))/4.0;
humanForceLeft = map(analogRead(FORCE_SENSOR_LEFT),0,1023,-50,50);
humanForceRight = map(analogRead(FORCE_SENSOR_RIGHT),0,1023,-50,50);
Vector<float> aa = imu.getArex();
imuTilt = aa.z;
}
void identifyMovementScene(){
if(footPressureLeft > footPressureThreshold && footPressureRight < footPressureThreshold){ currentScene = 0; }
else if(footPressureRight > footPressureThreshold && footPressureLeft < footPressureThreshold){ currentScene = 0; }
else if(footPressureLeft > stairPressureThreshold && imuTilt > 5){ currentScene = 1; }
else if(footPressureRight > stairPressureThreshold && imuTilt > 5){ currentScene = 1; }
}
void adjustImpedanceParameters(){
if(currentScene == 0){
adaptiveImpedanceKp = 80+map(abs(humanForceLeft),0,50,0,20);
adaptiveImpedanceKd = 25+map(abs(humanForceRight),0,50,5,15);
}
else if(currentScene == 1){
adaptiveImpedanceKp = 120+map(abs(humanForceLeft),0,50,10,30);
adaptiveImpedanceKd = 40+map(abs(humanForceRight),0,50,10,20);
}
else{
adaptiveImpedanceKp = 50;
adaptiveImpedanceKd = 30;
}
adaptiveImpedanceKp = constrain(adaptiveImpedanceKp,40,150);
adaptiveImpedanceKd = constrain(adaptiveImpedanceKd,20,50);
}
void calculateImpedanceControl(){
if(currentScene == 0){ angleTarget = hipAngleLeft + 30; }
else if(currentScene == 1){ angleTarget = hipAngleLeft + 45; }
static float lastHipAngleLeft = 0;
float dAngle = (angleTarget - hipAngleLeft)/0.02;
float lastDAngle = (lastHipAngleLeft - hipAngleLeft)/0.02;
forceError = humanForceLeft - impedanceOutput;
impedanceOutput = adaptiveImpedanceKp *(angleTarget - hipAngleLeft)+ adaptiveImpedanceKd *(dAngle - lastDAngle)+0.5* forceError;
lastHipAngleLeft = hipAngleLeft;
}
void executeMotorOutput(){
if(impedanceOutput > 0){
digitalWrite(HIP_LEFT_DIR, HIGH);
analogWrite(HIP_LEFT_PWM,constrain(abs(impedanceOutput),0,255));
}else{
digitalWrite(HIP_LEFT_DIR, LOW);
analogWrite(HIP_LEFT_PWM,constrain(abs(impedanceOutput),0,255));
}
float rightImpedance = impedanceOutput *cos(30* PI /180);
if(rightImpedance > 0){
digitalWrite(HIP_RIGHT_DIR, HIGH);
analogWrite(HIP_RIGHT_PWM,constrain(abs(rightImpedance),0,255));
}else{
digitalWrite(HIP_RIGHT_DIR, LOW);
analogWrite(HIP_RIGHT_PWM,constrain(abs(rightImpedance),0,255));
}
float kneeOutput = impedanceOutput *0.6;
if(kneeOutput > 0){
digitalWrite(KNEE_LEFT_DIR, HIGH);
analogWrite(KNEE_LEFT_PWM,constrain(abs(kneeOutput),0,255));
}else{
digitalWrite(KNEE_LEFT_DIR, LOW);
analogWrite(KNEE_LEFT_PWM,constrain(abs(kneeOutput),0,255));
}
float rightKneeOutput = kneeOutput *cos(30* PI /180);
if(rightKneeOutput > 0){
digitalWrite(KNEE_RIGHT_DIR, HIGH);
analogWrite(KNEE_RIGHT_PWM,constrain(abs(rightKneeOutput),0,255));
}else{
digitalWrite(KNEE_RIGHT_DIR, LOW);
analogWrite(KNEE_RIGHT_PWM,constrain(abs(rightKneeOutput),0,255));
}
}
void stopAllMotors(){
analogWrite(HIP_LEFT_PWM,0);
analogWrite(KNEE_LEFT_PWM,0);
analogWrite(HIP_RIGHT_PWM,0);
analogWrite(KNEE_RIGHT_PWM,0);
}

4.5 上肢康复训练自适应阻抗外骨骼(主动助力模式)
场景说明:面向脑卒中或术后上肢康复患者,外骨骼需根据患者肢体运动能力(肌力等级)自适应调节阻抗刚度,当患者主动发力不足时降低阻抗辅助完成动作,当患者发力充足时增大阻抗增强训练强度,实现个性化康复训练,避免过度辅助或训练不足,核心解决'康复训练中人机协同与个性化阻抗调节'问题。
硬件配置:Arduino Mega 2560、BLDC 伺服驱动器、肌电传感器(EMG)、关节角度编码器、扭矩传感器、触控显示屏。
#include<Wire.h>
#include<Adafruit_GFX.h>
#include<Adafruit_SSD1306.h>
#include<EMG_Sensor.h>
#define SHOULDER_PWM 9
#define SHOULDER_DIR 8
#define ELBOW_PWM 10
#define ELBOW_DIR 7
#define EMG_BICEPS A0
#define EMG_TRICEPS A1
#define TORQUE_SENSOR A2
#define SHOULDER_ENCODER "2,3"
#define ELBOW_ENCODER "4,5"
#define OLED_RESET 6
Adafruit_SSD1306 display(128,64,&Wire, OLED_RESET);
float adaptiveKp = 60;
float adaptiveKd = 20;
float emgThreshold = 30;
float torqueTarget = 0;
float torqueError = 0;
int trainingMode = 1;
int muscleStrengthLevel = 0;
float shoulderAngle = 0, elbowAngle = 0;
float emgBiceps = 0, emgTriceps = 0;
float currentTorque = 0;
float impedanceOutputShoulder = 0, impedanceOutputElbow = 0;
void setup(){
Serial.begin(115200);
display.begin(SSD1306_SWITCHCAPVCC,0x3C);
display.clearDisplay();
display.display();
pinMode(SHOULDER_PWM, OUTPUT);
pinMode(SHOULDER_DIR, OUTPUT);
pinMode(ELBOW_PWM, OUTPUT);
pinMode(ELBOW_DIR, OUTPUT);
Encoder shoulderEnc(SHOULDER_ENCODER_A, SHOULDER_ENCODER_B);
Encoder elbowEnc(ELBOW_ENCODER_A, ELBOW_ENCODER_B);
stopAllMotors();
displayTrainingMode();
}
void loop(){
static unsigned long lastLoopTime = 0;
if(millis()- lastLoopTime < 30) return;
lastLoopTime = millis();
readRehabSensorData();
evaluateMuscleStrength();
adjustRehabImpedance();
calculateRehabImpedanceControl();
executeRehabMotorOutput();
updateDisplay();
}
void readRehabSensorData(){
emgBiceps = analogRead(EMG_BICEPS);
emgTriceps = analogRead(EMG_TRICEPS);
static float emgBicepsBuffer[5]={0};
static float emgTricepsBuffer[5]={0};
shiftArray(emgBicepsBuffer,5, emgBiceps);
shiftArray(emgTricepsBuffer,5, emgTriceps);
emgBiceps = averageArray(emgBicepsBuffer,5);
emgTriceps = averageArray(emgTricepsBuffer,5);
shoulderAngle = shoulderEnc.read()/1000.0*360;
elbowAngle = elbowEnc.read()/1000.0*360;
currentTorque = map(analogRead(TORQUE_SENSOR),0,1023,0,10);
}
void evaluateMuscleStrength(){
if(emgBiceps + emgTriceps < emgThreshold){ muscleStrengthLevel = 0; }
else if(emgBiceps + emgTriceps < 100){ muscleStrengthLevel = 1; }
else if(emgBiceps + emgTriceps < 200){ muscleStrengthLevel = 2; }
else if(emgBiceps + emgTriceps < 350){ muscleStrengthLevel = 3; }
else if(emgBiceps + emgTriceps < 500){ muscleStrengthLevel = 4; }
else{ muscleStrengthLevel = 5; }
}
void adjustRehabImpedance(){
switch(muscleStrengthLevel){
case 0:
adaptiveKp = 30; adaptiveKd = 10; torqueTarget = 2;
trainingMode = 1;
break;
case 1:
adaptiveKp = 45; adaptiveKd = 15; torqueTarget = 3; trainingMode = 1;
break;
case 2:
adaptiveKp = 60; adaptiveKd = 20; torqueTarget = 5; trainingMode = 1;
break;
case 3:
adaptiveKp = 80; adaptiveKd = 25; torqueTarget = 7; trainingMode = 1;
break;
case 4:
adaptiveKp = 100; adaptiveKd = 30; torqueTarget = 8; trainingMode = 1;
break;
case 5:
adaptiveKp = 120; adaptiveKd = 35; torqueTarget = 10;
trainingMode = 2;
break;
}
adaptiveKp = constrain(adaptiveKp,20,130);
adaptiveKd = constrain(adaptiveKd,8,40);
torqueTarget = constrain(torqueTarget,1,12);
}
void calculateRehabImpedanceControl(){
float shoulderAngleTarget = 60;
float elbowAngleTarget = 90;
float shoulderAngleError = shoulderAngleTarget - shoulderAngle;
float elbowAngleError = elbowAngleTarget - elbowAngle;
torqueError = torqueTarget - currentTorque;
impedanceOutputShoulder = adaptiveKp * shoulderAngleError + adaptiveKd *(shoulderAngleError /0.03)+0.3* torqueError;
impedanceOutputElbow = adaptiveKp * elbowAngleError + adaptiveKd *(elbowAngleError /0.03)+0.3* torqueError;
if(trainingMode == 2){
impedanceOutputShoulder = -adaptiveKp *(shoulderAngle - shoulderAngleTarget)- adaptiveKd *((shoulderAngle - shoulderAngleTarget)/0.03);
impedanceOutputElbow = -adaptiveKp *(elbowAngle - elbowAngleTarget)- adaptiveKd *((elbowAngle - elbowAngleTarget)/0.03);
}
}
void executeRehabMotorOutput(){
if(impedanceOutputShoulder > 0){
digitalWrite(SHOULDER_DIR, HIGH);
analogWrite(SHOULDER_PWM,constrain(abs(impedanceOutputShoulder),0,255));
}else{
digitalWrite(SHOULDER_DIR, LOW);
analogWrite(SHOULDER_PWM,constrain(abs(impedanceOutputShoulder),0,255));
}
if(impedanceOutputElbow > 0){
digitalWrite(ELBOW_DIR, HIGH);
analogWrite(ELBOW_PWM,constrain(abs(impedanceOutputElbow),0,255));
}else{
digitalWrite(ELBOW_DIR, LOW);
analogWrite(ELBOW_PWM,constrain(abs(impedanceOutputElbow),0,255));
}
}
void updateDisplay(){
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(10);
display.setCursor(0,0);
display.print("模式:");
display.print(trainingMode == 1?"主动助力":"阻抗训练");
display.setCursor(0,16);
display.print("肌力等级:");
display.print(muscleStrengthLevel);
display.setCursor(0,32);
display.print("肩角度:");
display.print(shoulderAngle);
display.setCursor(0,48);
display.print("扭矩:");
display.print(currentTorque);
display.display();
}
void displayTrainingMode(){
display.clearDisplay();
display.setTextSize(16);
display.setCursor(0,20);
display.print("康复训练");
display.setCursor(0,40);
display.print("模式就绪");
display.display();
}
void shiftArray(float arr[],int len,float newVal){
for(int i = 0; i < len -1; i++){ arr[i]= arr[i +1];}
arr[len -1]= newVal;
}
float averageArray(float arr[],int len){
float sum = 0;
for(int i = 0; i < len; i++){ sum += arr[i];}
return sum / len;
}
void stopAllMotors(){
analogWrite(SHOULDER_PWM,0);
analogWrite(ELBOW_PWM,0);
}
4.6 负重搬运自适应阻抗外骨骼(柔性支撑 + 抗扰动)
场景说明:面向物流搬运、高空作业等负重场景,外骨骼需自适应支撑不同重量的负载,实现'负载越重,支撑刚度越高;负载越轻,支撑柔顺性越好',同时具备抗扰动能力(如人体晃动、地面不平),减轻人体负重压力,避免刚性支撑导致的人体疲劳或关节损伤,核心解决'动态负载下的柔性支撑与抗扰动控制'问题。
硬件配置:Arduino Mega 2560、高扭矩 BLDC 驱动器、称重传感器、关节扭矩传感器、压力传感器、倾角传感器。
#include<Wire.h>
#include<HX711.h>
#include<Adafruit_LIS3DH.h>
#define HIP_PWM 9
#define HIP_DIR 8
#define KNEE_PWM 10
#define KNEE_DIR 7
#define WEIGHT_SENSOR_DT 2
#define WEIGHT_SENSOR_SCK 3
#define TORQUE_SENSOR_HIP A0
#define TORQUE_SENSOR_KNEE A1
#define FOOT_PRESSURE_LEFT A2
#define FOOT_PRESSURE_RIGHT A3
Adafruit_LIS3DH lis;
float loadAdaptiveKp = 50;
float loadAdaptiveKd = 20;
float loadWeight = 0;
float targetSupportTorque = 0;
float supportTorqueError = 0;
float disturbanceTilt = 0;
float hipTorque = 0, kneeTorque = 0;
float leftFootPressure = 0, rightFootPressure = 0;
float impedanceOutputHip = 0, impedanceOutputKnee = 0;
HX711 weightSensor;
void setup(){
Serial.begin(115200);
weightSensor.begin(WEIGHT_SENSOR_DT, WEIGHT_SENSOR_SCK);
weightSensor.set_scale(1.0);
lis.begin();
lis.setRange(LIS3DH_RANGE_8_G);
pinMode(HIP_PWM, OUTPUT);
pinMode(HIP_DIR, OUTPUT);
pinMode(KNEE_PWM, OUTPUT);
pinMode(KNEE_DIR, OUTPUT);
stopAllMotors();
}
void loop(){
static unsigned long lastLoopTime = 0;
if(millis()- lastLoopTime < 25) return;
lastLoopTime = millis();
readLoadAndDisturbanceData();
calculateTargetSupportTorque();
adjustLoadImpedanceParameters();
calculateLoadImpedanceControl();
executeLoadMotorOutput();
Serial.print("负载:"); Serial.print(loadWeight);
Serial.print(" 倾角扰动:"); Serial.print(disturbanceTilt);
Serial.print(" 髋输出:"); Serial.print(impedanceOutputHip);
Serial.print(" 膝输出:"); Serial.println(impedanceOutputKnee);
}
void readLoadAndDisturbanceData(){
loadWeight = weightSensor.read_average(10)/100.0;
loadWeight = constrain(loadWeight,0,50);
hipTorque = map(analogRead(TORQUE_SENSOR_HIP),0,1023,0,50);
kneeTorque = map(analogRead(TORQUE_SENSOR_KNEE),0,1023,0,50);
leftFootPressure = analogRead(FOOT_PRESSURE_LEFT);
rightFootPressure = analogRead(FOOT_PRESSURE_RIGHT);
float ax = lis.getAcceleration('x');
float ay = lis.getAcceleration('y');
disturbanceTilt = atan2(ay, ax)*180/ PI;
disturbanceTilt = constrain(disturbanceTilt,-30,30);
}
void calculateTargetSupportTorque(){
targetSupportTorque = loadWeight * 2.5;
targetSupportTorque = constrain(targetSupportTorque,0,125);
}
void adjustLoadImpedanceParameters(){
loadAdaptiveKp = 50+ loadWeight * 2;
loadAdaptiveKd = 20+ loadWeight * 0.8;
if(abs(disturbanceTilt)>5){ loadAdaptiveKd +=abs(disturbanceTilt)*2; }
if(abs(leftFootPressure - rightFootPressure)>150){ loadAdaptiveKp +=20; }
loadAdaptiveKp = constrain(loadAdaptiveKp,40,150);
loadAdaptiveKd = constrain(loadAdaptiveKd,15,50);
}
void calculateLoadImpedanceControl(){
float hipTorqueError = targetSupportTorque * 0.6- hipTorque;
float kneeTorqueError = targetSupportTorque * 0.4- kneeTorque;
float tiltCompensation = disturbanceTilt * 0.5;
impedanceOutputHip = loadAdaptiveKp * hipTorqueError + loadAdaptiveKd *(hipTorqueError /0.025)+ tiltCompensation;
impedanceOutputKnee = loadAdaptiveKp * kneeTorqueError + loadAdaptiveKd *(kneeTorqueError /0.025)+ tiltCompensation;
impedanceOutputHip = constrain(impedanceOutputHip,-255,255);
impedanceOutputKnee = constrain(impedanceOutputKnee,-255,255);
}
void executeLoadMotorOutput(){
if(impedanceOutputHip > 0){
digitalWrite(HIP_DIR, HIGH);
analogWrite(HIP_PWM,constrain(abs(impedanceOutputHip),0,255));
}else{
digitalWrite(HIP_DIR, LOW);
analogWrite(HIP_PWM,constrain(abs(impedanceOutputHip),0,255));
}
if(impedanceOutputKnee > 0){
digitalWrite(KNEE_DIR, HIGH);
analogWrite(KNEE_PWM,constrain(abs(impedanceOutputKnee),0,255));
}else{
digitalWrite(KNEE_DIR, LOW);
analogWrite(KNEE_PWM,constrain(abs(impedanceOutputKnee),0,255));
}
}
void stopAllMotors(){
analogWrite(HIP_PWM,0);
analogWrite(KNEE_PWM,0);
}
5. 要点解读
5.1 多源传感器融合:自适应控制的前提基础
自适应阻抗控制的核心是'精准感知外部条件(人体意图、环境、负载)',单传感器无法全面反映动态工况,需通过多源传感器融合实现全方位感知:
- 感知维度互补:案例 1 融合足底压力、关节角度、人机交互力、IMU 姿态,覆盖运动意图、环境状态;案例 2 融合肌电、扭矩、关节角度,区分患者发力程度;案例 3 融合称重、扭矩、足底压力、倾角,识别负载与扰动,多维度数据交叉验证,避免单一传感器误判。
- 数据滤波与校准:传感器原始数据存在噪声与误差,需通过移动平均、卡尔曼滤波等算法滤波,同时对传感器进行校准,确保数据准确性,为自适应调节提供可靠依据。
- 采样周期匹配:不同传感器采样需求不同,需匹配控制周期,避免高频传感器数据溢出或低频数据滞后,保证实时性与数据有效性,匹配外骨骼的动态响应速度。
5.2 自适应阻抗参数调节:动态工况的核心适配逻辑
自适应的本质是根据外部条件(场景、肌力、负载)动态调整阻抗参数(刚度 Kp、阻尼 Kd),使外骨骼在不同工况下兼顾'跟随性、支撑性、柔顺性',避免刚性或过软的极端输出:
- 参数与工况正相关:案例 4 中,楼梯场景刚度>平路刚度,确保跨越障碍的稳定性;案例 5 中,肌力等级越高,刚度越高,减少辅助、提升训练强度;案例 6 中,负载越重,刚度和阻尼越高,增强支撑能力,参数与工况形成明确的正相关映射,符合物理规律。
- 加入扰动补偿机制:除了常规工况,需考虑扰动因素,如案例 6 加入倾角扰动补偿、足底压力失衡补偿,通过额外增加阻尼或刚度抵消扰动,提升外骨骼抗干扰能力,适应地面不平、人体晃动等复杂环境。
- 参数限幅与安全边界:所有自适应参数必须设置安全边界,避免刚度过高导致关节碰撞,或刚度过低导致负载失控,确保硬件安全与人体舒适,这是自适应控制的底线约束。
5.3 闭环反馈控制:动态跟随的核心保障
自适应阻抗控制需构建'传感器反馈 - 控制算法 - 执行输出'的闭环,确保输出跟随目标变化,弥补开环控制的偏差:
- 闭环目标明确:不同场景闭环目标不同,案例 4 是力 - 位置闭环,跟随人体运动意图;案例 5 是扭矩闭环,匹配训练强度;案例 6 是扭矩 - 扰动闭环,实现柔性支撑,闭环目标与场景需求一一对应,避免目标混淆。
- 误差动态计算:闭环的核心是实时计算误差,误差越小,输出越接近目标,误差越大,输出调节幅度越大,使输出始终逼近目标,形成动态调节。
- PID 优化与实时计算:纯比例控制存在稳态误差,案例中引入积分和微分环节,积分消除稳态误差,微分抑制超调,提升动态响应,同时在中断中实时计算输出,避免 delay 导致的延迟,保证闭环实时性。
5.4 实时性与控制周期:外骨骼控制的性能约束
外骨骼需跟随人体快速运动,实时性是核心约束,控制周期过长会导致滞后,无法及时响应人体意图或环境变化:
- MCU 性能适配:标准 Arduino Uno 主频低,难以满足高频控制需求,三个案例均采用 Arduino Mega 2560,具备更多引脚与更快运算速度,支持多传感器并发采样与实时计算,避免 MCU 性能瓶颈。
- 控制周期精准控制:采用 millis 差值替代 delay 控制周期,确保周期稳定,同时在周期内完成数据采集、参数调节、控制计算、电机输出全流程,控制周期的设定要匹配人体运动的响应时间,过短增加 MCU 负担,过长导致响应滞后,平衡实时性与运算负载。
- 中断与主循环分工:关键任务如编码器脉冲计数、外部中断触发,放在中断中执行,避免被主循环阻塞,主循环专注控制逻辑,确保高频任务的实时响应,这是保障实时性的关键架构设计。
5.5 安全性与人体协同:外骨骼设计的底线原则
外骨骼直接作用于人体,安全性是首要原则,需从硬件、软件、控制逻辑多层面保障,同时实现人机协同的柔顺性:
- 硬件安全冗余:硬件上加装限位开关、过流保护电路,软件上设置电机输出限幅、负载上限、倾角限幅,案例 3 限制负载上限、倾角范围,避免超载或倾角过大导致倾倒,双重冗余确保极端情况下的安全。
- 力控制与柔顺性设计:避免刚性输出,通过阻抗控制实现力闭环,案例 4、5 通过力/扭矩误差调节输出,使外骨骼输出力跟随人体发力,实现柔顺跟随,案例 6 根据负载调节刚度,避免刚性支撑导致的人体疲劳,提升人机协同舒适性。
- 异常停机机制:当传感器故障、通信中断、负载超载等异常发生时,立即触发紧急停机,切断电机输出,这是最后一道安全防线,在三个案例中均预留紧急停机接口,确保异常时快速保护人体与设备。
注意,以上案例只是为了拓展思路,仅供参考。它们可能有错误、不适用或者无法编译。您的硬件平台、使用场景和 Arduino 版本可能影响使用方法的选择。实际编程时,您要根据自己的硬件配置、使用场景和具体需求进行调整,并多次实际测试。您还要正确连接硬件,了解所用传感器和设备的规范和特性。涉及硬件操作的代码,您要在使用前确认引脚和电平等参数的正确性和安全性。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online