Go Down

Topic: Projekt: Schrittmotor (Read 13318 times) previous topic - next topic

Serenifly

Serial.available() muss abgefragt werden bevor man mit read() einließt. Wenn nichts im Puffer ist, brauchst du auch nicht read() zu machen. Und nach read() ist available() in deinem Fall 0, also wird dieser Code nie ausgeführt. Der Rest sollte aber laufen.

Siehe Beispiel-Code hier:
http://arduino.cc/en/Serial/Available

Die ganzen if(x == ... && y == ...) Abfragen braucht man auch nur zu machen wenn wirklich etwas eingelesen wurde.

Ich habe mein Post oben mal editiert, aber der Code am Ende ist nicht getestet!

sonyfuchs

#16
Jul 28, 2013, 04:50 pm Last Edit: Jul 28, 2013, 04:52 pm by sonyfuchs Reason: 1
also, wenn ich nicht nach serial available abfrage läufts.

wenn doch dann sagt mir der serial monitor zu folgender eingabe
00 Senden
10 Senden
11 Senden
das hier:
Test
x: 0 - y: ÿ
1
x: 0 - y: ÿ
1
x: 1 - y: ÿ
1
x: 0 - y: ÿ
1
x: 1 - y: ÿ
1
x: 1 - y: ÿ
1

anscheinend sieht er die 3 eingaben als 6 an und ändert immer nur x und nie y.
und er blockiert den Motor mit NEN immer.

hier noch mal der momentane Code:

Code: [Select]
/*
 Der Motor ist bei 00 ausgeschaltet,
 mit 01 und 10 ändert man die Drehrichtung.
*/

int CLK =13; //Schrittgeschwindigkeit
int DIR =12; //Drehrichtung
int NEN = 9; //NOT ENABLE
int HAL = 8; //1/2
int QUA = 7; //1/4

char x;
char y;

void setup()
{
 pinMode(CLK, OUTPUT);
 pinMode(DIR, OUTPUT);
 pinMode(NEN, OUTPUT);
 pinMode(HAL, OUTPUT);
 pinMode(QUA, OUTPUT);
 Serial.begin(9600);
 Serial.println("Test");
 digitalWrite(NEN, HIGH);
}

void loop()
{
 static boolean clkEin=false;
 if (Serial.available() > 0)
 {
   x = Serial.read();
   y = Serial.read();
 
 
   Serial.print("x: ");
   Serial.print(x);
   Serial.print(" - y: ");
   Serial.println(y);
   Serial.println(digitalRead(NEN));
 }
 
 if (x == '1' && y == '0')
 {
   digitalWrite(DIR, LOW);
   digitalWrite(NEN, LOW);
   clkEin=true;
 }
 else if (x == '0' && y == '0')
 {
   digitalWrite(NEN, HIGH);
   clkEin=false;
 }
 else if (x == '1' && y == '1')
 {
   digitalWrite(DIR, HIGH);
   digitalWrite(NEN, LOW);
   clkEin=true;
 }
 if (clkEin)
 {
   digitalWrite(CLK, HIGH); // CLK einschalten
   delay(5);
   digitalWrite(CLK, LOW); // CLK ausschalten
   delay(5);  
 }
}

Serenifly

#17
Jul 28, 2013, 04:58 pm Last Edit: Jul 28, 2013, 05:16 pm by Serenifly Reason: 1
Frage auf > 1 ab (oder >=2), statt auf > 0. Dann wartet er bis mindestens 2 Zeichen da sind. So ließt er wahrscheinlich schon nach dem ersten Zeichen und das zweite ist noch nicht da. Das liegt daran, dass der Controller schneller als die serielle Übertragung ist.

Was bei dir jetzt noch ist, ist dass er die Einstellung/Umschaltung der Richtung und Enable immer macht. Auch wenn nichts gesendet wurde. Das funktioniert zwar, da die if-Abfragen dann false zurückgeben, aber du kannst das auch innerhalb von if(Serial.available...) { } packen. So dass wenn available() 0 zurück gibt (oder etwas kleiner als 2), nur der Takt-Impuls am Ende gemacht wird.


Mal als Anhaltspunkt:
9600 Baud sind 9600 Signalwechsel pro Sekunde, d.h. ein einzelnes Bit dauert ca. 100µs ! Bei 8 Datenbits, Start und Stop-Bits, dauert ein Zeichen damit eine ganze Millisekunde und mehr.

sonyfuchs

