Ein Servo im Hintergrund mit Timer2 Interrupt

Hallo ihr,

ich möchte die Bewegung eines Servos in den Hintergrund verbannen, sodass sie unabhänig durchgeführt, egal was der Code gerade noch so ausführt und das sich beide Programmteile sich nicht gegenseitig behindern.

Die meiste Zeit ist ein Servo ja dabei zu warten. Da dachte ich mir, in der Zeit könnte ich ja den eigentlichen Code ausführen.

Ich möchte also in einen Timerinterrupt springen, den Servo ansprechen und dann wieder in das Hauptprogramm.

Ich habe erstmal eine Test"skizze", fast schon eher einen Testtreiber geschrieben, um das ganze erstmal auszuprobieren.
Da Timer0 fürs Delay etc. benutzt wird und Timer1 fürs PWM, dachte ich mir, wäre es dann einzig sinnvolle den Timer2 zu währen. Mit seinen 8 bit läuft er ziemlich schnell über und selbst einem Prescaler von 1024 ist es immer noch etwas zu häufig.

Ich habe die etwas unschöne Lösung gewählt, dass nur jeder 16 (oder in der Realanwednung dann später 32.) Timer Interrupt den Servo ansteuert. Gibt es eine Lösung um das Problem zu umgehen?, Kann man irgendwie doch den Timer1 nehmen, oder aber die Frequenz auf 1MHz herunterregeln? Nebenbei spart das nicht Strom? Das Projekt soll auf Batterie betrieben werden.

Ich habe es so erstmal mit etwas lesen, den Servo so zum laufen gebracht. Leider verhält sich der Servo im Rücklauf etwas merkwürdig. Der Vorlauf funktioniert anscheinend ohne Probleme. Allerdings stockt der Servo im Rücklauf in regelmäßigen Abständen (5 Mal). Auf mich wirkt es so, als ob der Servo, vielleicht an machen stellen, sich sogar in die falsche Richtung bewegt und dadruch eine Art zucken entsteht.

Wisst ihr, wie man auch dieses Problem umgehen kann. Betreibt man den Servo normal (sequenzielles Programm) verhält er sich sich normal.

Den Code bin ich euch ja noch schuldig beglieben:

#include <Servo.h>
#define circle 15 //only move servo in ever (circle+1)-st interrupt
#define SERVO 6

Servo servo;
volatile int timer2Counter;  //Timer-Variable
volatile int i = 60; 
volatile boolean sweep = 0;
volatile uint8_t wait = 0;

void setup() { 
  servo.attach(SERVO);
  Serial.begin(2400);
  noInterrupts();
  TCCR2A = 0;  //delete all bits
  TCCR2B = 0;  //delete all bits
  timer2Counter = 0;
  TCNT2 = timer2Counter;//preload timer
  
  TCCR2B |= (1 << CS10);
  TCCR2B |= (1 << CS12);//Sets Prescaler to 1024
  TIMSK2 |= (1 << TOIE2);// enables timer overflow interrups
  interrupts();
} 

ISR(TIMER2_OVF_vect) {
  TCNT2 = timer2Counter;
  if (wait != circle)//only move servo everly (circle+1)st interrupt
    ++wait;
  else { //going to move servo
      if (sweep == 0 ) {//moving forth
      if (i == 120)//start backsweep next time
        sweep = 1;
      servo.write(i);
      ++i; 
      }   
    
    else {//backsweep
      --i;
      servo.write(i);
      delay(20);
      if (i == 60)//start forthsweep next time
        sweep = 0;
      }
    wait = 0;//start new counting, for which interrupt moves servo
    }
}

void loop() {
  if (wait == circle) {//testroutine
    Serial.print(wait);
    Serial.print(" ");
    Serial.print(sweep);
    Serial.print(" ");
    Serial.println(i);
    }
}

Vielen Dank und Liebe Grüße!

delay() funktioniert in ISRs nicht, da das selbst mit einem Timer läuft. Und das wird sich vielleicht auch mit dem Servo Timer beißen.

Du kannst aber in der ISR ein Flag setzen auf dass du in loop() abfragst. Dann weißt du dort dass was zu tun ist.

Ich verstehe jetzt nicht wieso Du so einen Aufwand machst.
Um einen Servo zu steuern nimmst Du die Bibliothek und änderst den Wert der Einstellung nur wenn Du es brauchst. Da bleibt viel Zeit um andere Sachen zu machen.
Grüße Uwe

