Arduino UNO SMD + RC Empfänger => Servozittern

Hallo,

erstmal vorab zu meiner “IST-Situation”. Wir arbeiten an der Arbeit mit 8 Auszubildenden (Zerspaner, Werkzeugmechaniker, Industrimechaniker, Mechatroniker, Elektroniker (zu denen ich auch gehöre)) daran, ein BobbyCar so umzubauen, dass es fernsteuerbar ist mit einer 4-CH RC Fernbedienung (Modelcraft GT4; zu finden bei Conrad). Als Antrieb dient ein Boschakkuschrauber, bei welchem der “Gashebel”, sowie Links-/Rechtslauf mit Servos betätigt werden sollen. Ebenso gibt es einen “großen” Servo für die Lenkung. Die Königsdisziplin für das BobbyCar wird am Ende der “Follow the Line” Wettkampf, bei dem das BobbyCar autark einer 18mm breiten schwarzen Linie folgen soll (Schwarzes Iso-Tape).

Bisher habe ich folgendes erreichen können:

über 2 LDR und eine weiße LED als Referenzlichtquelle ist der Arduino in der Lage über die Analogeingänge Differenzen zwischen “hell” & “dunkel” zu erkennen und den Lenkungsservo entsprechend nachzuregeln.

Was mir im Moment Kopfschmerzen bereitet, ist, dass ich das PWM Signal der Fernbedienung gerne durch den Arduino durschleifen möchte, da es so ist, dass wenn ich es mit pulseIn auswerten möchte, die Servos ohne Ende anfangen zu zittern und zu knurren, wenn ich die Pulslänge über “writeMicroseconds” wieder auf die Ausgänge schreibe, an welchen der Steuereingang des jeweiligen Servos hängt. Ich vermute, dass es mit der “Toleranz” / “Auflösung” von pulseIn zusammen hängt (4µs Toleranz / 5,5 µs Pulslängenänderung = 1° Verstellung des Servos).

Ich habe zuvor versucht das ganze in Anführungszeichen mathematisch zu lösen: arithmetischer Mittelwert aus 3 Signalen und das ganze dann durch Multiplikation und Division zu “glätten” (bei 10 als Faktor bspw. abschneiden der letzten Zahl), allerdings fährt der Servo dadurch extrem ruckelig (was ja auch klar ist, da er ja keine vollen 180° mehr als Befehlssatz hat, sondern entsprechend weniger und dadurch springt).

Ich habe mir schon mit meinem Elektronikerkollegen die Finger wund gegoogelt aber wir finden einfach keinen Lösungsansatz…

Ich hoffe ihr könnt mir helfen, dass der Arduino entweder im Stande ist, dass PWM Signal wirklich genau genug auszuwerten und diese Varianz in den Werten weg zu bekommen (“jitter” der Servos) oder aber (auch wenn es schade ist um die bisherige Arbeit am Script), das Signal einfach “durchzuschleifen” also das Signal 1 zu 1 im “Handbetrieb” von Eingang zu Ausgang zu schieben, so lange, wie es nun mal ansteht.

Vielen Dank im Voraus!

Liebe Grüße
neongruuen + Kollegen :slight_smile:

#include <Servo.h> //Servo Library importieren um Servosignal zu generieren

Servo lenkung; //Servo "Lenkung" definieren
Servo antrieb; //Servo "Antrieb" definieren
Servo fahrtrichtung; //Servo "Fahrtrichtung" definieren

/*
 Im Folgenden werden die Variablen deklariert; prinzipiell selbsterklärend; 
 avg => average Summenelemente zur Mittelwertsbildung
 pulseDELAY => TIMEOUT für pulseIn funktion -> nach Wert ohne Pulse -> kein Puls vorhanden
 glCH1 => Glättungsfaktor Channel 1
 glCH2 => Glättungsfaktor Channel 2
*/

