在这里插入图片描述
基于 Arduino 的 BLDC 差速驱动机器人(Speed and Steering Control) 是移动机器人领域中最经典、最基础的架构。该系统通过独立控制左右两个 无刷直流电机(BLDC) 的转速,利用速度差来实现机器人的前进、转向和原地旋转。

1、主要特点
差速转向运动学模型 (Differential Kinematics)
这是该系统的核心理论基础,通过简单的数学模型实现了复杂的平面运动。
运动原理:机器人的运动状态完全由左右轮的速度差决定:
直线行驶:机器人沿直线前进或后退。
曲线转向:机器人绕瞬时曲率中心转弯。速度差越大,转弯半径越小。
原地旋转 (Zero-turning):机器人绕其中心点进行零半径旋转。
阿克曼简化模型:相较于汽车的阿克曼转向结构,差速转向省去了复杂的转向连杆机构,机械结构极其简单,仅需两个独立的驱动轮和辅助支撑轮。
高效动力与闭环控制
采用 BLDC 电机 替代传统的有刷电机,显著提升了机器人的性能边界。
高能效比:BLDC 电机效率通常高达 85%-90%,配合电子调速器(ESC),在频繁启停的差速控制场景下能显著延长电池续航时间。
速度闭环控制:为了实现精准的差速转向,必须构建速度闭环。通过霍尔传感器或外置编码器实时采集轮速,利用 PID 控制算法 动态调整 PWM 占空比,确保左右轮的实际转速严格跟随设定值,克服地面摩擦力不均带来的偏差。
Arduino 平台的灵活性与扩展性
Arduino(特别是高性能型号如 ESP32、Teensy)为差速控制提供了丰富的软硬件资源。
多路 PWM 输出:Arduino 平台支持多路硬件 PWM 输出,可同时生成两路甚至更多路的精确 PWM 信号(通常为 50Hz,脉宽 1000μs-2000μs)来控制两个 ESC。
传感器融合:易于集成 IMU(惯性测量单元) 和 编码器。IMU 提供航向角信息,可用于修正机器人的直线行驶精度(例如,若检测到向右偏航,则适当增加右轮速度进行纠正)。

2、应用场景
教育与竞赛机器人平台
这是最广泛的应用领域。例如全国大学生智能汽车竞赛中的“双轮直立组”或“专科电磁组”,参赛学生利用差速驱动原理,结合陀螺仪进行姿态控制,实现车模的直立行走和快速转向。其结构简单、控制逻辑清晰,非常适合教学和算法验证。
室内服务与巡检机器人
在写字楼、商场、仓库等结构化环境中,差速驱动机器人凭借其原地旋转能力,可以在狭窄的通道内灵活穿梭。常用于送餐机器人、酒店引导机器人或机房巡检机器人,执行自主导航、定点停靠和避障任务。
轻型 AGV (Automated Guided Vehicle)
在自动化仓储物流中,差速驱动 AGV 用于搬运轻型货物。虽然其抗侧滑能力不如全向轮或舵轮结构,但在平整地面上,其成本低廉、维护简单的优点使其在特定场景下具有极高的性价比。
科研与算法验证原型
在机器人学研究中,差速驱动底盘常被用作验证 SLAM(同步定位与地图构建)、路径规划(如 A*、Dijkstra)、视觉伺服等算法的底层硬件平台。开发者无需关心复杂的机械结构,可专注于上层算法的开发。

3、 注意事项
机械结构与安装精度
轮子同轴度:左右驱动轮必须严格保持在同一轴线上,且轮距(Track Width)必须精确测量并用于运动学计算。任何安装偏差都会导致里程计(Odometry)计算错误,造成直线跑偏。
轮胎摩擦系数:建议使用高摩擦系数的橡胶轮胎,防止在光滑地面(如瓷砖、木地板)上发生打滑。若地面环境复杂,可适当增加机器人自重以提高抓地力。
电源管理与抗干扰
独立供电:BLDC 电机启动电流大,且换向时会产生电压波动。必须使用独立的稳压模块(如 LM2596)为 Arduino 逻辑电路供电,严禁直接共用未经稳压的电机电源,防止电机启停导致主控复位。
去耦电容:在 ESC 的电源输入端应就近并联 1000μF - 2200μF 的电解电容,以吸收电压尖峰,保护电调和电池。
控制算法与系统标定
PID 参数整定:差速控制对两个电机的同步性要求较高。建议先单独对左右电机进行 PID 参数整定,确保两者的动态响应特性一致。若 PID 参数差异过大,会导致系统响应迟缓或振荡。
里程计误差补偿:由于轮子直径误差和地面打滑,单纯的编码器里程计会产生累积误差。建议结合 IMU(MPU6050 等) 进行数据融合(如使用互补滤波或卡尔曼滤波),利用 IMU 的航向角信息修正里程计的航向偏差。
BLDC 电调的配置
信号类型匹配:确保使用的 ESC 接受标准的 RC PWM 信号(50Hz)。部分高端 FOC 驱动器可能使用 CAN 或 UART 协议,与 Arduino 的 analogWrite() 或 Servo.writeMicroseconds() 函数不兼容。
行程校准:在首次使用前,必须对 ESC 进行油门行程校准(Throttle Calibration),以确保 Arduino 发送的脉宽范围(如 1000-2000μs)能被 ESC 正确识别为最小和最大油门。

在这里插入图片描述
1、基于PWM信号的差速驱动基础控制(2电机独立控制)

// 定义左右电机的PWM引脚和方向引脚
#define LEFT_MOTOR_PWM 12
#define LEFT_MOTOR_DIR 14
#define RIGHT_MOTOR_PWM 13
#define RIGHT_MOTOR_DIR 15

// PWM参数
#define PWM_FREQ 20000  // 高频PWM减少噪声
#define PWM_RES 8       // 8位分辨率(0-255)

