Solved: Servo Zittern in Verbindung mit anderen Librarys

Hallo Leute,

nun bin ich wieder mal an einem Punkt, an dem ich nicht weiter komme.
Mein Projekt:
ein Grillregler, der die Temperturen im Grill mißt, über einen PID-Regler dann zwei Servos für Zuluft- und Abluftklappe ansteuert und das ganze nochauf SD-Karte mitloggt.

Bereits beim Testlauf fiel mir auf, dass die Servos ständig zittern und knurren, habe das aber auf die billigen Servos und den fliegenden Aufbau geschoben.

Nun, mit festem Aufbau und digitalen Servos ist das leider immer noch so - Wenn ich meinen Grillreglercode laufen lassen.
Wenn ich einen nackten Servosketch drauflade (gleiche Hardware) dann laufen die Servos völlig ruhig, wenn sich der Wert nicht ändert, dann stehen die Servos völlig still.

Wenn ich aber den kompletten Code laden, dann zittern die servos, auch wenn sich die STellgröße garnicht ändert.
Meine Vermutung ist, dass sich der Interrupthandler der Servolib mit einer der anderen Libs in die Quere kommt, und dadurch der Puls zur Servoansteuerung nicht gleichmäßig kommt.

ich habe diese Libs eingebunden:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <PID_v1.h>
#include <Bounce.h>
#include <SD.h>
#include <DS1307new.h>
#include <Servo.h>

Den ganzen Code kann ich nicht posten, weil ich inzwischen knapp 4000Zeilen Code in verschiedenen Dateien habe.
Was ich ausschliessen kann: Spannungsversorgung, Einstreuung, Übersprechen, gestörtes Signal.

Im Englischsrachigen Teil des Forums habe ich diverse Einträge gefunden, die einen Beeinflussung von wire.h und servo.h nahe legen.

Hat einer von euch sich damit schonmal rumgeschlagen, oder eine Idee, wie ich das vermeiden kann?
Das einzige, was mir momentan einfällt, ist in den Phasen, in denen sich der Stellwert nicht ändert, die Versorgung der Servos abzuschalten. Aber das wäre nur die quick & dirty Lösung.

Ich glaube TERWI hatte mal das Problem und war auch am verzweifeln.

Oh weh!

wenn Terwi daran schon verzweifelt, was soll dann ich erst machen! :disappointed_relieved:

guntherb:
wenn Terwi daran schon verzweifelt, was soll dann ich erst machen! :disappointed_relieved:

Am besten erstmal eine Diagnose des Problems.

Ich kenne mich mit Servos und der Servo-Library am Arduino zwar eher theoretisch aus als dass ich damit schon viel realisiert hätte, aber ich habe gesehen, dass es eine "write" Funktion zum Setzen des Sollwertes gibt und eine "refresh" Funktion zum Halten des gesetzten Werts, die in der Loop regelmäßig und oft genug aufgerufen werden muss.

Fragen:

  1. Wie oft bzw. in welchen Zeitabständen greift in Deinem Sketch denn die Regelung ein und wie oft werden mit "servo1.write(value);" neue Sollwerte gesetzt?

  2. Erfolgen die Aufrufe von "SoftwareServo::refresh();" in der loop oft genug, also mindestens alle 20 Millisekunden?

Könnte es sein, dass Du da bereits einen Bock geschossen hast?

Wenn nein und Du auch keine zeitkritischen Aktionen am Laufen hast, könnte man vielleicht die Interrupts blockieren, während die refresh-Funktion läuft.

Erste Idee: Während des Servo-refresh-Aufrufs mal testweise die Interrupts komplett zu blockieren.

noInterrupts();
SoftwareServo::refresh();
interrupts();

Ändert sich dann etwas am Servo-Zittern mit blockierten Interrupts während des Servo-refresh?
Zerreißt es laufende Ausgaben auf Serial? Funktioniert irgendwas nicht mehr?

Hallo Jurs,

nein, in der Servo.lib gibt es kein Refresh.

die Werte werden einfach mit servo.write() geschrieben und ab da übernimmt die Lib, alle 20ms (vermutlich mit Interrupt) den Puls auszugeben.

Ich habe schon probiert:

  • in der Loop jedesmal zu schreiben (ca. alle 10-25ms)
  • in Zeitscheiben zu schreiben (100ms, 1sek, 5sek)
  • wenn sich der Wert nicht ändert detach() und vor dem nächsten Ansteuern attach() (ist am schlechtesten)