int CH1in = 2;
int CH1;
int CH1puff = 0;
int CH2in = 3;
int CH2;
int CH2puff = 0;
int CH3in = 4;
int CH3;
int CH4in = 5;
int CH4;
int avg = 3;
int pulseDELAY = 21000;
int glCH1 = 15;
int glCH2 = 15;
boolean runAUTO = false;
boolean calib = false;
int calibNUM = 3;
int calibLDR1 = 0;
int calibLDR2 = 0;
int calibPUFF1 = 0;
int calibPUFF2 = 0;
int calibOffset = 50;
int lenkMid = 90;
int lenkOffset = 45;
int LDR1 = 0;
int LDR2 = 0;


void setup(){
  // Setup & Pre-Load
  // Kanäle als Input deklarieren; Serielle Schnittstelle starten ( Debugging ); Variablen zu Servo Kanälen (I/O) zuordnen
  pinMode (CH1in,INPUT);
  pinMode (CH2in,INPUT);
  pinMode (CH3in,INPUT);
  pinMode (CH4in,INPUT);
  lenkung.attach(6);
  antrieb.attach(7);
  fahrtrichtung.attach(8);
  //Debugging
  //Serial.begin(9600);
  //Serial.println(F("PWM-Signalwerte werden eingelesen"));
}

void loop() {

  // Maincode
  CH4 = pulseIn(CH4in, HIGH, 21000);

  if (1800 < CH4 && CH4 < 2100){ //bei jedem Drücken des Tasters für CH4 wird die Variable "runAUTO" getoggelt (1 & 0 hin und her geschaltet)
    runAUTO = !runAUTO;
    delay(1000);
  }

  if (runAUTO == 0){

    CH1puff=0; //Puffervariable für Signallaufzeit am Anfag eines Loops auf 0 setzten
    CH2puff=0; //Puffervariable für Signallaufzeit am Anfag eines Loops auf 0 setzten

    for (int i = 0; i < avg; i++){ //Mittelwertsbildung mit Summenwerten
      CH1 = pulseIn(CH1in, HIGH, pulseDELAY);
      CH1puff = CH1puff + CH1;
    }

    CH1 = CH1puff / avg;

    if (CH1 < 1010){ //Werte entstören
      CH1 = 1000;
    }
    if (1490 < CH1 && CH1 < 1510){
      CH1 = 1500;
    }
    if (CH1 > 1975){
      CH1 = 2000;
    }

    CH1 = CH1 / glCH1; //Werte glätten 
    CH1 = CH1 * glCH1;

    for (int i = 0; i < avg; i++){ //Mittelwertsbildung mit Summenwerten
      CH2 = pulseIn(CH2in, HIGH, pulseDELAY);
      CH2puff = CH2puff + CH2;
    }
    CH2 = CH2puff / avg;

    if (CH2 < 1005){ //Werte entstören
      CH2 = 1000;
    }
    if (1490 < CH2 && CH2 < 1510){
      CH2 = 1500;
    }
    if (CH2 > 1975){
      CH2 = 2000;
    }

    CH2 = CH2 / glCH2; //Werte glätten (15er Schritte)
    CH2 = CH2 * glCH2;

    CH3 = pulseIn(CH3in, HIGH, 21000);

    if (CH3 < 1300){
      fahrtrichtung.write(45);
    }
    if (CH3 > 1600){
      fahrtrichtung.write(135); 
    }


    // Ausgänge beschalten
    lenkung.writeMicroseconds(CH1); //Entstörten und geglätteten Impuls an Servo senden
    antrieb.writeMicroseconds(CH2); //Entstörten und geglätteten Impuls an Servo senden

  }

  /* ab hier automatisches Fahren!!! */

  // KALIBRIERUNG!! TO DO!!
  if (calib == 0 && runAUTO == 1){ //Wenn noch nicht Kalibriert, aber Automatik an
    int e = 0; // FOR Schleifen Variable extern defniert, zur weiteren verwendung, s.u.
    for (e; e < calibNUM; e++){ //Mittelwertsbildung mit Summenwerten
      calibLDR1 = analogRead(A0); //Analogeingang einlesen
      calibPUFF1 = calibPUFF1 + calibLDR1; //Summe aus "calibNUM"-Durchläufen bilden
      delay(10); //10 ms abwarten
      calibLDR2 = analogRead(A1); //Analogeingang einlesen
      calibPUFF2 = calibPUFF2 + calibLDR2; //Summe aus "calibNUM"-Durchläufen bilden
      delay(10); //10 ms abwarten
    }
    if (e == calibNUM){ //Wenn alle Durchläufe abgearbeitet
      calib = 1; //FLAG für Kalibrierung auf TRUE setzen
      calibLDR1 = calibPUFF1 / calibNUM; //Mittelwert bilden
      calibLDR2 = calibPUFF2 / calibNUM; //Mittelwert bilden

    }
  }
  if (calib == 1 && runAUTO == 0){ //Wenn Kalibriert, aber Automatik aus = Kalibrierung löschen (ermöglicht Fahren auf verschiedenen Untergründen
    calib = 0;
    calibPUFF1 = 0; //Kalibrierungspuffer löschen
    calibPUFF2 = 0; //Kalibrierungspuffer löschen
  }

  // KALIBRIERUNG ENDE!!

  if (runAUTO == 1 && calib == 1){ //Wenn Kalibriert & Automatik ein
    LDR1 = analogRead(A0); //Sensorik einlesen
    LDR2 = analogRead(A1); //Sensorik einlesen

    //Antrieb einfügen, wenn Mechanik steht!

    //Antrieb ENDE!

    if (LDR1 - calibLDR1 > calibOffset){ //Wenn Differenz zwischen kalibriertem Wert und Aktualwert größer Offset
      lenkung.write(lenkMid + lenkOffset); //Lenkung nachregeln
    }
    else if (LDR2 - calibLDR2 > calibOffset){ //Wenn Differenz zwischen kalibriertem Wert und Aktualwert größer Offset
      lenkung.write(lenkMid - lenkOffset); //Lenkung nachregeln
    }
    else {
      lenkung.write(lenkMid); //Wenn Differenz kleiner als Offset = Lenkung Mittelstellung
    }
  }

  //  DEBUGGING START!

  //  Serial.println(runAUTO);
  
  //Serial.println("CH1"); //Werte auf serieller Schnittstelle ausgeben
  //Serial.println(CH1);
  //Serial.println("CH2"); //Werte auf serieller Schnittstelle ausgeben
  //Serial.println(CH2);
  //Serial.println("CH3"); //Werte auf serieller Schnittstelle ausgeben
  //Serial.println(CH3);
  //Serial.println("CH4"); //Werte auf serieller Schnittstelle ausgeben
  //Serial.println(CH4);
  //delay(1000);
  
  // DEBUGGING ENDE!

}

