Arduino & Qt

Hey,

bin gerade dabei eine GUI mit Qt für mein Arduino zu programmieren.
Soweit so gut :slight_smile:

Ich habe es hinbekommen (durch langes suchen im Internet :o ) eine LED Ein- und Auszuschalten.
Meine frage nun ist, wie kann ich eine zweite LED bzw. in diesem Fall GPIO ansteuern?

Mein Code sieht in der Arduino Software wie folgt aus:

#define PIN_LED D7

void setup()
{
  pinMode(PIN_LED, OUTPUT);
  analogWrite(PIN_LED, 0);
  Serial.begin(9600);
}

void loop()
{
  if (Serial.available())
  {
    const int led_D7 = Serial.parseInt();
    if(led_D7 ==1)
    {
      digitalWrite(PIN_LED, HIGH);
    }
    else
    {
      digitalWrite(PIN_LED, LOW);
    }
  }
}

In Qt:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    arduino_is_available = false;
    arduino_port_name = "";
    arduino = new QSerialPort;


    qDebug() << "Number of available ports: " << QSerialPortInfo::availablePorts().length();
    foreach(const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()){
        qDebug() << "Has vendor ID: " << serialPortInfo.hasVendorIdentifier();
        if(serialPortInfo.hasVendorIdentifier()){
            qDebug() << "Vendor ID: " << serialPortInfo.vendorIdentifier();
        }
        qDebug() << "Has Product ID: " << serialPortInfo.hasProductIdentifier();
        if(serialPortInfo.hasProductIdentifier()){
            qDebug() << "Product ID: " << serialPortInfo.productIdentifier();
        }
    }


    foreach(const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()){
        if(serialPortInfo.hasVendorIdentifier() && serialPortInfo.hasProductIdentifier()){
            if(serialPortInfo.vendorIdentifier() == arduino_uno_vendor_id){
                if(serialPortInfo.productIdentifier() == arduino_uno_product_id){
                    arduino_port_name = serialPortInfo.portName();
                    arduino_is_available = true;
                }
            }
        }
    }

    if(arduino_is_available){
        // open and configure the serialport
        arduino->setPortName(arduino_port_name);
        arduino->open(QSerialPort::WriteOnly);
        arduino->setBaudRate(QSerialPort::Baud9600);
        arduino->setDataBits(QSerialPort::Data8);
        arduino->setParity(QSerialPort::NoParity);
        arduino->setStopBits(QSerialPort::OneStop);
        arduino->setFlowControl(QSerialPort::NoFlowControl);
    }else{
        // give error message if not available
        QMessageBox::warning(this, "Port error", "Couldn't find the Arduino!");
    }
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_encenderPushButton_clicked()
{
    if(arduino->isWritable()){
        arduino->write("1");
    }else{
        qDebug() << "Couldn't write to serial!";
    }
}

void Widget::on_apagarPushButton_clicked()
{
    if(arduino->isWritable()){
        arduino->write("0");
    }else{
        qDebug() << "Couldn't write to serial!";
    }
}

Du kannst einen String senden wie "1,0" für LED1 aus und "2,1" für LED2 an. Das mit einem Linefeed abschließen, auf dem Arduino die ganze Zeile einlesen und Parsen

:o :o :o :o

Dann müsste ich mein komplettes Programm ändern das ja einwandfrei funktioniert (ich weis es funktioniert nur für ein LED aber trotzdem)

Ich habe bis jetzt folgendes versucht:

Hab jetzt eine weitere LED definiert und in Qt zwei weitere Buttons definiert, leider funktioniert das nur nicht

#define PIN_LED D4

#define LED_PIN D7


void setup()
{
  pinMode(PIN_LED, OUTPUT);
  pinMode(LED_PIN, OUTPUT);
  analogWrite(PIN_LED, 0);
  analogWrite(LED_PIN, 0);
  Serial.begin(9600);
}

void loop()
{
  if (Serial.available())
  {
    const int led_D4 = Serial.parseInt();
    if(led_D4 ==1)
    {
      digitalWrite(PIN_LED, HIGH);
    }
    else
    {
      digitalWrite(PIN_LED, LOW);
    }
    
    const int led_D7 = Serial.parseInt();
    if(led_D7 ==1)
    {
      digitalWrite(LED_PIN, HIGH);
    }
    else
    {
      digitalWrite(LED_PIN, LOW);
    }    
  }
}

Natürlich geht das so nicht. ParseInt() zweimal hintereinander gemacht funktioniert nicht wie du denkst. Das zuverlässigste ist immer zeilenweise bis zum Zeilenende einzulesen und das Parsen per Hand zu machen. Dadurch hat man Einlesen und Auswertung getrennt. Wenn man was an den übertragenen Daten ändert muss man dann nicht die Einlese-Funktion neu schreiben, sondern nur die Auswertung anpassen

Die serielle Kommunikation ist nunmal langsam. Bei 9600 Baud brauchst du 1ms pro Zeichen. Anfängerfunktionen wie parseInt() warten dann einfach bis 1 Sekunde nichts mehr ankommt oder bis ein Zeichen kommt dass nicht zur Zahl gehört. Wenn man das vernünftig macht geht das auch, aber es nicht flexibel und nur sehr schlecht änderbar. Gut geht es am ehesten noch für einzelne Zahlen

