Serielle Kommunikation GUI mit Arduino

Hallo und zwar habe ich ein kleines phänomen, beim Übertragen der Daten von der GUI zum Arduino, der diese wiederum in die SD-Karte abspeichert. Kurz erklärt, wenn ich den Sendbutton meiner GUI betätigte, sollen die Drei Parameter an dem Arduino gesendet werden und in die SD (csv.datei) gepspeichert werden. D.h. bei jedem senden werden 3 parameter gesendet und es stehen immer 3 parametr pro zeile. So zusagen so: 3;4;4 2;1;0 Das klappt auch nur ab und an passiert es, dass die parameter folgendermaßen gespeichert werden: 3;4;4;2;1;0 und das soll nicht sein.

Was könnte das problem sein?

Mit GUI meinst du sicherlich ein Programm auf einem anderen Rechner (PC, Schläppi, MAC, ...) - oder ? Gegenfrage: Wie wertest du denn auf dem Arduino die eingehenden Daten aus ? Machst du das selbst zu Fuß oder verwendest du z.B. den Messenger ? Passen die COM-Einstellungen auf beiden Seiten ?

Hi also die GUI das ist eine selbstprogrammierte Benutzeroberfläche.
Hmm welchen messenger?
Meinst du mit EInstellung die Baudrate? Wenn ja, hab sie auf beiden Seiten auf 9600

Hier mal den code vom arduino:

void setup()
{
  //Serialport verbindung mit 9600Baudrate
  Serial.begin(9600);

  /*########LED Konfig#########*/
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2,OUTPUT);
  
  /*#######ENABLE-PIN########*/
  pinMode(EN,OUTPUT);
  /*###########################*/
 /*ChipSelect Pin , den standard als Ausgangs sonst geht SD library nicht*/ 
  pinMode(53, OUTPUT);
 /*######################################################################*/
 
 /*############################Taster-Konfig####################################*/
 pinMode(kipp_left, INPUT_PULLUP); //kippstellung links in pin30 INPUT_PULLUP->interner pullup, somit pullup aktivieren
 pinMode(kipp_right, INPUT_PULLUP);//kippstellung rechts in pin31 und wird input-widerstand aktiviert (intern)
 /*kippleft= low -> linksstellung
   kippright= low= rechtsstellung
  kippleft & kippright = HIGH -> mittelstellung */
/*##############################################################################*/
}


