Self Balancing Robot (Erster Erfolg - Video)

Ja, ich hab vor langer Zeit einen Roboter mit einer Achse gemacht per H-Bridge. Er hatte Vorne und Hinten Stützen. Nun habe ich einen Gyrosensor (MPU6050) und möchte dass er balanciert.

Gyrosensor liefert grob Werte im Stillstand, also wenn er gerade Steht, zwischen 2200 und 2600. Sprich: In diesem Bereich rauscht es.

So Naiv wie ich bin, taste ich mich immer an eine Problemstellung ran: Logischer Hintergrund: Wenn der Roboter einen bestimmten Wert (z.b 2600) übersteigt oder kleiner ist als 2200. Dann müssen die Motoren in eine entsprechende Richtung Drehen. Feinheiten wie: Dauer, Kraft würde ich dann per Try and Error anpassen.

Im Moment wackelt der Roboter unkontrolliert hin und her, scheint nicht klar zu kommen. Also muss ich etwas übersehen haben.

Müssen es Servomotoren sein? Ich mein, es müsste doch mit jedem Motor gehen. Ich habe normale DC-Motoren dran aus nem Legoset meiner Kindheit.

Falls es NICHT unbedingt Servo sein muss, frage ich mich woran dieses unkontrollierte hin und her Wackeln kommt. Ich hab da jetzt viele mögliche Ideen, warum das so sein könnte, die ich jetzt nicht unbedingt alle aufzählen werde.

Könnt ihr mir da helfen?

(Benutze die I2C Lib mit der MPU6050 lib)

hi,

volvodani rollt sicher bald rein. er hat sich einen segway gebaut und kann Dir sicher tips geben...

gruß stefan

Hier mal mein
bisheriger Quellcode. Die Werte habe ich angepasst
auf die Gleichgewichtslage meines Roboters!

Ausserdem ist jetzt der Pegel auf die Motoren (Also die Kraft),
angepasst an den Winkel. Also Größerer abweichender Winkel = Größere Kraft!
Nah an der Gleichgewichtslage = Geringere Kraft!

Sieht jetzt etwas besser aus, allerdings wackelt er trotzdem
unkontrolliert hin und her und gerät relativ schnell ausser Kontrolle!

 accelgyro.getMotion6(&gx, &gy, &gz, &ax, &ay, &az);

#ifdef OUTPUT_READABLE_ACCELGYRO


    if(gz > 6200)
    {
      gyro = gz * 0.039;
      gyl = gyro - 255;
      if (gyl < 0)
        gyl = 0;
      if (gyl > 255)
        gyl = 255;


      analogWrite(mState1, gyl);
      analogWrite(mState2, gyl);

      digitalWrite(m1B, LOW);
      digitalWrite(m2B, LOW);
      digitalWrite(m1A, HIGH);
      digitalWrite(m2A, HIGH);


    }
    if(gz < 5900)
    {
      gyro = gz * 0.043;
      gyl =  255 -gyro;

      if (gyl < 0)
        gyl = 0;
      if (gyl > 255)
        gyl = 255;

      Serial.println(gyl);
      analogWrite(mState1, gyl);
      analogWrite(mState2, gyl);

      digitalWrite(m1B, HIGH);
      digitalWrite(m2B, HIGH);
      digitalWrite(m1A, LOW);
      digitalWrite(m2A, LOW);

    }


#endif

Ähm, kurze Zwischenfrage: Wenn ich die Lib richtig gelesen habe, liest du mit dem getMotion6 nicht die Winkel sondern die WInkelgeschwindigkeiten aus. Da soltest du dir vielleicht eine andere Auslesemethode suchen.

Generell würde ich dir mal im Internet nach dem Thema PID - Regler zu Googlen. Damit kannst du, je nachdem wie gut der Regler laufen soll, mit relativ wenig Aufwand ein solides Resultat erreichen, wenn du die Abweichung deines IST - Wertes ( Der Winkel aus dem IMU) von deinem SOLL-Wert ( Winkel bei dem der Roboter senkrecht steht) kennst.