Das alles habe ich auch in einem Beispielsketch, in dem nur die Servolib aktiv ist probiert, immer mit dem Ergebnis, dass das Servo nicht zittert. (mit Ausnahme des letzten Versuches)

Also auch, wenn ich nur einmal den Servowinkel schreibe, und dann nichts mehr mache, zuckt und knarzt das Servo immer wieder. Das ist nix grosses, das ist höchstens 1-2°, und wieder zurück. Aber es nervt.

Weil der Grillregler vor allem nachts und draussen läuft, nervt das extrem, wenn hier ständig die Servos rumzucken!

guntherb:
nein, in der Servo.lib gibt es kein Refresh.

Welche Library meinst Du?
Nicht diese hier: Arduino Playground - HomePage
???

Bei der heißt es "you must call the SoftwareServo::refresh() method at least once every 50ms or so to keep your servos updating".

Also verwendest Du wohl eine andere Library.
Welche Servo-Library verwendest Du?

Und wenn Du mal nicht die bisher verwendete nutzt, sondern die oben verlinkte und in der loop "refresh" für die Servos verwendest?

Du hast doch die loop-Funktion auf hoher Drehzahl und ohne delay laufen? Oder vielleicht nicht?

Kann man nicht einfach nach Stellen des Servos diesen stromlos machen ?
Dann sollte das Zittern doch aufhören.

Aus einem anderen Hobbybereich weiss ich auch, dass es Unterschiede bzgl. des Halte-Verhaltens zwischen Analog- und Digitalservos gibt.

jurs:
Welche Library meinst Du?

interessante Frage!

Ich habe die Servo-lib die in der IDE mitgeliefert wurde;
die hier: Servo - Arduino Reference

mir scheint, die im Playground ist älter.
aber der Gedanke ist grundsätzlich richtig, ich könnte ja mal nach anderen Servo.lib suchen und die probieren.
Oder vielleicht doch die Versorgung abschalten....

Meine loop() läuft ohne delays.
Im Schnitt habe ich 400µs Loop wiederholrate, wenn eine der Zeitscheiben aufgerufen wird, können das aber auch 6-8ms werden.
Bei besonders Zeitaufwändigen Aktionen, wie z.B. wenn das Display komplett neu geschrieben wird, oder die SD-Karte gesteckt wird, können es auch mal 30ms sein. Aber das sind nicht die Zustände, die mir Probleme machen.
Im Normalbetrieb läuft die loop() mit maximal 8ms und auch da zittern die Servos. (Übrigens nicht immer beide gleichzeitig, mal das eine, mal das andere!)

guntherb:

jurs:
Welche Library meinst Du?

interessante Frage!

Ich habe die Servo-lib die in der IDE mitgeliefert wurde;
die hier: Servo - Arduino Reference

mir scheint, die im Playground ist älter.
aber der Gedanke ist grundsätzlich richtig, ich könnte ja mal nach anderen Servo.lib suchen und die probieren.
Oder vielleicht doch die Versorgung abschalten....

Meine loop() läuft ohne delays.
Im Schnitt habe ich 400µs Loop wiederholrate, wenn eine der Zeitscheiben aufgerufen wird, können das aber auch 6-8ms werden.
Bei besonders Zeitaufwändigen Aktionen, wie z.B. wenn das Display komplett neu geschrieben wird, oder die SD-Karte gesteckt wird, können es auch mal 30ms sein. Aber das sind nicht die Zustände, die mir Probleme machen.
Im Normalbetrieb läuft die loop() mit maximal 8ms und auch da zittern die Servos. (Übrigens nicht immer beide gleichzeitig, mal das eine, mal das andere!)

Du musst nicht die Versorgung abschalten. Nur detach reicht.

guntherb:
Ich habe die Servo-lib die in der IDE mitgeliefert wurde;
die hier: Servo - Arduino Reference

Ah, moderne Zeiten, mitgelieferte Servo-Library!

Ja, die basiert auf Interrupts. Frißt Interrupt-Zeitscheiben weg, frißt PWM-Pins weg und begrenzt die maximal ansteuerbare Servoanzahl. Irgendwie ist nicht alles besser was neuer ist.

Jedenfalls Servo-Interrupts beißen sich in dem Fall mindestens mit Serial-Interrupts, wenn Du z.B. Debug-Ausgaben auf Serial sendest. Das kann ggf. das exakte Timing nachteilig beeinflussen, wenn z.B. noch ungesendete Zeichen im seriellen Sendepuffer sind während dann der interruptgesteuerte automatische Servo-Refresh zuschlägt. Dann kann (Serial-)Interrupt auf (Servo-)Interrupt treffen und ein Interrupt muss warten, bis der andere beendet ist, bevor er zum Zuge kommt.

