LINUX.ORG.RU

Serial Port Communication Problem [QT4+/POSIX/В гугле искал]


0

0

Всем здравствуйте.

С Qt, C++ и ОПП знаком в принципе всего несколько недель, поэтому если не сложно объясните пожалуйста (или дайте ссылки) некоторые странности в работе моего кода.

Кратко опишу, что к чему. Есть USB устройство, которое на самом деле является «USB to Virtual COM-port» преобразователем. Соответственно под Linux'oм (Ubuntu 9.10, если это важно) есть корректно установленные драйвера и устройство обнаруживает себя как /dev/ttyUSB0. Дальше с помощью вот этого кода мне удаётся подключиться к устройству:

void main_window::on_pBushbuttonConnect_toggled(bool checked)
{
   struct termios comConf;
   if(checked)
   {
      //serial port there
      port.setFileName("/dev/ttyUSB0");
      if(!port.open(QIODevice::ReadWrite))
      {
         //error
      }
      else
      {
         tcgetattr(port.handle(), &comConf);

         cfmakeraw(&comConf);

         //params
         comConf.c_cflag |= CLOCAL;
         //baud rate - 38400
         cfsetispeed(&comConf, B38400);
         cfsetospeed(&comConf, B38400);
         //data bits - 8
         comConf.c_cflag &= (~CSIZE);
         comConf.c_cflag |= CS8;
         //parity - none
         comConf.c_cflag &= (~PARENB);
         //stop bits - 1
         comConf.c_cflag &= (~CSTOPB);
         //flow control - off
         comConf.c_cflag&=(~CRTSCTS);
         comConf.c_iflag&=(~(IXON|IXOFF|IXANY));

         if(tcsetattr(port.handle(), TCSANOW, &comConf) == -1)
         {
             //error
         }

      }
   }
   else
   {
      port.close();
   }
}

а вот с помощью этого куска кода, отправляю байт в устройство:

bool main_window::sendValue(int value)
{
   if(port.isOpen())
   {
      if(port.isWritable())
      {
         port.flush(); // <------ Вот этот момент
         port.putChar(value);
         return true;
      }
   } 
   return false;
}

Меня очень смущает наличее обозначенного стрелкой вызова. Дело в том, что без него байты не посылаются. И проблема заключается точно не в устройстве, так как сигнальные светодиоды не начинают моргать, а значит передача не началась. Собственно это и есть первый вопрос, почему для отправки байта надо очищать буфер? (кстати, принять байт вообще не получается)

Второй вопрос заключается в пропаже некоторых байтов, не все байты доходят до устройства, то есть если я подряд вызову пять или шесть функций «sendValue(120)», то из пяти байт в лучшем случае доходят три. И эта проблема опять же на стороне ПК или софта. То есть второй вопрос, как исключить пропажу байтов? (методы типа waitReadyWritten(-1) не работают... либо я их неправильно вызывал)

PS Аргументированные оскорбления и посылы курить маны принимаются :) QextSerialPort не подходит, так как GPL. Заранее спасибо.

если я правильно понимаю, то

port.flush(); // <------ Вот этот момент 
port.putChar(value);
надо вызывать в обратном порядке: сначала пишешь, а потом насильно очищаешь буфер. при этом содержимое должно уйти в устройство. а ты, наверное нескольно раз лупишь по кнопке и посылаются все байты, кроме последнего, за которым не следует flush().

ilyagoo ()

Почитай про буфферизацию ввода/вывода. Скорее всего твоя проблема именно в этом.

trex6 ★★★★★ ()

Вы всё-таки попробуйте написать на QextSerialPort. Если всё будет работать - тогда просто посмотрите в каком порядке он делает вызовы.

Dendy ★★★★★ ()

А чём сакральный смысл использования некоего потомка QIODevice, если для конфигурирования порта используются функции POSIX. Чем write не угодил-то?

Begemoth ★★★★★ ()

>>Меня очень смущает наличее обозначенного стрелкой вызова.

Дело в том, что он должен идти после записи.