Da ich hier in Gent in bei einer IBN sitzte kommt nur ne kurze Antwort raus. Und zwar brauchst du einen KomplimentärFilter oder Besser noch einen KalmanFilter. Mit diesem bekommst du dann den absoluten winkel gegenüber der "Erde" raus diesenpackt du dann als Sollwert auf einen PID Regler und dann musst du nur noch dafür sorgen das due evtl. (je nach trägheit deines Systems) bei großen Winkelabweichung etwas andere P- I- D- werte nimmst. Schau mal hier: https://github.com/TKJElectronics/KalmanFilter Unter examples findest du auch ein Beispiel mit einem Kalmanfilter der dir den Winkel über Serialprint rausgibt. Genauso brauchst du einen Regler da du nie deinen gewünschten Wert rausbekommst. Das heisst er wird immer übersteuern. Diesen Sketch versiehst du dann noch mit einem PID Regler http://playground.arduino.cc/Code/PIDLibrary Diesen muss dann nur noch tunen (auch hier gibt es in den Beispielen einen der unterschiedliche Werte nimmt bei unterschiedlichen abweichungen. Ich will ungern einfach meinem kompletten Sketch vom Segway reinpacken. Hier ist eher Hilfe zur Selbsthilfe.

Versuch mal dein "Glück" Hilfe wirst du hier sicher immer bekommen.

Also mein Programm sieht so aus 1. Winkel via Kalman auslesen und Filtern 2. Je nach Position deines Sensors ist der "Sollwert" für den PID 180° / 0° oder entsprechend 3. Die Grenzen des PID Ouptuts auf + und - Werte setzten (damit er vorwärts und rückwärts fahen kann). 4. Den unterscheid zum Soll brechenen und wenn die Abweichung z.B. >5° ist aggressivere PID Werten nehmen (Beispiel aus PID Lib) 5. Ansteuern deiner H-Brücke

P.S. Noch eine Anmerkung ich habe die Erfahrung mit der kleine L293D H-Brücke gemacht das die ziemlich Träge ist wenn sie an die Stromgrenze kommt, also d.h. wenn du beim Beschleunigen an die Grenze von 500mA kommst wird sie nur warm und schlatet ab und klatsch das dingen liegt am Boden.

Noch ein Tip nimm mal ein paar Potis zum einstellen der Werte dann musst du nicht jedes mal das Programm neu übertragen wenn du einen Wert änderst

Viel Erfolg Gruß Der Dani

Ich danke dir für deine dennoch für mich ausführliche Antwort. Ich werde mich an deine Ratschläge rantasten und ggf in nächster Zeit öfters hier meine Fragen stellen, muss mich jetzt erstmal reinlesen :-)

So, der Kalmanfilter funktioniert. Es läuft auch viel Smoother. Hab bisher aber noch keine PID-Regelung. Ich muss ehrlichgesagt zugeben: Ich weiss zwar, dass der Kalmanfilter wohl Messfehler korrigiert. Aber wie, keine Ahnung. Und das frustriert etwas.

Mag mir jemand grob erklären, was für ein Prinzip dahinter steckt? Dann tu' ich mich einfach hinterher leichte, mich in dieses Thema einzulesen.

Achja der Zwischenstand: Der Roboter reagiert jetzt , nach dem ich noch mit ner Linearen Gleichung den Ansteuerungspegel für die Motoren angeglichen habe (abhängig vom Winkel, Maximaler Winkel und GleichgewichtsWinkel) viel ruihger. Allerdings kommt er nach einiger Zeit ins Schwanken und schaukelt sich wie in einer Resonanzüberhöhung ziemlich auf.

Und genau da kommt wohl die Sache mit dem PID ins Spiel oder?

Also wenn ich das noch richtig aus der Uni in Erinnerung habe, geht der Kalman Filter davon aus, dass wir ein Messsignal haben, das nicht 100%ig dem korrekten Wert entspricht. Da verschiedene Messgrößen oder berechnete Größen in einem physikalischen Zusammenhang stehen, kann man diese nutzen, um für den das Messsignal eine Korrekturgröße zu bestimmen, mithilfe derer diese Zusammenhänge möglichst sauber erfüllt werden.
Sollte ich da was im Kopf verwürfelt haben, bitte korrigieren :slight_smile: müsste aber eigentlich passen.

Das Problem bei deinem jetzigen “Regler” ist, wenn ich das richtig gesehen habe, dass du feste Begrenzungen hast bzw. deine Stellgröße etwas springen kann. So etwas kann das ganze System instabil machen. Ein anderes Problem kann die Motoransteuerung sein. Du nutzt ja nur im Endeffekt ein Einschalt und Ausschaltsignal. Hier sollte der Motor eine definierte Aktion ausführen. Eventuell musst du hier einen zusätzlichen (unterlagerten) Drehzahlregler für den Motor vorsehen, damit das “System Motor” als solches schon stabil funktioniert.

