【花雕学编程】Arduino BLDC 之 Madgwick+编码器融合自平衡机器人
本文介绍了一种基于Arduino的BLDC电机自平衡机器人控制系统,采用Madgwick滤波算法与编码器数据融合技术。系统通过多源传感器融合架构,将IMU的高频姿态数据与编码器的低频位移信息互补结合,利用扩展卡尔曼滤波实现精确状态估计。该系统解决了传统平衡机器人积分漂移和运动扰动敏感问题,支持全地形运行、高精度定位和负载自适应控制。文章详细阐述了双闭环PID控制策略、卡尔曼滤波定位实现,并针对算力

“Arduino BLDC之Madgwick+编码器融合自平衡机器人”代表了移动机器人控制领域中一种高阶的状态估计与控制策略。该系统不再单纯依赖IMU(惯性测量单元)或电机编码器中的单一数据源,而是通过Madgwick滤波算法解算高动态姿态角,并与编码器提供的里程计/速度信息进行深度融合。这种架构旨在解决传统自平衡机器人存在的“积分漂移”与“运动扰动敏感”问题,实现更稳健的全姿态控制。
一、主要特点
多源异构传感器融合架构
该系统的核心在于构建了一个互补的感知网络,利用不同传感器在频域上的特性差异进行优势互补。
高频姿态通道(Madgwick):利用IMU中的陀螺仪(高频响应)和加速度计(低频参考),通过Madgwick算法实时解算出机器人的俯仰角(Pitch)和角速度。Madgwick算法基于四元数和梯度下降法,能有效抑制陀螺仪的积分漂移,提供短期动态精度高且无万向节死锁的连续姿态输出。
低频位置/速度通道(编码器):BLDC电机配备的编码器(磁编或光编)提供高精度的转子位置信息。通过对位置微分可获得极其纯净的线速度和位移信息。编码器数据不受重力场干扰,但无法直接提供绝对姿态角。
扩展卡尔曼滤波(EKF)或互补融合
单纯的Madgwick算法在动态加速度(如急加速)下,加速度计会误判重力方向,导致姿态角震荡。引入编码器数据可以修正这一缺陷。
融合逻辑:将Madgwick解算的姿态角作为系统的“短期动态基准”,将编码器积分获得的位移/速度作为“长期位置基准”。
算法实现:通常采用扩展卡尔曼滤波(EKF)或高级互补滤波器。EKF利用编码器的速度观测值来校正IMU在动态运动中产生的姿态误差,从而输出一个既无漂移、又抗动态加速度干扰的“融合角度”和“融合速度”。
基于融合状态的串级控制
串级PID控制:外环(位置环/速度环)基于编码器融合数据,确保机器人能稳定在目标位置或维持定速;内环(平衡环)基于Madgwick融合的高精度角度和角速度,负责维持动态平衡。
抗积分饱和:由于引入了绝对位置反馈(编码器),系统在遇到坡道或持续扰动时,不会像纯平衡环那样依赖积分项强行抬升车身导致积分饱和和剧烈抖动,而是通过微量移动来维持姿态,运动更加柔顺。
二、应用场景
全地形自平衡机器人
在非平整路面(如草地、斜坡、碎石路)上运行时,单纯的IMU容易受到振动和倾斜重力分量的干扰。
应用:通过融合编码器的地面接触信息,机器人能区分“车身倾斜”和“地面坡度”,从而在保持平衡的同时,适应复杂的地形变化,防止因误判倾角而失控。
高精度姿态控制与倒车入库
在需要精确控制车身姿态和位置的场景中(如自动充电对接、狭窄通道通行)。
应用:编码器提供的毫米级位移精度,结合Madgwick提供的0.1度级角度精度,使得机器人能够执行复杂的动作序列,如原地掉头、直线倒车等,而不会出现传统平衡车“越走越偏”或“原地打转”的累积误差。
载人/载物自平衡平台
当负载变化较大(如有人乘坐或货物重量变化)时,机器人的重心和转动惯量会发生剧烈变化。
应用:融合系统能通过编码器反馈的实际位移来验证控制指令的有效性,实时调整控制参数(如前馈增益),确保在不同负载下都能保持一致的操控手感和稳定性。
三、需要注意的事项
算法复杂度与硬件算力瓶颈
挑战:Madgwick算法本身计算量尚可,但若引入EKF进行深度数据融合,计算量将显著增加。传统的8位Arduino Uno/ Nano在运行高频率(>200Hz)的EKF + PID + 电机驱动代码时,极易出现算力不足、控制周期抖动甚至死机。
对策:必须升级至32位高性能微控制器,推荐使用Arduino Due(Cortex-M3)、Teensy 4.0/4.1(Cortex-M7,主频600MHz+)或ESP32。这些平台具备硬件浮点运算单元(FPU),能高效处理四元数运算和矩阵计算。
时间同步与采样频率匹配
挑战:数据融合的效果高度依赖于传感器数据的时效性。如果IMU数据和编码器数据的时间戳不同步,或者采样频率差异过大,融合算法的性能将大打折扣,甚至引入噪声。
对策:利用硬件定时器中断来同步数据采集。确保IMU的DMP(数字运动处理器)或FIFO与编码器的读取操作在同一个控制周期内完成。建议控制环路的更新频率稳定在200Hz-500Hz之间。
机械打滑与轮径误差
挑战:编码器数据依赖于轮子与地面的纯滚动接触。在湿滑地面或轮子打滑时,编码器会产生错误的速度反馈,导致融合算法误判,进而引发车身前倾或后仰。
对策:在软件中加入“打滑检测”逻辑(如编码器速度突变而IMU角速度未变),此时应降低编码器在融合算法中的权重(减小卡尔曼增益),转而更多地依赖IMU数据。
磁场干扰与地磁校准
注意:虽然Madgwick六轴算法不依赖磁力计,但如果在某些姿态初始化或航向保持场景中使用了九轴版本(加入磁力计),必须注意BLDC电机产生的强磁场会严重干扰磁力计读数。
对策:尽量使用六轴Madgwick算法;若必须使用磁力计,需对电机进行磁屏蔽,并在上电时执行严格的“8字形”校准,且在运行中避免靠近大电流导线。