Собственно это и есть первый вопрос, почему для отправки байта надо очищать буфер?

Потому что ядро не сразу записывает кэш в устройство. При очищении буфера же ядро записывает.

Второй вопрос заключается в пропаже некоторых байтов, не все байты доходят до устройства, то есть если я подряд вызову пять или шесть функций «sendValue(120)», то из пяти байт в лучшем случае доходят три. И эта проблема опять же на стороне ПК или софта. То есть второй вопрос, как исключить пропажу байтов? (методы типа waitReadyWritten(-1) не работают... либо я их неправильно вызывал)

Не все байты могут не доходить по разным причинам. Одна из них - тот самый вызов выше. Другая - то, как ты их принимаешь. Кстати, что за девайс используется? Другие причины могут быть в неправильно выставленном режиме.

И я бы посоветовал не использовать для этих вещей qt в принципе, так как профита тут ноль. Я в свое время тоже сперва на qt думал сделать, но оно того не стоит.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от Begemoth

> А чём сакральный смысл использования некоего потомка QIODevice, если для конфигурирования порта используются функции POSIX. Чем write не угодил-то?

Скорее всего автор пишет на Qt4 и естественное желание найти библиотеку для последовательного порта под этот же фреймворк.

Но если вам интересен сакральный смысл именно QIODevice, то это просто абстрактный интерфейс, который использует множество классов в Qt4. Другими словами ключевое слово здесь - Qt4. Был бы другой фреймворк - реализовался бы интерфейс под него.

Dendy ★★★★★ ()
Ответ на: комментарий от MuZHiK-2

> И я бы посоветовал не использовать для этих вещей qt в принципе, так как профита тут ноль.

Профит в том, чтобы как минимум использовать QDataStream для записи/чтения данных и детектирования ошибок и неполного чтения из буфера (QDataStream::ReadPastEnd и QDataStream::ReadCorruptData). Если конечно автор не хочет писать свой велосипед.

Dendy ★★★★★ ()
Ответ на: комментарий от Dendy

>>Профит в том, чтобы как минимум использовать QDataStream для записи/чтения данных и детектирования ошибок и неполного чтения из буфера (QDataStream::ReadPastEnd и QDataStream::ReadCorruptData). Если конечно автор не хочет писать свой велосипед.

В RS-232 нету детектирования ошибок - бит или пришел, или нет, поэтому весь этот кутешный шлак не нужен - лишний слой. Никаких тут велосипедов писать не надо - просто надо использовать неблокирующее чтение. Советую прочитать этот ман: http://www.easysw.com/~mike/serial/serial.html , после него работать с серийниками будет одно удовольствие.

Кстати, причем тут ReadPastEnd и неполное чтение? ReadPastEnd сигнализирует о том, что ты читаешь за пределами девайса.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от MuZHiK-2

> В RS-232 нету детектирования ошибок ...

Вы видно не совсем разобрались как работает «кутешный шлак». Детектирование ошибок - нарушение протокола, когда со стороны девайса приходит не тот байт, что вы ожидали. Варианты: физически повреждена линия связи, устройство нарушает протокол, либо собственная программа нарушает протокол. В любом случае это поведение должно быть обработано, для чего в Qt есть готовое API.

Кстати, причем тут ReadPastEnd и неполное чтение? ReadPastEnd сигнализирует о том, что ты читаешь за пределами девайса.


ReadPastEnd говорит о том, что пакет представительского уровня ещё не пришёл целиком, возможно по причине того, что отправитель делает несколько flush подряд по мере записи этого пакета в буфер, хотя бы потому, что целиком пакет в буфер последовательного порта не помещается. Это поведение тоже должно быть обработано.

Dendy ★★★★★ ()
Ответ на: комментарий от Dendy

>>Детектирование ошибок - нарушение протокола, когда со стороны девайса приходит не тот байт, что вы ожидали.

Офигеть, а откуда компорт знает, какой придет байт следующим?

Варианты: физически повреждена линия связи, устройство нарушает протокол, либо собственная программа нарушает протокол.