Das Überschwingen könnte entstehen, weil ein P-Regler keine Stellgröße mehr bildet wenn die Regeldifferenz 0 ist. Der I-Anteil behebt dieses Problem.

Also dieses PID-Tuning macht mich wahnsinnig.
Ich spiel zurzeit mit diesen Werten rum und mit der PID-SampleTime.
Default ist ja 200 ms. Ich hab jetzt einfach mal auf 10 ms gesetzt.

Im Moment verhält der Roboter sich so:
Er kippt, nach dem er anfangs Vibriert auf eine Seite, und beschleunigt in diese Richtung um das
zu Kompensieren, aber anstatt sich wieder aufzurichten, rast er mir davon!

Hier mal der Aktuelle Code:

Definitionen:

//###################################################
//###################################################
int mState1 = 2; 
int m1A = 28;
int m1B = 30;

int mState2 = 4;
int m2A = 32;
int m2B = 34;

double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint,8,4,20, DIRECT);

/* 
Kp: Determines how aggressively the PID reacts to the current amount of error (Proportional) (double >=0)
Ki: Determines how aggressively the PID reacts to error over time (Integral) (double>=0)
Kd: Determines how aggressively the PID reacts to the change in error (Derivative) (double>=0)
*/
double safeAngle = 240; // 240 = Gleichgewicht

//###################################################
//###################################################

Setup:

  Setpoint = safeAngle;
  myPID.SetMode(AUTOMATIC);
 myPID.SetOutputLimits(-255, 255);
 myPID.SetSampleTime(10);

Steuerung mit KalmanFilter:

void loop() {

  while(i2cRead(0x3B,i2cData,14));
  accX = ((i2cData[0] << 8) | i2cData[1]);
  accY = ((i2cData[2] << 8) | i2cData[3]);
  accZ = ((i2cData[4] << 8) | i2cData[5]);
  tempRaw = ((i2cData[6] << 8) | i2cData[7]);  
  gyroX = ((i2cData[8] << 8) | i2cData[9]);
  gyroY = ((i2cData[10] << 8) | i2cData[11]);
  gyroZ = ((i2cData[12] << 8) | i2cData[13]);

  accXangle = (atan2(accY,accZ)+PI)*RAD_TO_DEG;
  accYangle = (atan2(accX,accZ)+PI)*RAD_TO_DEG;

  double gyroXrate = (double)gyroX/131.0;
  double gyroYrate = -((double)gyroY/131.0);
  gyroXangle += gyroXrate*((double)(micros()-timer)/1000000); // Calculate gyro angle without any filter  
  gyroYangle += gyroYrate*((double)(micros()-timer)/1000000);

  compAngleX = (0.93*(compAngleX+(gyroXrate*(double)(micros()-timer)/1000000)))+(0.07*accXangle); // Calculate the angle using a Complimentary filter
  compAngleY = (0.93*(compAngleY+(gyroYrate*(double)(micros()-timer)/1000000)))+(0.07*accYangle);

  kalAngleX = kalmanX.getAngle(accXangle, gyroXrate, (double)(micros()-timer)/1000000); // Calculate the angle using a Kalman filter
  kalAngleY = kalmanY.getAngle(accYangle, gyroYrate, (double)(micros()-timer)/1000000);
  timer = micros();

  temp = ((double)tempRaw + 12412.0) / 340.0;
  /*


  //################## ROBOT CODE ######################
  //################## ROBOT CODE ######################
  //################## ROBOT CODE ######################


  Input = kalAngleY;
  myPID.Compute();
Serial.print(abs(Output));Serial.print("    ");Serial.println(kalAngleY);

  if(Output < 0) // NACH VORNE FAHREN
  {

//     Serial.print(abs(Output));Serial.print("    ");Serial.println(kalAngleY);
    
    analogWrite(mState1, abs(Output));
    analogWrite(mState2, abs(Output));

    digitalWrite(m1B, HIGH);
    digitalWrite(m2B, HIGH);
    digitalWrite(m1A, LOW);
    digitalWrite(m2A, LOW);

  }
  if(Output > 0)
  {
    
//       Serial.print(abs(Output));Serial.print("    ");Serial.println(kalAngleY);
    analogWrite(mState1, abs(Output));
    analogWrite(mState2, abs(Output));

    digitalWrite(m1B, LOW);
    digitalWrite(m2B, LOW);
    digitalWrite(m1A, HIGH);
    digitalWrite(m2A, HIGH);
  }
}

Es gibt einen Unterschied zwischen “quote” und “code” beim Formattieren :slight_smile:

Um PID Regler einzustellen gibt es feste Verfahren:
http://www.rn-wissen.de/index.php/Regelungstechnik#Dimensionierung_des_Reglers

Man fängt erst mal mit einem reinen P-Regler an bestimmt einen vernünftigen Kp Wert. Erst danach gibt man den I- und D-Anteil hinzu.

Dein D-Anteil scheint mir sehr hoch zu sein. Kein Wunder, dass der Regler da so aggressiv auf Änderungen reagiert. Der ist sowieso nur dazu da, damit langsame Regler (z.B. mit Integral-Strecken) schnell auf Änderungen reagieren können. Bei schnellen System wie dem hier reicht da deshalb meistens ein sehr kleiner Kd Wert.

hi,

ist wirklich nicht böse gemeint, aber:

Er kippt, nach dem er anfangs Vibriert auf eine Seite, und beschleunigt in diese Richtung um das zu Kompensieren, aber anstatt sich wieder aufzurichten, rast er mir davon!

ich stell's mir grade vor, und ich muß ziemlich lachen...

gruß stefan

@Eisbär :D :D :D :D, hättest sehen sollen wie ich hinterher gerannt bin!

Oh Leute Leute... Ich hatte nicht nachgedacht! Der Gyrosensor war vorne befestigt (Weißes Breadboard). Das kann natürlich nicht ordentlich funktionieren!

Ich habe jetzt den Sensor mittig über die Achse gebaut und siehe da: Der Roboter schlägt nicht mehr auf. Er hält tatsächlich das Gleichgewicht. Das einzige was es jetzt noch zu tunen gilt: Er fährt nach ner Zeit LEICHT vor oder Zurück, man muss den dann immer etwas zurückstupsen. Aber das Ergebniss ist jetzt schon sehr beeindruckend :)

Hier mal der Zwischenstand auf Video. Ich werde morgen mal versuchen ihn völlig ruihg zu bekommen. Es sind ja immerhin an die 6 Stellschrauben an denen man da drehen kann :-)

http://www.youtube.com/watch?v=ku7puhVka-c

Danke an euch alle . Ihr habt mir gut geholfen :-)

Starke Leistung! Pluskarma verdient! :D

Sieht ganz gut schon aus ;) Gut das ich mein Lego Krams damals behalten hab ;)

Perfekt umgesetzt. Sieht echt genial aus. Sieht nur nach Feintuning aus. (wobei die bei so leichtem Spielzeug als wesentlich ungefählciher rausstellt als bei einem DIY Segway) Packst du auch mal deinen Aktuellen sketch hier rein. Gruß Der Dani

Hast du etwa ein RCX-Set geschlachtet?? Würd ich mir nie trauen-hab selber ein noch komplettes...und bastele auch damit ab und zu. Nen Segway übrigens auch vor einigen Jahren mal, aber nur mit zwei Lichtsensoren- entsprechend war das Ergebnis auch.

Deiner macht seine Sache doch schon recht toll. Dass er nach einer Weile anfängt, loszumarschieren, halte ich ganz einfach für Drift- eventuell läuft da der Regler langsam, aber sicher aus dem normalen Bereich?

Hier mal der aktuelle Code:

#include <PID_v1.h>
#include <Wire.h>
#include "Kalman.h" // Source: https://github.com/TKJElectronics/KalmanFilter

Kalman kalmanX; // Create the Kalman instances
Kalman kalmanY;

/* IMU Data */
int16_t accX, accY, accZ;
int16_t tempRaw;
int16_t gyroX, gyroY, gyroZ;


double accXangle, accYangle; // Angle calculate using the accelerometer
double temp; // Temperature
double gyroXangle, gyroYangle; // Angle calculate using the gyro
double compAngleX, compAngleY; // Calculate the angle using a complementary filter
double kalAngleX, kalAngleY; // Calculate the angle using a Kalman filter

uint32_t timer;
uint8_t i2cData[14]; // Buffer for I2C data

const uint8_t IMUAddress = 0x68; // AD0 is logic low on the PCB
const uint16_t I2C_TIMEOUT = 1000; // Used to check for errors in I2C communication

uint8_t i2cWrite(uint8_t registerAddress, uint8_t data, bool sendStop) {
  return i2cWrite(registerAddress,&data,1,sendStop); // Returns 0 on success
}

uint8_t i2cWrite(uint8_t registerAddress, uint8_t *data, uint8_t length, bool sendStop) {
  Wire.beginTransmission(IMUAddress);
  Wire.write(registerAddress);
  Wire.write(data, length);
  uint8_t rcode = Wire.endTransmission(sendStop); // Returns 0 on success
  if (rcode) {
    Serial.print(F("i2cWrite failed: "));
    Serial.println(rcode);
  }
  return rcode; // See: http://arduino.cc/en/Reference/WireEndTransmission
}

uint8_t i2cRead(uint8_t registerAddress, uint8_t *data, uint8_t nbytes) {
  uint32_t timeOutTimer;
  Wire.beginTransmission(IMUAddress);
  Wire.write(registerAddress);
  uint8_t rcode = Wire.endTransmission(false); // Don't release the bus
  if (rcode) {
    Serial.print(F("i2cRead failed: "));
    Serial.println(rcode);
    return rcode; // See: http://arduino.cc/en/Reference/WireEndTransmission
  }
  Wire.requestFrom(IMUAddress, nbytes,(uint8_t)true); // Send a repeated start and then release the bus after reading
  for (uint8_t i = 0; i < nbytes; i++) {
    if (Wire.available())
      data[i] = Wire.read();
    else {
      timeOutTimer = micros();
      while (((micros() - timeOutTimer) < I2C_TIMEOUT) && !Wire.available());
      if (Wire.available())
        data[i] = Wire.read();
      else {
        Serial.println(F("i2cRead timeout"));
        return 5; // This error value is not already taken by endTransmission
      }
    }
  }
  return 0; // Success
} 

//##################### Motoren H-Bridge ############
//###################################################
int mState1 = 2; 
int m1A = 28;
int m1B = 30;

int mState2 = 4;
int m2A = 32;
int m2B = 34;

String gData = "";
double torQ = 0;
int onOff = HIGH;
// ########## Tuning Werte ############

double safeAngle = 175; // 173 = Gleichgewicht; Ohne Batterie = 168.5
double kp=26, ki=26, kd=0.55; //Bisher ganz gute Werte: 20,  9,  0.5
int smpTime = 5; //PID-SampleTime
int limitPID = 250;

//######################

double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint,kp,ki,kd, DIRECT);

