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

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

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

У меня есть код Arduino, который отправляет вещи через Serial, я запрограммировал его на включение и выключение светодиода на контакте 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), окно зависает.

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

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

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.

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

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

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

почему 20 в serialPort->waitForReadyRead(20)?

eyllanesc 10.08.2018 20:13

bool QSerialPort :: waitForReadyRead (int msecs = 30000): эта функция блокируется до тех пор, пока новые данные не станут доступны для чтения и не будет послан сигнал readyRead (). Таймаут функции истечет через миллисекунды msecs; время ожидания по умолчанию составляет 30000 миллисекунд. Если msecs равно -1, время ожидания этой функции не истечет. Функция возвращает истину, если сигнал readyRead () испускается и есть новые данные, доступные для чтения; в противном случае возвращается false (если произошла ошибка или время ожидания операции истекло).

Victor de Luca 10.08.2018 22:19

хорошо, вы можете предоставить минимальный воспроизводимый пример, ваш код должен быть минимальным, но также полным

eyllanesc 10.08.2018 22:23

Извините, скоро отредактирую и выложу полный код

Victor de Luca 11.08.2018 00:21

Пожалуйста, предоставьте дополнительный код.

Talent Developer 11.08.2018 01:37

Я редактировал, ссылка на полный код в самом низу поста

Victor de Luca 13.08.2018 04:20
0
6
1 255
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

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

stty -F /dev/ttyACM0 raw

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

Я тестировал это, но это не сработало. Я поищу больше о том, как сообщить Qt, что прием завершен, может проблема в том, что

Victor de Luca 13.08.2018 04:26
Ответ принят как подходящий

Метод 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, см. Пример этот ответ.

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