Eine Möglichkeit wäre, Serial auf einer anderen, höheren Baudrate laufen zu lassen.
Wenn Du Serial jetzt auf 9600 laufen hast, z.B. mal Serial auf 115200 ausprobieren.
Änderung?

Die andere Sache ist die: Bei so einem Regler brauchst Du Serial doch nur für Debug-Ausgaben während der Programmentwicklung, oder und sonst für nichts anderes? Dann wäre eine andere Möglichkeit, per bedingter Kompilierung den Quellcode zur Kompilierung von zwei verschiedenen Versionen vorzusehen:

  • Debug-Version zur Programmentwicklung mit Debug-Ausgaben auf Serial.
  • Final-Version zum Einsetzen ohne Serial
    Weißt Du, wie Du "bedingte Kompilierung" nutzen kannst, um komfortabel dieselbe Quellcodeversion zu nutzen? Also um aus demselben Quellcode einen Sketch mit Serial-Debugausgaben und einen Sketch ohne Serial-Ausgaben erzeugen zu können?

guntherb:
mir scheint, die im Playground ist älter.

Älter muß nicht in jedem Fall schlechter sein. Die ältere Version knabbert z.B. keine PWM-Pins weg und ist auch in der Servoanzahl nicht limitiert.

guntherb:
Meine loop() läuft ohne delays.
Im Schnitt habe ich 400µs Loop wiederholrate, wenn eine der Zeitscheiben aufgerufen wird, können das aber auch 6-8ms werden.
Bei besonders Zeitaufwändigen Aktionen, wie z.B. wenn das Display komplett neu geschrieben wird, oder die SD-Karte gesteckt wird, können es auch mal 30ms sein. Aber das sind nicht die Zustände, die mir Probleme machen.

Das hört sich völlig einwandfrei für die Nutzung der alten Servo-Library an. Die refresh-Aufrufe sollen regelmäßig in Abständen von max. 20 ms erfolgen und höchstens 50 ms nicht überschreiten.

Vielleicht würde ich das mit den Änderungen an "Serial" (höhere Baudrate, Debug- und Final-Version) zuerst ausprobieren, damit Du die mitgelieferte Servo-Lib ggf. weiternutzen kannst. So ganz ohne Not braucht man auf mitgelieferte Libs ja vielleicht auch nicht verzichten.

Hallo Jurs,

das mit der bedingten Compilierung ist mir im Prinzip klar, habs aber beim Arduino noch nie verwendet.
ich kommentiere einfach das Serial.begin() im Setup aus, dann ist es ende mit Serial Monitor. :blush:

ich lasse den Smon auf 57600baud laufen, 115k sind mit USB-HUB nicht sicher.

aber der wichtigste Hinweis ist dein Tip mit der alten Servolib. Ich denke, ich werde die mal einbauen und testen, das klingt vielversprechend! :wink:

mde110:
Du musst nicht die Versorgung abschalten. Nur detach reicht.

Nein, reicht leider nicht.
Wenn, dann nur, falls man detach irgendwie mit dem Servosignal synchronisieren könnte. Wenn ich detach mache, dann dreht das Servo immer wieder mal im Abschalten ein Stückchen weg. Schon probiert.

Und wenn du den Pin manuell auf OUTPUT LOW schaltest?
Bei mir ging das detach immer super... komisch.

mde110:
Und wenn du den Pin manuell auf OUTPUT LOW schaltest?
Bei mir ging das detach immer super... komisch.

auch schon probiert - hilft nicht.
mir scheint, dass es zeitliche Zusammenhänge gibt.
Beim detach hatte eigentlich immer nur das Servo an einem Ausgang gelegentliches Zucken der andere nie. (Servos wurden getauscht, Effekt blieb am Ausgang)
Da ich aber keine eindeutige Zusammenhänge erkennen konnte (ich habe vieles probiert!) und die Gefahr bestand, dass bei einer Änderung in der Software an anderer Stelle das System sich wieder anders verhält, habe das mit dem detach verworfen.