Also mit der Abfrage nach >=2 funktioniert es jetzt wunderherrlich.
Dass erscheint mir auch sehr logisch.
Mit dem NEN Problem komm ich aber nicht so ganz mit. Ich hoffe du verzeihst mir meine Begriffsstuzigkeit.
Der wechsel ist zu schnell für den serial monitor, oder wie ist das gemeint?
Ich seh ja auch dass der NEN im monitor nicht mit dem Tatsächlichen übereinstimmt, da er manchmal auf 1 steht und der Motor trotzdem dreht.
Wie änder ich dass nun ab?

Serenifly

Das mit der Zeit war nur für die serielle Übertragung PC -> Arduino gemeint. Es dauert halt ca. 2ms um zwei Zeichen zu schicken. Deshalb kannst du nach dem Empfang des ersten Zeichens nicht gleich das zweite auslesen, sondern musst warten bis beide da sind.


Serial.println(digitalRead(NEN)) gibt den aktuellen Wert des Pins aus. Das ist bevor du umschaltest. Wenn du jetzt sagts "Motor stop", gibt er da den alten Wert aus. Nicht den neuen. Kann sein, dass du damit durcheinander kommst.

Mach das mal so:
Code: [Select]

void loop()
{
 static boolean clkEin=false;

 if (Serial.available() > 0)
 {
   x = Serial.read();
   y = Serial.read();
 
 
   Serial.print("x: ");
   Serial.print(x);
   Serial.print(" - y: ");
   Serial.println(y);
 
   if (x == '1' && y == '0')
   {
      digitalWrite(DIR, LOW);
      digitalWrite(NEN, LOW);
      clkEin=true;
   }
   else if (x == '0' && y == '0')
   {
     digitalWrite(NEN, HIGH);
     clkEin=false;
   }
   else if (x == '1' && y == '1')
   {
     digitalWrite(DIR, HIGH);
     digitalWrite(NEN, LOW);
     clkEin=true;
   }

   Serial.println(digitalRead(NEN));
 }
 
 if (clkEin)
 {
   digitalWrite(CLK, HIGH); // CLK einschalten
   delay(5);
   digitalWrite(CLK, LOW); // CLK ausschalten
   delay(5);  
 }
}


Dann führt er 1. Die Änderungen nur aus wenn wirklich was eingelesen wurde und 2. Wird NEN erst nach dem Umschalten ausgelesen. Das ist wahrscheinlich eher was du willst.

sonyfuchs

Vielen Dank, dass läuft schon mal.
Jetzt versuch ich eine Anlauframpe für den Motor zu schreiben. Da er z.B. mit einem Takt von 3ms läuft und mit einem von 2 oder 1 ms nur vor sich hin summt. Kann die delay Funktion auch float, oder wie mach ich da eine Unterteilung die kleiner ist als eine ms? Oder soll ich einen Timer benutzen? Wobei ich nicht weiß wie das Funktionieren soll.

sth77

Delays zu verwenden ist grundsätzlich Käse. Wenn du das aber eher zum Testen nimmst und Zwischenwerte brauchst, kannst du das mit delayMicroseconds machen.
Mein verwaister Arduino-Blog: http://www.sth77.de/ - letzter Eintrag: Diamex-ISP in der irgendwann mal aktuellen Arduino-IDE 1.6.4

sonyfuchs

Ah Ok, wie wähle ich den einen Timer aus?

Serenifly

Schau dir das Beispiel BlinkWithoutDelay an, wie man Delays ohne Blockierung macht:
http://arduino.cc/en/Tutorial/BlinkWithoutDelay

Statt millis() kannst du da theoretisch auch micros() verwenden (was in 4µs Schritten zählt), wobei der Zähler dafür innerhalb von nur 70 Minuten überläuft, was in dem Moment wahrscheinlich ein Problem ist.

Aber für Millisekunden braucht man keinen extra Timer, da die IDE da schon einen im Hintergrund einrichtet.


sonyfuchs

guten Tag werte Community und vielen Dank für den letzten Link.
Hab ne kleine Pause im Projekt eingelegt, aber heute wieder angefangen weiter zu arbeiten.

Was hat sich verändert?
1. Das Programm läuft jetzt ohne delay, also mit Timern, und ich muss sagen es läuft viel besser.
2. Man kann jetzt in ° angeben wie der Motor sich drehen soll.