void setup() {
  // 配置PWM通道
  ledcSetup(0, PWM_FREQ, PWM_RES);  // 左电机
  ledcSetup(1, PWM_FREQ, PWM_RES);  // 右电机
  ledcAttachPin(LEFT_MOTOR_PWM, 0);
  ledcAttachPin(RIGHT_MOTOR_PWM, 1);

  // 设置方向引脚为输出
  pinMode(LEFT_MOTOR_DIR, OUTPUT);
  pinMode(RIGHT_MOTOR_DIR, OUTPUT);

  // 初始化电机停止
  setMotorSpeed(0, 0);
}

void loop() {
  // 示例1:前进(速度=100)
  setMotorSpeed(100, 100);
  delay(1000);

  // 示例2:右转(左电机速度>右电机)
  setMotorSpeed(150, 50);
  delay(500);

  // 示例3:原地旋转(左电机正转,右电机反转)
  digitalWrite(LEFT_MOTOR_DIR, HIGH);
  digitalWrite(RIGHT_MOTOR_DIR, LOW);
  ledcWrite(0, 150);
  ledcWrite(1, 150);
  delay(1000);

  // 停止
  setMotorSpeed(0, 0);
  delay(1000);
}

// 设置电机速度(-255到255,负值表示反转)
void setMotorSpeed(int leftSpeed, int rightSpeed) {
  // 左电机方向与速度
  digitalWrite(LEFT_MOTOR_DIR, leftSpeed >= 0 ? HIGH : LOW);
  ledcWrite(0, abs(leftSpeed));

  // 右电机方向与速度
  digitalWrite(RIGHT_MOTOR_DIR, rightSpeed >= 0 ? HIGH : LOW);
  ledcWrite(1, abs(rightSpeed));
}

2、基于PS2遥控器的差速驱动控制(解析遥控器输入)

#include <PS2X_lib.h>  // PS2遥控器库

PS2X ps2x;
int error = 0;

// 电机引脚
#define LEFT_MOTOR_PWM 12
#define LEFT_MOTOR_DIR 14
#define RIGHT_MOTOR_PWM 13
#define RIGHT_MOTOR_DIR 15

void setup() {
  Serial.begin(115200);
  
  // 初始化PS2遥控器
  error = ps2x.config_gamepad(18, 19, 23, 21);  // CLK, CMD, ATT, DAT
  if (error != 0) Serial.println("PS2遥控器初始化失败");

  // 初始化PWM和方向引脚
  ledcSetup(0, 20000, 8);
  ledcSetup(1, 20000, 8);
  ledcAttachPin(LEFT_MOTOR_PWM, 0);
  ledcAttachPin(RIGHT_MOTOR_PWM, 1);
  pinMode(LEFT_MOTOR_DIR, OUTPUT);
  pinMode(RIGHT_MOTOR_DIR, OUTPUT);
}

void loop() {
  ps2x.read_gamepad();  // 读取遥控器数据

  // 获取摇杆值(-128 ~ 127)
  int ly = ps2x.Analog(PSS_LY) - 128;  // 左摇杆Y轴(前后)
  int rx = ps2x.Analog(PSS_RX) - 128;  // 右摇杆X轴(转向)

  // 差速控制:速度=Y轴,转向=X轴
  int leftSpeed = ly - rx;  // 左电机速度 = 前进/后退 + 转向
  int rightSpeed = ly + rx; // 右电机速度 = 前进/后退 - 转向

  // 限制速度范围(-255到255)
  leftSpeed = constrain(leftSpeed, -255, 255);
  rightSpeed = constrain(rightSpeed, -255, 255);

  // 设置电机
  digitalWrite(LEFT_MOTOR_DIR, leftSpeed >= 0 ? HIGH : LOW);
  digitalWrite(RIGHT_MOTOR_DIR, rightSpeed >= 0 ? HIGH : LOW);
  ledcWrite(0, abs(leftSpeed));
  ledcWrite(1, abs(rightSpeed));

  delay(50);  // 控制更新频率
}

3、基于ROS的差速驱动控制(串口通信)

#include <ros.h>
#include <geometry_msgs/Twist.h>

ros::NodeHandle nh;

// 电机引脚
#define LEFT_MOTOR_PWM 12
#define LEFT_MOTOR_DIR 14
#define RIGHT_MOTOR_PWM 13
#define RIGHT_MOTOR_DIR 15

// 电机速度变量
float leftSpeed = 0, rightSpeed = 0;

void messageCb(const geometry_msgs::Twist& msg) {
  // 解析ROS Twist消息(线速度和角速度)
  float linear = msg.linear.x;  // 前后速度(m/s)
  float angular = msg.angular.z; // 旋转速度(rad/s)

  // 差速模型:将线速度和角速度转换为轮速
  float wheelSeparation = 0.3;  // 轮距(需根据实际调整)
  float wheelRadius = 0.05;     // 轮半径(需根据实际调整)

  // 计算左右轮速度(单位:转/秒)
  float leftRPM = (linear - angular * wheelSeparation / 2.0) / wheelRadius;
  float rightRPM = (linear + angular * wheelSeparation / 2.0) / wheelRadius;

  // 转换为PWM值(简化:假设1 RPM = 1 PWM单位)
  leftSpeed = constrain(leftRPM * 50, -255, 255);  // 比例系数需调参
  rightSpeed = constrain(rightRPM * 50, -255, 255);
}

ros::Subscriber<geometry_msgs::Twist> sub("cmd_vel", messageCb);

void setup() {
  nh.initNode();
  nh.subscribe(sub);

  // 初始化PWM和方向引脚
  ledcSetup(0, 20000, 8);
  ledcSetup(1, 20000, 8);
  ledcAttachPin(LEFT_MOTOR_PWM, 0);
  ledcAttachPin(RIGHT_MOTOR_PWM, 1);
  pinMode(LEFT_MOTOR_DIR, OUTPUT);
  pinMode(RIGHT_MOTOR_DIR, OUTPUT);
}

