QSerialPort имеет доступные байты, но не может прочитать

Я пишу приложение Qt GUI, которое получает и отправляет данные в Arduino через последовательный порт. Он пишет правильно, но при попытке прочитать это не работает.

Моя проблема:

У меня есть код Arduino, который отправляет данные через последовательный порт, я запрограммировал его на включение и выключение светодиода на выводе 13 в зависимости от полученных данных:

#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 = "";

  }  

 }

}

Я использую следующий сигнал и слот в классе MainWindow:

#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";
    }
}

И метод Arduino::Read(), который вызывается предыдущим слотом:

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;
}

При записи в Arduino он работает хорошо, светодиод меняется, как и должно быть, проблема возникает, когда Qt пытается прочитать ответные данные.

В большинстве случаев, когда Arduino что-то отправляет, сигнал испускается и serialPort->bytesAvailable() возвращает число, которое не равно нулю, но serialPort->waitForReadyRead(20) время ожидания без получения чего-либо и serialPort->readAll() возвращает пустую строку QString. Если я использую serialPort->waitForReadyRead(-1), окно зависает.

Самое странное, что в случайный момент, когда что-то отправлено, все работает хорошо, и функция возвращает все данные, которые не могли быть прочитаны ранее.

Вот пример того, что происходит:

1-я попытка (неудача)

Qt записывает PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") в последовательный порт

Arduino LED меняет свое состояние (включается)

Arduino записывает ARDUINO_TURNED_LED_ON ("LEDON") в последовательный порт

Qt читает пустую строку QString из Arduino

2-я попытка (неудача)

Qt записывает PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") в последовательный порт

Arduino LED меняет свое состояние (выключается)

Arduino записывает ARDUINO_TURNED_LED_OFF ("LEDOFF") в последовательный порт

Qt читает пустую строку QString из Arduino

3-я попытка (это случайная попытка, что все работает)

Qt записывает PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") в последовательный порт

Arduino LED меняет свое состояние (включается)

Arduino записывает ARDUINO_TURNED_LED_ON ("LEDON") в последовательный порт

Qt читает "LEDONLEDOFFLEEDON" из Arduino, это все, что не могло быть прочитано ранее.

4-я попытка (неудача)

Qt записывает PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") в последовательный порт

Arduino LED меняет свое состояние (выключается)

Arduino записывает ARDUINO_TURNED_LED_OFF ("LEDOFF") в последовательный порт

Qt читает пустую строку QString из Arduino

5-я попытка (это еще одна случайная попытка, что все работает)

Qt записывает PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") в последовательный порт

Arduino LED меняет свое состояние (включается)

Arduino записывает ARDUINO_TURNED_LED_ON ("LEDON") в последовательный порт

Qt читает "LEDOFFLEEDON" из Arduino, это все, что не могло быть прочитано ранее.

Я тестировал с использованием последовательного монитора Arduino IDE, и он работал так, как и предполагалось: я написал "CHANGELED", затем светодиод изменился, и Arduino все время отвечал "LEDON" или "LEDOFF".

Что я могу сделать, чтобы решить эту проблему? Спасибо

Изменить: полный код можно найти здесь

2 ответа

Решение

Read метод неправильный и ненужный. Вы никогда не должны использовать waitFor методы. Вы также не должны использовать QString при работе с простыми данными ASCII, которые QByteArray отлично справляется:

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());
}

Сделка проста: ReadData может вызываться с любым количеством доступных байтов - даже одним байтом. Таким образом, вы должны накапливать данные, пока не будет получен полный "пакет". В вашем случае пакеты могут быть определены только путем сопоставления полной строки ответа.

Ты бы сделал свою жизнь намного проще, если бы Arduino послал полные строки - замени Serial.write с Serial.println, Тогда строки представляют собой пакеты, и вам не нужно самостоятельно буферизовать данные:

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";
  }
}

Увидеть? Так намного проще.

Вы также можете использовать Qt для "моделирования" кода Arduino без использования реального оборудования Arduino, см. Этот ответ для примера.

Это звучит как проблема буферизации. Ваше приложение Qt получает байты, но ничто не говорит о том, что прием завершен. Я бы добавил перевод строки (\n) в качестве символа-терминатора после каждой строки, просто используйте Serial.println() вместо Serial.write(), Тогда вы можете использовать readLine() в Qt и всегда будьте уверены, что вы получите ровно одну полную командную строку.

Если вы работаете в Linux, вы также можете попробовать подключить ваше устройство к raw с

stty -F /dev/ttyACM0 raw

чтобы последовательные символы передавались напрямую вместо буферизации строки по умолчанию (см. в чем разница между "необработанным" и "приготовленным" драйвером устройства?).
Обратите внимание, что тогда у вас может появиться новая проблема: ваше приложение Qt может быть настолько быстрым, что вы получите только одно письмо за раз при обратном вызове.

Другие вопросы по тегам