Dann müsste ich mein komplettes Programm ändern das ja einwandfrei funktioniert

Bei den paar Zeilen Code ist das doch kein Verlust

Bin leider Neuling :confused:

kannst du mir konkret sagen was ich tun muss?

Formatierung des Kommandos ist als String:
LED Nr. (ab 1) | Komma/Strichpunkt | [0 .. 1] | Linefeed/Newline

Also z.B.:
"1,0"
"12,1"

Jeweils mit einem LF am Ende. Das erste ist die LED Nr. die auch mehrere Ziffern haben kann. Das zweite ist 0 oder 1 für aus/an.

Wie das aufgebaut ist kann man auch durchaus ändern. Man könnte da auch ganze Texte schicken und wirklich "an" und "aus" schreiben. Lediglich die Auswertung muss dann geändert werden.

const unsigned int READ_BUFFER_SIZE = 11;    //Platz für 10 Zeichen + Terminator

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  char* str = readLine(Serial);
  if (str != NULL)      //liefert true wenn das LF eingelesen wurde
  {
    Serial.print(str);
    Serial.print(" --- ");

    byte led = atoi(strtok(str, ";,"));
    byte cmd = atoi(strtok(NULL, ";,"));

    if (led == 0)
      Serial.println("Falscher Befehl");
    else
    {
      Serial.print("LED ");
      Serial.print(led);
      Serial.print(' ');

      if (cmd == 0)
        Serial.println("aus");
      else
        Serial.println("an");
    }
  }
}

char* readLine(Stream& stream)
{
  static byte index;
  static char buffer[READ_BUFFER_SIZE];

  while (stream.available())
  {
    char c = stream.read();

    if (c == '\n')              //wenn LF eingelesen
    {
      buffer[index] = '\0';     //String terminieren
      index = 0;
      return buffer;            //melden dass String fertig eingelesen wurde
    }
    else if (c >= 32 && index < READ_BUFFER_SIZE - 1)   //solange noch Platz im Puffer ist
    {
      buffer[index++] = c;    //Zeichen abspeichern und Index inkrementieren
    }
  }
  return NULL;                //noch nicht fertig
}

Auf 0 oder 1, oder einzelne Ziffern allgemein, könnte man mit if(*str == '0') auch direkt abfragen. Aber ich habe das Splitten und Konvertieren mal drin gelassen damit man nicht zwei verschiedene Versionen drin hat. Ist unnötig aber es schadet nichts

Was mich immer stört, und eigentlich nur ein Anfänger-Fehler sein sollte:

   if(led_D7 ==1)
   {
     digitalWrite(LED_PIN, HIGH);
   }
   else
   {
     digitalWrite(LED_PIN, LOW);
   }

kann man auch viel einfacher schreiben:

     digitalWrite(LED_PIN, led_D7);

Cool, wo ist der Bus der was wissen wollte?

Eine solche Anmerkung unter einen Post zu schreiben in dem erwähnt wird das man "Neuling ist", ist so Notwendig wie ein weiteres Haar auf dem Kopf und Hilft kein bisschen weiter.

Danke für nichts.

Hi

Deiner Äußerung entnehme ich, daß Du (mindestens in Sachen Arduino) nicht sonderlich dazu lernen möchtest und auch in Zukunft eher mit dem Stock, als mit dem Roboter Deine Probleme lösen willst - außerdem ist's nicht sonderlich klug, den Helfer 'wenig intelligent' von der Seite her anzumachen - Du wirst aber wohl wissen, wie Du durch's Leben kommen willst.
Gerade als Anfänger musst Du nicht in sämtliche Fallen tappen, welche die 'Erfahreneren' schon hinter sich gelassen haben - Wenn Du's nicht anders willst: Dann eben per Stock.

Viel Erfolg mit Deiner doch etwas eigenen Art.
Nichts desto Trotz: Sofern ich mich an Dich erinnere, werde ich Dich zukünftig in Ruhe lassen.

MfG

Edit
Etwas 'Würze' raus genommen - hoffe, der Rest kann so bleiben.

Kirikkayis:
Cool, wo ist der Bus der was wissen wollte?

Eine solche Anmerkung unter einen Post zu schreiben in dem erwähnt wird das man "Neuling ist", ist so Notwendig wie ein weiteres Haar auf dem Kopf und Hilft kein bisschen weiter.

Danke für nichts.

Als Neuling solltest Du bedeutend ruhiger sein. Wenn man Dir hilft dann nimm das so an oder frage hier nicht um Hilfe.

Grüße Uwe

Inwiefern soll die Antwort von postmaster-io hilfreich sein?
Es frage hatte nichts mit der Semantik / Programmierstil zu tun.

Ab sofort werde ich hier dann auch unter jedem Post die Semantik des Programmierers verbessern und keinerlei auf die Frage eingehen.