void loop() {
  // 设置电机方向与速度
  digitalWrite(LEFT_MOTOR_DIR, leftSpeed >= 0 ? HIGH : LOW);
  digitalWrite(RIGHT_MOTOR_DIR, rightSpeed >= 0 ? HIGH : LOW);
  ledcWrite(0, abs(leftSpeed));
  ledcWrite(1, abs(rightSpeed));

  nh.getHardware()->loop();
  delay(10);
}

要点解读
差速驱动原理
速度控制:左右电机同向旋转实现前进/后退。
转向控制:左右电机差速旋转实现转弯(如左电机快、右电机慢则右转)。
原地旋转:左右电机反向旋转(如案例1示例3)。
电机驱动方式
使用PWM+方向引脚控制BLDC电机(通过ESC或驱动模块)。
ESP32的ledcWrite()支持高频PWM(如20kHz),减少电机噪声。
方向引脚(DIR)决定电机正反转(HIGH/LOW)。
输入信号处理
遥控器控制(案例2):解析摇杆数据,直接映射到电机速度。
ROS控制(案例3):通过geometry_msgs/Twist接收线速度(linear.x)和角速度(angular.z),并转换为差速指令。
需根据实际机器人参数(如轮距、轮径)调整转换公式。
安全与限制
使用constrain()限制PWM范围,防止电机过载。
初始化时停止电机(案例1的setMotorSpeed(0, 0))。
可增加急停功能(如检测遥控器特定按键或ROS紧急消息)。
性能优化
避免在loop()中使用delay(),改用非阻塞计时(如millis())。
ROS通信中优化串口频率(如50Hz),避免数据堆积。
可引入PID控制(如案例3的ROS版本)提高速度跟踪精度。

在这里插入图片描述
4、基础差速控制机器人
场景:入门级两轮差速底盘,实现基本移动和转向。
核心逻辑:左右轮独立速度控制 + 简单差速转向。

#include <SimpleFOC.h>
#include <ESP32Servo.h>  // ESP32 PWM控制

// 左右BLDC电机
BLDCMotor motorLeft(7);   // 左电机,7对极
BLDCMotor motorRight(7);  // 右电机,7对极

// 编码器
Encoder encLeft(2, 3, 1024);   // 左编码器
Encoder encRight(18, 19, 1024); // 右编码器
void doA_left() { encLeft.handleA(); }
void doB_left() { encLeft.handleB(); }
void doA_right() { encRight.handleA(); }
void doB_right() { encRight.handleB(); }

// 机器人物理参数
#define WHEEL_RADIUS 0.035     // 轮半径3.5cm
#define WHEEL_DISTANCE 0.20    // 轮距20cm
#define ROBOT_MASS 2.0         // 机器人质量2kg
#define MAX_LINEAR_SPEED 1.0   // 最大线速度1m/s
#define MAX_ANGULAR_SPEED 3.14 // 最大角速度π rad/s

// 控制输入
struct DiffControl {
  float linear = 0.0;   // 线速度 m/s
  float angular = 0.0;  // 角速度 rad/s
  unsigned long timestamp = 0;
};
DiffControl cmd;

// 机器人状态
struct RobotState {
  float leftSpeed = 0.0;    // 左轮速度 m/s
  float rightSpeed = 0.0;   // 右轮速度 m/s
  float linearSpeed = 0.0;  // 机器人线速度
  float angularSpeed = 0.0; // 机器人角速度
  float x = 0.0, y = 0.0;   // 位置
  float heading = 0.0;      // 航向
  float battery = 12.6;     // 电池电压
};
RobotState state;

// 速度控制PID
float speedKp = 2.0, speedKi = 5.0, speedKd = 0.0;
float leftErrorIntegral = 0, rightErrorIntegral = 0;
float lastLeftError = 0, lastRightError = 0;

// 转向控制PID
float turnKp = 3.0, turnKi = 0.5, turnKd = 0.1;
float headingErrorIntegral = 0;
float lastHeadingError = 0;

// 急停标志
bool emergencyStop = false;
unsigned long lastCmdTime = 0;
#define CMD_TIMEOUT 1000  // 1秒指令超时

void setup() {
  Serial.begin(115200);
  delay(1000);
  
  Serial.println("===== 差速驱动机器人控制系统 =====");
  Serial.println("ESP32 + 双BLDC电机 + 差速控制");
  
  // 1. 初始化编码器
  initEncoders();
  
  // 2. 初始化BLDC电机
  initDifferentialMotors();
  
  // 3. 校准系统
  calibrateSystem();
  
  // 4. 启动安全监控
  startSafetyMonitor();
  
  Serial.println("系统就绪");
  Serial.println("命令格式: L线速度,A角速度 (例: 0.5,0.2)");
  Serial.println("特殊命令: S-停止, E-急停, R-重置");
}

void loop() {
  static unsigned long lastControlTime = 0;
  unsigned long now = micros();
  float dt = (now - lastControlTime) / 1000000.0;
  
  if (dt >= 0.01) {  // 100Hz控制频率
    // 1. 读取控制指令
    readControlInput();
    
    // 2. 差速运动学计算
    WheelSpeeds wheelSpeeds = calculateWheelSpeeds(cmd);
    
    // 3. 应用加速度限制
    wheelSpeeds = limitAcceleration(wheelSpeeds, dt);
    
    // 4. 速度控制
    controlWheelSpeeds(wheelSpeeds, dt);
    
    // 5. 更新里程计
    updateOdometry(dt);
    
    // 6. 安全检查
    checkSafety();
    
    lastControlTime = now;
  }
  
  // 7. 执行FOC控制
  motorLeft.loopFOC();
  motorRight.loopFOC();
  
  // 8. 状态显示
  static unsigned long lastDisplay = 0;
  if (millis() - lastDisplay >= 200) {
    displayRobotStatus();
    lastDisplay = millis();
  }
  
  // 9. 处理串口命令
  if (Serial.available()) {
    handleSerialCommand();
  }
  
  // 10. 看门狗
  vTaskDelay(1);
}

