肥仔教程网

SEO 优化与 Web 开发技术学习分享平台

(五)如何一步一步实现两轮平衡车_两轮平衡车怎么调整平衡

通过串口调整KP,KI,KD,初步实现直立平衡

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


MPU6050 mpu;

// PID参数
float kp = 14.0;   // 比例系数
float ki = 0.6;    // 积分系数
float kd = 0.8;    // 微分系数
float ki_limit = 50.0; // 积分限幅

// 控制参数
float target_angle = -5.0;
const int motor_deadzone = 30;
const int motor_max = 255;
const float max_angle = 40.0;   // 最大允许角度
const float min_angle = -40.0;  // 最小允许角度

// PID计算变量
float error = 0, last_error = 0;
float integral = 0, derivative = 0;
float output = 0;

// 控制模式
enum ControlMode { MANUAL, AUTOMATIC };
ControlMode control_mode = AUTOMATIC;

// MPU变量
float angleY = 0;
float gyroY = 0;
unsigned long last_time = 0;

// 电机引脚定义
const int motor1_pwm = 15;
const int motor1_dir = 16;
const int motor2_pwm =17;
const int motor2_dir = 18;

// I2C引脚定义
const int i2c_sda = 41;
const int i2c_scl = 42;

bool mpu_connected = false;

// 串口参数调节相关变量
bool param_adjust_mode = false;
char adjust_param_type = ' ';
String inputString = "";

void initializeMPU6050() ;
void automaticControl();
void readMPU6050();
void stopMotors();
void setMotorSpeed(int pwm, bool forward);
void serialEvent() ;
void displayStatus();
void calculatePID();
void showHelp();


void setup() {
  Serial.begin(115200);
  delay(1000);
  
  Serial.println("PID自平衡小车控制程序");
  
  // 初始化I2C
  Wire.begin(i2c_sda, i2c_scl, 400000);
  delay(100);
  
  // 初始化MPU6050
  initializeMPU6050();
  
  // 设置电机引脚
  pinMode(motor1_pwm, OUTPUT);
  pinMode(motor1_dir, OUTPUT);
  pinMode(motor2_pwm, OUTPUT);
  pinMode(motor2_dir, OUTPUT);
  
  // 初始停止电机
  stopMotors();
  
  last_time = micros();
  
  Serial.println("\n=== PID自平衡小车控制程序 ===");
  Serial.println("角度范围限制: -40° 到 +40°");
  Serial.println("目标角度: " + String(target_angle) + "°");
  Serial.println("当前PID参数: Kp=" + String(kp, 2) + " Ki=" + String(ki, 3) + " Kd=" + String(kd, 2));
  Serial.println("控制模式:");
  Serial.println("  a - 自动平衡模式");
  Serial.println("  m - 手动控制模式");
  Serial.println("手动控制命令:");
  Serial.println("  f - 正转");
  Serial.println("  b - 反转");
  Serial.println("  s - 停止");
  Serial.println("PID参数调节:");
  Serial.println("  p - 调节Kp (或直接输入 p数值,如 p14.0)");
  Serial.println("  i - 调节Ki (或直接输入 i数值,如 i0.1)");
  Serial.println("  d - 调节Kd (或直接输入 d数值,如 d0.8)");
  Serial.println("  r - 重置PID积分项");
  Serial.println("  t - 调整目标角度 (或直接输入 t数值,如 t6.0)");
  Serial.println("  ? - 显示帮助信息");
  Serial.println("当前模式: 自动平衡");
  Serial.println("----------------------------");
}

void initializeMPU6050() {
  mpu.initialize();
  mpu_connected = mpu.testConnection();
  if (mpu_connected) {
    Serial.println("MPU6050连接成功");
    
    // 配置MPU6050
    mpu.setFullScaleGyroRange(MPU6050_GYRO_FS_250);
    mpu.setFullScaleAccelRange(MPU6050_ACCEL_FS_2);
    mpu.setDLPFMode(MPU6050_DLPF_BW_10);
  } else {
    Serial.println("MPU6050连接失败");
  }
}

void loop() {
  // 读取MPU6050数据(如果连接)
  if (mpu_connected) {
    readMPU6050();
  }
  
  // 根据当前模式执行相应控制
  if (control_mode == AUTOMATIC) {
    // 自动平衡模式 - 使用PID控制
    calculatePID();
    automaticControl();
  }
  
  // 显示状态
  static unsigned long last_display = 0;
  if (millis() - last_display > 500) {
    displayStatus();
    last_display = millis();
  }
  
  serialEvent();
  delay(10);
}

void readMPU6050() {
  int16_t ax, ay, az, gx, gy, gz;
  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  
  // 计算角度
  angleY = atan2(ax, az) * 180.0 / PI;
  
  // 计算陀螺仪角速度 (度/秒)
  gyroY = gy / 131.0; // ±250°/s 量程,灵敏度 131 LSB/(°/s)
  
  // 互补滤波:融合加速度计和陀螺仪数据
  unsigned long current_time = micros();
  float dt = (current_time - last_time) / 1000000.0;
  last_time = current_time;
  
  float alpha = 0.96; // 互补滤波系数
  static float angle_filtered = 0;
  angle_filtered = alpha * (angle_filtered + gyroY * dt) + (1 - alpha) * angleY;
  angleY = angle_filtered;
}