1、双闭环PID自平衡控制系统
#include <MadgwickAHRS.h>
#include <Encoder.h>
#include <PID_v1.h>
// Madgwick滤波器参数
Madgwick filter; // 默认采样率100Hz
float beta = 0.1; // 陀螺仪权重系数
// 编码器配置
Encoder leftEnc(2, 3); // A/B相正交编码
Encoder rightEnc(4, 5);
long lastLeftPos = 0, lastRightPos = 0;
// PID控制器
double targetAngle = 0; // 目标倾角(弧度)
double Kp = 80, Ki = 5, Kd = 2; // 姿态环PID参数
PID attitudePID(¤tAngle, &motorPower, targetAngle, Kp, Ki, Kd);
void setup() {
filter.begin(100); // 设置IMU采样频率
attitudePID.SetMode(AUTOMATIC);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
static unsigned long lastTime = millis();
if (millis() - lastTime > 10) { // 10ms控制周期
// 1. 获取IMU姿态数据
float roll = radiansToDegrees(filter.getRoll());
float pitch = radiansToDegrees(filter.getPitch());
currentAngle = roll; // 使用横滚角作为反馈量
// 2. 读取编码器速度
long leftSpeed = (leftEnc.read() - lastLeftPos) * ENC_CPR / 60.0; // RPM
long rightSpeed = (rightEnc.read() - lastRightPos) * ENC_CPR / 60.0;
// 3. 计算运动学模型所需变量
float chassisVelocity = (leftSpeed + rightSpeed) / 2.0;
float turnRate = (rightSpeed - leftSpeed) / WHEELBASE;
// 4. 执行PID控制输出
double correction = attitudePID.Compute();
setMotorSpeeds(baseThrottle + correction, baseThrottle - correction);
// 5. 更新状态变量
lastLeftPos = leftEnc.read();
lastRightPos = rightEnc.read();
lastTime = millis();
}
}
要点解读:
传感器互补滤波:通过Madgwick融合加速度计/陀螺仪数据,有效抑制高频振动噪声。
编码器脉冲计数:采用四倍频技术提高分辨率(AB相正交解码可达4×原始脉冲)。
级联控制架构:外层姿态环输出作为内层速度环给定值,形成双闭环控制系统。
非线性补偿:根据轮径差异动态调整左右电机增益系数(需预先标定)。
安全边界限制:当角度超过±30°时自动切断动力并触发声光报警。
2、自适应卡尔曼滤波定位系统
#include <KalmanFilter.h>
#include <MadgwickFusion.h>
#include <EncoderReader.h>
// 扩展卡尔曼滤波器结构体
struct StateEstimate {
float x, y, theta; // 位置和航向角
float vx, vy, wz; // 线速度和角速度
};
KalmanFilter<6, 3> kf; // 6维状态向量,3维观测输入
// 编码器数据处理
EncoderReader leftReader(2, 3), rightReader(4, 5);
float calculateOdometry(float& deltaTheta) {
float dl = leftReader.getDeltaPosition();
float dr = rightReader.getDeltaPosition();
deltaTheta = (dr - dl) / WHEELBASE;
return (dl + dr) / 2.0 * cos(lastTheta + deltaTheta/2); // X增量
}
void setup() {
kf.init(STATE_SIZE, INPUT_SIZE); // 初始化卡尔曼矩阵
loadCovarianceMatrices(); // 加载预存的过程噪声Q和测量噪声R
}
void loop() {
// 1. 获取IMU增量数据
imu::Vector<3> gyro = readGyroscope();
imu::Vector<3> accel = readAccelerometer();
// 2. Madgwick预测步
filter.updateIMU(gyro.x(), gyro.y(), gz.z(),
accel.x(), accel.y(), accel.z());
// 3. 里程计推算
float deltaTheta, dX;
dX = calculateOdometry(deltaTheta);
// 4. 构建状态转移矩阵
matlabPrepareTransitionMatrix(deltaT);
kf.predict();
// 5. 观测值融合
float obs[3] = {dX, deltaTheta, filter.getYaw()};
kf.correct(obs);
// 6. 输出最优估计
StateEstimate est = kf.getState();
navigateToTarget(est.x, est.y, est.theta);
}
要点解读:
误差状态建模:将过程噪声协方差Q设置为随速度变化的对角矩阵以提高适应性。
雅可比矩阵计算:在非线性系统中需实时计算状态转移矩阵的偏导数。
故障检测机制:当创新序列超出3σ范围时判定该时刻观测无效。
异步数据处理:采用插值算法同步不同频率的IMU和编码器数据流。
地图匹配增强:结合激光雷达点云进行SLAM回环检测修正累积误差。
3、模糊逻辑防跌倒系统
#include <FuzzyLogic.h>
#include <MadgwickStabilizer.h>
#include <EncoderFeedback.h>
// 定义模糊变量
FuzzyVar tiltAngle("Tilt"); // 倾斜角度 [-30°, +30°]
FuzzyVar motorLoad("Load"); // 电机负载电流 [0A, 5A]
FuzzyRuleEngine ruleBase;
void setup() {
// 初始化模糊规则库
ruleBase.addRule("IF Tilt IS Large THEN BrakeHard");
ruleBase.addRule("IF Load IS High AND Tilt IS Medium THEN SlowDown");
// 配置Madgwick滤波器
stabilizer.begin(200); // 提高采样率应对快速倾倒
}
void loop() {
// 1. 获取危险指标
float currentTilt = abs(stabilizer.getRoll());
float drawCurrent = readPhaseCurrent(); // 三电阻采样法测相电流
// 2. 模糊推理
tiltAngle.setValue(currentTilt);
motorLoad.setValue(drawCurrent);
float riskScore = ruleBase.evaluate();
// 3. 分级响应策略
if (riskScore > CRITICAL_LEVEL) {
emergencyBraking(); // 启动机械刹车+反向扭矩
activateWarningLight();
} else if (riskScore > CAUTION_LEVEL) {
reduceMotorPower(); // 平滑减速避免二次失衡
}
// 4. 持续平衡控制
performRegularStabilization(); // 调用常规PID维持直立
}
要点解读:
隶属度函数设计:采用高斯型分布更符合人体工程学的容忍区间特性。
多判据加权:综合倾斜角度、角速度、电机负荷等多项指标评估风险等级。
容错控制策略:当主控死机时由看门狗触发被动防护措施(如弹射降落伞)。
学习机制植入:记录每次险情数据用于后续神经网络训练优化决策树。
能耗优化平衡:在保证安全的前提下允许小幅晃动以减少频繁调节功耗。