void initEncoders() {
  Serial.println("初始化编码器...");
  
  // 左编码器
  encLeft.init();
  encLeft.enableInterrupts(doA_left, doB_left);
  
  // 右编码器
  encRight.init();
  encRight.enableInterrupts(doA_right, doB_right);
  
  Serial.println("编码器初始化完成");
}

void initDifferentialMotors() {
  Serial.println("初始化差速电机...");
  
  // 链接传感器
  motorLeft.linkSensor(&encLeft);
  motorRight.linkSensor(&encRight);
  
  // 配置电机参数
  BLDCMotor* motors[2] = {&motorLeft, &motorRight};
  for (int i = 0; i < 2; i++) {
    motors[i]->voltage_power_supply = 12.0;
    motors[i]->phase_resistance = 0.5;
    motors[i]->pole_pairs = 7;
    motors[i]->KV_rating = 1000;
    
    // 配置速度控制器
    motors[i]->PID_velocity.P = 0.5;
    motors[i]->PID_velocity.I = 10.0;
    motors[i]->PID_velocity.D = 0.0;
    motors[i]->PID_velocity.output_ramp = 1000.0;
    motors[i]->LPF_velocity.Tf = 0.01;
    
    // 配置电流控制器
    motors[i]->PID_current_q.P = 3.0;
    motors[i]->PID_current_q.I = 300.0;
    motors[i]->PID_current_q.D = 0.0;
    motors[i]->PID_current_q.output_ramp = 10000.0;
    
    // 初始化FOC
    motors[i]->init();
    motors[i]->initFOC();
    
    Serial.print("电机");
    Serial.print(i == 0 ? "左" : "右");
    Serial.println(" 初始化完成");
  }
  
  Serial.println("差速电机初始化完成");
}

WheelSpeeds calculateWheelSpeeds(DiffControl cmd) {
  // 差速运动学逆解
  // 公式: V_left = V_linear - ω * d/2
  //       V_right = V_linear + ω * d/2
  
  WheelSpeeds speeds;
  
  // 限制控制输入
  cmd.linear = constrain(cmd.linear, -MAX_LINEAR_SPEED, MAX_LINEAR_SPEED);
  cmd.angular = constrain(cmd.angular, -MAX_ANGULAR_SPEED, MAX_ANGULAR_SPEED);
  
  // 计算轮速
  speeds.left = cmd.linear - cmd.angular * WHEEL_DISTANCE / 2.0;
  speeds.right = cmd.linear + cmd.angular * WHEEL_DISTANCE / 2.0;
  
  // 转换为角速度 (rad/s)
  speeds.left /= WHEEL_RADIUS;
  speeds.right /= WHEEL_RADIUS;
  
  // 限制轮速
  float maxWheelSpeed = MAX_LINEAR_SPEED / WHEEL_RADIUS;
  speeds.left = constrain(speeds.left, -maxWheelSpeed, maxWheelSpeed);
  speeds.right = constrain(speeds.right, -maxWheelSpeed, maxWheelSpeed);
  
  return speeds;
}

WheelSpeeds limitAcceleration(WheelSpeeds target, float dt) {
  // 加速度限制
  
  static WheelSpeeds current = {0, 0};
  
  // 最大加速度
  float maxAccel = 2.0 / WHEEL_RADIUS;  // 转换为轮加速度
  
  // 左轮加速度限制
  float leftAccel = (target.left - current.left) / dt;
  if (abs(leftAccel) > maxAccel) {
    float sign = (leftAccel > 0) ? 1 : -1;
    target.left = current.left + sign * maxAccel * dt;
  }
  
  // 右轮加速度限制
  float rightAccel = (target.right - current.right) / dt;
  if (abs(rightAccel) > maxAccel) {
    float sign = (rightAccel > 0) ? 1 : -1;
    target.right = current.right + sign * maxAccel * dt;
  }
  
  current = target;
  return target;
}

void controlWheelSpeeds(WheelSpeeds target, float dt) {
  // 轮速控制
  
  if (emergencyStop) {
    // 急停状态
    motorLeft.move(0);
    motorRight.move(0);
    leftErrorIntegral = 0;
    rightErrorIntegral = 0;
    return;
  }
  
  // 获取实际轮速
  float actualLeft = motorLeft.shaft_velocity;
  float actualRight = motorRight.shaft_velocity;
  
  // 左轮PID控制
  float leftError = target.left - actualLeft;
  leftErrorIntegral += leftError * dt;
  leftErrorIntegral = constrain(leftErrorIntegral, -10, 10);
  float leftErrorDeriv = (leftError - lastLeftError) / dt;
  lastLeftError = leftError;
  
  float leftControl = speedKp * leftError + 
                     speedKi * leftErrorIntegral + 
                     speedKd * leftErrorDeriv;
  
  // 右轮PID控制
  float rightError = target.right - actualRight;
  rightErrorIntegral += rightError * dt;
  rightErrorIntegral = constrain(rightErrorIntegral, -10, 10);
  float rightErrorDeriv = (rightError - lastRightError) / dt;
  lastRightError = rightError;
  
  float rightControl = speedKp * rightError + 
                      speedKi * rightErrorIntegral + 
                      speedKd * rightErrorDeriv;
  
  // 应用控制
  motorLeft.move(leftControl);
  motorRight.move(rightControl);
  
  // 更新状态
  state.leftSpeed = actualLeft * WHEEL_RADIUS;
  state.rightSpeed = actualRight * WHEEL_RADIUS;
  state.linearSpeed = (state.leftSpeed + state.rightSpeed) / 2.0;
  state.angularSpeed = (state.rightSpeed - state.leftSpeed) / WHEEL_DISTANCE;
}