Wenn Du nur noch zum Stunk machen hier bleiben willst, ist es wohl besser, Du gehst.

Wenn Du keine Fragen beantwortest, wirst Du wohl kaum sinnvolle Hilfe erwarten können.

Gruß Tommy

Serenifly:
Formatierung des Kommandos ist als String:
LED Nr. (ab 1) | Komma/Strichpunkt | [0 .. 1] | Linefeed/Newline

Also z.B.:
"1,0"
"12,1"

Jeweils mit einem LF am Ende. Das erste ist die LED Nr. die auch mehrere Ziffern haben kann. Das zweite ist 0 oder 1 für aus/an.

Wie das aufgebaut ist kann man auch durchaus ändern. Man könnte da auch ganze Texte schicken und wirklich "an" und "aus" schreiben. Lediglich die Auswertung muss dann geändert werden.

const unsigned int READ_BUFFER_SIZE = 11;    //Platz für 10 Zeichen + Terminator

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  char* str = readLine(Serial);
  if (str != NULL)      //liefert true wenn das LF eingelesen wurde
  {
    Serial.print(str);
    Serial.print(" --- ");

byte led = atoi(strtok(str, ";,"));
    byte cmd = atoi(strtok(NULL, ";,"));

if (led == 0)
      Serial.println("Falscher Befehl");
    else
    {
      Serial.print("LED ");
      Serial.print(led);
      Serial.print(' ');

if (cmd == 0)
        Serial.println("aus");
      else
        Serial.println("an");
    }
  }
}

char* readLine(Stream& stream)
{
  static byte index;
  static char buffer[READ_BUFFER_SIZE];

while (stream.available())
  {
    char c = stream.read();

if (c == '\n')              //wenn LF eingelesen
    {
      buffer[index] = '\0';    //String terminieren
      index = 0;
      return buffer;            //melden dass String fertig eingelesen wurde
    }
    else if (c >= 32 && index < READ_BUFFER_SIZE - 1)  //solange noch Platz im Puffer ist
    {
      buffer[index++] = c;    //Zeichen abspeichern und Index inkrementieren
    }
  }
  return NULL;                //noch nicht fertig
}




Auf 0 oder 1, oder einzelne Ziffern allgemein, könnte man mit if(*str == '0') auch direkt abfragen. Aber ich habe das Splitten und Konvertieren mal drin gelassen damit man nicht zwei verschiedene Versionen drin hat. Ist unnötig aber es schadet nichts

Hey, hab nun deinen Vorschlag durchgeführt und meinen Arduino Code "Modifiziert" ...

#define PIN_LED8 D8
#define PIN_LED7 D7
#define PIN_LED6 D6

void setup()
{
  pinMode(PIN_LED8, OUTPUT);
  analogWrite(PIN_LED8, 0);

  pinMode(PIN_LED7, OUTPUT);
  analogWrite(PIN_LED8, 0);

  pinMode(PIN_LED6, OUTPUT);
  analogWrite(PIN_LED8, 0);
  
  Serial.begin(9600);
}

void loop()
{
  if (Serial.available()){
    int led_pin = Serial.parseInt();
    int led_brightness = Serial.parseInt();
    write_leds(led_pin, led_brightness);
    }
}

void write_leds(int ledpin, int brightness)
{
  if(ledpin == 8){
    analogWrite(PIN_LED8, brightness);
    return;
  }
    if(ledpin == 7){
    analogWrite(PIN_LED7, brightness);
    return;
  }
    if(ledpin == 6){
    analogWrite(PIN_LED6, brightness);
    return;
  }
  return;
}

Wenn ich nun mein Arduino Monitor Plotter starte und beispielsweise "8 255" eingebe, dann leuchtet meine LED im GPIO8 auf. Problem nun ist mit Qt.
Ich habe den Code ebenfalls Modifiziert:

void Widget::on_encenderPushButton_clicked()
{
    if(arduino->isWritable()){
        arduino->write("8");
        arduino->write("255");
    }else{
        qDebug() << "Couldn't write to serial!";
    }
}

void Widget::on_apagarPushButton_clicked()
{
    if(arduino->isWritable()){
        arduino->write("8");
        arduino->write("0");
    }else{
        qDebug() << "Couldn't write to serial!";
    }
}

Problem: Leider reagiert kann ich mein GPIO nicht ansteuern...

Das Leerzeichen zwischen 8 und 255 fehlt bei der QT-Variante gegenüber dem seriellen Monitor.

Gruß Tommy

Danke.

Es funktioniert nun, es hat nicht nur ein Leerzeichen gefehlt sondern auch ein Komma (bzw. das Komma ist kein muss).

Für die, die es Interessiert.
Qt:

void Widget::on_encenderPushButton_clicked()
{
    if(arduino->isWritable()){
        arduino->write("8, 255");
        //arduino->write("255");
    }else{
        qDebug() << "Couldn't write to serial!";
    }
}

void Widget::on_apagarPushButton_clicked()
{
    if(arduino->isWritable()){
        arduino->write("8, 0");
        //arduino->write("0");
    }else{
        qDebug() << "Couldn't write to serial!";
    }
}