Компортом это никак не обрабатывается. Либо есть передача, либо нету.

В любом случае это поведение должно быть обработано, для чего в Qt есть готовое API.

Посмотри исходники предлагаемой тобою поделки и ты увидишь, что все эти кутешные примочки не используются. Тогда зачем платить больше и завязывать передачу данных на куте? Куте нужно только для гуевин, для большего оно не годится.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от MuZHiK-2

Офигеть, а откуда компорт знает, какой придет байт следующим?

Ключевая фраза была: «для чего в Qt есть готовое API». При неверном чтении сложного пакета из потока из переопределённого оператора QDataStream & operator>>( QDataStream & stream, MyPacket & packet ) можно установить флаг ошибки, на который будет реагировать парсер.

Куте нужно только для гуевин, для большего оно не годится.

Так бы сразу и сказали.

Dendy ★★★★★ ()
Ответ на: комментарий от MuZHiK-2

Да, забыл ответить.

Посмотри исходники предлагаемой тобою поделки и ты увидишь, что все эти кутешные примочки не используются.

Во-первых, я её не предлагал. Речь шла о вообще «зачем можно захотеть использовать Qt4 для работы с компортом». Посмотрел исходники вышеприлагаемого http://fireforge.net/projects/qserialdevice по ссылке kuzulis, там как раз имплементируется QIODevice.

Dendy ★★★★★ ()
Ответ на: комментарий от Dendy

>>Ключевая фраза была: «для чего в Qt есть готовое API». При неверном чтении сложного пакета из потока из переопределённого оператора QDataStream & operator>>( QDataStream & stream, MyPacket & packet ) можно установить флаг ошибки, на который будет реагировать парсер.

Давай еще раз, только медленно. Допустим, я на приемной стороне имею подключенный к чему-то компорт и принимаю данные. Приходит ко мне байт допустим, а я ждал 2 байта. Что, мне сразу ошибку выставлять? Модемы, например, могут данные кусками выдавать. Тогда выставление ошибки будет неверным действием. Единственный вменяемый метод чтения с компорта - это периодический опрос на наличие новых данных и чтение этих данных в буферную линию задержки, где данные и анализируются на наличие нужных пакетов твоего протокола обмена. По-другому работать с компортом не удастся из-за его природы. И именно поэтому, если ты посмотришь в исходники Qextserialport, то и не обнаружишь там никакой обработки ошибок при передаче/приеме данных - сигнализируется ровно о том, то пришло/ушло. Поэтому у меня большие сомнения в том, как ты будешь в QDataStream детектить ошибки приема/передачи. Это не tcp. Поэтому и появились такие вещи, как Zmodem.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от Dendy

>Посмотрел исходники вышеприлагаемого http://fireforge.net/projects/qserialdevice по ссылке kuzulis, там как раз имплементируется QIODevice.

ненене.. я уже отказался от QIODevice и перевел проект на QObject. --- Ну да ладно, это не относится к теме...

kuzulis ★★ ()
Ответ на: комментарий от Dendy

>>Речь шла о вообще «зачем можно захотеть использовать Qt4 для работы с компортом».

Вот в свете мною вышесказаного профита в куте - ноль. Как и в кроссплатформенности куте.

Посмотрел исходники вышеприлагаемого http://fireforge.net/projects/qserialdevice по ссылке kuzulis, там как раз имплементируется QIODevice.

Там ровно такая же пьянка. Делать обработку данных в QIODevice будет только клинический идиот. Если ты работал с компортом, то должен понимать, что обработку данных делать следует на уровне приложения, все что ниже - должно отвечать только за передачу данных. Ровно как и QDataStream. Подумай над тем, что компорт - это char device.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от kuzulis

>>ненене.. я уже отказался от QIODevice и перевел проект на QObject. --- Ну да ладно, это не относится к теме...

Не понял, что ты хотел этим сказать - девайс все равно наследуется от объекта.

MuZHiK-2 ★★★★ ()

