跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

Arduino BLDC 机器人 IMU 角度读取与 PID 互补滤波控制

Arduino BLDC 机器人通过 IMU 获取姿态数据,利用互补滤波融合加速度计与陀螺仪信息以修正漂移,再经 PID 控制器计算电机输出实现动态平衡。文章涵盖基础平衡车、四轴飞行器及云台稳定系统的代码实现,详解滤波器参数整定、PID 调优策略及多传感器卡尔曼滤波方案,并提供硬件抗干扰与安全保护建议,适合嵌入式控制开发者参考实践。

静心发布于 2026/3/27更新于 2026/10/2188 浏览
Arduino BLDC 机器人 IMU 角度读取与 PID 互补滤波控制

Arduino BLDC 机器人 IMU 角度读取与 PID 互补滤波控制

基于 Arduino 平台实现 BLDC 机器人的姿态闭环控制,核心在于通过 IMU 获取角度数据,利用互补滤波融合传感器信息以修正漂移,再经 PID 控制器计算电机驱动力矩。这套架构是自平衡机器人、倒立摆或稳定云台的技术基石。

系统核心架构

在这个闭环系统中,各模块分工明确:

  • 感知中枢(传感器融合):互补滤波解决了单一传感器的局限。它利用低通滤波提取加速度计的长期稳定分量(重力方向),同时用高通滤波处理陀螺仪的高频角速度数据。在嵌入式系统中,这通常简化为加权平均:Angle = α * (Angle + Gyro_Rate * dt) + (1-α) * Accel_Angle。系数 α 一般取 0.95~0.98,决定了系统对陀螺仪的信任度。相比卡尔曼滤波,互补滤波计算量极小,非常适合资源受限的 Arduino。
  • 决策大脑(PID 控制器):负责将姿态误差转化为电机指令。比例项(P)提供恢复力,决定系统刚度;微分项(D)提供阻尼,抑制超调和振荡;积分项(I)在此类快速动态环中通常设为 0 或极小值,以防累积过冲。
  • 执行机构(BLDC 驱动):BLDC 电机功率密度高、响应快,适合跟随 PWM 指令产生恢复力矩。对于自平衡应用,通常工作在速度环模式。

应用场景

这套方案适用于多种场景:

  1. 两轮自平衡机器人:检测车身俯仰角,PID 计算左右轮目标转速,维持垂直平衡。
  2. 云台稳定系统:IMU 检测姿态,驱动电机反向旋转抵消载体晃动。
  3. 倒立摆实验:验证控制算法有效性。
  4. 双足/多足机器人基座:控制躯干俯仰和横滚角,防止倾倒。

自平衡机器人示意图

基础实战:两轮自平衡机器人

我们先从最经典的倒立摆开始。这里需要读取 MPU6050 数据,进行互补滤波,再通过 SimpleFOC 库驱动 BLDC 电机。

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

MPU6050 mpu;
BLDCMotor motor(7);
Encoder encoder(2, 3); // 编码器引脚

// PID 参数
float Kp = 40.0, Ki = , Kd = ;
 targetAngle = ; 
 previousError = , integral = ;


 alpha = ; 
 dt = ;    
 filteredAngle = ;

{
  Serial.();
  Wire.();
  mpu.();
  mpu.(MPU6050_GYRO_FS_250);
  mpu.(MPU6050_ACCEL_FS_2);
  
  motor.();
  encoder.();
  motor.(&encoder);
}

{
  
   ax, ay, az, gx, gy, gz;
  mpu.(&ax, &ay, &az, &gx, &gy, &gz);

  
   accelAngle = (ay, az) * RAD_TO_DEG;
   gyroRate = gx / ; 
    gyroAngle = ;
  gyroAngle += gyroRate * dt;
  filteredAngle = alpha * (filteredAngle + gyroAngle * dt) + ( - alpha) * accelAngle;

  
   error = targetAngle - filteredAngle;
  integral += error * dt;
   derivative = (error - previousError) / dt;
   output = Kp * error + Ki * integral + Kd * derivative;
  previousError = error;

  
  motor.((output, , ));

  
  Serial.();
  Serial.(filteredAngle);
  Serial.();
  Serial.(output);

  (dt * ); 
}
10.0
0.5
float
0.0
// 目标角度(垂直平衡点)
float
0
0
// 互补滤波参数
float
0.98
// 加速度计权重
float
0.01
// 采样时间 (s)
float
0
void setup()
begin
9600
begin
initialize
setFullScaleGyroRange
setFullScaleAccelRange
init
init
linkEncoder
void loop()
// 1. 读取 IMU 数据
int16_t
getMotion6
// 2. 互补滤波计算角度
float
atan2
float
131.0
// 转换为°/s
static
float
0
1
// 3. PID 控制
float
float
float
// 4. 电机控制(限制输出范围)
move
constrain
-10
10
// 调试输出
print
"Angle: "
print
print
" Output: "
println
delay
1000
// 保持固定采样周期

