Problem mit Servo steuerung

Moin,
ich habe eine Servosteuerung geschrieben, in der man einen Wert von 1-180 im Terminal eigeben können soll, und der Servo soll dann diese Position einnehmen. Aber wenn ich die Zahlen 1 - 9 eingebe, (nach stellen des Servos gibt er den Wert mit Serial.prinln zurück) bekomme ich statt 1, 10 zurück und bei 9, 90. Das ist auch bei allen anderen Zahlen so. Wenn ich jetzt 2 Zahlen gleichzeitig eingebe, z.B. 85, stellt er den Servo erst auf 80° und dann auf 50° und gibt erst 80 und dann 50 zurück. Was mache ich falsch?

#include <Servo.h>

Servo se;
char inByte[32];
int i = 0;
int pos;

void setup(){
  se.attach(4);
  se.write(90);
  Serial.begin(9600);
  Serial.println("Ready...");
}

void loop(){
  if(Serial.available()){
    while(Serial.available()>0){
      inByte[i] = Serial.read();
      inByte[i+1] = '/0'; // Das habe ich hier im Forum gesenhen, damit ich das Teil zum int machen kann
      i++;
    }
    i = 0;
    pos = atof(inByte);
    se.write(pos);
    Serial.println(pos);
  }
}

Oha, da sind mehrere Macken drin:

  • Das Zeichen '\0' (als Nullbyte) wird mit Backslash ("") geschrieben, nicht mit Slash ("/").

  • atof() wandelt einen ASCII-String in eine Fließkommazahl, nicht in einen int. Verwende dafür besser atoi(), sonst wird die Fließkommabibliothek mit in deinen Sketch gebaut. Das kostet Platz und ist langsam.

  • du hast die serielle Schnittstelle auf 9600 BPS gestellt, das sind etwa 1000 Zeichen pro Sekunde. Deine while-Schleife läuft aber sehr viel schneller als 1000x pro Sekunde. Nach dem ersten gelesenen Zeichen
    wird also die Abbruchbedingung (Serial.available()>0) zuschlagen, einfach weil das nächste Zeichen noch nicht da ist.
    Besser ist es da, gar keine Schleife zu verwenden:

#include <Servo.h>

Servo se;
char inByte[12];
int i = 0;
char zeichen;
char fertig = 0;
int pos;

void setup(){
  se.attach(4);
  se.write(90);
  Serial.begin(9600);
  Serial.println("Ready...");
}

void loop()
{
  // erst mal serielle Schnittstelle auswerten
  if(Serial.available()){
    zeichen = Serial.read();
    if((zeichen >='0') && (zeichen <='9') && (i < 10)) // numerisches Zeichen?
    {
      fertig = 0;
      inByte[i] = zeichen; // zeichen in den String uebernehmen
      inByte[i+1] = '\0'; // Ein String hat in C/C++ ein Nullbyte am Ende
      i++;
    }
    else fertig = 1; // ein nicht-numerisches Zeichen beendet die Zahl
  }
  
  // Wenn von der Seriellen was kam kann das jetzt umgewandelt werden
  if((fertig == 1) && (i > 0)) // nur bei i>0 haben wir was gelesen! 
  {
    pos = atoi(inByte);
    se.write(pos);
    Serial.println(pos);
    i = 0;
  }
  
  // hier kann evtl. weitere Verarbeitung erfolgen
}

loop() schaut in jedem Durchgang, ob ein Zeichen angekommen ist. Falls ja, wird überprüft ob es eine Ziffer ist. Ist es eine, wird sie an den String angehängt (bis zu 10 Ziffern), andernfalls wird die Zahleneingabe beendet ("fertig = 1;").
Dann schaut loop() ob eine Zahl komplett angekommen ist (also i > 0 und fertig == 1). Wenn ja, wird der Servo gestellt und i wieder genullt.