/* 
 Kp: Determines how aggressively the PID reacts to the current amount of error (Proportional) (double >=0)
 Ki: Determines how aggressively the PID reacts to error over time (Integral) (double>=0)
 Kd: Determines how aggressively the PID reacts to the change in error (Derivative) (double>=0)
 */


//###################################################
//###################################################


void setup() {  
  Serial.begin(115200);
  Serial1.begin(115200);
  Wire.begin();
  i2cData[0] = 7; // Set the sample rate to 1000Hz - 8kHz/(7+1) = 1000Hz
  i2cData[1] = 0x00; // Disable FSYNC and set 260 Hz Acc filtering, 256 Hz Gyro filtering, 8 KHz sampling
  i2cData[2] = 0x00; // Set Gyro Full Scale Range to ±250deg/s
  i2cData[3] = 0x00; // Set Accelerometer Full Scale Range to ±2g
  while(i2cWrite(0x19,i2cData,4,false)); // Write to all four registers at once
  while(i2cWrite(0x6B,0x01,true)); // PLL with X axis gyroscope reference and disable sleep mode 

    while(i2cRead(0x75,i2cData,1));
  if(i2cData[0] != 0x68) { // Read "WHO_AM_I" register
    Serial.print(F("Error reading sensor"));
    while(1);
  }

  delay(100); // Wait for sensor to stabilize

  while(i2cRead(0x3B,i2cData,6));
  accX = ((i2cData[0] << 8) | i2cData[1]);
  accY = ((i2cData[2] << 8) | i2cData[3]);
  accZ = ((i2cData[4] << 8) | i2cData[5]);

  accYangle = (atan2(accX,accZ)+PI)*RAD_TO_DEG;
  accXangle = (atan2(accY,accZ)+PI)*RAD_TO_DEG;

  kalmanX.setAngle(accXangle); 
  kalmanY.setAngle(accYangle);
  gyroXangle = accXangle;
  gyroYangle = accYangle;
  compAngleX = accXangle;
  compAngleY = accYangle;

  timer = micros();

  pinMode(mState1, OUTPUT);
  pinMode(mState2, OUTPUT);

  Setpoint = safeAngle;
  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(0 - limitPID, limitPID);
  myPID.SetSampleTime(smpTime);


}