Blink without delay ist dein Freund :wink:

Serenifly:
delay() funktioniert in ISRs nicht, da das selbst mit einem Timer läuft. Und das wird sich vielleicht auch mit dem Servo Timer beißen.

Vielen Dank. Das hat, das Problem des "Stotterns" gelöst. Denn habe den Code wohl einfach nicht mehr überblickt! Die delay()-Funktion war hier total sinnfrei. :wink:

@uwefed:

Nachtrag: Erstmal danke, für die Nachfrage. Hat mir erstmal geholfen. Die Sache aus einem anderen Blickwinkel zu betrachten. Denn der Code oben wirkt auf mich, nicht so ganz elegant. Würde mich sehr freuen, wenn ihr noch mehr Ideen hättet ihn zu verbessern. Hoffe, dass ich dich richtig verstanden habe.
Original: Das Problem ist, dass das Gesamtprojekt schon noch etwas größer ist und noch größer wird. Ich wollte wollte est außerdem möglichst Prozedual entwerfen und habe wegen der Länge und Unübersichtlichkeit schon massiv Code ausgelagert und er wird noch länger. Man kann normal auch keine zwei Prozeduren parallel laufen lassen. Wenn ich dich aber richtig verstehe, räts du das normale dalay() gegen millis() zu tauschen? Mhh... ich überlege gerade die Konsequenzen:

  1. Dann würde ich aber für die serialEvent()-Funktion nichts gewinnen, oder? Dann müsste, ich also die ganzen Eingaben und Ausgaben in der loop()-Funktion bearbeiten.
  2. Unschön wird es erst, wenn der Code so lang wird, dass die Abarbeitung so lange dauert, dass das bis er die Abfrage erreicht hat und den Servo weiter bewegen sollte, noch nicht so weit ist und das wird wohl bald passieren.
    Wenn ich das richtig überblicke, ist die langsamste Anwendung das Senden von Daten. Bei 9600 Baud sind das etwa 1 Zeichen pro ms. Dann habe ich, wenn zwischen zwei Ansteurungen des Servos je nach einstellung 16 bis 32 Zeichen Zeit. Da sich das in Senden und Empfangen aufteilt, ist das im besten Fall jetzt schon knapp.
    Ich wollte das Projekt nähmlich später über Bluetooth steuern und das SoftwareSerial spinnt doch bei höheren Übertragungsraten? Aber das ist schon ein ganz anderes Thema. :wink: Nebenbei, gibt es irgendwo eine Liste, wieviel (relative) Rechenzeit eine Anweißung braucht?

Vielen Dank soweit,

Mümmel

Hast du schon mal überlegt, wie lange der Servo für die jeweilige Drehbewegung braucht?
normale Modellbau Servos benötigen unter Umständen 1 Sekunde oder mehr für eine Bewegung von 180°. extrem schnelle Servos machen das in 0,15 Sekunden.

wenn die Frequenz der Ansteuerung zu hoch wird, ist das unter Umständen gar nicht gut für das Teil. es kommt natürlich auf den Verwendungszweck an, aber ich glaube, eine relativ langsame Ansteuerung ist für einen Trägersystem wie einen Servo gut genug. Und ich glaube nicht, dass man dafür Interrupts braucht. alle 50 bis 100 ms sind je nach Servo mehr als genug, damit der überhaupt hinterher kommt.

Was soll denn mit dem Servo überhaupt gesteuert werden?

Also verbaut ist ein HS-311 von HiTEC. Spezifiziert ist bei 4.8 V eine Zeit von 190 ms für 60°. Also etwas unter 3.2 ms/° und ich verwende bisher wie gesagt zwischen 16 und 32 ms. Drauf sitzt ein Ultraschallsensor und eine Halterung. Nun ist ja auch etwas an Gewicht drauf, aber weiß nicht, wie stark sich das auf die Zeit auswirkt. Mehr als eine halbe Packung Schokolade wiegen? Also ein paar zehn Gramm?

Das ist ein mittelschnelles 3kg Servo, das Gewicht sollte sich nicht auswirken.

Aber bei mehr als 5º bzw. 10º Winkeländerung hinkt das Servo hinterher, wenn du sofort eine Messung auslöst. Du solltest mit der Messung warten, die Wartezeit abhängig von der Winkeländerung. Auch würde ich eine neue Position erst ans Servo schicken, wenn es die alte Position sicher erreicht hat (Wartezeit aufgrund Winkeländerung ausrechnen zzgl. etwas Polster) und dann die Messung im Kasten ist. Lässt sich alles mit millis() und auf Basis von "Blink without delay" lösen.

