0
votes

I'm writing a Qt GUI application that receives and sends data to an Arduino over serial. It writes correctly, but when trying to read it doesn't work.

My problem is:

I have an Arduino code that sends things over Serial, I programmed it to turn on and off a LED on pin 13 depending on the received data:

#define ARDUINO_TURNED_LED_ON "LEDON"
#define ARDUINO_TURNED_LED_OFF "LEDOFF"
#define PC_REQUESTED_LED_STATE_CHANGE u8"CHANGELED"

void setup() 
{
 // put your setup code here, to run once:
 Serial.begin(9600);
 pinMode(13, OUTPUT);
}

String serialCmd = "";
bool ledState=false;

void loop()
{
 // put your main code here, to run repeatedly:
 if (Serial.available())
 {
  serialCmd=Serial.readString();

  if (serialCmd==PC_REQUESTED_LED_STATE_CHANGE)
  {
   if (ledState)
   {
     digitalWrite(13, LOW);
     Serial.write(ARDUINO_TURNED_LED_OFF);
   }
   else
   {
    digitalWrite(13, HIGH);
    Serial.write(ARDUINO_TURNED_LED_ON);
   };

   ledState=!ledState;
   serialCmd = "";

  }  

 }

}

I'm using the following signal and slot at MainWindow class:

#define ARDUINO_TURNED_LED_ON "LEDON"
#define ARDUINO_TURNED_LED_OFF "LEDOFF"
#define PC_REQUESTED_LED_STATE_CHANGE u8"CHANGELED"

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

/* Create Object the Class QSerialPort*/
    serialPort = new QSerialPort(this);

    /* Create Object the Class Arduino to manipulate read/write*/
    arduino = new Arduino(serialPort);
    
    //connect signal:
    connect(serialPort, SIGNAL(readyRead()), this, SLOT(ReadData()));
}

//slot
void MainWindow::ReadData()
{
    QString received = arduino->Read();
    qDebug() << "received data:" << received;

    if (received==ARDUINO_TURNED_LED_ON)
    {
        qDebug() << "led on";
    }
    else if (received==ARDUINO_TURNED_LED_OFF)
    {
        qDebug() << "led off";
    }
}

And the Arduino::Read() method that is called by the previous slot:

QString Arduino::Read()
{
 QString bufRxSerial;

 qDebug() << "bytes available:" << serialPort->bytesAvailable();

 /* Awaits read all the data before continuing */
 while (serialPort->waitForReadyRead(20)) {
     bufRxSerial += serialPort->readAll();
 }
 return bufRxSerial;
}

When writing to the Arduino it works well, the LED changes as it should, the problem happens when Qt tries to read the replied data.

In most of the times that the Arduino sends something, the signal is emitted and serialPort->bytesAvailable() returns a number that is not zero, but serialPort->waitForReadyRead(20) times out without receiving anything and serialPort->readAll() returns an empty QString. If I use serialPort->waitForReadyRead(-1) the window freezes.

The weirdest thing is that, in a random moment that something is sent, everything works well and the function returns all the data that couldn't be read previously.

Here's an example of what happens:

1st attempt (fails)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns on)

Arduino writes ARDUINO_TURNED_LED_ON ("LEDON") to the serial port

Qt reads an empty QString from the Arduino

2nd attempt (fails)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns off)

Arduino writes ARDUINO_TURNED_LED_OFF ("LEDOFF") to the serial port

Qt reads an empty QString from the Arduino

3rd attempt (this is a random attempt that everything works)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns on)

Arduino writes ARDUINO_TURNED_LED_ON ("LEDON") to the serial port

Qt reads "LEDONLEDOFFLEDON" from the Arduino, that is everything that couldn't be read previosly.

4th attempt (fails)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns off)

Arduino writes ARDUINO_TURNED_LED_OFF ("LEDOFF") to the serial port

Qt reads an empty QString from the Arduino

5th attempt (this is another random attempt that everything works)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns on)

Arduino writes ARDUINO_TURNED_LED_ON ("LEDON") to the serial port

Qt reads "LEDOFFLEDON" from the Arduino, that is everything that couldn't be read previosly.

I tested using the Arduino IDE serial monitor and it worked as it was supposed to be: I wrote "CHANGELED", then the LED changed and the Arduino replied "LEDON" or "LEDOFF" all the times.

What can I do to solve this problem? Thanks

Edit: the complete code can be found here

2
why 20 in serialPort->waitForReadyRead(20)?eyllanesc
bool QSerialPort::waitForReadyRead(int msecs = 30000): This function blocks until new data is available for reading and the readyRead() signal has been emitted. The function will timeout after msecs milliseconds; the default timeout is 30000 milliseconds. If msecs is -1, this function will not time out. The function returns true if the readyRead() signal is emitted and there is new data available for reading; otherwise it returns false (if an error occurred or the operation timed out).Victor de Luca
okay, you could provide a minimal reproducible example, your code should be minimal but also completeeyllanesc
I'm sorry, I'll edit and put a complete code soonVictor de Luca
Please provide more code.Talent Developer

2 Answers

0
votes

The Read method is incorrect and unnecessary. You should never use the waitFor methods. You also shouldn't be using QString when dealing with simple ASCII data that QByteArray handles fine:

class MainWindow : ... {
  QByteArray m_inBuffer;
  ...
};

void MainWindow::ReadData() {
  m_inBuffer.append(arduino->readAll());
  QByteArray match;
  while (true) {
    if (m_inBuffer.startsWith((match = ARDUINO_TURNED_LED_ON))) {
      qDebug() << "led on";
    } else if (m_inBuffer.startsWith((match = ARDUINO_TURNED_LED_OFF))) {
      qDebug() << "led off";
    } else {
      match = {};
      break;
    }
  }
  m_inBuffer.remove(0, match.size());
}

The deal is simple: ReadData can be called with any number of bytes available - even a single byte. Thus you must accumulate data until a full "packet" is received. In your case, packets can only be delineated by matching a full response string.

You'd make your life much easier if Arduino sent full lines - replace Serial.write with Serial.println. Then the lines are packets, and you don't need to buffer the data yourself:

void MainWindow::ReadData() {
  while (arduino->canReadLine()) {
    auto line = arduino->readLine();
    line.chop(1); // remove the terminating '\n'
    QDebug dbg; // keeps everything on a single line
    dbg << "LINE=" << line;
    if (line == ARDUINO_TURNED_LED_ON)
      dbg << "led on";
    else if (line == ARDUINO_TURNED_LED_OFF)
      dbg << "led off";
  }
}

See? It's much simpler that way.

You can also leverage Qt to "simulate" Arduino code without running real Arduino hardware, see this answer for an example.

0
votes

This sounds like a buffering problem. Your Qt application receives the bytes, but there is nothing that tells it the reception is complete. I would add line feeds (\n) as terminator character after each string, just use Serial.println() instead of Serial.write(). Then you can use readLine() in Qt and always be sure that what you get is exactly one complete command string.

If you're on Linux you could also try to stty your device to raw with

stty -F /dev/ttyACM0 raw

to have the serial characters sent straight through instead of default line buffering (see What’s the difference between a “raw” and a “cooked” device driver?).
Note that then you might get a new issue: Your Qt application might be so fast that you receive only one letter at a time in your callback.