Выше уже говорили: во-первых, не надо пытаться работать с RS232 посредством буферизованных операций ввоода/вывода, во-вторых, есть нормальные функции read/write, в-третьих, что за QIODevice вы используете?, есть же старые добрые termio и termios.

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от MuZHiK-2

>Не понял, что ты хотел этим сказать - девайс все равно наследуется от объекта.

то, что в QIODevice реализовано много всего как бы «лишнего» для данного случая (т.е. мтоды которые там несовсем подходили) и поэтому перевел на QObject с реализацией методов по-своему в связи со спецификой данного вида девайсов.

kuzulis ★★ ()
Ответ на: комментарий от Eddy_Em

>Выше уже говорили: во-первых, не надо пытаться работать с RS232 >посредством буферизованных операций ввоода/вывода

Согласен

во-вторых, есть нормальные функции read/write

Никто не спорит.

в-третьих, что за QIODevice вы >используете?, есть же старые добрые termio и termios.

Опять же никто не спорит.

Но вы забываете, что речь все-таки идет о Qt4! Поэтому идет привязка в классам Qt4..

--

ЗЫ: это понятно, что весь этот код можно реализовать и без Qt4 - НО раз уж используется Qt - то сам «бох» велел использовать код который тесно завязан с Qt!!! т.к. библы QextSerialPort и QSerialDevice - это библы ДЛЯ Qt4!

anonymous ()
Ответ на: комментарий от anonymous

QT - shit

библы QextSerialPort и QSerialDevice - это библы ДЛЯ Qt4!

а есть ли там библиотеки для небуферизованного ввода/вывода? Или постоянно flush делать?

Eddy_Em ☆☆☆☆☆ ()
Ответ на: QT - shit от Eddy_Em

Re: QT - shit

>а есть ли там библиотеки для небуферизованного ввода/вывода? Или постоянно flush делать?

в смысле?

Могу говорить только о QSerialDevice - тут ничо делать не надо, т.к. в коде уже всё оно сделано «за вас». Если интересно - то качайте из SVN и пробуйте.

Насчет QextSerialPort не скажу - т.к. мне неинтересен проект :)

anonymous ()
Ответ на: комментарий от MuZHiK-2

Приходит ко мне байт допустим, а я ждал 2 байта. Что, мне сразу ошибку выставлять?

Вот видите, вы не разобрались в API QIODevice/QDataStream, а уже что-то советуете. Рассказываю.

QIODevice - абстрактный интерфейс для реализации потоков ввода/вывода, отнаследован от QObject как минимум чтобы иметь возможность испускать сигнал при получении новых данных. Интерфейс этот абстрактный как раз затем, чтобы напрямую использовать не его, а уже готовые классы для работы с ним. В данном случае - QDataStream.

QDataStream имеет три глобальных состояния:

  • Ok - протокол не нарушен, в буфере QIODevice возможно лежит неполный пакет, размер которого можно узнать с помощью QIODevice::bytesAvailable()
  • ReadPastEnd - устанавливается из переопределённого QDataStream & operator>>( QDataStream & stream, MyPacket & packet ) в случае когда байтов для чтения MyPacket нехватает. При этом парсер будет знать, что пакет packet не валиден и нужно отложить чтение до следуюего прихода данных.
  • ReadCorruptData - также устанавливается из переопределённого QDataStream & operator>>( QDataStream & stream, MyPacket & packet ), но уже когда по имеющимся в потоке байтам понятно, что там лежит что-то не то. При этом парсер будет знать, что пакет packet не валиден и уже сам решит, стоит ли прекратить чтение и закрыть устройство или же попытаться синхронизироваться с потоком мусора, если протокол это позволяет.

В следующий раз рекомендую по умолчанию воспринимать разработчиков Qt4 не как «клинических идиотов», а как создателей универсальной библиотеки, с вылизаным за десяток лет API.

Dendy ★★★★★ ()
Ответ на: комментарий от Dendy

>В следующий раз рекомендую по умолчанию воспринимать разработчиков Qt4 не как «клинических идиотов», а как создателей универсальной библиотеки, с вылизаным за десяток лет API.