void loop() {

  while(i2cRead(0x3B,i2cData,14));
  accX = ((i2cData[0] << 8) | i2cData[1]);
  accY = ((i2cData[2] << 8) | i2cData[3]);
  accZ = ((i2cData[4] << 8) | i2cData[5]);
  tempRaw = ((i2cData[6] << 8) | i2cData[7]);  
  gyroX = ((i2cData[8] << 8) | i2cData[9]);
  gyroY = ((i2cData[10] << 8) | i2cData[11]);
  gyroZ = ((i2cData[12] << 8) | i2cData[13]);

  accXangle = (atan2(accY,accZ)+PI)*RAD_TO_DEG;
  accYangle = (atan2(accX,accZ)+PI)*RAD_TO_DEG;

  double gyroXrate = (double)gyroX/131.0;
  double gyroYrate = -((double)gyroY/131.0);
  gyroXangle += gyroXrate*((double)(micros()-timer)/1000000);
  gyroYangle += gyroYrate*((double)(micros()-timer)/1000000);

  compAngleX = (0.93*(compAngleX+(gyroXrate*(double)(micros()-timer)/1000000)))+(0.07*accXangle); 
  compAngleY = (0.93*(compAngleY+(gyroYrate*(double)(micros()-timer)/1000000)))+(0.07*accYangle);

  kalAngleX = kalmanX.getAngle(accXangle, gyroXrate, (double)(micros()-timer)/1000000); 
  kalAngleY = kalmanY.getAngle(accYangle, gyroYrate, (double)(micros()-timer)/1000000);
  timer = micros();

  temp = ((double)tempRaw + 12412.0) / 340.0;



  //################## ROBOT CODE ######################
  //################## ROBOT CODE ######################
  //################## ROBOT CODE ######################

  gData += (char)Serial1.read();

  if(gData == "0")
    Setpoint = safeAngle;
      if(gData == "1")
    Setpoint = safeAngle - 1;
  if(gData == "2")
    Setpoint = safeAngle + 1;

  if(gData == "3")

  if(gData == "4")


  gData = "";
  Input = kalAngleY;
  myPID.Compute();

  torQ = Output;
   onOff = HIGH; 

  if(abs(Output) < 1)
   {
     onOff = LOW;
    //  torQ = 1;
   }



  if(Output > 0) // NACH VORNE FAHREN
  {

    analogWrite(mState1, abs(torQ)+((limitPID) + (255 - limitPID))); 
    analogWrite(mState2, abs(torQ)+((limitPID) + (255 - limitPID))+5);

    digitalWrite(m1B, onOff);
    digitalWrite(m2B, onOff);
    digitalWrite(m1A, LOW);
    digitalWrite(m2A, LOW);


  }
  if(Output < 0)
  {

    analogWrite(mState1, abs(torQ)+((limitPID) + (255 - limitPID))); //Anpassen an die Grenzen des PID-Korrekturwertes
    analogWrite(mState2, abs(torQ)+((limitPID) + (255 - limitPID))+5);   //Inkl. Individuelle Anpassung an unterschiede der beiden Motoren

    digitalWrite(m1B, LOW);
    digitalWrite(m2B, LOW);
    digitalWrite(m1A, onOff);
    digitalWrite(m2A, onOff);


  }


}

Egal bei welchen PID-Werden... :-(

Kann es sein, dass du an den Grenzen der Sensor-Auflösung bist? Wenn du den Sensor weiter weg von den Rädern ( höher ) montierst, kannst du die Regelungsparameter etwas sanfter einstellen ...

Aber das sieht doch schon mal super aus, Glückwunsch! Klar hast du nur eine Gleichgewichtsregelung und keine Positionsregelung, also driftet er dir langsam davon, was sonst ;) Wie reagiert er eigentlich auf sanftes Schieben ?