neongruuen:
Bisher habe ich folgendes erreichen können:

Eure Programmierkenntnisse im Umgang mit Mikrocontrollern sind minimal, oder?

Das hier ist beispielsweise extrem unelegant:

 if (1800 < CH4 && CH4 < 2100){ //bei jedem Drücken des Tasters für CH4 wird die Variable "runAUTO" getoggelt (1 & 0 hin und her geschaltet)
    runAUTO = !runAUTO;
    delay(1000);
  }

Das bedeutet ja, beim Betätigen der Funktion wird das Programm für eine Sekunde blockiert und die Reaktion erfolgt erst mit einer Sekunde Verzögerung. Kommt es auf Zeit bei Eurem Wettkampf nicht an? Also egal ob ein Auto erst eine Sekunde nach dem Drücken von “Start/Stop” losfährt, spielt keine Rolle? Und dass es nach dem erneuten Drücken noch eine Sekunde lang ungebremst weiterfährt ist auch egal?

Das ginge auch anders.

neongruuen:
Was mir im Moment Kopfschmerzen bereitet, ist, dass ich das PWM Signal der Fernbedienung gerne durch den Arduino durschleifen möchte, da es so ist, dass wenn ich es mit pulseIn auswerten möchte, die Servos ohne Ende anfangen zu zittern und zu knurren, wenn ich die Pulslänge über “writeMicroseconds” wieder auf die Ausgänge schreibe, an welchen der Steuereingang des jeweiligen Servos hängt. Ich vermute, dass es mit der “Toleranz” / “Auflösung” von pulseIn zusammen hängt (4µs Toleranz / 5,5 µs Pulslängenänderung = 1° Verstellung des Servos).