Ja, da kann man schon verzweifeln.
Ich hab bei meiner Lib auch diverse Probleme gehabt. (rcarduin.tk)
Eine generelle Lösung gibt es leider nicht. Man muss schauen woher die Störungen kommen. Eigentlich ist die neue Lib schon gut. Durch die Interruptsteuerung ist die nicht mehr so anfällig gegen Störungen z.B. von der SD Lib. (Da kommt es manchmal ja zu Latenzen im 100ms Bereich.) Für den ATTinyx5 mußte ich eine komplett eigene Lib schreiben, weil die SoftwareServo nicht mit meiner Lib zum Auslesen von Fernsteuerempfängern harmonierte. (eigentlich mit keiner Lib, die auf den Systemtimer zugreift.)
Schau mal genau, welche Lib die Probleme macht. Einfach mit Bedingter Compilierung die entsprechenden Bibliotheken auskommentieren, (Die Codeteile, die die verwenden natürlich auch :-)) und schauen, bei welcher Lib das Zittern ein Ende hat.
Abschalten ist übrigens keine Lösung. Denn der Servo ist dann stromlos und kann nicht mehr gegen evt. äußere Änderungen gegenhalten. (Dafür ist er ja auch da)

Ich habe die "alte" Servolib nicht zu laufen gebracht.
Auch nachdem ich in der Lib das #include <arduino.h> eingefügt hatte, was zum compilieren nötig war, ging einfach nix.
Auch das Beispiel lief nicht.

ok. Alle Libs rausschmeissen um den "Störenfried" zu finden wäre evt eine Lösung, aber dann eine neue, eigene Lib schreiben, das geht definitiv über meine Kenntnisse. (und, ehrlich gesagt, fehlt mir auch die Lust dazu)

willie1968:
Abschalten ist übrigens keine Lösung. Denn der Servo ist dann stromlos und kann nicht mehr gegen evt. äußere Änderungen gegenhalten. (Dafür ist er ja auch da)

In meinem Spezielle Anwendungsfall ist das kein Problem.
Wie eingangs erwähnt, betätigen die Servos Lüftungsschieber eines Grills. Die haben keine Rückstellkräfte.
Auch ist das System so langsam, dass es reicht, wenn ich alle paar Sekunden mal prüfe, ob sicher das Stellsignal überhaupt geändert hat, und dann ggf für eine Sekunde die Spannung einschalte und die Servos stelle.

Ich habe mir jetzt mal ein paar P-Kanal Mosfet bestellt um die Versorgung der Servos zu schalten. Ich werde berichten.

guntherb:
Ich habe die "alte" Servolib nicht zu laufen gebracht.

Die Lib wird wohl auch nur laufen, wenn Du die in der Arduino-Installation vorhandene Servo-Lib von der Platte putzt, denn da bestehen Namensgleichheiten bei Dateinamen, Objekten und Funktionen und damit kommt der Compiler bestimmt nicht klar, dass er trotz gleicher Dateinamen die Lib einbindet, die Dir gerade vorschwebt.

Ich frage mich, wozu man überhaupt eine Library braucht, wenn nur ein bis zwei Servos anzusteuern sind. Da reicht doch eine simple Funktion mit delayMicroseconds() drin, die man regelmäßig von der loop aus aufruft und die folgendes macht:

  • falls weniger als 20 ms seit dem letzten Servo-Refresh vergangen sind => Ende der Funktion
  • falls mindestens 20 ms vergangen sind ==> Impuls ans Servo
    Dabei wird ein Winkel von 0 bis 180 auf eine Impulsdauer von 1500 bis 2000 Mikrosekunden umgerechnet.

Bei Dir sind von der Anzahl her zwei Servos anzusteuern, oder?
Das schreibe ich so runter.
Weißt Du zufällit, ob der Servo-Pegel normalerweise LOW ist und der Impuls HIGH, oder ob es umgekehrt ist? Sonst schaue ich mal in einer vorhandenen Servo-Lib nach.

jurs:
Das schreibe ich so runter.

Das ist nicht nötig, aber vielen Dank fürs Angebot! :*

Die Idee an sich ist nicht schlecht, und das ist etwas, was ich auch hinkriege.
das werde ich mal ausprobieren, wenn ich mal Zeit habe!

guntherb:
Die Idee an sich ist nicht schlecht, und das ist etwas, was ich auch hinkriege.
das werde ich mal ausprobieren, wenn ich mal Zeit habe!

Falls jemand zwei Servos hat und Lust zu testen, anbei mein Code für einen 2-Servo Test-Sketch:

// Two-Servo-Test by 'jurs' for German Arduino Forum
// Code is UNTESTED - Please feel free to test by yourself!