Naja,

ich gehe ja immer nur einen Grad weiter, warte die 16 ms, 32 ms... messe und gehe dann den Winkel weiter... (Selbst wenn ich den Servo z.B. fünf Grad bewegen sollte, ist das im Projekt bisher so implementiert, dass der Servo dann 5*16 ms Zeit hat.)

Parallel dazu diesem Problem, soll noch etwas Mathematik mit der ENtfernungsmessung angestellt werden. Sollen Räder gedreht werden, Strings von der seriellen Schnittstelle gelesen werden und Befehle entsprechend ausgeführt werden. Wobei ich dieses noch in einem Char Feld umschreiben wollte.

Ich habe mit der millis()- Funktion halt zwei Sorgen. Zum einem beim Servo, dass die Rechenzeit so groß ist, dass der Sevo Phasenweise etwas länger steht. Wie berechtigt, dass ist weiß ich noch nicht, ich müsste es Mal implementieren und dann Mal schauen.

Zum anderen beim Programmfluss in Funktionen. (Ganz anderes Thema.) Wenn ich statt delay() etwas wie

 if (zeitdifferenz >= WARTEZEIT) {...}

in einer Funktion steht, dieser entsprechende Programmteil der Funktion nie ausgeführt wird somit z.B. in einer Funktion für die Steuerung eines Rades das Rad sich immer weiter dreht. Wie man sieht der alte Haudegen, bin ich leider noch nicht und mir fehlt noch etwas an Erfahrung. Aber der Weg ist das Ziel. :wink:

Liebe Grüße!

Du musst dir wie schon mehrfach gesagt, das "Blink without delay" verinnerlichen.
Anstatt if (zeitdifferenz >= WARTEZEIT) {...} solltest du

if (millis()-lastAction>= WARTEZEIT) {lastAction=millis(); tu was; ...} machen, und lastAction unbedingt global oder static unsigned long definieren.

Was die Geschwindigkeit des Arduino anbetrifft, kann der so einiges leisten, wenn kein delay() verwendet wird und du timeouts vermeidest. Speziell bei seriellen Eingaben musst du darauf achten, dass du durch unvollständige oder falsche Eingaben keine Hänger im Programm hast. readStringUntil() oder auch pulseIn() sind solche Kandidaten.

SR04: Bau dir testweise 2 LaserLED dran, die die "Messkeule" abbilden, dann siehst du auch, was tatsächlich im Messbereich liegt.

und Beide Funktionen habe ich drinn. Das readStringUntil wollte ich sowie los werden. Die PulseIn habe ich die Abfragezeit etwas gekürzt, sodass der Schall nur wenig extra Zeit hat für als er für die maximale Messbare Entfernung bräuchte. Kann man das PulseIn den für den Sensor umgehen?

Ich muss Mal sehen, wie ich das mit dem Millis machen soll, denn gerade die Funktionen für das drehen der Räder brauchen sehr lange und deren delay() sperrt viel...

Dann bliebe von meiner Warte aus nur noch die Frage, wie ich die Geschwindigkeit der seriellen Übertragung soweit reduziere, dass diese mir kein Schnitt durch die Rechnung macht. 9600 Baud sind definitiv dann zu langsam...

und Danke. Die ToDo-Liste füllt sich. :wink:
Liebe Grüße!

  1. nimm 115200 baud. Der UART Puffer hilft auch, wenn die Abfrage nicht rechtzeitig durchgeführt wird. Sind glaube ich 64 oder 128 Byte.

  2. Statt pulseIn: Sie dir mal die NewPing lib an. Soweit ich weiss, reduziert sich da auch die Afragezeit aufs absolute Minimum, wenn du die max. Entfernung angibst.

  3. Drehen der Räder: Wenn du auf millis() umstellst, mach das immer nur in einzelnen Programmteilen, ohne den Rest vom Sketch, und lasse eine LED mitblinken. An der Blinkfrequenz kannst du erkennen, wenn es noch irgendwo hakt. Und du brauchst für jede "Millis()-Timerfunktion eine eigene "LetzteAktionWurdeAusgefuehrtAm"-Variable, static unsigned long deklariert. Und ggf. Flag-Variablen, wenn du eine Funktion von einer anderen abhängig machen willst, z.B. erst messen wenn Servo in Position und dann wieder "Servo weiterdrehen wenn Messung abgeschlossen und plausibel"