Ты это второму мужику рассказываешь. Забей, у него священная война идёт.

Deleted ()
Ответ на: комментарий от Dendy

Вот видите, вы не разобрались в API QIODevice/QDataStream, а уже что-то советуете. Рассказываю.

Я прекрасно знаю, как оно работает.

В следующий раз рекомендую по умолчанию воспринимать разработчиков Qt4 не как «клинических идиотов», а как создателей универсальной библиотеки, с вылизаным за десяток лет API.

Где я назвал разработчиков Qt идиотами? Я идиотами назвал тех, что для сабжевой задачи использовал бы Qt. Смотри, что мы имеем при использовании Qt:

  • QObject со всей его шнягой
  • QIODevice с отнаследованным от него классом
  • QDataStream с отнаследованным от него классом
  • Слой обработки поступивших данных

Не хило ли для такой простой задачки, как приём данных с компорта? Весь этот шлак будет еле шевелиться. Да, для каких-то вещей QdataStream хороша, и там оверхеад будет оправданным (вдумайся в слово Stream - тут такого стрима обычно нету), но явно не для компорта. Если тем более нет требования кроссплатформенности.

Самым вменяемым решением тут будет создание QThread и вынос всей логики через посиксовые фукции в него. Он же и будет эмитить пришедшие пакеты с даннными.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от MuZHiK-2

> Слой обработки поступивших данных

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

QObject со всей его шнягой

QIODevice с отнаследованным от него классом



Один QIODevice, он же QObject, созданый не обязательно в куче.

QDataStream с отнаследованным от него классом


Очень смешно. Вы в исходник qdatastream.h загляните для разнообразия. От QDataStream не наследуются, его экземпляр - всего лишь вспомогательный класс-обёртка, создающийся на стеке парсером и передающийся в тела функций для вычитывания/записи данных и коллекционирования информации об ошибках, которую в конце парсер использует для проверки успешно ли вычитались пакеты.

Всё это плохо ассоциируется с

Я прекрасно знаю, как оно работает.


И последующими выводами о производительности.

Dendy ★★★★★ ()
Ответ на: комментарий от Dendy

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

Это делается на уровне приложения, а не приемника, логика в приложении должна быть. Нижний слой должен отвечать за приём данных, а приложение должно решать - битые они, можно ли их скорректировать. Потому что при передаче по компорту не всегда возможна повторная передача.

Очень смешно. Вы в исходник qdatastream.h загляните для разнообразия. От QDataStream не наследуются, его экземпляр - всего лишь вспомогательный класс-обёртка, создающийся на стеке парсером и передающийся в тела функций для вычитывания/записи данных и коллекционирования информации об ошибках, которую в конце парсер использует для проверки успешно ли вычитались пакеты.

Дорогуша, в это что тогда: «ReadPastEnd - устанавливается из переопределённого QDataStream & operator>>» - твои слова?

И последующими выводами о производительности.

Видно, ты даже не в курсе, что иногда производительность приоритетна.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от MuZHiK-2

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

Проверки на ошибку могут быть одновременно и у приёмника, и у приложения. Это никак не противоречит идеологии QDataStream.

Дорогуша, в это что тогда: «ReadPastEnd - устанавливается из переопределённого QDataStream & operator>>» - твои слова?


Вы ведь сами сказали, что

Я прекрасно знаю, как оно работает.


А позже:

QDataStream с отнаследованным от него классом


Видимо ваше «прекрасно знаю» заключается не в чтении документации Qt, а в анализе моего поста, в котором «переопределённого» нужно было прочитать как «перегруженного», ибо и так ясно, что переопределённых операторов << и >> не бывает.

Видно, ты даже не в курсе, что иногда производительность приоритетна.


Для этого вы сначала должны доказать что в Qt производительность падает, по сравнению с другим API. Единственный оверхед - это 1 malloc() + 1 free() на pimpl QObjectPrivate при создании/удалении устройства. Плюс возможно внутренний буффер QIODevice для корректного возврата данных в поток с помощью QIODevice::ungetChar(), который, в принципе, вы можете и не использовать.