void calculatePID() {
  // 计算误差
  error = target_angle - angleY;
  
  // 积分项 (防饱和)
  integral += error;
  if (integral > ki_limit) integral = ki_limit;
  if (integral < -ki_limit) integral = -ki_limit;
  
  // 微分项 (使用陀螺仪数据作为微分项)
  derivative = -gyroY; // 负号是因为方向
  
  // PID输出
  output = kp * error + ki * integral + kd * derivative;
  
  // 限制输出范围
  output = constrain(output, -motor_max, motor_max);
}

void automaticControl() {
  // 检查角度是否超出安全范围
  if (angleY > max_angle || angleY < min_angle) {
    // 角度超出安全范围,停止电机
    stopMotors();
    integral = 0; // 重置积分项
    return;
  }
  
  // 使用PID输出控制电机
  int motor_pwm = abs(output);
  
  // 处理死区
  if (motor_pwm < motor_deadzone) {
    motor_pwm = 0;
  }
  
  // 根据PID输出方向控制电机
  if (output > 0) {
    setMotorSpeed(motor_pwm, true); // 正转
  } else if (output < 0) {
    setMotorSpeed(motor_pwm, false); // 反转
  } else {
    stopMotors(); // 停止
  }
}

void stopMotors() {
  // 设置PWM为0
  analogWrite(motor1_pwm, 0);
  analogWrite(motor2_pwm, 0);
  
  // 设置方向引脚为确定状态
  digitalWrite(motor1_dir, LOW);
  digitalWrite(motor2_dir, LOW);
}

void setMotorSpeed(int pwm, bool forward) {
  // 如果PWM为0,停止电机
  if (pwm == 0) {
    stopMotors();
    return;
  }
  
  // 处理死区
  if (pwm < motor_deadzone) {
    stopMotors();
    return;
  }
  
  // 限制PWM值
  pwm = constrain(pwm, motor_deadzone, motor_max);
  
  // 控制电机
  if (forward) {
    digitalWrite(motor1_dir, HIGH);
    digitalWrite(motor2_dir, HIGH);
  } else {
    digitalWrite(motor1_dir, LOW);
    digitalWrite(motor2_dir, LOW);
  }
  
  analogWrite(motor1_pwm, pwm);
  analogWrite(motor2_pwm, pwm);
}

void displayStatus() {
  Serial.print("角度: ");
  Serial.print(angleY, 1);
  Serial.print("° | 目标: ");
  Serial.print(target_angle, 1);
  Serial.print("° | 误差: ");
  Serial.print(error, 2);
  Serial.print(" | PID输出: ");
  Serial.print(output, 2);
  
  Serial.print(" | 积分: ");
  Serial.print(integral, 2);
  Serial.print(" | 微分: ");
  Serial.print(derivative, 2);
  
  Serial.print(" | PID参数: Kp=");
  Serial.print(kp, 2);
  Serial.print(" Ki=");
  Serial.print(ki, 3);
  Serial.print(" Kd=");
  Serial.print(kd, 2);
  
  Serial.print(" | 模式: ");
  Serial.print(control_mode == AUTOMATIC ? "自动" : "手动");
  
  // 显示角度状态
  if (angleY > max_angle || angleY < min_angle) {
    Serial.print(" | 状态: 超出安全范围!");
  } else if (output > 0) {
    Serial.print(" | 状态: 正转");
  } else if (output < 0) {
    Serial.print(" | 状态: 反转");
  } else {
    Serial.print(" | 状态: 平衡");
  }
  
  Serial.println();
}

// 处理直接参数设置命令 (如 "p14.0")
bool processDirectCommand(String command) {
  if (command.length() < 2) return false;
  
  char param_type = command.charAt(0);
  String value_str = command.substring(1);
  float new_value = value_str.toFloat();
  
  switch(param_type) {
    case 'p': // kp
      kp = new_value;
      Serial.print("Kp设置为: ");
      break;
    case 'i': // ki  
      ki = new_value;
      Serial.print("Ki设置为: ");
      break;
    case 'd': // kd
      kd = new_value;
      Serial.print("Kd设置为: ");
      break;
    case 't': // 目标角度
      target_angle = new_value;
      Serial.print("目标角度设置为: ");
      break;
    default:
      return false; // 未知命令
  }
  
  Serial.println(new_value, 4);
  displayStatus();
  return true;
}