Dadurch wird loop() nicht durch eine while()-Schleife blockiert und kann mehrere tausend Durchläufe pro Sekunde schaffen, so dass man "nebenbei" jetzt noch Luft für andere Aufgaben hat (Sensoren auslesen, LEDs blinken lassen etc...).
Wichtig ist halt nur, dass keine dieser Aktionen den Arduino blockiert.

Gruß
pi

ok danke erstmal.
aber dein Code geht nicht.
Wenn ich ihn übertrage und den Arduino resette, stellt sich der Servo in Mittel position. Auf Zahlen eingaben reagiert er nicht. Aber wenn ich sinnlos irgentwelche buchstaben eintippe, bekomm ich immer Zahlen wie
-243242 und +234234 zurück ausgegeben. Im code direkt konnte ich jetzt keinen Fehler finden. Woran liegts?

Philipp

Fangen wir mal beim Servo an: Der sweep-Sketch funktioniert?

Welche Werte musst du denn für 0°, 90° und 180° übergeben?

Dann: Ist der String korrekt wenn du ihn in eine Zahl wandelst? Einfach mal auf der Konsole ausgeben.

Gruß
pi

servo geht.
Würde meine Methode gehen, wenn ich eine größere Baudrate nähme?
Welche ist denn die Maximal mögliche mit dem Duemilanove?

Philipp

Ehm - nein.

Die Baudrate der Seriellen hat da wohl nichts mit zu tun, die muss halt nur auf beiden Seiten gleich eingestellt sein. Bei 9600 auf dem Arduino brauchst du auch 9600 auf der Gegenstelle. Ist bei 11520 aber genauso.

Kommen denn die Zahlen so zurück wie du sie eingegeben hast?

Gruß
pi