void loop()
{
  //wird ein zeichen über Serialport empfangen?
  if (Serial.available() > 0)
  {
    //falls ja, schreibe diesen in inByte
    inByte = Serial.read();
  
  /*##################################I2C################################*/  
    //falls inByte= $ -> ASCII(36), Arduino im Sendemodus
      if (inByte==36)//falls $=36 ankommt arduino sendet, sonst empfangen
      {
        blink_send();
        read_SD();
      }//endif
       if (inByte==63) //falls ?=63 in Byte dann führe funktion i2c-scan durch
       {
        i2c_scan();
       }
       
         if(inByte==38)
           {
            digitalWrite(EN,HIGH);
            Serial.println("EN-EIN");
          
           } 
    //falls Arduino &(38) empfängt , d.h. er empfänngt von GUI
       else //(inByte==38)
       {
       
        blink_receive();
        write_SD();
          
      
       }//endif
     /*###############################SPI###################################*/
      //falls Arduinio ascii(33)->! empfängt, dann soll er in datei für spi dieparameter schreiben   
        if(inByte==33)
        {
          write_spi();
        }//endif
        
        if (inByte==35)
        {
          read_spi();
        }//endif
   }//endif serial available




void write_SD()
{
        if (Serial.available()>0)
        {
          if (inByte ==13) //enter
          {
            Serial.print("Initializing SD Card...");
              if (!SD.begin(chipSelect))
              {
                Serial.println("Initialization failed");
                return; //tue nichts
              }//endif
               Serial.println("SD Card initialized");
                if (SD.exists("test2.csv"))
                {
                  Serial.print("File exists");
                }
                else
                {
                 Datei=SD.open("test2.csv", O_CREAT | O_WRITE);
                 Datei.println("Adresse;Register;Data");
                 Datei.close();
                }
  
           //Schreibzugriff
           Datei= SD.open("test2.csv", O_CREAT | O_WRITE); //Das gleiche wie FILE_WRITE. doch es beschleunigt das Schrieben auf SD 
           //Datei=SD.open("test2.txt", FILE_WRITE);
           
               
          //falls Datei ok, schreibe 
          if (Datei)
          {
           
            //delay(150); //verzögerung, da schreiben auf SD karte langsamer ist
             //Datei.write(Test); //ohne zeilenumbruch
             //Datei.write(13); //schreibe nach jedem eintrag ein carriage return line feed, damit strings zeilenweise einetragen werden
                       
               
             Datei.println(Text);
             
              //inhalt von Array Text in SD schreiben(commands.txt)-> println mit zeilenumbruch
             Datei.close(); //Damit änderung gespeichert werden, diese Datei schliessen
             Serial.println("Data stored in SD card"); 
             
             
                   
            /*Nachdem enter gekommen ist und daten gespeichert wurden
            setze Array wieder auf 0 und lösche inhalt*/
            index=0;  //index auf 0 initialisieren
            memset(&Text[0], 0, sizeof(Text)); //durch memset wird Array gelöscht 
            Serial.println("Array initialized");
         }//endif datei vorhanden?
       }//endif kontroll enter
          else//fals kein enter,soll weiter in array speichern
          {
            if (index <400)//schauen , ob array overflow hat
            {
              Text[index]=inByte; //solange kein ´Enter vom Arduino empfangen wird, schreibe in Array
              index++; //und zähle position hoch -> z.b. wenn ich hallo sende-> jeder buchstabe bekommt eine position
            }//endif check array of overfolw
          }//end else
        }//endif serial available
}//end fu

Und das is der code von der GUI:

Private Sub Button1_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_i2c_receive.Click
        RichTextBox1.ForeColor = Color.Black


        '/------Kontrollvariale an Arduino =1 , somit weiß er das er an GUI senden soll---/
        If Not SerialPort_i2c.IsOpen = True Then
            SerialPort_i2c.Open()

        End If

        'SerialPort_i2c.Write(Chr(kontrol_receive)) 'muss in char gekcastet werden ,da arduino diesen in byte umwandelt
        SerialPort_i2c.Write("$") 'kontrollvariable

End Sub


rivate Sub btn_i2c_send_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_i2c_send.Click
        'Mit try wird versucht Übertragung an Arduino durchzuführen'
        Try


If CheckBox_EN.Checked = True Then
                SerialPort_i2c.Write("&")
            End If


            SerialPort_i2c.Write(Numeric_addr_i2c.Value & ";") '; damit in csv datei in spalten

            SerialPort_i2c.Write(Numeric_reg_i2c.Value & ";")

            If CheckBox_i2c.Checked = True Then
                SerialPort_i2c.Write(Numeric_data_i2c.Value) 'durch vbr = Enter, d.h. wenn ein ganzer String wird ein enter gesendet und arduino weiß ok gsnzer String
            End If
            'SerialPort_i2c.Write(Numeric_data_i2c.Value) 'durch vbr = Enter, d.h. wenn ein ganzer String wird ein enter gesendet und arduino weiß ok gsnzer String
            SerialPort_i2c.Write(vbCr)
            MsgBox("Transfer successful")

            If SerialPort_i2c.IsOpen = True Then
                SerialPort_i2c.Close()
            End If

            'End If

            'Falls Übertragung fehlschlägt, Catch block um fehler auszubügeln und eigene message von vb'
        Catch ex As Exception
            MsgBox("Transfer failed - Please check the connection" & vbCrLf & ex.Message)
        End Try
            End Sub

Ob das in eine neue Zeile kommt, liegt doch nur an der Routine die die SD-Karte beschreibt, oder? Das Linefeed sollte ja nicht seriell gesendet werden, sondern nach jedem Datensatz per Hand eingefügt werden. Da sehe ich nicht wie er das verschlucken kann.

CR und LF sind aber zwei getrennte Zeichen. 13 ist ein CR. LF ist 10. write("\n\r") könnte da auch funktionieren. Das sind Kürzel dafür.

an welcher stelle meinst du das? Bzw wie meinst du das es liegt an der SD?

Hier:

            //delay(150); //verzögerung, da schreiben auf SD karte langsamer ist
             //Datei.write(Test); //ohne zeilenumbruch
             //Datei.write(13); //schreibe nach jedem eintrag ein carriage return line feed, damit strings zeilenweise einetragen werden
                      
             Datei.println(Text);
             
              //inhalt von Array Text in SD schreiben(commands.txt)-> println mit zeilenumbruch
             Datei.close(); //Damit änderung gespeichert werden, diese Datei schliessen

Kannst du da nicht einfach Datei.println() machen? Das sollte alles erledigen ohne dass man zusätzliche Steuerzeichen braucht. Ansonsten schreibt man da normalerweise glaube ich nur ein LF. Ein CR braucht man nicht. So ist zumindest auf dem PC, wenn ich mich da korrekt erinnere.

Du machst aber print(text) und danach nur ein CR. Versuche mal statt dessen ein LF mit 10 oder “\n”

EDIT: sehe gerade dass das eh auskommentiert ist. Aber ich weiß trotzdem nicht wieso das an der seriellen Übertragung liegen soll.

Du kannst übrigens bei ASCII direkt auf die Characters abfragen. Wenn du z.B. wissen willst ob ein “A” angekommen ist, musst du nicht auf den ASCII-Code abfragen sondern kannst “if(inByte == ‘A’)” schreiben. Machst das ganze leserlicher.

ok, also du meinst so:

Datei.println(Text);
Datei.println();  // bzw. Datei.println(\n)

Das würde dir eine Leerzeile dazwischen einfügen. Kannst aber mal probieren ob die Variante konsistent läuft.

Theoretisch sollte ja das funktionieren:

Datei.println(Text);

Die Zeichen kommen immer an, aber manchmal fehlt das Linefeed aus irgendeinem Grund

Die Alternative dazu ist das:

Datei.print(Text);
Date.println();

Macht genau das gleiche in zwei Befehlen. Sollte eigentlich nicht nötig sein, aber versuchen kann mans mal.

Das mit dem "\n" hatte ich hier gemeint:

Datei.write(10)

Statt dessen kann man Datei.write("\n") schreiben, oder Datei.print("\n");

Oder "\r" statt 13. Da sieht man eher was gemeint ist. Und vor allem geht es in normalen String-Literalen wie Datei.print("Mein Text hier\n")

ok werde es versuchen, werde mich nochmal melden um zu berichten. Sonst an sich is jetzt kein logik fehler in dem code, oder? Weil da hab ich auch gesucht und konnte nichts finden

So genau habe ich mir das jetzt nicht angeguckt (ist auch schwer sich da richtig rein zuarbeiten als Außenstehender), aber ich würde sagen wenn die Zeichen zuverlässig ankommen passt es. Du hast sie ja glaube ich mal testweise mit Serial zurückgeschickt um das auszuprobieren. Oder immerhin eine Bestätigung geschickt.

Das SD Zeug sollte auch funktionieren. Open -> Write -> Close. So kompliziert ist das in der Theorie nicht.

Vielleicht passt was mit dem Text array nicht richtig. Sollte aber durch memset korrekt terminiert sein...

Ich hab da neulich auch an so was ähnlichem gebastelt, damit ich was einheitliches und (halbwegs) sicheres habe, um zwischen meinem Steuer-PC und dem Arduino zu “sprechen”.
Logo sicher/vielleicht nicht unbedingt “State of the Art”, aber funktioniert gut und sauber.
Diese Unmenge an SerialPrint gehören da im Endzustand nicht hin - soll ja auch nur als Anleitung dienen…

#include <Messenger.h>

#define MAXCMD 20
#define MAXVAL 20
#define MAXMOT 5

// Instantiate Messenger object with the message function and the default separator (the space character)
Messenger message = Messenger(','); 

// Define messenger function
void messageCompleted() {
  boolean cmdOK, motOK, numOK, endOK = false;
  int val[MAXVAL];               // max. MAXVAL (20) values per command
  int cmd, mot, num, i = 0;
  if ( message.checkString("##") ) 
  {
    Serial.println(" Start - Command recognized !");
    Serial.print("Command-Number: ");
    cmd = message.readInt();
    cmdOK = ((cmd > 0) && (cmd <= MAXCMD)); 
    Serial.println(cmd,DEC); 
    Serial.print("Motor-Number: ");
    mot = message.readInt();
    motOK = ((mot > 0) && (mot <= MAXMOT));
    Serial.println(mot,DEC); 
    Serial.print("Number of values: ");
    num = message.readInt();
    numOK = (num < MAXVAL);
    Serial.println(num,DEC); 
    while ( message.available() ) 
    {
      Serial.print("Value #");
      Serial.print(i,DEC);
      Serial.print(": ");
      if (i < (MAXVAL))          // remind val only if it fits to array 0 to 19 !
      {
        val[i] = message.readInt();
      }
      Serial.println(val[i],DEC); 
      if ( message.checkString("**") ) 
      { 
        Serial.println(" Stop - Command recognized !"); 
        endOK = true;
      }
      i++;   // increment the var-counter
    } 
    if (numOK) {numOK = ((i + 1) >= num);}  // only if it was OK before !
    if (cmdOK && motOK && numOK && endOK)  // evaluate only if everything is OK !
    {
      switch (cmd) 
      { case 1:      // Get available Mem
           Serial.println("... do something with cmd = 1"); 
        break;
        case 2:
           Serial.println("... do something with cmd = 2"); 
        break;
        case 10:
           Serial.println("... do something with cmd = 10"); 
        break;
        case 20:
           Serial.println("... do something with cmd = 20"); 
        break;
        default:
        {
          Serial.print("A not yet defined command is called: "); 
          Serial.println(cmd); 
        }
      }
    }
    else
    { 
      Serial.print("incorrect/incomplete command - analysis/function cancelled"); 
    }
  } 
  else 
    Serial.println("Command must begin with ##");

}

void setup() 
{
  Serial.begin(115200); 
  message.attach(messageCompleted);
}

void loop() 
{
  // The following line is the most effective way of 
  // feeding the serial data to Messenger
  while ( Serial.available( ) ) message.process(Serial.read( ) );
  // ... do something else in the loop ...
}

Funktioneren tut es dann so:
Der Messenger “pult” eine eingegangene Zeile seitens Serial in einzelne Sequenzen - jeweils mit Komma (oder einem anderen Zeichen) getrennt auseinander.
Jedes Kommando seitens des PC muss mit “##,” beginnen - der Sicherheit halber. Könnte man auch weglassen …
Danach folgt eine Latte von Zahlen (ebenfalls duch “,” getrennt), die wie folgt interpretiert werden:

  • Ein Kommando ala “Zahl,” - darin unterscheide ich dann, welche Funktion aufgerufen wird.
  • Die Angabe, welcher Motor gesteuert werden soll. (For my use only :.) Das kann man weglassen oder für andere Dinge missbrauchen.
  • Die Anzahl folgender Datenwerte. Je nach dem wie MAXVAL definiert ist, werden auch nur max. so viele in ein Array gelesen.
  • Nun im Prinzip eine Anzahl von “Zahl,” entsprechend num. Darf aber auch mehr sein - wird ignoriert
    (Es fehlt noch der Check, ob auch genügend Zeichen lt. num gekommen sind …) Ergänzt.
    Zum Schluss muss (!) ein “**” (2 Sternchen) folgen, damit sichergestellt ist, dass der gesamte Befehl richtig rübergekommen ist.

In den jeweiligen Case’s von Switch kann man nun nach Lust und Laune auf die jeweiligen Kommandos reagieren, Werte übergeben oder auch welche lesen, oder …
Das kann man nun zusammenkürzen oder aufblasen, wie es dnn beliebt.

Damit das Beispiel hier funktioniert, muss der Messenger als Lib eingebunden werden
Siehe hier: http://playground.arduino.cc/code/messenger

Danke sehr für eure unterstützung, werde es ausprobieren. Zu dem Array, was könnte da nicht passen?

und zu dem messenger, das kannte ich noch nicht,is ja super. Das is doch wie eine art serial.available oder, der den Port abhört?

SO hab es nochmal probiert, es ist etwas besser geworden, allerdings habe ich manchmal das problem, dass manchmal das Steuerzeichen, was ich mitsende mitgeschrieben wird? Iwas läuft da nicht sauber ab :( Habt ihr noch eine Idee?

Wenn du ein Array ala int val[MAXVAL] mit MAXVAL = 20 definierst, so "darfst" du da problemlos Werte von val[0] = ... bis val[19] schreiben (das Array beginnt immer bei 0 !). Schreibst du einen Wert nach val[20], meckert der Compiler zwar nicht (jedenfalls der Arduino), aber du beschreibst damit einen Bereich, der dir "nicht gehört". Das könnte fatale Folgen für die weitere Programm ausführung haben .... Wenn du gar keinen val beschreibst stört das nicht - auch nicht wenn die Anzahl der zu speichernden Werte eben unter Maxval liegt.

Schau dir das mal genau an mit dem Messenger. In der Loop wird jedesmal auf Serial.available geprüft und wenn vorhanden in den "Speicher" des Messengers geschrieben. Wenn ein (CR)-Kommando via Serial kommt wertet der Messenger alle gesammelten Bytes als "fertiges" Kommando aus.

Dabei wird dann intern die Zeichenkette anhand ihrer Trennzeichen (ich habe ein Komma genommen - ist definierbar) und bildet daraus einzelne "Sequenzen", die man dann mit den div. readxxxx Befehlen auslesen kann. Man sollte allerdings schn wissen, was da kommt. Spann's dir mal in Ruhe rein ...

Nachtrag - milito war da eben schneller: Schau doch mal am Ende der while-Schleife in meinem kleinen Proggie: Da sende ich neben dem Anfang (2x #) auch am Ende zwei "Sonderzeichen" (2 x ), die ich mit readchar("*") auslese. Falss diese Zeichen übrigens nicht als nächste "da" sind, wird auch kein Element aus der Messenger-Liste entfernt ! Weil: Diese Abfrage steht ja in der while-Schleife ! Guckst du !

Und eben bedenken dass c-strings mit NULL aufhören müssen. Dass heißt du darfst nur length - 2 beschreiben, da der letzte character NULL sein muss.

z.B.

char text[20];
memset(text, 0, sizeof(text));  // <-- text ist ein Pointer auf text[0]. Daher ist das das gleiche wie &text[0]

Geht von 0-19 und du darfst dann nur bis 18 schreiben.

Hallo,danke nochmals hab das etwas bessern können, doch Problem ist, dass er mir auch die Kontrollvariablen, die der Arduino von der GUI erhält mit in die Datei schreibt also so etwa: ??88;4;120

Und das ? wird von der GUI abgesendet, wenn ich den Button Scan(I2c-scanner) durhführe. Nun die Frage, soll ich vllt, nachdem ich diese Kontrollvariable sende, den Port wieder schliessen?

Siehe hier den Codeabschnitt:

 Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_scani2c.Click

        If Not SerialPort_i2c.IsOpen = True Then
            SerialPort_i2c.Open()

        End If
        SerialPort_i2c.Write(kontrol_scan)

Wüsstet ihr, wie man die beheben kann?

Den Port schließen würde ich tunlichst unterlassen ! Denn jedesmal, wenn du den Port schliesst / öffnest, wurd der Arduino resettet und das Programm beginnt von vorne ! ... ich hatte mich auch schon darüber gewundert.

Lies doch noch mal meine Kommandozeilen-Auswertung ein paar Postings zuvor. Wenn du alle deine "Befehle" oder Werte mit einem Komma (oder auch ein anderes Zeichen trennst, zerlegt dir der messenger eine ganze Zeile (bis zum CR) in entsprechend viele einzelne Segmente, die du dann jeweils mit messenger.next mit dem Inhalt abrufen kannst.

Interssieren dich dann z.B. die ?? oder irgendwelche CRC-Werte nicht, dann ignorier sie einfach - aus der Segmentliste muss du die alledings soch auslesen.

Hi habs es versucht, komme habe aber immer noch das Problem :D wie kann ich es lösen , bin echt am verzweifeln :(.

Warum tunlichst vermeiden die ports zu schliessen oder /öffnen, ich kann sie doch nicht öffen lassen? Das Problem ist, dass er diese Kontrollvariablen nicht immer mit auflistes sondern ab und an . Kann mir das auch nicht mehr erklären .

Weiß jemand etwas?

Nach Tagen der Proggerei eben zu diesem Thema mit meiner App/GUI auf dem PC sind ein paar Erleuchtungen aufgetaucht. :astonished:

In diesem Posting/Fred hatte ich bereits dazu etwas geschrieben:

D.h., der Arduino “resettet immer” beim Aufbau einer seriellen Kommunikation ist nur teilweise / bedingt richtig - nämlich nur in dem Fall, wenn das steuernde Programm (egal welche Plattform / welches Gerät) an der DTR-Leitung (DTR = Data Terminal Ready, siehe http://de.wikipedia.org/wiki/RS-232#Verkabelung_und_Stecker - etwas nach unten scrollen) “zieht” und damit eben besagten Reset auslöst.

Beim Neubeschreiben ist das natürlich zwingend erforderlich, aber warum muss der Monitor das auch unbedingt machen ?
Wer das also nicht möchte, muss sich schon seine eigene “Äbb” schreiben, worin sie / er volle Kontrolle über die serielle Schnittstelle und deren Kommunikation hat.

Wie ich in meinem Besipiel etwas weiter vorne schon beschrieben haben, wird ja die “normale” Kommunikation" in Richtung Arduino in der Art gelöst, das in der LOOP ein Befehl in der Art von
while ( Serial.available( ) ) message.process(Serial.read( ) );
steht. Hier in Verbindung mit dem messenger, der ganze Zeilen bis zum “CR” ausliest und man damit recht praktisch einen Befehl erkennen kann.

Obiges wird nun einmal in der Loop ausgewertet und ggf. in weiteren Funktionen (sei es im Sketch selbst oder in Funktionen aus LIBs) ausgewertet.
Man beachte & Bedenke: Der Arduino ist nun mal kein Multitasking System, sondern arbeitet seine Sachen alle der Reihe nach ab !
Sind diese “Arbeiten” nun etwas zeit- und / oder rechenintensiv, dauert es entsprechend, bis die LOOP wieder gesendete Zeichen abarbeiten kann.
Und: Der serielle Speicher ist nur 64 Byte groß !
D.h., bei mehreren längeren Befehlen (warum auch immer) oder auch vielen kleinen Kommandos nacheinander könnte ( ! ) der Puffer (schnell) überlaufen und Zeichen/Befehler werden verschluckt. Sehr unschön … und vor allem bei fehlendem Logger o.ä. kaum nachzuvollziehen - weil: … passiert nur manchmal !?

Ich habe mir dazu “senderseitig” eine App gebastelt (auf dem PC - in Delphi 7), welche ein Kommando sendet und welches der Arduino nach Ausführung zunächst bestätigen muss - je nach dem u.a. auch, ob der Befehl nun erfolgreich war oder eben auch nicht → mit entsprechender Reaktion darauf.
Funktioniert so weit prima, aber wie immer das große ABER:

Möglicherweise hat ja auch der Arduino was zu sagen und meldet sich mal so spontan zwischendurch, wenn mal grade auf ein Befehls-Echo oder auch nicht gewartet wird …
In meinem Fall mit meiner MegaStepper-Lib z.B.:

  • Not-Aus-Schalter wurde gedrückt
  • Endschalter haben ausgelöst (-> Not-Stopp)
  • Temperatur(en) oder Ströme des / der Motoren zu hoch (Warnung !)
  • “Das (Fahr-) Ziel wurde erreicht !” (mit ner akustischen Meldung von “Uschi” … :D)
  • … oder x-beliebig denkbares, was für die steuernde Appikation an Werten / Stati von Interesse ist.

Wie ihr euch vorstellen könnt, laufen solche Informationen (samt Auswertung der “Echo’s” gemäß Murphy’s Gesetzen immer zur unpassenden Zeit ein - insbesondere wenn gerade ein neuer Befehl unterwegs ist…
Ich bin da schon recht weit, aber ‘bis fertig’ zum weiterreichen dauerts noch ne kleine Weile.