К примеру пришли N байт, а вы ожидаете строку, размером N+1 байт, значит нужно подождать ещё 1 байт, а предыдущие N где-то сохранить. Этот функционал типичен практически для любых потоков и реализуется внутренним буфером QIODevice, который при желании можно переопределить своим.

В итоге имеем вызовы виртуальных методов + инлайновые функции для чтения данных из QDataStream (если затраты на вызов тела критичны). Никаких выделений памяти и промежуточных копирований. О каком падении производительность вы пытаетесь здесь рассказать?

Dendy ★★★★★ ()
Ответ на: комментарий от Dendy

>>Проверки на ошибку могут быть одновременно и у приёмника, и у приложения. Это никак не противоречит идеологии QDataStream.

Зато противоречит здравому смыслу. Ага, вдруг данные в памяти поменяются! Давай еще 10 раз проверим.

Видимо ваше «прекрасно знаю» заключается не в чтении документации Qt, а в анализе моего поста, в котором «переопределённого» нужно было прочитать как «перегруженного», ибо и так ясно, что переопределённых операторов << и >> не бывает.

Я знаю куте, и знаю, где оно проигрывает.

Для этого вы сначала должны доказать что в Qt производительность падает, по сравнению с другим API. Единственный оверхед - это 1 malloc() + 1 free() на pimpl QObjectPrivate при создании/удалении устройства. Плюс возможно внутренний буффер QIODevice для корректного возврата данных в поток с помощью QIODevice::ungetChar(), который, в принципе, вы можете и не использовать.

А теперь читаем про сигналы и про то, во сколько раз они проигрывают обычным колбекам. Нет потери производительности, говоришь? Я молчу про кутешную метаинфу у каждого объекта.

В итоге имеем вызовы виртуальных методов + инлайновые функции для чтения данных из QDataStream (если затраты на вызов тела критичны). Никаких выделений памяти и промежуточных копирований. О каком падении производительность вы пытаетесь здесь рассказать?

Таблица виртуальных методов - уже потеря производительности по сравнению с обычным С.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от MuZHiK-2

> Зато противоречит здравому смыслу. Ага, вдруг данные в памяти поменяются! Давай еще 10 раз проверим.

Имеются в виду разные проверки на ошибку, одни можно проверить только на этапе чтения из потока, другие - только после анализа самих данных.

Пример: согласно протоколу сейчас должна прийти C-строка, завершающаяся нулём, размер строки вместе с терминатором - до 10 символов. Пакет вычитывает байты из QDataStream и не обнаруживает ноль в пределах 10 символов, это противоречит протоколу, пакет ставит флаг QDataStream::ReadCorruptData и завершает чтение. Допустим строка вычиталась корректно, на выходе парсер видит состояние QDataStream::Ok и, допустим, ищет некое значение, связаное с этой строкой-ключём. Если значения по ключу нет - это нарушает протокол и парсер уже решает что делать с девайсом, при этом флаги в QDataStream естественно не используются, потому как назначение QDataStream - только передать информацию о возможной ошибке от пакета к парсеру.

Вот пример двух независимых проверок, логично разнесённых в соответствующие ветки кода.

Я знаю куте, и знаю, где оно проигрывает.


Вопрос сейчас о конкретном наборе её классов, а не о фреймворке целиком.

А теперь читаем про сигналы и про то, во сколько раз они проигрывают обычным колбекам.


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

Таблица виртуальных методов - уже потеря производительности по сравнению с обычным С.


То-есть в мифической потерей производительности вы вините полиморфизм C++, так бы сразу и сказали, что Qt и вообще все программы на C++ следует закопать. А также досыпать в могилку Java, Obj-C (привет, Apple! ты не знал, что твои программы тормозят?) и все скриптовые языки без разбору. Да и 90% программ на С, если подумать. Оставить только ассемблер, что может быть быстрее машинных кодов, верно?

Dendy ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.