PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Softwarekonzept für autonomen Roboter



pemiso
11.02.2013, 11:10
Hallo Forum,

nach vielen Prinziptests mit Motorsteuerungen und Sensoren bin ich dabei, einen Rasenmährobbi aufzubauen.

Für die Motoransteuerung mit DualMc33926 habe ich für Arduino Routinen entwickelt, die auf Richtungs- und Geschwindigkeitsänderung hin eine sanfte Korrektur ohne Beschleunigungs- und Stromspitzen bewirken. Momentan arbeite ich im Testbetrieb mit Delays. Das führt dazu, dass die Motoren natürlich dummerweise immer nur mit entsprechender Zeitverzögerung nacheinander angesteuert werden. 1. Ziel ist, aus dem loop beide Maschinen quasi zeitgleich zu steuern, wobei die jeweilige Richtungs- und Geschwindigkeitsanpassung "im Hintergrund" laufen soll. Das werde ich dann wohl per Interrupt machen müssen.

Aus diversen Beiträgen habe ich entnommen, dass vom Grundgedanken her folgendes anzustreben sei:

Interrupts liefern Zustände einzelner Sensoren. Diese werden in Zustandsvariablen für den loop-Zugriff verfügbar gemacht.
Beispiel: Sonar liefert Abstand vorn < 10 cm, hinten, rechts und links frei.

Im Loop werden abhängig vom Systemzustand Reaktionsroutinen aufgerufen. Diese müssten dann Schlussfolgerungen aus den Parameterkombinationen ziehen und als Reaktion andere Parameter ändern.
Beispiel: loop erkennt Kollosionsgefahr und setzt die Geschwindigkeitsvorgabe beider Radantriebe auf 0.

Interruptroutine der Motorsteuerung erkennt, Zielwerte weichen von aktuellen Werten ab und setzt die neuen Vorgeben an den Motoren um.

Sonarinterrupt liefert Abstand vorn < 7 cm, hinten, rechts und links frei.

Loop leitet Wendemanöver ein.

...

Zu den beschriebenen beispielhaften Anforderungen kommen weitere Peripherieauswertungen: LCD-Ausgabe, ArduinoManager remote control, Bedienpanelauswertung Folientastatur, Bumperkontakte, IR-Signale zur Navigation, Regensensor, Messung der Akkukapazität, Messung der Stromaufnahmen der Antriebe insbesondere des Mähwerks, Regensensor, Radencoder zur ODOmetrie-Navigation.

Was mir fehlt, ist das Wissen über das Zusammenspiel zwischen loop und Interruptroutinen zur Quasiparallelisierung. Konkret am Beispiel der Motorsteuerung: solange Istwerte mit den Zielwerten (Zustandsvariable) nicht übereinstimmen, muesste eine Interruptroutine für jeden Motor im z.B. 1 ms-Takt anspringen, um Übereinstimmung herzustellen. DAZWISCHEN aber wird weiterhin der loop ausgeführt.

Ihr seht, ich nähere mich dem Thema auf eine möglicherweise umständliche Art. Ich glaube jedoch, dass diese Prinzipüberlegungen Kernproblem eines jeden Robbi-Projektes seien müssten und somit ein allgemein beschriebenes beispielhaftes Konzept viele beflügeln würde.

Aber vielleicht habe ich einfach nur nicht gründlich genug bei der Suche gewesen und es gibt bereits solche Beiträge.

Ich freue mich über jeden Beitrag, der mein Unwissen schmälert :-)

Danke und frohes Schaffen ...

PICture
11.02.2013, 11:25
Hallo!


Was mir fehlt, ist das Wissen über das Zusammenspiel zwischen loop und Interruptroutinen zur Quasiparallelisierung.

Es sollte Programmamlaufdiagramm (PAD) dabei helfen. Nur als Beispiel: http://www.rn-wissen.de/index.php/PIC_Assembler#Programmablaufdiagramm_.28PAD.29 .;)