void updateOdometry(float dt) {
  // 更新里程计
  
  // 获取轮子位移
  static float lastLeftPos = 0, lastRightPos = 0;
  float leftPos = motorLeft.shaft_angle;
  float rightPos = motorRight.shaft_angle;
  
  float deltaLeft = (leftPos - lastLeftPos) * WHEEL_RADIUS;
  float deltaRight = (rightPos - lastRightPos) * WHEEL_RADIUS;
  
  // 计算机器人位移
  float deltaLinear = (deltaLeft + deltaRight) / 2.0;
  float deltaTheta = (deltaRight - deltaLeft) / WHEEL_DISTANCE;
  
  // 更新位置
  state.x += deltaLinear * cos(state.heading + deltaTheta / 2.0);
  state.y += deltaLinear * sin(state.heading + deltaTheta / 2.0);
  state.heading += deltaTheta;
  
  // 归一化航向到[-π, π]
  while (state.heading > PI) state.heading -= 2 * PI;
  while (state.heading < -PI) state.heading += 2 * PI;
  
  lastLeftPos = leftPos;
  lastRightPos = rightPos;
}

void readControlInput() {
  // 模拟读取遥控输入
  // 这里可以替换为实际接收机
  
  static DiffControl lastCmd = {0, 0, 0};
  
  // 模拟控制:缓慢增加速度
  static unsigned long lastAutoCmd = 0;
  if (millis() - lastAutoCmd > 5000) {  // 每5秒自动命令
    cmd.linear = random(-10, 11) / 10.0;  // -1.0 到 1.0
    cmd.angular = random(-10, 11) / 10.0; // -1.0 到 1.0
    cmd.timestamp = millis();
    lastAutoCmd = millis();
  }
  
  // 指令超时检查
  if (millis() - cmd.timestamp > CMD_TIMEOUT) {
    cmd.linear = 0;
    cmd.angular = 0;
  }
}

5、精确转向与航向控制
场景:需要精确航向控制的机器人,如自动导航车。
核心逻辑:IMU航向反馈 + PID转向控制。

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

// IMU传感器
MPU6050 mpu6050(Wire);
#define IMU_SDA 21
#define IMU_SCL 22

// 航向控制参数
float targetHeading = 0.0;      // 目标航向
float currentHeading = 0.0;     // 当前航向
float headingError = 0.0;       // 航向误差
float headingErrorIntegral = 0.0;
float lastHeadingError = 0.0;
float headingKp = 2.0, headingKi = 0.1, headingKd = 0.5;

// 互补滤波
float compFilterHeading = 0.0;
float alpha = 0.98;  // 互补滤波系数
unsigned long lastIMUTime = 0;

// 转向模式
enum TurnMode {
  TURN_RATE,      // 角速度控制
  TURN_ANGLE,     // 角度控制
  TURN_POINT      // 定点转向
};
TurnMode turnMode = TURN_RATE;

// 转向控制器
class HeadingController {
private:
  float kp, ki, kd;
  float errorIntegral = 0;
  float lastError = 0;
  float outputLimit = 2.0;  // 最大输出角速度
  
public:
  HeadingController(float p, float i, float d) : kp(p), ki(i), kd(d) {}
  
  float calculate(float target, float current, float dt) {
    // 计算航向误差
    float error = normalizeAngle(target - current);
    
    // PID计算
    errorIntegral += error * dt;
    errorIntegral = constrain(errorIntegral, -outputLimit/ki, outputLimit/ki);
    
    float errorDeriv = (error - lastError) / dt;
    lastError = error;
    
    float output = kp * error + ki * errorIntegral + kd * errorDeriv;
    output = constrain(output, -outputLimit, outputLimit);
    
    return output;
  }
  
  void reset() {
    errorIntegral = 0;
    lastError = 0;
  }
  
private:
  float normalizeAngle(float angle) {
    while (angle > PI) angle -= 2 * PI;
    while (angle < -PI) angle += 2 * PI;
    return angle;
  }
};

HeadingController headingCtrl(headingKp, headingKi, headingKd);

// 直线跟踪
class LineTracking {
private:
  float targetLine = 0.0;      // 目标直线
  float lineError = 0.0;       // 直线误差
  float lineKp = 1.0, lineKd = 0.1;
  float lastLineError = 0.0;
  
public:
  float calculate(float currentX, float currentY, float targetX, 
                  float targetY, float heading, float dt) {
    
    // 计算直线误差
    // 直线方程: (y - y1) = m(x - x1)
    float dx = targetX - currentX;
    float dy = targetY - currentY;
    
    if (abs(dx) < 0.001) {
      // 垂直线
      lineError = currentX - targetX;
    } else {
      float m = dy / dx;  // 斜率
      float c = targetY - m * targetX;  // 截距
      
      // 点到直线距离
      lineError = (m * currentX - currentY + c) / sqrt(m * m + 1);
    }
    
    // PD控制
    float errorDeriv = (lineError - lastLineError) / dt;
    lastLineError = lineError;
    
    float correction = lineKp * lineError + lineKd * errorDeriv;
    
    return correction;
  }
  
  void setTarget(float x1, float y1, float x2, float y2) {
    targetLine = atan2(y2 - y1, x2 - x1);
  }
};

LineTracking lineTracker;

void setupIMU() {
  Serial.println("初始化IMU...");
  
  Wire.begin(IMU_SDA, IMU_SCL);
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);
  
  Serial.println("IMU校准完成");
}

void updateIMUHeading(float dt) {
  // 更新IMU航向
  
  mpu6050.update();
  
  // 读取陀螺仪
  float gyroZ = mpu6050.getGyroZ() * PI / 180.0;  // 转换为rad/s
  
  // 读取加速度计
  float accX = mpu6050.getAccX();
  float accY = mpu6050.getAccY();
  float accZ = mpu6050.getAccZ();
  
  // 加速度计航向
  float accHeading = atan2(accY, accX);
  
  // 互补滤波
  compFilterHeading = alpha * (compFilterHeading + gyroZ * dt) + 
                     (1 - alpha) * accHeading;
  
  // 更新当前航向
  currentHeading = compFilterHeading;
  
  // 限制到[-π, π]
  while (currentHeading > PI) currentHeading -= 2 * PI;
  while (currentHeading < -PI) currentHeading += 2 * PI;
}