4、基础Madgwick+编码器融合自平衡控制
#include <Wire.h>
#include <MPU6050.h>
#include <MadgwickAHRS.h>
#include <Encoder.h>
MPU6050 mpu;
Madgwick filter;
Encoder leftEnc(2, 3); // 编码器引脚
Encoder rightEnc(18, 19);
float targetAngle = 0.0; // 目标平衡角度
float Kp = 2.0, Ki = 0.1, Kd = 0.5; // PID参数
float integral = 0, lastError = 0;
void setup() {
Serial.begin(115200);
Wire.begin();
mpu.initialize();
filter.begin(100); // 采样率100Hz
}
void loop() {
// 1. 读取IMU数据
int16_t ax, ay, az, gx, gy, gz;
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
// 2. Madgwick滤波融合(加速度+陀螺仪)
float ax_g = ax / 16384.0; // 转换为g单位
float ay_g = ay / 16384.0;
float az_g = az / 16384.0;
float gx_dps = gx / 131.0; // 转换为°/s
float gy_dps = gy / 131.0;
float gz_dps = gz / 131.0;
filter.updateIMU(gx_dps, gy_dps, gz_dps, ax_g, ay_g, az_g);
float roll = filter.getRoll(); // 获取融合后的横滚角
// 3. 编码器速度反馈(左右轮)
long leftPos = leftEnc.read();
long rightPos = rightEnc.read();
float leftSpeed = (leftPos - lastLeftPos) * 0.01; // 假设10ms采样周期
float rightSpeed = (rightPos - lastRightPos) * 0.01;
lastLeftPos = leftPos;
lastRightPos = rightPos;
// 4. PID控制(带速度前馈)
float error = roll - targetAngle;
integral += error;
float derivative = error - lastError;
float pidOutput = Kp * error + Ki * integral + Kd * derivative;
// 速度前馈补偿(假设目标速度为0,仅抗扰动)
float feedforward = 0.1 * (leftSpeed + rightSpeed); // 简单比例补偿
float totalOutput = pidOutput + feedforward;
// 5. 电机控制(差速驱动)
int leftPWM = constrain(90 + totalOutput, 0, 180); // 假设90为停止
int rightPWM = constrain(90 - totalOutput, 0, 180);
// 实际需通过BLDC驱动库(如SimpleFOC)输出电流/力矩
lastError = error;
Serial.print("Roll: "); Serial.print(roll);
Serial.print(" PID: "); Serial.println(totalOutput);
delay(10);
}
5、动态调参+激光雷达避障融合
#include <MadgwickAHRS.h>
#include <NewPing.h> // 超声波模拟激光雷达
#include <SimpleFOC.h> // BLDC驱动库
Madgwick filter;
NewPing sonar(7, 8, 400); // 超声波引脚
BLDCMotor motor = BLDCMotor(7); // 电机极对数
float Kp_dynamic = 2.0;
float obstacleDist = 0;
void loop() {
// 1. 姿态融合(同案例1)
float roll = filter.getRoll();
// 2. 激光雷达避障(超声波模拟)
obstacleDist = sonar.ping_cm();
if (obstacleDist < 30 && obstacleDist > 0) {
Kp_dynamic = 3.5; // 接近障碍物时增大增益
} else {
Kp_dynamic = 2.0;
}
// 3. 动态PID控制
float error = roll;
float pidOutput = Kp_dynamic * error; // 简化版,实际需完整PID
// 4. BLDC力矩控制(SimpleFOC)
motor.move(pidOutput); // 直接输出电流/力矩
delay(10);
}
6、SLAM定位+自平衡复合控制
#include <MadgwickAHRS.h>
#include <FastLED.h> // 模拟SLAM地图显示
#include <ESP32Servo.h> // 高性能伺服控制
Madgwick filter;
CRGB leds[10]; // 模拟SLAM栅格地图
Servo leftMotor, rightMotor;
void setup() {
FastLED.addLeds<NEOPIXEL, 5>(leds, 10); // LED引脚
leftMotor.attach(12);
rightMotor.attach(13);
}
void loop() {
// 1. 姿态融合(同案例1)
float roll = filter.getRoll();
// 2. 模拟SLAM定位(通过LED显示障碍物)
if (roll > 5) { // 假设倾斜代表遇到障碍物
leds[0] = CRGB::Red; // 标记障碍物
} else {
leds[0] = CRGB::Green;
}
FastLED.show();
// 3. 自平衡控制(简化版)
int motorPWM = map(roll, -30, 30, 0, 180);
leftMotor.write(motorPWM);
rightMotor.write(180 - motorPWM); // 差速转向
delay(50);
}
要点解读
传感器融合优先级
Madgwick滤波需以高频率(≥100Hz)运行,优先处理IMU数据以减少姿态延迟。
编码器速度反馈需与IMU时间戳对齐,避免运动补偿误差。
动态调参策略
根据倾斜角度或障碍物距离动态调整PID参数(如案例2中Kp_dynamic),防止固定参数导致失控。
在斜坡或不平地面时,需降低积分项防止积分饱和(如案例4中的integral限制)。
BLDC控制模式选择
优先使用力矩模式(电流环)而非速度模式,以直接响应前馈指令(如案例5中motor.move())。
高性能平台(如ESP32)需配合FOC算法实现低速大扭矩输出。
多任务架构设计
采用“上位机(SLAM/避障)+下位机(自平衡)”分层控制(如案例6的LED模拟),减轻Arduino实时计算负担。
使用硬件定时器或中断确保控制周期稳定性(如案例4中的delay(10)需替换为精确计时)。
抗干扰与容错机制
电源端并联大电容(如4700μF)抑制电机启动时的电压跌落(参考案例4的注释)。
添加机械限位开关和软件行程限制,防止升降机构超程损坏(如案例5的超声波安全距离检测)。
注意,以上案例只是为了拓展思路,仅供参考。它们可能有错误、不适用或者无法编译。您的硬件平台、使用场景和Arduino版本可能影响使用方法的选择。实际编程时,您要根据自己的硬件配置、使用场景和具体需求进行调整,并多次实际测试。您还要正确连接硬件,了解所用传感器和设备的规范和特性。涉及硬件操作的代码,您要在使用前确认引脚和电平等参数的正确性和安全性。

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



所有评论(0)