Warum stellst du den Servo nur um 1º weiter? So genau zielt der SR04 dann doch nicht.

PulseIn() blockiert den Arduino gleich wie delay();

Grüße Uwe

Vielen Dank soweit!!!
Ich werde mich dann melden, wenn es geklappt hat oder nicht. :wink: Wird aber dauern, bin kurz nach einer OP im Arm und die rechte Hand tut gerade heute besonders weh... (warte gerade noch auf die Reha und sollte eigentlich nicht so viel wie bisher machen... Schreiben tut mir eigentlich auch nicht so gut, aber das Projekt liegt mir so am Herzen.)
Ich habe erstmal gestern viel gelesen. Ich sah bisher den Flaschenhals bei Serial weil ich bisher dachte, dass ich die Kommunikation über Bluetooth machen wollte und dafür wurde auf den Seiten, die ich bisher gelesen habe zu SoftwareSerial mit 9600 Baud geraten. Aber ich werde besser wohl IEEE 802.15.4 nehmen... Da sollten auch laut den Seiten die 115200 Baud die ich auch über Kabel gerne nehme kein Problem sein. Aber den Teil mache ich erst nach der Reha in ein paar Wochen. Erstmal muss ja der Kern in Ordnung sein. Hier abe ich auch nichts zum Löten und nur das wichtigste mit.
Warum wurde eigentlich in all den Büchern die ich bisher gelesen habe 9600 bds gewählt und auf das "Blink without delay" soweiträumig verzichtet. Ab und an kam es schon vor, dann aber glaub nicht unter dem Namen und gerne Mal eher ein Interrupt genommen.

Vielen Dank für den Tipp mit der NewPing lib, werde mal rein schauen, denn das PulseIn blockiert bisher bis 4 ms (im Überschlag). Werde mich da Mal hineinfuchsen.

Danke auch für den Tipp mit der LED. :wink: Warum ich nur einen Grad nehme? Weil es besser aussieht? :wink: Ne, echt^^ Hab es gerne wenn er sich einigermaßen gleichmäßig bewegt. Oder kann man das umgehen? Kann man den Servo eigentlich leiser machen? :wink:

Nur mit Links schreiben ist für einen Rechtshänder recht blöde. Vielen Dank!!! Das waren schon echt viele Verbessungsvorschläge.

9600 ist eigentlich ein quasi-standard von früher. Bei langen Leitungen (bis zu 100m) muß man auch mit der Geschwindigkeit runtergehen. ansonsten kurze Leitungen kanns ruhig schneller gehen.

Grüße Uwe

Das pulseIn geht schnell, wenn alles funktioniert. Wenn aber der Sensor kaputt ist, oder ein Kabel mit Wackelkontakt, oder ..., dann blockiert das Programm und das Ding fährt viellleicht gegen die Wand oder so :wink: :wink:
Bluetooth muss auch mehr als 9600 baud machen.

Blink without delay ist im Prinzip nur ein einfaches Beispiel für eine bestimmte Funktion. Und bei nicht blockierendem Code solltest du alles in solche Funktionen packen und dann im Loop nur noch die Funktionen nacheinander abrufen. Und wenn eben der Sensor spinnt, sollte die Funtion einen Error ausgeben und der rest sollte weitermachen und sogar ggf. den error auswerten, dir zB. ne SMS aufs Handy schicken.
Also z.B. so:
loop
{
sr04_abfragen();
seriell_abfragen();
andereSensorenAbfragen();
fahrtKalkulieren()
lenkungEinstellen();
raederDrehen();
sontstnochwas();
warnlampeBlinken();
}

und innerhalb der Programmteile fragst du dann über "Blink without delay" ab, ob der Programmteil zeitlich schon dran ist und ggf. sogar, welcher Teil ausgeführt werden soll. Damit eben der SR04 erst misst, wenn das Servo in Position ist.

Hi,

der Testtreiber für das Blink_without_delay für den Servo funktioniert. Habe auch die New Pib Liberary benutzt, die misst sehr viel genauer als mein eigener Code mit PulseIn. :slight_smile:

Zu Anmerkung, ob der das statt Zigbee nicht auch mit Bluetooth schnell zu übertragen geht, habe ich folgenden Link gefunden:

Schaute recht vielversprechend aus! Hat das schon Mal wer Probiert. Habe nur einen Tread zum Thema Kabelloses Programmieren gefunden, in dem Uwe zum XBee riet.

Liebe Grüße!