float controlHeading(float dt) {
  // 航向控制
  
  float angularCmd = 0;
  
  switch(turnMode) {
    case TURN_RATE:
      // 角速度直接控制
      angularCmd = cmd.angular;
      break;
      
    case TURN_ANGLE:
      // 角度控制
      angularCmd = headingCtrl.calculate(targetHeading, currentHeading, dt);
      break;
      
    case TURN_POINT:
      // 定点转向
      angularCmd = turnToPoint(dt);
      break;
  }
  
  return angularCmd;
}

float turnToPoint(float dt) {
  // 转向到目标点
  
  float targetX = 1.0;  // 目标点X
  float targetY = 1.0;  // 目标点Y
  
  // 计算目标角度
  float dx = targetX - state.x;
  float dy = targetY - state.y;
  float targetAngle = atan2(dy, dx);
  
  // 航向控制
  float angularCmd = headingCtrl.calculate(targetAngle, currentHeading, dt);
  
  return angularCmd;
}

void followLine(float dt) {
  // 直线跟踪
  
  float targetX1 = 0, targetY1 = 0;  // 直线起点
  float targetX2 = 2, targetY2 = 2;  // 直线终点
  
  // 设置直线
  lineTracker.setTarget(targetX1, targetY1, targetX2, targetY2);
  
  // 计算直线跟踪修正
  float lineCorrection = lineTracker.calculate(state.x, state.y, 
                                               targetX1, targetY1, 
                                               currentHeading, dt);
  
  // 计算目标航向
  float targetAngle = atan2(targetY2 - targetY1, targetX2 - targetX1);
  
  // 合成角速度命令
  float headingCorrection = headingCtrl.calculate(targetAngle, currentHeading, dt);
  float angularCmd = headingCorrection + lineCorrection;
  
  // 更新命令
  cmd.angular = angularCmd;
  cmd.linear = 0.3;  // 固定线速度
}

6、自适应差速与防滑控制
场景:复杂地形或高速运行,需要自适应控制和防滑。
核心逻辑:负载自适应 + 滑移检测 + 扭矩分配。

#include <SimpleFOC.h>
#include <Filters.h>
#include <PID_AutoTune_v0.h>

// 电流传感器
#define SHUNT_RESISTANCE 0.001  // 1mΩ
#define OPAMP_GAIN 20
InlineCurrentSense currentSenseLeft(SHUNT_RESISTANCE, OPAMP_GAIN, A0, A1, A2);
InlineCurrentSense currentSenseRight(SHUNT_RESISTANCE, OPAMP_GAIN, A3, A4, A5);

// 自适应PID
class AdaptivePID {
private:
  float kp, ki, kd;
  float errorIntegral = 0;
  float lastError = 0;
  float adaptiveGain = 1.0;
  float learningRate = 0.01;
  float errorHistory[10] = {0};
  int historyIndex = 0;
  float outputLimit = 5.0;
  
public:
  AdaptivePID(float p, float i, float d) : kp(p), ki(i), kd(d) {}
  
  float calculate(float error, float dt, float processVariable) {
    // 自适应增益调整
    adaptGain(error, processVariable);
    
    // 更新误差历史
    errorHistory[historyIndex] = error;
    historyIndex = (historyIndex + 1) % 10;
    
    // PID计算
    errorIntegral += error * dt;
    errorIntegral = constrain(errorIntegral, -outputLimit/ki, outputLimit/ki);
    
    float errorDeriv = (error - lastError) / dt;
    lastError = error;
    
    float output = kp * error + ki * errorIntegral + kd * errorDeriv;
    output *= adaptiveGain;
    output = constrain(output, -outputLimit, outputLimit);
    
    return output;
  }
  
  void autoTune(float setpoint, float processVariable, float dt) {
    // 简单的自整定算法
    
    static float lastOutput = 0;
    static unsigned long lastOscillationTime = 0;
    static int oscillationCount = 0;
    
    float output = calculate(setpoint - processVariable, dt, processVariable);
    
    // 检测振荡
    if ((lastOutput > 0 && output < 0) || (lastOutput < 0 && output > 0)) {
      oscillationCount++;
      unsigned long now = millis();
      
      if (oscillationCount > 5) {
        // 检测到持续振荡,调整参数
        float oscillationPeriod = (now - lastOscillationTime) / 1000.0;
        adjustZieglerNichols(oscillationPeriod);
        oscillationCount = 0;
      }
      
      lastOscillationTime = now;
    }
    
    lastOutput = output;
  }
  
  void reset() {
    errorIntegral = 0;
    lastError = 0;
    adaptiveGain = 1.0;
  }
  
private:
  void adaptGain(float error, float processVariable) {
    // 基于误差和过程变量调整增益
    
    // 计算误差统计
    float errorMean = 0;
    for (int i = 0; i < 10; i++) {
      errorMean += errorHistory[i];
    }
    errorMean /= 10.0;
    
    float errorStd = 0;
    for (int i = 0; i < 10; i++) {
      float diff = errorHistory[i] - errorMean;
      errorStd += diff * diff;
    }
    errorStd = sqrt(errorStd / 10.0);
    
    // 自适应调整
    if (errorStd < 0.1) {
      // 误差稳定,减小增益
      adaptiveGain = max(adaptiveGain - learningRate, 0.5);
    } else if (errorStd > 1.0) {
      // 误差波动大,增加增益
      adaptiveGain = min(adaptiveGain + learningRate, 2.0);
    }
    
    // 基于速度调整
    float speed = abs(processVariable);
    if (speed < 0.1) {
      // 低速增加增益
      adaptiveGain *= 1.2;
    } else if (speed > 1.0) {
      // 高速减小增益
      adaptiveGain *= 0.8;
    }
  }
  