oberallgeier
11.02.2013, 12:32
... fehlt ... das Wissen über das Zusammenspiel zwischen loop und Interruptroutinen ...Mein Gefährt ist kein Rasen(der)mäher, das ist deutlich kleiner. Egal, die Regelungsproblematik ist vergleichbar. Natürlich gabs im Anfang eine Art Diagramm wie PICture meint, später wurde das aktualisiert. Sah während der Entwicklung ungefähr so aus (klick mal). (https://www.roboternetz.de/community/threads/36121-Autonom-in-kleinen-Dosen-R2_D03-Nachfolger-R3D01?p=402546&viewfull=1#post402546)

Dort siehst Du auch unter "Der Ablauf sieht in einem simplen Diagramm damit so aus:" wie ich alternierend die Regelungsroutine für je einen Motor aufrufe, pro Motor aber mit 100 Hz.

Geregelt wird bei mir IN der ISR - weil ich die Dauer der Routine kenne und im Raster so schnell bin, dass ich andere Routinen nicht störe. Deshalb rechne ich die Regelung auch ausschließlich interger. Direkt unter dem Codebeispiel siehst Du meine Geschwindigkeitsmessung - die aber eine inverse Geschwindigkeit ergibt, sprich: Zeit pro Weg. Und im dritten Codefenster des Links steht >meine< Regelungsroutine.

Die Regelungsparamter für den P I D-Regler habe ich durch Messung der Sprungantwort ausgelegt, siehe z.B. hier (klick wieder) (https://www.roboternetz.de/community/threads/36121-Autonom-in-kleinen-Dosen-R2_D03-Nachfolger-R3D01?p=400139&viewfull=1#post400139) und anderswo im Thread (gibts reichlich *ggg*). Dazu kam natürlich für den K-Wert die Messung der Geschwindigkeit in Abhängigkeit des Ansteuerwertes des Motors (=PWM-Wert). Das wurde wegen des späteren Rechenaufwandes natürlich linearisiert.

Du siehst - alles läuft hintereinander ab. ABER - die Motorzeitkonstanten bei meinen Gefährten liegen - im System - bei rund zehn Millisekunden, da ist bei der Regelung das 100-Hz-Raster ausreichend. Zumal die Geschwindigkeitsmessung mitunter NICHT viel schneller Ergebnisse herschaufelt.

Ein Fahr-Video (https://www.roboternetz.de/community/threads/36121-Autonom-in-kleinen-Dosen-R2_D03-Nachfolger-R3D01?p=495507&viewfull=1#post495507) zeigt den sauberen Geradauslauf - und auch die saubere Funktion bei anderen Manövern.

Im ganzen Thread sind immer wieder Auslegungsfragen behandelt, wie gesagt meine Lösung. Und es ist ein langer Thread :( :( :(

Viel Erfolg

RoboHolIC
11.02.2013, 13:48
Ihr seht, ich nähere mich dem Thema auf eine möglicherweise umständliche Art.
Ich denke: Nein, denn wenn man es nicht so macht, kommt allzu leicht ein BER heraus. (Hmm, vielleicht doch besser: BBI)



Ich glaube jedoch, dass diese Prinzipüberlegungen Kernproblem eines jeden Robbi-Projektes seien müssten und somit ein allgemein beschriebenes beispielhaftes Konzept viele beflügeln würde.
Bestimmt, aber es kann auch Spaß machen, selber drauf zu kommen; kostet halt Zeit, sich das zu erarbeiten. Dann hat man aber auch was gelernt.
Im übrigen gibt es mehrere Wege, ein Ziel zu erreichen. Bei meinem asuroiden Erstling habe ich alles, was einer zyklischen Bearbeitung bedarf, in die ISR gepackt. Abgearbeitet muss es so oder so werden. Was dort automatisch, also im Hintergrund, arbeitet, muss in der LOOP, also der Bewegungs- oder Ablaufsteuerung, den Reaktionen auf Umweltereignisse, nicht mehr explizit angestoßen werden. Das ähnelt ein wenig der Aufgabenteilung zwischen Stammhirn (Lebenserhaltung, Reflexe) und Großhirn (Planung, Strategie, Denkfehler *hüstel*). Man könnte die ISR in einem solchen Zuschnitt auch ganz treffend als Hardwaretreiber++ bezeichnen.
Das Gesagte bezieht sich allerdings auf einen Achtbitter mit nicht-priorisierbarem Interruptkonzept. Wenn ein Controller an dieser Stelle flexibler ist, hat das auch wieder gewisse Rückwirkung auf die mögliche Softwarestruktur.

pemiso
11.02.2013, 14:56
Hi PICture,
danke für die schnelle Antwort. Natürlich ist in Ablaufplan ein weiterer Schritt, sobald es in die Details und in Richtung Umsetzung geht (ein Bild sagt mehr als 1000 Worte). Das Drama ist: soweit bin ich noch nicht - ich habe noch die Verständnisprobleme beim "zeitlichen Verschränken" der verschiedenen Aktionen.

Softwareentwicklung eines grösseren Systems ist - wenn man wie in meiner Situation - nicht aus reichem Erfahrungsschatz im Bereich der zu lösenden Detailprobleme schöpfen kann, ein ständiges Pendeln zwischen "bottom up" und "top down" Sicht, um Prinziplösungen zu finden und zu verifizieren, sie ins Konzept einzupassen und wenn ok, weiterzugehen zum nächsten Detail ...

Z.B. Encoderauswertung: wenn ein Anstieg auf einem Digitalpin einen Impuls signalisiert, könnte der in einer Variablen hochgezählt werden und Interruptroutine ist beendet. Das passiert automatisch, wenn Signaländerungen / Impulse kommen. Im Loop muss dann zur Kopie des Zählerstandes Interrupt ausgeschaltet, die Kopie des Zählerstandes erzeugt und Interrupt reaktiviert werden. Das ist dann ein Prinzipbaustein.

Vielleicht hast Du doch Recht und ich dokumentiere erst einmal grob, meine Prototypanforderung. Damit kann ich mich bei Detailfragen verständlicher machen und schrittweise verfeinern.

Danke auch an oberallgeier. Diese Antwort hilft mir, wenn ich das alles so überfliege, beim Grundverständnis in die richtige Richtung - nämlich auf weitere Fragen :-) Ich werde das alles mal genauer anschauen.

Irgendwo tauchte die Frage nach der optimalen PWM-Frequenz zur Motoransteuerung auf. Mit den Arduino Standardwerten bekomme ich beim Hochdrehen im unteren Bereich zunächst Pfeifen, bevor der Motor hochdreht. Mein Gefährt mit ca 18 kg Masse ist natürlich auf mit 2x 24V/1,5A 1:90 untersetzten Radantrieben kein Selbstläufer.

Danke erstmal und einen schönen Abend ...

oberallgeier
11.02.2013, 15:13
... die Frage nach der optimalen PWM-Frequenz zur Motoransteuerung ...Die kommt (immer) recht massiv. Hier (klick) (https://www.roboternetz.de/community/threads/33469-Gibt-es-die-optimale-PWM-Frequenz-für-kleine-E-Motore) habe ich mal für kleine Motoren eine Frage gestellt, das wurde diskutiert, aber es gibt auch Hinweise für die größeren Oschis.


... Antwort hilft mir, wenn ich das alles so überfliege ... werde das alles mal genauer anschauen ...Überfliegen ist gut, "alles genauer" ... bloß nicht. Kostet manchmal zu viel Zeit, lieber nach Anregungen vom Überfliegen nen eigenen Weg suchen, testen, Erfahrung auswerten ...


... ich dokumentiere erst einmal grob, meine Prototypanforderung ...Genau - dann weiß man (manchmal) wo und warum man etwas ändert.

pemiso
11.02.2013, 15:19
Hallo RoboHolIC,

dann interpretiere ich als 1. Ansatz: das am häufigsten auftretende Ereignis z.B. Rad-Encoderimpuls wird genutzt, um einen Interrupt auszulösen. In der ISR erfolgt die Umrechnung in zurückgelegte Wege, neue x-/y-Pos, es wird geprüft, ob Richtungen und Geschwindigkeiten der Radantriebe nachgesteuert werden müssen (Vorgabewerte aus Systemzustandsvariablen) und es können dann durchaus auch andere Sensoren abgefragt werden?

Dann muss ich mir jetzt mal ein Testprogramm in der Richtung basteln ...

Danke ...

oberallgeier
11.02.2013, 15:28
.... Rad-Encoderimpuls wird genutzt, um einen Interrupt ... Umrechnung in zurückgelegte Wege ...Langsam, langsam! Überleg mal, was Dein Interrupt bedeutet. Entweder zählst Du die Encoderimpulse in einem bestimmten Zeitraster oder Du misst die Zeit zwischen zwei Endoder-Interrupts.

So - was bedeutet ein Encoder-Interrupt? Dass seit dem letzten ein BESTIMMTER Weg zurückgelegt wurde 1). Also brauchst Du (hatte ich) da nicht extra Wege berechnen, wenn Du beim Regelungsansatz statt "Meter" das Längenmass "Abstand zwischen zwei Encoder-Interrupts" zu Grunde legst. Ist nur ne andere Einheit, aber keine andere Größe!

Und wenn Du diese Einheit nimmst, dann hast Du beim "Entweder" die - abgeleitete - Einheit Wege pro Zeit(raster) und beim "oder" die Dimension Zeit pro Weg (und das ist eine inverse Geschwindigkeit - die kann die Regelung bei entsprechendem Ansatz auch verarbeiten).

Etwas überlegen hilft manchmal aufwendige(re) Be-/Rechnungen zu vereinfachen.

1) Das gilt natürlich nur theoretisch - da gäbs noch nen eher unbekannten Schlupf und solche Unartigkeiten. Der Alptraum jedes Odometrie-Fuzzies.

pemiso
11.02.2013, 15:54
nach meinem Verständnis muss der Prozessor doch angestossen werden, um überhaupt einen Impuls zählen zu können, oder ein extra Zählerbaustein zählt sie und wird bei Abfrage im festen Zeitraster wieder genullt???

oberallgeier
11.02.2013, 16:28
nach meinem Verständnis muss der Prozessor doch angestossen werden, um ... zählen zu können ...Ohhh - mein Prozessor läuft, wenn ich im Saft gebe, die Fuses ok sind usw. mehr Anstoß braucht der nicht. Aber Du meinst vermutlich, dass Du dem Prozessor irgendwie Dein Encodersignal "mitteilen" musst. Oder nicht?

Das Geheimnis heißt z.B. externer Interrupt, dann natürlich auch "Timer/Counter" (lies dazu mal das Datenblatt Deines Prozessors durch, da gibts mehrere Timer/Counter die verwendbar sind um a) Timer zu spielen - also ne Zeit zu messen oder einen Zeittakt zu bewirken oder b) um Counter zu spielen, d.h. z.B. Encoder-Signal-Flanken zu zählen . . . und da kann man sogar meist zwischen steigender, fallender oder irgendeiner Flanke auswählen.


... Für die Motoransteuerung ... habe ich ... Routinen entwickelt, die ... Korrektur ... bewirken ...Das know how müsstest Du ja schon hier angewandt haben ! ? ! ? :-k

pemiso
11.02.2013, 18:49
Hallo oberallgeier - ja danke, dass Ihr mir alle so auf die Spur helft!!!
Bei der timer-Doku des Prozessors bin ich auch gelandet, muss ich mir aber erstmal richtig reinziehen! Bei der Motoransteuerung habe ich bis dato noch gaaaanz primitiv gearbeitet - aller Anfang ist schwer ... s.u.

Der nächste Schritt ist die Erweiterung um die Nutzung der timer-Möglichkeiten ...

// sketch to test motor M1 with pololu mc33926 carrier
/* Pin map
M1D1, M2D1 jumpered to LOW onbord
M1D2, M2D2 jumpered to HIGH onboard
EN jumpered to HIGH onboard
SLEW connected to arduino GND
*/
int M1G = 0; // Geschwindigkeit M1, M2
int M2G = 0;
int M1R = 1; // Richtung M1, M2 vorwärts
int M2R = 1;
int M1RG = M1R * M1G; // gerichtete Gecschwindigkeit M1, M2
int M2RG = M2R * M2G;


int M1IN1 = 4; // M1 IN1
int M1IN2 = 5; // M1 IN2
int INV = 6; // digital pin direction invert for both motor channelsint M1R = 1; // Richtung M1 vorwärts
int M2IN1 = 8; // M2 IN1
int M2IN2 = 9; // M2 IN2

int M1GND = M1IN1; // aktiver digitaler PWM-Pin M1, M2 default für Vorwärtsfahrt
int M2GND = M2IN1;
int M1PWM = M1IN2; // aktiver digitaler GND-Pin M1, M2 default für Vorwärtsfahrt
int M2PWM = M2IN2;

int pause = 1500;
int motordelay = 2;
// --------------------------


void setup () {
pinMode(INV, OUTPUT);
pinMode(M1IN1, OUTPUT);
pinMode(M2IN1, OUTPUT);
pinMode(M1IN2, OUTPUT);
pinMode(M2IN2, OUTPUT);
Serial.begin(9600);


Serial.println("Test M1 on Dual MC33926 Motor Carrier");
// Motorinitialisierung
digitalWrite(INV, LOW); // direction invert default value for both motor channels
digitalWrite(M1GND,LOW); // Motorlaufrichtung auf default
digitalWrite(M2GND,LOW);
analogWrite(M1PWM,0); // Motordrehzahl auf 0
analogWrite(M2PWM,0);
}


// --------------------------
void loop () {
Serial.println(""); Serial.println(""); Serial.println(""); Serial.println("");
M1RGW (1,155); M2RGW (1,155);
M1RGW (1,250); M1RGW (1,250);
M1RGW (-1,316); M2RGW (-1,316);
M1RGW(1,455); M2RGW(1,455);
M1RGW(-1,400); M2RGW(-1,400);
M1RGW(1,0); M2RGW(1,0);
delay(10000);


/*
// Serial.print("M1GND / M1PWM: "); Serial.print(M1GND); Serial.print(" / "); Serial.println(M1PWM);
M2RGW(1,0);
//delay(10000);
// Serial.print("M2GND / M2PWM: "); Serial.print(M2GND); Serial.print(" / "); Serial.println(M2PWM);
// Serial.println(""); Serial.println(""); Serial.println(""); Serial.println("");
// Serial.print("M1GND / M1PWM: "); Serial.print(M1GND); Serial.print(" / "); Serial.println(M1PWM);
M1RGW (1,155);
//delay(pause);
// Serial.print("M2GND / M2PWM: "); Serial.print(M2GND); Serial.print(" / "); Serial.println(M2PWM);
M2RGW (1,155);
//delay(pause);
// Serial.print("M1GND / M1PWM: "); Serial.print(M1GND); Serial.print(" / "); Serial.println(M1PWM);
M1RGW (1,250);
//delay(pause);
M1RGW (-1,316);
//delay(pause);
// Serial.print("M2GND / M2PWM: "); Serial.print(M2GND); Serial.print(" / "); Serial.println(M2PWM);
M2RGW(1,455);
//delay(pause);
M2RGW(-1,400);
//delay(pause);
M1RGW(1,0);
//delay(10000);
// Serial.print("M1GND / M1PWM: "); Serial.print(M1GND); Serial.print(" / "); Serial.println(M1PWM);
M2RGW(1,0);
*/
}


// --------------------------
void M1A(int G1, int G2) {
// abbremsen
// Serial.print("bremse von "); Serial.print(G1); Serial.print(" auf "); Serial.print(G2); Serial.print(": ");
int G = 0; for (G = G1; G >= G2; G--) {
analogWrite(M1PWM, G * 51 / 80); // reskalieren auf 255
delay(motordelay);
// Serial.print(" "); Serial.print(G); Serial.print(" ");
}
// Serial.println("");
M1G = G2;
// Serial.print(" M1G ");Serial.println(M1G);
}


// --------------------------
void M1B(int G1, int G2) {
// beschleunigen
// Serial.print("beschleunige von "); Serial.print(G1); Serial.print(" auf "); Serial.print(G2); Serial.print(": ");
int G = 0;
for (G = G1; G <= G2; G++) {
analogWrite(M1PWM, G * 51 / 80); // reskalieren auf 255
delay(motordelay);
// Serial.print(" "); Serial.print(G); Serial.print(" ");
}
// Serial.println("");
M1G = G2;
// Serial.print(" M1G ");Serial.println(M1G);
}


// --------------------------
void M2A(int G1, int G2) {
// abbremsen
// Serial.print("bremse M2 von "); Serial.print(G1); Serial.print(" auf "); Serial.print(G2); Serial.print(": ");
int G = 0; for (G = G1; G >= G2; G--) {
analogWrite(M2PWM, G * 51 / 80); // reskalieren auf 255
delay(motordelay);
// Serial.print(" "); Serial.print(G); Serial.print(" ");
}
// Serial.println("");
M2G = G2;
// Serial.print(" M2G ");Serial.println(M2G);
}


// --------------------------
void M2B(int G1, int G2) {
// beschleunigen
// Serial.print("beschleunige M2 von "); Serial.print(G1); Serial.print(" auf "); Serial.print(G2); Serial.print(": ");
int G = 0;
for (G = G1; G <= G2; G++) {
analogWrite(M2PWM, G * 51 / 80); // reskalieren auf 255
delay(motordelay);
// Serial.print(" "); Serial.print(G); Serial.print(" ");
}
// Serial.println("");
M2G = G2;
// Serial.print(" M2G ");Serial.println(M2G);
}


// --------------------------
void M1RW (int M1RN) {
M1R = M1RN; // Richtungswechsel
// Serial.print("R wechselt auf ");Serial.println(M1R);
if (M1GND == M1IN1) {
M1GND = M1IN2;
M1PWM = M1IN1;
}
else {
M1GND = M1IN1;
M1PWM = M1IN2;
}
digitalWrite(M1GND, LOW);
analogWrite(M1PWM, 0); // Stop nach Richtungswechsel
// Serial.print("M1GND / M1PWM: "); Serial.print(M1GND); Serial.print(" / "); Serial.println(M1PWM);
}


// --------------------------
void M2RW (int M2RN) {
M2R = M2RN; // Richtungswechsel
// Serial.print("R wechselt auf ");Serial.println(M1R);
if (M2GND == M2IN1) {
M2GND = M2IN2;
M2PWM = M2IN1;
}
else {
M2GND = M2IN1;
M2PWM = M2IN2;
}
digitalWrite(M2GND, LOW);
analogWrite(M2PWM, 0); // Stop nach Richtungswechsel
// Serial.print("M2GND / M2PWM: "); Serial.print(M2GND); Serial.print(" / "); Serial.println(M2PWM);
}


// --------------------------
void M1RGW (int M1RN, int M1GN) { // M1-Richtungs-Geschindigkeits-Wechsel auf M1-Rchtungs-Geschwindigkeit-Neu
int M1RGN = 0; // Richtungs-Geschwindigkeit M1 Neu
// Serial.println("");
// Serial.print(" fahre auf "); Serial.print(M1RN); Serial.print(" / "); Serial.println(M1GN);
M1RG = M1R * M1G; // aktuelle gerichtete Geschwindigkeit
M1RGN = M1RN * M1GN; // neue ...

if (M1RGN == M1RG) {
// keine Änderung nötig
// Serial.print("### unverändert: RGalt / neu "); Serial.print(M1RG);Serial.print(" / ");Serial.println(M1RGN);
}
else {
if (M1GN > 400) {
M1GN = 400; // Limitieren auf 400
M1RGN = M1RN * M1GN;
// Serial.println(" Limitiert auf 400");
}

// Richtung * Geschwindigkeit alt / neu sind unterschiedlich


if (M1RN != M1R) {
// Richtungen alt / neu ungleich
// Serial.print("### R alt / neu sind unterschiedlich: "); Serial.print(M1R);Serial.print(" / ");Serial.println(M1RN);
M1A(M1G, 0); // Abbremsen von M1G auf 0
M1RW (M1RN); // Richtungswechsel
M1B(0, M1GN); // Beschleunigen auf Geschwindigkeit-Neu
}
else {
if (M1GN > M1G) {
M1B(M1G, M1GN); // Beschleunigen auf Geschwindigkeit-Neu
}
else {
M1A(M1G, M1GN); // Abbremsen auf Geschwindigkeit-Neu
}
}
}
}


// --------------------------
void M2RGW (int M2RN, int M2GN) { // M1-Richtungs-Geschindigkeits-Wechsel auf M1-Rchtungs-Geschwindigkeit-Neu
int M2RGN = 0; // Richtungs-Geschwindigkeit M1 Neu
// Serial.println("");
// Serial.print(" fahre auf "); Serial.print(M2RN); Serial.print(" / "); Serial.println(M2GN);
M2RG = M2R * M2G; // aktuelle gerichtete Geschwindigkeit
M2RGN = M2RN * M2GN; // neue ...

if (M2RGN == M2RG) {
// keine Änderung nötig
// Serial.print("### unverändert: M2 RGalt / neu "); Serial.print(M2RG);Serial.print(" / ");Serial.println(M2RGN);
}
else {
if (M2GN > 400) {
M2GN = 400; // Limitieren auf 400
M2RGN = M2RN * M2GN;
// Serial.println(" Limitiert auf 400");
}

// Richtung * Geschwindigkeit alt / neu sind unterschiedlich


if (M2RN != M2R) {
// Richtungen alt / neu ungleich
// Serial.print("### M2 R alt / neu sind unterschiedlich: "); Serial.print(M2R);Serial.print(" / ");Serial.println(M2RN);
M2A(M2G, 0); // Abbremsen von M2G auf 0
M2RW (M2RN); // Richtungswechsel
M2B(0, M2GN); // Beschleunigen auf Geschwindigkeit-Neu
}
else {
if (M2GN > M2G) {
M2B(M2G, M2GN); // Beschleunigen auf Geschwindigkeit-Neu
}
else {
M2A(M2G, M2GN); // Abbremsen auf Geschwindigkeit-Neu
}
}
}
}

- - - Aktualisiert - - -

Ergänzung: das Codebeispiel läuft in der Arduino Entwicklungsumgebung auf Mega2560 mit dem Pololu MC99326 Dual Motor Controller Carrier (nicht Shield!!!).