hier mein Code:
Code: [Select]
const int CLK =24; //Schrittgeschwindigkeit
const int DIR =25; //Drehrichtung
const int NEN =22; //NOT ENABLE
const int HAL =23; //Halbschritt
int  CLKstate = LOW; //Zwischenspeicher für Taktzustand
long previousMicros = 0; //Letztes Timerupdate
long interval = 2000;  //Zeit zwischen zwei Schritten
char BET;      // Betrieb
char RIC;      // Richtung
char HSC;      // Halbschritt
int  ANZ;      // Anzahl Schritte
char eingabe[8]; //Die eingegeben Zeichen
int gradt = 0;  // Tausenderstelle
int gradh = 0;  // Hunderterstelle
int gradz = 0;  // Zehnerstelle
int grade = 0;  // Einerstelle
int grad = 0;   // bewegung in Grad
double gradD = 0; // Gradangabe Dezimal
 
void setup()
{
  pinMode(CLK, OUTPUT);
  pinMode(DIR, OUTPUT);
  pinMode(NEN, OUTPUT);
  pinMode(HAL, OUTPUT);
  digitalWrite(NEN, HIGH);
  Serial.begin(9600);   
  Serial.println("ABC D , A: an/aus, B: links/rechts, C: halbschritt/vollschritt, D: Schritte(4 stellig)");
}

void loop()
{
  unsigned long currentMicros = micros(); // Variable die vom Timer hochgezählt wird
  if (Serial.available() >= 8)           // warten bis zwei Zeichen eingegeben wurden
  {
    for(int i = 0; i < 8 ; i++)
    eingabe[i] = Serial.read();

    BET = eingabe[0];
    RIC = eingabe[1];
    HSC = eingabe[2];
   
    gradt = eingabe[4];
    gradh = eingabe[5];
    gradz = eingabe[6];
    grade = eingabe[7];
   
    grad = (gradt-48)*1000+(gradh-48)*100 + (gradz-48)*10 + (grade-48);
    gradD = (double)grad / 360.0 * 192.0;
    ANZ = (int)gradD;
   
    for ( int i = 0; i < 7 ; i++)             //Ausgabe Anfang
     {
      Serial.print("Eingabe[");
      Serial.print(i);
      Serial.print("]: ");
      Serial.print(eingabe[i]);
      Serial.print("  -  ");
     }
    Serial.print("Eingabe[7]: ");
    Serial.println(eingabe[7]);
     
    Serial.print("Betrieb: ");               
    Serial.print(BET);                       
    Serial.print(" - Richtung: ");           
    Serial.print(RIC);
    Serial.print(" - Halbschritt: ");
    Serial.print(HSC);
    Serial.print(" - Schritte: ");
    Serial.println(ANZ);                      //Ausgabe Ende
 
    if (BET == '0')                 //AUS
      digitalWrite(NEN, HIGH);
    else if (BET == '1')            //AN
      digitalWrite(NEN, LOW);
    if (RIC == '0')                 //im Uhrzeigersinn
      digitalWrite(DIR, LOW);     
    else if (RIC == '1')            //gegen Uhrzeigersinn
      digitalWrite(DIR, HIGH);
    if (HSC == '0')                 //ohne Halbschritt
      digitalWrite(HAL, LOW);       
    else if (HSC == '1')            //mit Halbschritt
      digitalWrite(HAL, HIGH);
     
    Serial.print("NEN: ");              //Testen der Verrieglung
    Serial.println(digitalRead(NEN));   //später rausnehmen!!!
  }
  if (currentMicros- previousMicros > interval && BET == '1' && ANZ > 0 && ANZ <3600)
  {
    previousMicros = currentMicros;
    if (CLKstate == LOW)
      CLKstate = HIGH;
    else
      CLKstate = LOW;
    digitalWrite(CLK, CLKstate);
    ANZ--;
    if(HSC == '0')
    ANZ--;
  }
  else if ( ANZ <= 0 )
  {
    BET = '0';
    digitalWrite(NEN, HIGH);
  }
 
}


was ich noch schaffen möchte und wozu ich eure Hilfe bräuchte:
1. bin ich ein bisschen unglücklich mit der Eingabe. Es müssen 8 Zeichen rein, in der Form: xxx yyyy wobei yyyy die Grad für die Dreheung sind. gibt man jetzt ein Zeichen zu viel oder zu wenig ein, macht der Motor nix. Das soll auch so sein. Aber leider spinnt er dann bei der nächsten korekten Eingabe rum und ich weiß nicht wie ich dies verhindern soll.
2. würde ich gerne, für den Motor, eine Rampe einbauen. Leider weiß ich so garnicht wie das geht.