  void adjustZieglerNichols(float oscillationPeriod) {
    // Ziegler-Nichols方法调整PID
    
    float Ku = 2.0;  // 临界增益
    float Tu = oscillationPeriod;  // 振荡周期
    
    // Z-N参数
    kp = 0.6 * Ku;
    ki = 1.2 * Ku / Tu;
    kd = 0.075 * Ku * Tu;
  }
};

AdaptivePID leftSpeedPID(2.0, 5.0, 0.1);
AdaptivePID rightSpeedPID(2.0, 5.0, 0.1);

// 滑移检测
class SlipDetection {
private:
  float slipThreshold = 0.3;  // 30%滑移率
  float wheelAccelLimit = 10.0;  // 轮加速度限制
  float lastSpeeds[2] = {0, 0};
  unsigned long lastTime = 0;
  int slipCount[2] = {0, 0};
  bool slipDetected[2] = {false, false};
  
public:
  void update(float leftSpeed, float rightSpeed, float targetLeft, float targetRight) {
    unsigned long now = millis();
    float dt = (now - lastTime) / 1000.0;
    
    if (dt > 0.01) {
      // 计算加速度
      float leftAccel = (leftSpeed - lastSpeeds[0]) / dt;
      float rightAccel = (rightSpeed - lastSpeeds[1]) / dt;
      
      // 检测异常加速度
      if (abs(leftAccel) > wheelAccelLimit) {
        slipCount[0]++;
      } else {
        slipCount[0] = max(slipCount[0] - 1, 0);
      }
      
      if (abs(rightAccel) > wheelAccelLimit) {
        slipCount[1]++;
      } else {
        slipCount[1] = max(slipCount[1] - 1, 0);
      }
      
      // 计算滑移率
      float leftSlip = 0, rightSlip = 0;
      if (abs(targetLeft) > 0.1) {
        leftSlip = abs((targetLeft - leftSpeed) / targetLeft);
      }
      if (abs(targetRight) > 0.1) {
        rightSlip = abs((targetRight - rightSpeed) / targetRight);
      }
      
      // 滑移检测
      slipDetected[0] = (leftSlip > slipThreshold) || (slipCount[0] > 5);
      slipDetected[1] = (rightSlip > slipThreshold) || (slipCount[1] > 5);
      
      lastSpeeds[0] = leftSpeed;
      lastSpeeds[1] = rightSpeed;
      lastTime = now;
    }
  }
  
  bool isSlipping(int wheel) {
    return slipDetected[wheel];
  }
  
  void reset() {
    slipCount[0] = slipCount[1] = 0;
    slipDetected[0] = slipDetected[1] = false;
  }
};

SlipDetection slipDetector;

// 扭矩分配
class TorqueDistributor {
private:
  float leftTorque = 0, rightTorque = 0;
  float maxTorque = 5.0;  // 最大扭矩5Nm
  float torqueBalance = 0.5;  // 扭矩平衡系数
  
public:
  void distribute(float totalTorque, float steering, float slipLeft, float slipRight) {
    // 基础扭矩分配
    leftTorque = totalTorque * torqueBalance;
    rightTorque = totalTorque * (1 - torqueBalance);
    
    // 转向补偿
    leftTorque -= steering * 0.5;
    rightTorque += steering * 0.5;
    
    // 滑移补偿
    if (slipLeft) {
      leftTorque *= 0.7;  // 左轮滑移,降低扭矩
      rightTorque *= 1.1;  // 右轮增加扭矩补偿
    }
    if (slipRight) {
      rightTorque *= 0.7;
      leftTorque *= 1.1;
    }
    
    // 扭矩限制
    leftTorque = constrain(leftTorque, -maxTorque, maxTorque);
    rightTorque = constrain(rightTorque, -maxTorque, maxTorque);
    
    // 更新平衡系数
    updateBalance();
  }
  
  float getLeftTorque() { return leftTorque; }
  float getRightTorque() { return rightTorque; }
  
private:
  void updateBalance() {
    // 自适应调整扭矩平衡
    
    static float leftLoad = 0, rightLoad = 0;
    
    // 读取电流估算负载
    DQCurrent_s leftCurrent = currentSenseLeft.getFOCCurrents();
    DQCurrent_s rightCurrent = currentSenseRight.getFOCCurrents();
    
    leftLoad = sqrt(leftCurrent.d * leftCurrent.d + leftCurrent.q * leftCurrent.q);
    rightLoad = sqrt(rightCurrent.d * rightCurrent.d + rightCurrent.q * rightCurrent.q);
    
    // 调整平衡系数
    float totalLoad = leftLoad + rightLoad;
    if (totalLoad > 0.1) {
      torqueBalance = rightLoad / totalLoad;  // 负载重的轮子分配更多扭矩
    }
    
    torqueBalance = constrain(torqueBalance, 0.3, 0.7);
  }
};

TorqueDistributor torqueDistributor;

// 地形适应
class TerrainAdaptation {
private:
  float terrainRoughness = 0;
  float terrainSlope = 0;
  float lastHeights[4] = {0, 0, 0, 0};
  int sampleCount = 0;
  
public:
  void update(float leftSpeed, float rightSpeed, float leftCurrent, float rightCurrent) {
    // 估算地形粗糙度
    
    // 计算速度波动
    static float lastLeftSpeed = 0, lastRightSpeed = 0;
    float leftAccel = (leftSpeed - lastLeftSpeed) / 0.01;
    float rightAccel = (rightSpeed - lastRightSpeed) / 0.01;
    
    // 粗糙度指标
    float roughness = (abs(leftAccel) + abs(rightAccel)) / 2.0;
    
    // 低通滤波
    terrainRoughness = terrainRoughness * 0.9 + roughness * 0.1;
    
    // 坡度估算
    float currentDiff = leftCurrent - rightCurrent;
    terrainSlope = currentDiff * 0.1;  // 简化估算
    
    lastLeftSpeed = leftSpeed;
    lastRightSpeed = rightSpeed;
  }
  