Also ich habe jetztmal das Serial.println(inByte) in die if abfrage gepackt, in der abgefragt wird, ob es ein numerisches Zeichen oder ein Buchstabe ist. Da bekomm ich bei der Eingabe 123 zu erst eine 1, dann eine 12 und dann eine 123 zurück. Soweit ist dass dann ja gut, aber wenn ich dann noochmal 123 eigeben wird es zu dem bestehenden dazuaddiert. Also kommt dann zuerst 1231 dann 12312 und dann 123123 zurück. Das deutet darauf, dass die abfrage if((fertig == 1...
garnicht aufgerufen wird. Den Fehler habe ich gefunden. Du hattest die Esle fertig = 1; als else für die if((zeichen>='0'.. eingesetzt, aber so wird die else nie aufgerufen. Dann habe ich die else für if(Serial.availa.... eingestzt. Jetzt geht es. Aber das problem ist jetzt noch, dass der Block, in dem der Servo positioniert wird if((fertig ==1
immer wieder durchlaufen wird. Dadrin habe ich auch noch eine Serial.prinln und in der console wird die ganze Zeit über der Wert ausgegeben, der Aktuell ist. zumindest geht es jetzt mit eingabe der Zahlen, dass die pos Variable auch den richtigen Wert bekommt.

Philipp

Du denkst aber daran, dass du zum Beenden einer Zahl ein nicht-numerisches Zeichen eingeben musst?

Weil der Arduino ja nicht ekennen kann, ob nach "12" noch eine "3" kommt oder nicht, musst du ihm da schon helfen, eben durch ein Zeichen das keine Ziffer ist.
Genau in diesem Fall wird dann mein "else fertig = 1" aufgerufen.

Gruß
pi

das ist schon klar aber so wie du das Programmiert hast, wird die else fertig =1; nie ausgerufen, da dieser Aufruf in der if srial.available verschachtelt ist, und wenn keine Zahl mehr im Buffer ist, wird dieser Aufruf garnichtmehr gemacht, so wird die else fertig = 1 nie ausgeführt.
So wie ich das jetzt umgeschrieben habe, wird erstgeschaut ob noch Zahlen im Buffer sind, und wenn das nicht der Fall ist, wird else fertig = 1 aufgerufen.

Wenn du die Zahlen von der Arduino-IDE aus dem seriellen Terminal heraus verschickst, dann ist immer nach den Ziffern ein Zeilenvorschub (0x13 oder 0x10) mit dabei, so dass mein "else"-Kriterium greift.

Womit verschickst du denn die Zahlen dass nach der letzten Ziffer gar nichts mehr kommt?

Die Abfrage auf "serial.available" halte ich für bedenklich, besonders bei deiner niedrigen Transferrate. Da wird das auch mit Sicherheit mitten in der Zahl vorkommen.

Gruß
pi

Ich verschicke die Zahlen mit dem Arduino Terminal
Aber bei mir kommt am ende kein weiteres Zeichen
Philipp

OK, habs mal ausprobiert, du hast recht: es gibt kein Zeilenendezeichen.

Aber wenn man selbt ein solches Zeichen bereitstellt, dann funktioniert es. Ein beliebiges Zeichen tut es schon, such dir eins aus 8-).

Das kann man sich sogar zunutze machen:
Wenn du z.B. zwei Servos ansteuern möchtest kann das zusätzliche Zeichen für den Servo stehen: "A" ist der eine, "B" der andere.

Sende "90A45B" um beide zu stellen, oder "180B" um nur einen zu stellen.

Bruß
pi

ich möchte später auch 2 Servos ansteuern, aber ich hatte vor 6 bytes zusenden. Also z.b. Servo 1 = 180 und Servo 2 ist 45.
Dann wollte ich 180045 senden. Und dann char servo_1[] = {inByte[0],inByte[1],inByte[2],};
und mit servo 2 dann genauso.
Aber es ist ja so, dass ich in das inByte immer noch ans ende '\0' setzten muss. Muss ich das dann auch ans ende von den servo_1 /2 chars machen? Also: char servo_1[] = {inByte[0],inByte[1],inByte[2],'\0'};

Philipp

Nein, das Nullbyte dient dazu, das String-Ende anzuzeigen. Ein solches gibt es aber definitionsgemäß nur einmal ("... nur die Wurst hat zwei!").

Mit den festen Stringlängen kannst du dir das Einlesen natürlich einfacher machen. Ich würde allerdings trotzdem Zeichen zur Separation der Dreiergruppen einfügen, damit die Synchronisation besser klappt.

Sollte sich der Arduino irgendwann mal verzählen (z.B. weil eine ISR ihn für eine Sekunde ausgebremst hat und er ein paar Zeichen verloren hat) kann er wieder aufsetzen:

018-049-160-086-119-16-012-048-157-167-049-202

01804916008611916012048157167049202

Ein Zeichen fehlt: die 6. Dreiergruppe besteht nur aus zwei Ziffern.
Durch die Bindestriche lässt sich das aber auch für den Arduino sehr viel leichter feststellen als im reinen Zeichenstrom. Da würde er dann statt "16-012-048-157" ein "160-120-481-" lesen und somit drei ziemlich heftige Fehleinstellungen machen. An der 481 kann man evtl. erkennen, dass hier ein Fehler vorliegt, aber es gibt keinen Hinweis, wie das korrigiert werden kann.

Da die Servos üblicherweise mit 50 Hz angesteuert werden hast du also bei 9600 BPS etwa 20 Zeichen für eine Übertragung. Das reicht für 5 Servos mit 4 Zeichen (3 Ziffern und 1 Separator).

Gruß
pi

So, ich konnte den Code ebenfalls gebrauchen, vielen Dank für die gute Vorarbeit!
Ich hab es jetzt so umgesetzt, wie pi es vorgeschlagen hatte:

Sende "90A45B" um beide zu stellen, oder "180B" um nur einen zu stellen.

Funktioniert bestens, und war wirklich leicht seinen code anzupassen.
Mein Teil des codes ist wahrscheinlich etwas unsauber, sollte aber zu lesen sein.
Ich bin selbst neu hier und versuch grade, mir auf Basis eines RC-Monstertrucks einen Roboter als lernplattform zu bauen.
Und da kam es mir ganz gelegen, das Ding auch ohne intelligentes Verhalten einfach vom PC aus fern zu steuern.

//05/06/10
//sketch zur Ansteuerung eines Modellautos mit zwei Kanälen per Arduino
//sketch for controlling a model car with two channels with an arduino

#include <Servo.h>

Servo servo;  //Servo für die Lenkung /Servo for steering
Servo fahrt;  //Fahrtregler / speed controller
char inByte[12];
int i = 0;
char zeichen;
char fertig = 0;
int pos;

void setup(){
  servo.attach(4);  //Pin für den Servo / Pin for the servo
  servo.write(90);  
  fahrt.attach(3);  //Pin für den Fahrtregler / Pin for the speed controller
  fahrt.write(90);
  Serial.begin(9600);
  Serial.println("vehicle ready and waiting for instructions...");
}

void loop()
{
  // erst mal serielle Schnittstelle auswerten
  if(Serial.available()){
    zeichen = Serial.read();
    if((zeichen >='0') && (zeichen <='9') && (i < 10)) // numerisches Zeichen?
    {
      fertig = 0;
      inByte[i] = zeichen; // zeichen in den String uebernehmen
      inByte[i+1] = '\0'; // Ein String hat in C/C++ ein Nullbyte am Ende
      i++;
    }
    else fertig = 1; // ein nicht-numerisches Zeichen beendet die Zahl
}
  
  // Wenn von der Seriellen was kam kann das jetzt umgewandelt werden
  if((fertig == 1) && (i > 0)) // nur bei i>0 haben wir was gelesen!
  {
    pos = atoi(inByte);
    if(zeichen == 'A')
    fahrt.write(pos);
    Serial.println("Fahrtregler auf ");
    Serial.println(pos);
  }
    if(zeichen == 'B'){
    servo.write(pos);
    Serial.println("Servo auf ");
    Serial.println(pos);
  }
    i = 0;
  }
  
  // hier kann evtl. weitere Verarbeitung erfolgen


Gruß,
Philipp

Ok,
ist doch noch ein entscheidender Fehler drin,
der hat mich gestern Abend noch einige Nerven gekostet.
Hinter der 'If' anweisung für den Servo fehlt ein {, und dementsprechend auch am Ende, dort muss nur ein } angefügt werden.
Hier der korrigierte Code:

//05/06/10
//sketch zur Ansteuerung eines Modellautos mit zwei Kanälen per Arduino
//sketch for controlling a model car with two channels with an arduino

#include <Servo.h>

Servo servo;  //Servo für die Lenkung /Servo for steering
Servo fahrt;  //Fahrtregler / speed controller
char inByte[12];
int i = 0;
char zeichen;
char fertig = 0;
int pos;

void setup(){
  servo.attach(4);  //Pin für den Servo / Pin for the servo
  servo.write(90);  
  fahrt.attach(3);  //Pin für den Fahrtregler / Pin for the speed controller
  fahrt.write(90);
  Serial.begin(9600);
  Serial.println("vehicle ready and waiting for instructions...");
}

void loop()
{
  // erst mal serielle Schnittstelle auswerten
  if(Serial.available()){
    zeichen = Serial.read();
    if((zeichen >='0') && (zeichen <='9') && (i < 10)) // numerisches Zeichen?
    {
      fertig = 0;
      inByte[i] = zeichen; // zeichen in den String uebernehmen
      inByte[i+1] = '\0'; // Ein String hat in C/C++ ein Nullbyte am Ende
      i++;
    }
    else fertig = 1; // ein nicht-numerisches Zeichen beendet die Zahl
}
  
  // Wenn von der Seriellen was kam kann das jetzt umgewandelt werden
  if((fertig == 1) && (i > 0)) // nur bei i>0 haben wir was gelesen!
  {
    pos = atoi(inByte);
    if(zeichen == 'A'){
    fahrt.write(pos);
    Serial.println("Fahrtregler auf ");
    Serial.println(pos);
  }
    if(zeichen == 'B'){
    servo.write(pos);
    Serial.println("Servo auf ");
    Serial.println(pos);
  }
    i = 0;
  }
  
  // hier kann evtl. weitere Verarbeitung erfolgen

}

na dass hört sich ja schon ganz Interessant an. Viel glück noch. Wie sendest du die Daten an dein Modell? Ich bin auch grade dabei sowas in der Art zuplanen mit einem Schiffmodell. Dazu habe ich mir 2 XBEEs besorgt. Sendest du die Daten über den Normalen sender der Fernbedienung?

philipp

Vielen Dank!
Man könnte die Daten über den normalen Fernbedienungssender schicken, dann müsste man die Fernbedienung mit einem PPm Signal speisen, welches der Arduino aus den Seriellen daten generiert.
Doch das war nicht mein ziel, ich wollte die Fahrzeugelektronik direkt mit dem Arduino ansteuern, nicht über den ursprünglichen Empfänger.
Daher gehen die Daten momentan noch über USB an das Modell.
Bei der nächsten Pollin-Bestellung werde ich für ein paar euro noch jeweils einen Sender und Empfänger für serielle Dateb kaufen, die senden zwischen 400 und 900 MHz und kosten nur ca. 3 euro.
XBee's wären die ideallösung, sind mir aber arg teuer.
Endziel wird es wohl sein, einen WLAN-Router von ebay mit einer opensource firmware zu bespielen und dann da irgendwie ein Webcam-Stream vom fahrzeug einspeisen und eine Serielle verbindung zum Arduino herstellen.
Das wäre das genialste überhaupt, dann könnte man das fahrzeug über den nintendo DS, Ipod Touch, PC, Wii, PS3, Internet usw. steuern :smiley:
Aber das geht nur, wenn ich gute Lösungen für den Router im netz finde. Aber man darf ja träumen^^

Für deine Lösung mit den XBee's:
ich denke ich hab da eine nette Spielerei für dich:

Das Programm gibt einen seriellen Datenstream aus, der zusammen mit dem Arduino programm aus meinem Letzten Post dazu genutz werden kann, ein beliebiges RC fahrzeug mit (bisher) 2 Kanälen vom PC aus mit einem Joystick zu steuern.
Ich werde das Programm wohl ab und an weiterentwickeln, es ist bis jetzt nicht sonderlich komplex. Solltest du Interesse daran haben, schicke ich es dir gerne per mail oder ICQ, das zip Archiv ist 190kb groß, ein Installer ist enthalten. Ich werde es auch wohl noch auf einige Kanäle erweitern und evtl. ein Arduino programm schreiben bzw. modifizieren, so dass es möglich ist, ein Signal in die Schüler/Lehrer Buchse an einer Fernbedienung einzuspeisen und so das Modell unberührt zu lassen. Das Thema interessiert mich sehr, ich hoffe ich komme in der nächsten zeit dazu, es weiter zu verfolgen.
lg, Philipp

mit welcher Sprache haste das Programm geschrieben? Ich habe mir 2 XBEE Pros bestellt bei Projet, da kosten die nur 24 Euro pro stück und nicht 40-50 Euro. Warum weiß ich nicht. Ich habe auchmal ein Programm mit VC# 2008 geschrieben mit dem ich einen Servo an nem Arduino über USB steuern kann.

Philipp

Ist in Visual Basic 2008 geschrieben,
was Desktopumgebungen angeht bin ich immer noch ein Visual Basic Symphatisant^^ Seit meinem 10ten Lebensjahr mit Visual basic 6.0 dabei, daher nehm ich es immer noch gerne. Quellcode kannste von mir aus haben, falls du dich mit Visual Basic arrangieren willst.
Die Programmierumgebung gibts ja kostenlos in der Express Version direkt von microsoft.