进阶场景:四轴飞行器姿态控制

如果是四轴飞行器,我们需要同时处理俯仰(Pitch)和滚转(Roll)。这里使用舵机模拟电机输出,逻辑类似但涉及混合矩阵。

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

MPU6050 mpu;
Servo motors[4]; // 4 个电机

// PID 参数(俯仰/滚转/偏航)
float Kp_pitch = 1.2, Ki_pitch = 0.05, Kd_pitch = 0.8;
float Kp_roll = 1.2, Ki_roll = 0.05, Kd_roll = 0.8;
float targetPitch = 0, targetRoll = 0;

// 互补滤波
float alpha = 0.95;
float pitchAngle = 0, rollAngle = 0;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  mpu.initialize();
  // 初始化电机
  for (int i = 0; i < 4; i++) {
    motors[i].attach(5 + i); // 引脚 5-8
    motors[i].write(90);     // 初始中立位置
  }
}

void computeAngles() {
  int16_t ax, ay, az, gx, gy, gz;
  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

  // 加速度计角度
  float accelPitch = atan2(-ax, sqrt(ay * ay + az * az)) * RAD_TO_DEG;
  float accelRoll = atan2(ay, az) * RAD_TO_DEG;

  // 陀螺仪积分
  static float gyroPitch = 0, gyroRoll = 0;
  gyroPitch += gy / 131.0 * 0.01;
  gyroRoll += gx / 131.0 * 0.01;

  // 互补滤波
  pitchAngle = alpha * (pitchAngle + gyroPitch) + (1 - alpha) * accelPitch;
  rollAngle = alpha * (rollAngle + gyroRoll) + (1 - alpha) * accelRoll;
}

void loop() {
  static unsigned long lastTime = 0;
  unsigned long now = millis();
  if (now - lastTime >= 10) { // 100Hz 控制频率
    lastTime = now;
    // 1. 计算姿态角
    computeAngles();

    // 2. PID 控制(简化版:仅俯仰和滚转)
    static float iTerm_pitch = 0, iTerm_roll = 0;
    float errorPitch = targetPitch - pitchAngle;
    float errorRoll = targetRoll - rollAngle;
    iTerm_pitch += errorPitch * 0.01;
    iTerm_roll += errorRoll * 0.01;
    float outputPitch = Kp_pitch * errorPitch + Ki_pitch * iTerm_pitch;
    float outputRoll = Kp_roll * errorRoll + Ki_roll * iTerm_roll;

    // 3. 电机混合(十字型布局)
    int throttle = 1100; // 基础油门(PWM 值)
    int m1 = throttle + outputPitch + outputRoll;
    int m2 = throttle - outputPitch + outputRoll;
    int m3 = throttle - outputPitch - outputRoll;
    int m4 = throttle + outputPitch - outputRoll;

    // 4. 限制输出并写入电机
    for (int i = 0; i < 4; i++) {
      int val = i == 0 ? m1 : i == 1 ? m2 : i == 2 ? m3 : m4;
      motors[i].write(constrain(map(val, 1000, 2000, 0, 180), 0, 180));
    }
  }
}

云台稳定系统(单轴)

云台需要更平滑的控制,通常目标角度是固定的(如水平 90 度)。这里我们增加了 dt 的动态调整和安全限制。

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

MPU6050 mpu;
BLDCMotor motor(7);
Encoder encoder(2, 3);

// PID 参数
float Kp = 2.0, Ki = 0.1, Kd = 0.05;
float targetAngle = 90.0; // 目标角度(度)

// 互补滤波
float alpha = 0.92;
float filteredAngle = 0;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  mpu.initialize();
  motor.init();
  encoder.init();
  motor.linkEncoder(&encoder);
}