  float getAdaptationFactor() {
    // 返回地形适应因子
    float factor = 1.0;
    
    if (terrainRoughness > 2.0) {
      factor *= 0.7;  // 粗糙地形降低增益
    }
    if (abs(terrainSlope) > 0.2) {
      factor *= 0.8;  // 坡度降低增益
    }
    
    return factor;
  }
  
  float getRoughness() { return terrainRoughness; }
  float getSlope() { return terrainSlope; }
};

TerrainAdaptation terrainAdapter;

void adaptiveControl(float dt) {
  // 自适应控制
  
  // 1. 读取电流
  DQCurrent_s leftCurrent = currentSenseLeft.getFOCCurrents();
  DQCurrent_s rightCurrent = currentSenseRight.getFOCCurrents();
  
  float leftCurrentMag = sqrt(leftCurrent.d * leftCurrent.d + leftCurrent.q * leftCurrent.q);
  float rightCurrentMag = sqrt(rightCurrent.d * rightCurrent.d + rightCurrent.q * rightCurrent.q);
  
  // 2. 滑移检测
  slipDetector.update(state.leftSpeed, state.rightSpeed, 
                      cmd.linear, cmd.angular);
  
  bool leftSlipping = slipDetector.isSlipping(0);
  bool rightSlipping = slipDetector.isSlipping(1);
  
  // 3. 地形适应
  terrainAdapter.update(state.leftSpeed, state.rightSpeed, 
                       leftCurrentMag, rightCurrentMag);
  float terrainFactor = terrainAdapter.getAdaptationFactor();
  
  // 4. 扭矩分配
  float totalTorque = cmd.linear * 2.0;  // 简化扭矩估算
  float steeringTorque = cmd.angular * 0.5;
  
  torqueDistributor.distribute(totalTorque, steeringTorque, 
                               leftSlipping, rightSlipping);
  
  // 5. 自适应PID控制
  float leftTarget = torqueDistributor.getLeftTorque() * 0.2;  // 转换为速度参考
  float rightTarget = torqueDistributor.getRightTorque() * 0.2;
  
  float leftControl = leftSpeedPID.calculate(leftTarget - state.leftSpeed, 
                                            dt, state.leftSpeed);
  float rightControl = rightSpeedPID.calculate(rightTarget - state.rightSpeed, 
                                              dt, state.rightSpeed);
  
  // 应用地形适应
  leftControl *= terrainFactor;
  rightControl *= terrainFactor;
  
  // 6. 应用控制
  motorLeft.move(leftControl);
  motorRight.move(rightControl);
}

要点解读
差速运动学原理与实现
核心公式:V_left = V_linear - ω * d/2,V_right = V_linear + ω * d/2。案例4的calculateWheelSpeeds()实现了这个转换。
单位转换:控制输入是m/s和rad/s,需要转换为轮子角速度rad/s:ω_wheel = V_linear / R。
运动范围限制:必须同时限制MAX_LINEAR_SPEED和MAX_ANGULAR_SPEED,防止超调。
运动学正解:从轮速计算机器人状态:V_robot = (V_left + V_right)/2,ω_robot = (V_right - V_left)/d。
两种转向控制模式的区别
角速度控制(案例4的TURN_RATE):直接控制机器人旋转速度。响应快,适合遥控操作。但无法精确控制最终朝向。
角度控制(案例5的TURN_ANGLE):控制机器人转到特定角度。需要IMU反馈,精度高。案例5的HeadingController实现闭环控制。
航向归一化:角度控制必须处理-π到π的跳变。案例5的normalizeAngle()函数是关键。
IMU在差速控制中的关键作用
互补滤波:案例5的alpha=0.98融合陀螺仪(短期稳定)和加速度计(长期参考)。这是低成本IMU的标准方法。
航向漂移补偿:纯积分陀螺仪会漂移。需要周期性用加速度计或磁力计校正。
转向中心估算:IMU可检测实际转向中心是否与几何中心匹配,修正运动学模型。
颠簸检测:加速度计可检测地面不平,触发控制参数调整。
自适应与防滑控制策略
滑移检测:案例6通过比较目标速度与实际速度检测滑移。slipThreshold=0.3表示30%的速度差认为是滑移。
加速度检测:轮子加速度突然增大可能表示打滑。wheelAccelLimit=10.0限制加速度。
扭矩重分配:检测到单边滑移时,减少该轮扭矩,增加另一轮扭矩补偿。
地形适应:案例6的terrainAdapter根据电流和速度波动估计地形,调整控制增益。
自适应PID:案例6的AdaptivePID根据误差统计自动调整增益,适应不同负载。
工程实施的性能优化
控制频率分层:
电流环:20kHz(FOC内部)
速度环:100-200Hz
位置环:50-100Hz
航向环:50Hz
加速度限制:案例4的limitAcceleration()防止急启急停。最大加速度2.0m/s²是合理值。
积分抗饱和:所有PID都必须限制积分项。案例4的constrain(errorIntegral, -10, 10)。
安全机制:
指令超时:案例4 CMD_TIMEOUT=1000ms无指令自动停止。
电流保护:监控电机电流,超限降额。
温度保护:监控电机和控制器温度。
编码器选择:至少1000线,4倍频。6.5寸轮子,1000线对应0.2mm分辨率。

注意,以上案例只是为了拓展思路,仅供参考。它们可能有错误、不适用或者无法编译。您的硬件平台、使用场景和Arduino版本可能影响使用方法的选择。实际编程时,您要根据自己的硬件配置、使用场景和具体需求进行调整,并多次实际测试。您还要正确连接硬件,了解所用传感器和设备的规范和特性。涉及硬件操作的代码,您要在使用前确认引脚和电平等参数的正确性和安全性。

在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