void serialEvent() {
  while (Serial.available()) {
    char cmd = Serial.read();
    
    if (param_adjust_mode) {
      // 参数调节模式 (两步输入)
      if (cmd == '\n') {
        float new_value = inputString.toFloat();
        
        switch(adjust_param_type) {
          case 'p': // kp
            kp = new_value;
            Serial.print("Kp设置为: ");
            break;
          case 'i': // ki  
            ki = new_value;
            Serial.print("Ki设置为: ");
            break;
          case 'd': // kd
            kd = new_value;
            Serial.print("Kd设置为: ");
            break;
          case 't': // 目标角度
            target_angle = new_value;
            Serial.print("目标角度设置为: ");
            break;
        }
        
        Serial.println(new_value, 4);
        displayStatus();
        
        // 退出参数调节模式
        param_adjust_mode = false;
        adjust_param_type = ' ';
        inputString = "";
        
        Serial.println("输入指令继续调节 (p:Kp, i:Ki, d:Kd, t:目标角度, ?:帮助)");
      } else {
        inputString += cmd;
      }
    } else {
      // 命令模式
      switch(cmd) {
        case 'm': // 切换到手动模式
          control_mode = MANUAL;
          stopMotors();
          integral = 0; // 重置积分项
          Serial.println("切换到手动控制模式");
          break;
          
        case 'a': // 切换到自动模式
          control_mode = AUTOMATIC;
          integral = 0; // 重置积分项
          Serial.println("切换到自动平衡模式");
          break;
          
        case 'f': // 正转 (仅在手动模式下有效)
          if (control_mode == MANUAL) {
            setMotorSpeed(100, true);
            Serial.println("正转");
          } else {
            Serial.println("请在手动模式下使用此命令");
          }
          break;
          
        case 'b': // 反转 (仅在手动模式下有效)
          if (control_mode == MANUAL) {
            setMotorSpeed(100, false);
            Serial.println("反转");
          } else {
            Serial.println("请在手动模式下使用此命令");
          }
          break;
          
        case 's': // 停止 (在两种模式下都有效)
          stopMotors();
          integral = 0; // 重置积分项
          Serial.println("停止");
          break;
          
        case 'p': // 调节Kp (两步输入)
          adjust_param_type = 'p';
          param_adjust_mode = true;
          inputString = "";
          Serial.println("请输入Kp值 (当前值: " + String(kp, 2) + "): ");
          break;
          
        case 'i': // 调节Ki (两步输入)
          adjust_param_type = 'i';
          param_adjust_mode = true;
          inputString = "";
          Serial.println("请输入Ki值 (当前值: " + String(ki, 3) + "): ");
          break;
          
        case 'd': // 调节Kd (两步输入)
          adjust_param_type = 'd';
          param_adjust_mode = true;
          inputString = "";
          Serial.println("请输入Kd值 (当前值: " + String(kd, 2) + "): ");
          break;
          
        case 't': // 调节目标角度 (两步输入)
          adjust_param_type = 't';
          param_adjust_mode = true;
          inputString = "";
          Serial.println("请输入目标角度值 (当前值: " + String(target_angle, 1) + "): ");
          break;
          
        case 'r': // 重置积分项
          integral = 0;
          Serial.println("PID积分项已重置");
          break;
          
        case '?': // 显示帮助
          showHelp();
          break;
          
        case '\n': // 忽略换行符
          break;
          
        default:
          // 尝试处理直接命令 (如 "p14.0")
          String fullCommand = String(cmd);
          while (Serial.available()) {
            char nextChar = Serial.read();
            if (nextChar == '\n') break;
            fullCommand += nextChar;
          }
          
          if (!processDirectCommand(fullCommand)) {
            Serial.println("未知命令,输入 ? 查看帮助");
          }
          break;
      }
    }
  }
}

void showHelp() {
  Serial.println("\n=== 自平衡小车控制命令 ===");
  Serial.println("模式控制:");
  Serial.println("  a - 切换到自动平衡模式");
  Serial.println("  m - 切换到手动控制模式");
  Serial.println("手动控制:");
  Serial.println("  f - 正转");
  Serial.println("  b - 反转");
  Serial.println("  s - 停止");
  Serial.println("PID参数调节 (两种方式):");
  Serial.println("  方式1 - 分两步输入:");
  Serial.println("    p - 调节比例系数 Kp");
  Serial.println("    i - 调节积分系数 Ki");
  Serial.println("    d - 调节微分系数 Kd");
  Serial.println("    t - 调整目标角度");
  Serial.println("  方式2 - 直接输入:");
  Serial.println("    p14.0 - 设置Kp为14.0");
  Serial.println("    i0.1 - 设置Ki为0.1");
  Serial.println("    d0.8 - 设置Kd为0.8");
  Serial.println("    t6.0 - 设置目标角度为6.0°");
  Serial.println("其他:");
  Serial.println("  r - 重置PID积分项");
  Serial.println("  ? - 显示此帮助信息");
  Serial.println("----------------------------");
  Serial.println("当前PID参数: Kp=" + String(kp, 2) + " Ki=" + String(ki, 3) + " Kd=" + String(kd, 2));
  Serial.println("目标角度: " + String(target_angle) + "°");
  Serial.println("----------------------------");
}


控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言