void loop() {
  static unsigned long lastTime = 0;
  unsigned long now = millis();
  float dt = (now - lastTime) / 1000.0;
  if (dt > 0.1) dt = 0.1; // 限制最大 dt

  // 1. 读取 IMU
  uint16_t ax, ay, az, gx, gy, gz;
  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

  // 2. 互补滤波(假设绕 X 轴旋转)
  float accelAngle = atan2(ay, az) * RAD_TO_DEG;
  float gyroRate = gx / 131.0;
  static float gyroAngle = 0;
  gyroAngle += gyroRate * dt;
  filteredAngle = alpha * (filteredAngle + gyroAngle * dt) + (1 - alpha) * accelAngle;

  // 3. PID 控制
  static float integral = 0;
  float error = targetAngle - filteredAngle;
  integral += error * dt;
  float derivative = (error - (lastTime ? (targetAngle - (alpha * (filteredAngle + gyroAngle * dt) + (1 - alpha) * accelAngle)) : 0)) / dt;
  float output = Kp * error + Ki * integral + Kd * derivative;

  // 4. 电机控制
  motor.move(output);

  // 调试输出
  if (now - lastTime >= 50) { // 20Hz 打印
    Serial.print("Target: ");
    Serial.print(targetAngle);
    Serial.print(" Actual: ");
    Serial.print(filteredAngle);
    Serial.print(" Output: ");
    Serial.println(output);
    lastTime = now;
  }
}

高级方案:自适应与卡尔曼滤波

当环境复杂时,简单的互补滤波可能不够。我们可以引入自适应系数或扩展卡尔曼滤波(EKF)来提高精度。

自适应互补滤波

根据运动状态动态调整 α。静止时用 Madgwick 滤波校准,运动时用互补滤波保证实时性。

#include <SimpleFOC.h>
#include <MPU6050_tockn.h>
#include <MadgwickAHRS.h>

// ... (省略部分头文件定义)

// 自适应滤波参数
struct AdaptiveFilter {
  float alpha = 0.98f;
  float alpha_min = 0.90f;
  float alpha_max = 0.995f;
  float beta = 0.1f;
  float innovation = 0.0f;
  float innovation_threshold = 0.1f;
  float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f;
  float roll = 0.0f, pitch = 0.0f, yaw = 0.0f;
  float angularVelocity[3] = {0, 0, 0};
};
AdaptiveFilter adaptiveFilter;

// ... (其他结构体定义)

// 自适应互补滤波函数
void adaptiveComplementaryFilter(float ax, float ay, float az, float gx, float gy, float gz, float dt) {
  // 计算加速度置信度
  float acc_mag = sqrt(ax * ax + ay * ay + az * az);
  float acc_confidence = 1.0f;
  if (abs(acc_mag - 1.0f) > 0.2f) {
    acc_confidence = 0.1f; // 降低置信度
  }

  // 计算动态滤波系数
  float dynamic_alpha = adaptiveFilter.alpha;
  if (acc_confidence < 0.5f) {
    dynamic_alpha = 0.995f; // 增加陀螺权重
  } else {
    dynamic_alpha = 0.98f;
  }
  dynamic_alpha = constrain(dynamic_alpha, adaptiveFilter.alpha_min, adaptiveFilter.alpha_max);

  // 计算角度
  float acc_roll = atan2(ay, az);
  float acc_pitch = atan2(-ax, sqrt(ay * ay + az * az));
  float gyro_roll = adaptiveFilter.roll + gx * dt;
  float gyro_pitch = adaptiveFilter.pitch + gy * dt;

  // 融合
  adaptiveFilter.roll = dynamic_alpha * gyro_roll + (1.0f - dynamic_alpha) * acc_roll;
  adaptiveFilter.pitch = dynamic_alpha * gyro_pitch + (1.0f - dynamic_alpha) * acc_pitch;
  adaptiveFilter.yaw = gyro_yaw; // 简化的航向积分

  // 更新角速度
  adaptiveFilter.angularVelocity[0] = gx;
  adaptiveFilter.angularVelocity[1] = gy;
  adaptiveFilter.angularVelocity[2] = gz;
}

扩展卡尔曼滤波 (EKF)

对于高精度需求,可以使用 EKF 融合多传感器数据(加速度计、磁力计、陀螺仪),并估计零偏。

#include <SimpleFOC.h>
#include <BasicLinearAlgebra.h>
#include <math.h>

using namespace BLA;

class ExtendedKalmanFilter {
private:
  static const int STATE_DIM = 7;
  static const int MEAS_DIM = 6;
  Matrix<STATE_DIM, 1> x;
  Matrix<STATE_DIM, STATE_DIM> P;
  Matrix<STATE_DIM, STATE_DIM> F;
  Matrix<STATE_DIM, STATE_DIM> Q;
  Matrix<MEAS_DIM, STATE_DIM> H;
  Matrix<MEAS_DIM, MEAS_DIM> R;
  float dt = 0.01f;

public:
  ExtendedKalmanFilter() { init(); }