#define SERVO1PIN  4    // Ein Servo an D4
#define SERVO2PIN  5    // Ein Servo an D5
#define MINPULSE 1000   // Minimale Impulslänge bei Linksausschlag
#define MAXPULSE 2000   // Maximale Impulslänge bei Rechtsausschlag

void twoServoRefresh(byte pin1, byte angle1, byte pin2, byte angle2)
{
  static unsigned long lastRefresh=0;
  if (millis()-lastRefresh<20) return; // noch keine 20 ms vergangen
  lastRefresh=millis(); // Zeit des erfolgten Refresh merken
  if (angle1>180) angle1=180; // Stellwinkel bis 180 Grad erlaubt
  if (angle2>180) angle2=180;
  int pulselen1 = MINPULSE + (MAXPULSE-MINPULSE)*(long)angle1/180;
  int pulselen2 = MINPULSE + (MAXPULSE-MINPULSE)*(long)angle2/180;
//  noInterrupts();
  digitalWrite(pin1,HIGH); // Beide Refresh-Impulse "gleichzeitig" starten
  digitalWrite(pin2,HIGH); // Beide Refresh-Impulse "gleichzeitig" starten
  if (pulselen1<pulselen2) // der erste Refresh-Impuls ist der kürzere
  {
    delayMicroseconds(pulselen1);
    digitalWrite(pin1,LOW);
    delayMicroseconds(pulselen2-pulselen1);
    digitalWrite(pin2,LOW);
  }
  else  // der zweite Refresh-Impuls ist der kürzere oder gleichlang
  {
    delayMicroseconds(pulselen2);
    digitalWrite(pin2,LOW);
    delayMicroseconds(pulselen1-pulselen2);
    digitalWrite(pin1,LOW);
  }
//  interrupts();  
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(SERVO1PIN,OUTPUT);
  pinMode(SERVO2PIN,OUTPUT);
}


void loop() {
 int servo1angle, servo2angle;
 if ((millis()/10000)%2==0) // Immer je 10 Sekunden lang
 {
   servo1angle=45;
   servo2angle=135;
 }
 else   // nach 10 Sekunden Servoposition wechseln
 {
   servo1angle=135;
   servo2angle=45;
 }
 twoServoRefresh(SERVO1PIN, servo1angle, SERVO2PIN, servo2angle);
}

Der Code ist vollkommen ungetestet, da ich keine Servos hier habe.
Die Grundlagen zur Servoansteuerung habe ich diesem Wikipedia-Artikel entnommen:

und entsprechend eingebaut:

  • Normalpegel ist LOW, Impulse sind HIGH
  • Mikrosekundengenaues Timing zwischen 1000 ?s (Linksausschlag) und 2000 ?s (Rechtsausschlag)
  • Aufruf alle ca. 20 Millisekunden, aber es kommt nicht so genau darauf an (daher Aufruf aus der loop)

Falls die Servos immer noch Zittern (ggf. in anderen Sketchen, die I2C-Hardware und anderes ansteuern), einfach mal die Kommentarstriche bei "noInterrupts" und "interrupts" entfernen für eine noch exaktere Einhaltung des Timings.

Irgendwo muss ich mal in meinen alten Sachen rumkramen, da hatte ich vor etlichen Jahren Modellbauartikel verpackt und es müßten auch Servos dabei sein.

Würde mich ja selbst mal interessieren, wie das funktioniert.

Hoi jurs,

hab's getestet mit 2 Billig-Servos aus der Bucht (diese durchsichtigen hellblauen Plastikteile) sowie in Kombi mit einem "richtigen" Servo 8)
Läuft tadellos ohne Zittern & Zagen - TopJob :smiley:

Ich bin mit meinem Servogelumpe auch noch nicht wirklich weiter.
Habs aus Frust erst mal ans Ende der ToDo-Liste gestellt.
Ich gehe nach wie vor (bei mir) davon aus, das sich da irgendwas intern im Mega "beharkt".
Allerdings wird bei mir mit externem Interrupt getaktet - scheint das Hauptprob zu sein ......

Hier würde ich das auch vermuten.
Eine andere Sache ist: Verwendet man Standard-Servos, muss die Wiederholfrequenz ziemlich genau 20ms sein.
Geht man darüber hinaus, bekommen manche Servo's auch das große Zittern, bzw. werden sehr ungenau im Stellbreich und ruckeln.

Ich würde mal hier einfach solo mit der Servo-Lib anfangen und nen Poti als Steller verwenden.
Dann sukzessive die weiteren Libs und Funktionen einbinden, bis sich der Störenfried zu erkennen gibt.