Normale Servos haben nicht mehr als eine Auflösung von 3°, so dass ein Jitter von 1,5°*5,5µs/°= ca. 8 µs noch nicht zum großem Servozappeln führen sollte.

Nun ist es aber so, dass Du einen Jitter von mindestens 12 µs hast, und das ist unvermeidbar:

  • 4 µs kann der Timer0 Interrupt das Programm blockieren
    (wird von Arduino für millis(), micros() und delay-Funktionen verwendet)
  • 4 µs kann Deine interruptgesteuerte Servo-Library das Programm blockieren
  • 4 µs ist der Messfehler der micros() Funktion

Macht zusammen 12 µs Jitter und das sollte zu einem ganz, ganz leichten “Brummen” der Servos führen, ohne dass sie dabei wirklich anfangen, großartig zu zucken.

neongruuen:
Ich habe zuvor versucht das ganze in Anführungszeichen mathematisch zu lösen: arithmetischer Mittelwert aus 3 Signalen und das ganze dann durch Multiplikation und Division zu “glätten” (bei 10 als Faktor bspw. abschneiden der letzten Zahl), allerdings fährt der Servo dadurch extrem ruckelig (was ja auch klar ist, da er ja keine vollen 180° mehr als Befehlssatz hat, sondern entsprechend weniger und dadurch springt).

Besser wäre eine exponentielle Glättung der Werte mit einem Tiefpassfilter.

neongruuen:
Ich habe mir schon mit meinem Elektronikerkollegen die Finger wund gegoogelt aber wir finden einfach keinen Lösungsansatz…

Hier habe ich mal vor kurzem einen Code zum Pollen von 4 RC-Empfängerkanälen gepostet:

Ohne Verwendung von pulseIn und es werden alle Kanäle möglichst schnell nacheinander gescannt.
Voraussetzung: Die Reihenfolge der gefeuerten Impulse der RC-Kanäle steht fest
(muß nicht identisch sein mit der Kanalnumerierung)

Probiere mal aus, ob Du mit dem dort geposteten Sketch die RC-Empfängerimpulse bekommst, und wie stark der Jitter ist. Für eine Mittelwertbildung würde ich dann noch eine exponentielle Glättung / Tiefpassfilter dranmachen, und dann solltest Du äußerst exakte µs-Impulswerte haben, mit denen Du die angeschlossenen Servos ansteuern kannst.

Hey,

danke schonmal für Deine Antwort!

Ja sie sind minimal aber wir sind auch Elektroniker für Automatisierungstechnik und arbeiten vorzugsweise mit S7 und weniger mit Mikrocontrollern, das Arbeiten mit dem Arduino vorallem in Bezug auf RC-Technik ist für uns mehr "try 'n error" als wirkliches Wissen, aber wir lernen ja so oder so etwas dabei.

Ich weiß, dass das nicht gerade die "vornehme Art" ist, das ganze zu lösen, allerdings muss das Auto 7 Challenges im Wettkampf bestehen muss, zwischen den Challenges ist die Zeit egal & beim Follow the Line, worum es bei der "runAUTO" Sache geht, ist es zu vernachlässigen, dass er nach einer Sekunde erst loslegt mit der Kalibrierung und bei ca 3-5 km/h ist es auch zu vernachlässigen, wenn er eine Sekunde "ausrollt" (schlimmer wäre so ein Delay beim richtigen fahren, da das Getriebe auf 30 km/h dimensioniert ist).

Liebe Grüße
neongruuen.