  void init() {
    x = {1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    P.Identity(); P *= 0.1f;
    Q.Identity(); Q(0, 0) = Q(1, 1) = Q(2, 2) = Q(3, 3) = 1e-6f;
    Q(4, 4) = Q(5, 5) = Q(6, 6) = 1e-8f;
    R.Identity(); R(0, 0) = R(1, 1) = R(2, 2) = 0.1f;
    R(3, 3) = R(4, 4) = R(5, 5) = 0.5f;
    updateJacobians();
  }

  void update(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz, float dt) {
    this->dt = dt;
    predict(gx, gy, gz);
    updateMeasurement(ax, ay, az, mx, my, mz);
  }

  void getEulerAngles(float &roll, float &pitch, float &yaw) {
    float q0 = x(0), q1 = x(1), q2 = x(2), q3 = x(3);
    float norm = sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
    q0 /= norm; q1 /= norm; q2 /= norm; q3 /= norm;
    roll = atan2(2.0f * (q0 * q1 + q2 * q3), 1.0f - 2.0f * (q1 * q1 + q2 * q2));
    pitch = asin(2.0f * (q0 * q2 - q3 * q1));
    yaw = atan2(2.0f * (q0 * q3 + q1 * q2), 1.0f - 2.0f * (q2 * q2 + q3 * q3));
  }

  // ... (predict, updateMeasurement 等内部方法略,逻辑同上)
};

ExtendedKalmanFilter ekf;

关键要点总结

在实际调试中,有几个细节往往决定了成败:

  1. 互补滤波机制:核心公式是 angle = α × (上次角度 + 陀螺积分) + (1-α) × 加速度计角度。α=0.98 表示信任陀螺仪为主,但需配合零漂补偿策略。
  2. IMU 校准:静态校准必不可少。设备静止时采集样本计算零偏,必要时加入温度补偿和尺度校准。
  3. PID 整定:建议采用'由小到大'的试凑法。先设 I、D 为 0,增大 P 直到临界振荡,再加入 D 抑制超调。对于平衡车,D 项至关重要。
  4. 实时性保障:严禁在主循环中使用 delay() 阻塞。应使用硬件定时器中断或 millis() 精确控制采样周期(建议 ≥100Hz)。
  5. 安全保护:必须设置输出限幅和紧急停止机制。例如检测到角度超限或 IMU 数据异常时,立即切断电机电源。

实际应用中,代码需要根据具体硬件配置(如电机类型、传感器型号)进行调整。务必确认引脚连接正确,并在通电前检查电源隔离情况,避免大电流干扰导致复位。

目录

  1. Arduino BLDC 机器人 IMU 角度读取与 PID 互补滤波控制
  2. 系统核心架构
  3. 应用场景
  4. 基础实战:两轮自平衡机器人
  5. 进阶场景:四轴飞行器姿态控制
  6. 云台稳定系统(单轴)
  7. 高级方案:自适应与卡尔曼滤波
  8. 自适应互补滤波
  9. 扩展卡尔曼滤波 (EKF)
  10. 关键要点总结

更多推荐文章

查看全部
  • 金融领域自然语言处理应用与实战指南
  • C++ STL list 容器底层实现详解
  • 大模型、AI 大模型与 GPT 模型详解
  • VS Code 无法找到 Python 解释器的解决方案及版本选择
  • 利用 DeepSeek 辅助开发贪吃蛇游戏
  • 领域大模型的挑战与机遇:从构建到应用
  • Windows 本地部署 Ollama 与 OpenClaw,构建 AI 生产力系统
  • C++ 多容器非空检查的逻辑陷阱与最佳实践
  • LazyLLM 框架实战:代码专家智能体进阶模块开发指南
  • 预训练语言模型与 BERT 实战应用
  • AI 辅助 imToken 生态开发:智能合约交互与监控脚本生成实践
  • 哈希算法原理、特点及应用场景详解
  • Windows 系统下 Git 安装与配置指南
  • Android 端 Whisper 中文语音识别实战:模型部署与性能优化
  • 学习 Python 的 10 个核心理由
  • Python 兼职接单渠道与实战建议
  • K-means 聚类算法:原理、步骤与 Python 实现
  • 基于星辰 RPA 的小红书自动发文机器人实现
  • Web Worker:前端多线程的隐形引擎
  • VMware 虚拟机 CentOS 磁盘扩容实战:解决 growpart 报错与 LVM 调整
  • 相关免费在线工具

    • 加密/解密文本

      使用加密算法(如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