Tips zur GUI-Programmierung mit XCode - Arduino, C++, Serielle Schnittstelle

  • Tips zur GUI-Programmierung mit XCode - Arduino, C++, Serielle Schnittstelle

    Hallo liebe Community,

    ich wälze schon seit etlichen Tagen verschiedenste Tutorien und weitere Quellen, aber es scheint so, dass ich ein paar Tipps von den Profist brauche :)

    Ich bin gerade dabei ein C++ Programm zu basteln, das auf die Serielle Schnittstelle zugreift und von dort Daten eines Arduinos (Mikrokontrollerboard, arduino.cc/) liest.
    Das Programm läuft im Terminal ohne GUI schon ganz gut (siehe Anhang C++ Serial). Ankommende Daten lese ich mit den Funktionen in der Klasse "CSerial" ein und speichere sie in zwei Integer Variablen namens "nick" und "roll", wie das im genauen funktioniert ist eigentlich irrelevant. Wichtig ist nur, dass ich "nick" und "roll" mit "getNick()" und "getRoll()" in der Main abrufen kann:

    Quellcode

    1. int main()
    2. {
    3. CSerial mySerial;
    4. while (1) {
    5. usleep(1000);
    6. mySerial.readBuffer();
    7. cout << "Nick: " << mySerial.getNick() << " Roll: " << mySerial.getRoll() << endl;
    8. }
    9. return 0;
    10. }
    Alles anzeigen


    Im Terminal sehe ich zumindest plausible Daten, also gehe ich davon aus, dass der Code in "CSerial.cpp" und "CSerial.h" stimmt.

    Jetzt möchte ich "CSerial.cpp" und "CSerial.h" benutzen und eine GUI drumrum basteln. Zu meiner Schande muss ich gestehen, dass ich wenig bis gar keine Ahnung von Cocoa und Objective-C habe. Die Tutorien, die ich bisher durchgearbeitet haben, brachten mich nicht sonderlich weiter, wahrscheinlich wäre ein Buch nicht schlecht.
    Das XCode-Projekt, das ich bis jetzt fertig gebracht habe, befindet sich ebenfalls im Anhang (QuadroGui.zip). Es sind noch keine wirklichen Funktionalitäten programmiert worden, das meiste dient nur zu Testzwecken.

    Jedenfalls möchte ich die Funktionalität, die mir das Terminal bot, auf die GUI übertragen. Ich versuche deshalb die "nick" und "roll" Datenwerte auf eine NSTextView, auf einen Schieberegler oder auf ein NSTextField auszugeben, wenn ich auf den Button "receive" drücke.

    Quellcode

    1. CSerial mySerial;
    2. - (IBAction)receive:(id)sender {
    3. mySerial.readBuffer();
    4. int nick = mySerial.getNick();
    5. int roll = mySerial.getRoll();
    6. NSString* nickstr = [NSString stringWithFormat:@"%d", nick];
    7. NSString* rollstr = [NSString stringWithFormat:@"%d", roll];
    8. [textField setString:nickstr];
    9. [slider_roll setIntValue:nick];
    10. [text_d setStringValue:nickstr];
    11. [text_i setStringValue:rollstr];
    12. usleep(10);
    13. }
    Alles anzeigen


    Leider funktioniert es kein bisschen, das Programm hängt sich sogar auf, wenn ich auf "receive" drücke. Und ich bin mit meinem Latein am Ende.
    So wie ich das verstanden habe, kann ich C++ Klassendefinition einfach mit Objective-C Code mischen. Das sollte doch keine Probleme machen, oder?

    Letztendlich sollen die Daten, die der Arduino auf die Serielle Schnittstelle sendet, in Echtzeit angezeigt und aktualisiert werden. Momentan erwarte ich einen neuen Datensatz alle 100ms an der Seriellen Schnittstelle. Von der GUI erwarte ich deshalb, dass sie mir die neuen Daten immer in Echtzeit anzeigt sobald "receive" gedrückt wurde. Und sobald "send" (oder ein anderer Button) gedrückt wird, soll die Kommunikation beendet werden und keine Daten mehr angezeigt werden.

    Ich hoffe einfach, dass sich jemand von euch die Mühe macht und vielleicht über meinen Programmcode schaut. Komm grad einfach nicht weiter. Mir würden auch schon generelle Tipps und Tricks für den Umgang mit Cocoa reichen.

    Vielen Dank und viele Grüße
    Matthias
  • Kenne mich mit C++ nicht mehr aus, aber müsste man in receive: mySerial nicht erst einmal mit new CSerial() oder wie auch immer erzeugen?

    Hast Du mal per Debugger geschaut an welcher Stelle im Code die App genau hängen bleibt?

    Das usleep(10); in receive: solltest Du Dir sparen, damit die GUI nicht unnötig blockiert wird.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von MCDan ()

  • Danke euch zwei für eure netten und ausführlichen Kommentare!


    Kenne mich mit C++ nicht mehr aus, aber müsste man in receive: mySerial nicht erst einmal mit new CSerial() oder wie auch immer erzeugen?


    Das Objekt erzeuge ich mit

    Quellcode

    1. CSerial mySerial;


    und dann wird automatisch der Konstruktorder Klasse aufgerufen und das Objekt initialisiert. Also auf der Kommandozeile funktioniert mein Programm ja, von dem her denke ich, dass der C++ Code schon in Ordnung ist.


    Hast Du mal per Debugger geschaut an welcher Stelle im Code die App genau hängen bleibt?


    Ja mit dem Debugger habe ich schon rumgespielt, aber mich noch nicht so wirklich intensiv damit beschäftigt. Vielleicht sollte ich da noch genauer nachhaken...


    Das usleep(10); in receive: solltest Du Dir sparen, damit die GUI nicht unnötig blockiert wird.


    Ok, das mach ich wieder raus :) wird glaube ich sowieso nicht benötigt.


    schau dir mal die beiliegenden dateien an, damit kann man den arduino (nur originale, keine nachbauten von saintsmart) ganz gut steuern. ich weiß jetzt nicht, ob der zweck für dich passen könnte, ich brauchte nur die richtung zum arduino hin.


    Vielen Dank! Genau so etwas habe ich gesucht, nur bisher nicht gefunden. Compilieren tut es auch und der Arduino spricht auch mit dem Programm, jetzt muss ich mich nur noch ans Verstehen machen - aber das kann eine Weile dauern.
    Lieber wäre mir eigentlich C++ gewesen, weil ich bisher noch nie mit Cocoa gearbeitet habe. Aber vielleicht muss ich einfach in den sauren Apfel beißen.
    Ich schau mir heute Abend mal das Programm genauer an. Wenn ich etwas nicht versteh' oder nicht weiterkomm' weiß ich ja wo ich fragen kann :)
  • Hey nochmal!

    Ich hab mir den Code von @bastl jetzt mal genauer angeschaut. Mit dem Code komm ich so langsam klar, allerdings hapert es noch ein bisschen. Vielleicht könnt ihr mir einen Tipp geben.

    In der Datei ORSSerialPortDemoController.m werden die Daten der Seriellen Schnittstelle mit

    Quellcode

    1. - (void)serialPort:(ORSSerialPort *)serialPort didReceiveData:(NSData *)data

    eingelesen. Die Daten stehen nachher in der Variablen "string" zur Verfügung:

    Quellcode

    1. NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];


    Das Problem, das ich jetzt allerdings habe, ist folgendes:

    Mein Arduino sendet immer wieder und ohne Unterbrechung Daten im Format

    Quellcode

    1. ;-1234,5678\n

    Allerdings fängt das Cocoa-Programm nicht beim Semikolon an zu lesen und hört beim \n auf, sondern es liest ziemlich willkürlich Daten ein. Es kann deshalb auch vorkommen, dass in der Variablen "string" mal ein Datensatz der Form

    Quellcode

    1. 34,5678\n;-12

    vorkommt.

    Habt ihr eine Idee wie ich das umgehen könnte? Ich sollte am besten in der Variablen "string" immer Daten der Form

    Quellcode

    1. ;-1234,5678\n

    haben damit ich die Daten weiterverarbeiten kann.

    Viele Grüße
    Matthias
  • Genau so:
    ;)

    Quellcode

    1. #include <SoftwareSerial.h>
    2. SoftwareSerial mySerial(10, 11); // RX, TX
    3. int led = 13;
    4. int rx = 10;
    5. int active;
    6. void setup()
    7. {
    8. // Open serial communications and wait for port to open:
    9. Serial.begin(57600);
    10. while (!Serial) {
    11. ; // wait for serial port to connect. Needed for Leonardo only
    12. }
    13. // set the data rate for the SoftwareSerial port
    14. mySerial.begin(19200);
    15. pinMode(led, OUTPUT);
    16. pinMode(rx, INPUT);
    17. }
    18. void loop() // run over and over
    19. {
    20. active = digitalRead(rx); // read the input pin
    21. digitalWrite(led, active); // sets the LED to the value
    22. int val, high, low;
    23. if(mySerial.available()) { //keep reading and printing from serial until there are bytes in the serial buffer
    24. //Nick
    25. Serial.print(";"); //Identifier
    26. high = mySerial.read();
    27. low = mySerial.read();
    28. val = ((high << 8) + low);
    29. Serial.print(val, DEC);
    30. //Roll
    31. Serial.print(","); //Identifier
    32. high = mySerial.read();
    33. low = mySerial.read();
    34. val = ((high << 8) + low);
    35. Serial.print(val, DEC);
    36. //Serial.print(";");
    37. Serial.print("\n");
    38. }
    39. }
    Alles anzeigen


    Der Arduino liest über UART Daten von einem weiteren Mikrokontroller ein und leitet diese dann direkt an die serielle Schnittstelle des Computers weiter. Und der andere Mikrokontroller sendet nacheinander vier 8Bit-Zahlenwerte, die vom Arduino zu zwei 16Bit-Zahlenwerte zusammengesetzt werden.
  • Die Geschwindigkeit runtersetzen macht keinen Unterschied.

    Wenn ich die beiden Werte in zwei getrennten Zeilen sende kann es ja trotzdem vorkommen, dass ich im eingelesenen String statt

    Quellcode

    1. ;-1234
    2. ,5678


    zum Beispiel so einen Wert stehen habe:

    Quellcode

    1. -1234
    2. ,5678
    3. ;


    Der Code, der die Serielle Schnittstelle ausliest, unterscheidet meines Wissens nach nicht nach Anfang und Ende der Übertragung vom Arduino, sondern liest ein paar bestimmte Werte ein, sendet ein "newDataAvailable" und beginnt mit der Aufnahme neuer Daten.

    Geschickt wäre es, wenn ich irgendo sagen könnte: Lese Daten von der Seriellen Schnittstelle ein bis ein "\n" auftaucht. So habe ich das mit meinem C++ Programm gelöst. Erst wenn ein "\n" auftaucht, soll ein "newDataAvailable" gesendet werden und mit der Aufnahme neuer Daten fortgefahren werden.
  • Ich hab gerade mal mit einer NSLog Ausgabe geschaut was überhaupt im String stehen kann. So wie es aussieht wird nicht mal immer alles eingelesen was der Arduino sendet, sondern es kann auch mal nur

    Quellcode

    1. ;-12


    im String stehen. Hast du eine Idee wie ich dem Programm sagen könnte, dass es alle Daten bis zu einem "\n" einlesen soll?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von Bartimaeus ()

  • So nochmal ich.

    Ich möchte euch kurz Rückmeldung geben, dass es jetzt funktioniert. In der Datei ORSSerialPort.m habe ich folgenden Code

    Quellcode

    1. char buf[1024];
    2. long lengthRead = read(localPortFD, buf, sizeof(buf));
    3. if (lengthRead>0)
    4. {
    5. NSData *readData = [NSData dataWithBytes:buf length:lengthRead];
    6. if (readData != nil) dispatch_async(dispatch_get_main_queue(), ^{
    7. [self receiveData:readData];
    8. });
    9. }


    durch diesen

    Quellcode

    1. char buf[256], b[1];
    2. int pos = 0;
    3. char until = '\n';
    4. long lengthRead;
    5. do {
    6. lengthRead = read(localPortFD, b, sizeof(b)); //ein Zeichen nacheinander lesen
    7. if( lengthRead == 0 ) {
    8. usleep( 10 * 1000 ); // 10ms warten und nochmal probieren
    9. continue;
    10. }
    11. buf[pos] = b[0]; pos++; //Ansonsten schreibe das eben gelesen Zeichen in den Buffer
    12. } while (b[0] != until); //wenn das letzte Zeichen ein \n ist, mit lesen aufhören.
    13. buf[pos] = 0; //String abschließen
    14. if (pos > 0)
    15. {
    16. NSData *readData = [NSData dataWithBytes:buf length:pos];
    17. if (readData != nil) dispatch_async(dispatch_get_main_queue(), ^{
    18. [self receiveData:readData];
    19. });
    20. }
    Alles anzeigen


    ersetzt. Jetzt wird ein ganzer Datensatz eingelesen bis das Zeiche \n auftaucht. Erst danach wird der String an den Controller weitergegeben.
    Vielleicht kann damit irgendjemand auch was anfangen. Ich finde es jedenfalls sehr nützlich das so zu machen, denn dann weiß man immer ganz genau was gerade im String steht.

    Danke für eure Hilfe!