p.S. Die Motoren befinden sich an einem alten Roboter, an dem sich auch ein paar alte optische Sensoren befinden. Ich weiß, es ist das falsche Forum für sowas, aber kennt sich einer mit sowas aus, ich bin mir nicht ganz sicher wie ich die zu verschalten hab.

Serenifly

Zu wenig eingeben sollte eigentlich keine Rolle spielen, da er immer wartet bis mindestens 8 da sind und vorher nichts macht.

Um überschüssige Zeichen zu verwerfen kannst du mal probieren den Empfangspuffer nach dem Einlesen von 8 Zeichen zu leeren. Also nochmal abfragen ob immer noch Daten da sind und diese einfach ins Nirwana einlesen. So ähnlich:

Code: [Select]

if (Serial.available() >= 8)
{
    ....
    ....

    while(Serial.available() > 0)
        Serial.read();
}



Du kannst dir auch mit .NET ein eigenes GUI schreiben und die Daten damit senden. Dann kannst du da schon die Eingabe filtern und ungültige Daten abfangen.

sonyfuchs

Eine GUI wollt ich eigentlich (erstmal) nicht machen.
Zu kleine Eingaben sind leider doch ein Problem.
Beispiel:
ich gebe
       111 234
ein, dann macht er nix. Das ist in Ordnung.
Jetzt gebe ich als zweites
       111 1000
ein, dann macht er:
       111 2341
Warum kann ich leider nicht sagen.

Serenifly

#27
Sep 01, 2013, 03:08 pm Last Edit: Sep 01, 2013, 03:15 pm by Serenifly Reason: 1
Ok, klar. Das hatte ich nicht bedacht. Die Daten sind natürlich trotzdem im Puffer. Sie werden nur nicht ausgelesen. Das ist jetzt ein Problem, da du ja auf den Empfang der Daten warten musst. Du kannst daher nicht einfach den Puffer leeren wenn zu wenig Daten da sind.

Man könnte vielleicht den Anfang und/oder das Ende eines Pakets mit einem bestimmten Zeichen markieren, z.B. irgendein Buchstabe. Dann darfst du aber nicht auf available() >= 8 abfragen, sondern alles einlesen was da ist und dann auf das entsprechende Zeichen warten. Du kannst dann Zählen wie viele Zeichen empfangen wurden und Eingaben mit zu wenigen Zeichen verwerfen, bzw. bei einem neuen Start-Zeichen neu anfangen.

Ein GUI ist hier wesentlich flexibler und du musst dich um sowas nicht kümmern. Da kannst du alle Daten als Byte übertragen. Wenn du dich da nicht in eine andere Sprache einarbeiten musst ist das auch kein allzu großer Aufwand. Ungültige Eingaben des SerialMonitors abzufangen kostet auch viel Arbeitszeit.

EDIT:
Man könnte auch einfach nach dem Empfang des ersten Zeichens etwas warten und dann schauen wie viele Bytes da sind. Vielleicht die Baudrate etwas erhöhen damit es schneller geht. Wenn dann nach der Zeit zu wenig Daten im Puffer stehen, kann man die Daten verwerfen.

sonyfuchs

Ok, dann die nächste Frage: womit entwerf ich den ne GUI?
Und wie mach ich dass die Variable "interval" nach jedem Wechsel von "CLKstate" um z.B. 1 gesenkt wird?
Also wie ist die Abfrage für einen Wechsel von 1 auf 0 und von 0 auf 1?

Serenifly

Was kannst du denn noch an Programmiersprachen? Persönlich nehme ich dafür .NET und C#. Aber man kann das auch mit VB.NET, Java oder Delphi machen. Wenn du das noch nie gemacht hast, dann lass es vielleicht. Grundlegende Kenntnisse sollten schon vorhanden sein. Die Zeit, die du verwendest um die Sprache und die IDE zu lernen kannst auch in das Arduino Programm stecken :)

Du kannst wie gesagt auch beim SerialMonitor bleiben. Natürlich gibt es da auch Möglichkeiten, die Empfangenen Zeichen zu filtern.

Code: [Select]

Also wie ist die Abfrage für einen Wechsel von 1 auf 0 und von 0 auf 1?

Eine Variable lastCLKState einführen, die vor dem Abfragen ob die Zeit vergangen ist auf CLKState gesetzt wird. Dann kann man einfach "if(lastCLKState != CLKState)" machen

Das Toggeln geht übrigens in einer Zeile:
CLKState = !CLKState;

Go Up