Russian Qt Forum

Qt => Вопросы новичков => Тема начата: vorotislav от Декабрь 24, 2014, 12:07



Название: Qt + последовательный порт
Отправлено: vorotislav от Декабрь 24, 2014, 12:07
Добрый день. Осваиваю чтение данных из последовательного порта, и возник такой вопрос.
Вот есть метод, который считывает данные:

Код:
QByteArray data;
data.append(_port.readAll());

И на выходе я получаю массив байт.
С этим все понятно.
А в каком виде байты будут? Я без устройства, и поэтому мне приходится гадать, в каком виде каждый байт будет. Знаю точно, что пакет данных выглядит примерно так: (здесь и далее, в хексе)

00 22 11 F0 88 F0 F0 F0 F0 11 F0 77 EE F0 3C

Где первый байт, это байт синхронизации, и он всегда 00, а все остальное еще и кодированные байты, то есть их надо каким то образом декодировать. Подскажите, каким образом это делать? У меня есть таблица, где прописано, что байт 1 это допустим в кодированном виде байт 11. Из чего я могу сделать вывод, что операцию кодирования\декодирования надо будет делать через обычное сравнивание. А как мне каждый байт сравнить? Пробовал так:

Код:
if (data.at(0) == 0xF0)

На что мне сразу предупреждение, что результат проверки всегда будет false:
предупреждение: comparison is always false due to limited range of data type [-Wtype-limits]
if (data.at(0) == 0xF0)
^

Вот, и я вообще запутался. Более того, изначально я не знаю, какой длины пакет. Тот пример, что я привел, это минимальный пакет - заголовок + контрольная сумма. В таком случае, я не знаю, весь ли пакет пришел, пока не расшифрую заголовок пакета.
И еще вопрос, касаемо контрольной суммы. Она всегда находится в конце пакета и составляет 4 байта. И так же в кодировке. Контрольная сумма CRC16, табличная. Нашел (на этом форуме) для Qt метод:
Код:
quint16 crc16_modbus(const QByteArray &array)
{

    static const quint16 wCRCTable[] = {
    0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
    0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
    0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
    0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
    0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
    0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
    0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
    0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
    0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
    0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
    0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
    0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
    0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
    0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
    0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
    0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
    0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
    0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
    0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
    0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
    0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
    0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
    0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
    0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
    0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
    0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
    0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
    0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
    0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
    0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
    0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
    0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };

    quint8 nTemp;
    quint16 wCRCWord = 0xFFFF;


    for (int i=0; i < array.length();++i)
    {
        nTemp = (quint8)array.at(i) ^ wCRCWord;
        wCRCWord >>= 8;
        wCRCWord ^= wCRCTable[nTemp];
    }

    return wCRCWord;
}

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

Заранее извиняюсь за, возможно, столь примитивные вопросы, никогда прежде не занимался работой с байтами


Название: Re: Qt + последовательный порт
Отправлено: Пантер от Декабрь 24, 2014, 12:14
Вот что тебе понадобится: QByteArray::constData, qFromLittleEndian, qFromBigEndian, reinterpret_cast.
А readAll плохо использовать. Нужно читать по мере прихода данных и, если не все пришло, хранить их в буфере, а когда все придет парсить.


Название: Re: Qt + последовательный порт
Отправлено: Пантер от Декабрь 24, 2014, 12:17
[offtop]
Так и хочется написать небольшую лекцию/хаутушку по данному вопросу, но времени нет. :( Все достаточно просто, главное, понять сам подход.
[/offtop]


Название: Re: Qt + последовательный порт
Отправлено: vorotislav от Декабрь 24, 2014, 12:27
Пантер, да, лекции бы не помешало. В теории, все понятно и элементарно. Как только дело дошло до реализации - запутался окончательно.
Парсить заголовок придется сразу, потому что, как я написал выше, длина пакета строится таким образом: 11 байт заголовок (в 10 и 11 байте - длина данных указана) + данные + 4 байта контрольная сумма. Итого, пакет может быть от 15 байт, до 525 байт.
Спасибо за наводку, буду изучать.
Кстати, Пантер, не подскажете такой момент:
Вот возьмем заголовок: 11 байт. В кодированном виде он так и занимает, 11 байт. Но в декодированном виде идет разделение на старший и младший полубайт. Вот например те же 10 и 11 байты, они в декодированном виде разделяются. 10 - младший полубайт, 11 - старший. Как мне с этим работать? И про контрольную сумму я задал вопрос в первом сообщении, не могли бы ответить?


Название: Re: Qt + последовательный порт
Отправлено: Пантер от Декабрь 24, 2014, 12:31
Можно так считывать.
Код
C++ (Qt)
const quint16 dataSize = *reinterpret_cast<quint16*>(data.constData() + 10);
 
или так
Код
C++ (Qt)
const quint16 dataSize = qFromLittleEndian<quint16>(data.constData() + 10);
 
Второй способ предпочтительнее.


Название: Re: Qt + последовательный порт
Отправлено: Пантер от Декабрь 24, 2014, 12:33
Контрольная сумма - это число. Вычитываешь эталонную из данных, расчитываешь свою по нужному куску данных и сравниваешь.


Название: Re: Qt + последовательный порт
Отправлено: 8Observer8 от Декабрь 25, 2014, 11:51
Цитировать
А в каком виде байты будут?
Можно вывести принятые байты на экран и посмотреть, что посылалось и что пришло

Я так делаю. Пишу одну программу, которая посылает пакеты (эмулятор устройства), по этому примеру: https://github.com/8Observer8/SendingDataToComPort

Пишу вторую (целевую) программу, которая принимает данные, по этому примеру: https://github.com/8Observer8/ReceivingDataFromComPort

Создаю пару виртуальных портов с помощью бесплатной программы VSPE (Virtual Serial Ports Emulator) и соединяю эти программы, то есть, допустим, одна программа посылает в COM1, а другая - считывает из COM2


Название: Re: Qt + последовательный порт
Отправлено: vorotislav от Декабрь 26, 2014, 00:43
8Observer8, именно так и делаю :) У меня есть две программы, соединенные по сом-порту, сами порты сделаны именно той программой, что вы посоветовали.
Господа, а не подскажите, по этой же теме такой вопрос.
Исходя из ТЗ, в программа должна одновременно прослушивать сразу 3 com-порта, данные на все три передаются одинаковые, сделано для того, чтоб исключить обрыв канала. И возник вопрос, имеет ли смысл прослушивание портов выносить в отдельный поток, или может быть даже в 3 потока, или хватит главного? С потоками дела не имел, только пробовал, благо примеров достаточно, в частности QSerialPort + thread, но постоянно возникали некие ошибки. ВОт прежде чем окунаться с головой в многопоточное программирование, решил у вас поинтересоваться. Заранее благодарен!


Название: Re: Qt + последовательный порт
Отправлено: Пантер от Декабрь 26, 2014, 08:02
Если читать ассинхронно, то потоки не нужны.


Название: Re: Qt + последовательный порт
Отправлено: Hellraiser от Декабрь 26, 2014, 08:47
Почти так: в простейшем случае можно обойтись без отдельных потоков. Но как только появляется необходимость поддержания непрерывного обмена с девайсами при условии выдерживания миллисекундных таймаутов, особенно под виндой, то сразу появляется необходимость выноса обмена из гуи-потока в отдельный.


Название: Re: Qt + последовательный порт
Отправлено: 8Observer8 от Декабрь 26, 2014, 10:41
Исходя из ТЗ, в программа должна одновременно прослушивать сразу 3 com-порта, данные на все три передаются одинаковые, сделано для того, чтоб исключить обрыв канала. И возник вопрос, имеет ли смысл прослушивание портов выносить в отдельный поток, или может быть даже в 3 потока, или хватит главного?
Не представляю какие должны быть требования, чтобы вынудить использовать потоки для данной задачи. Данные приходят ассинхронно. Программа спокойно работает, пока не сработает очередной слот, куда пришли данные.  Мне видится такая проблема: как узнать, что на выбранном канале произошёл обрыв? Я когда-то несколько лет работал инженером в этом КБ (http://kbep.ru/), где делают системы управления для газотурбинных двигателей военных самолётов. В связи с этим конкретный пример. Допустим, нам нужно считывать показания температуры с элемента изделия. У нас есть прибор подсоединённый к компьютеру через три последовательных порта. Из прибора идут три провода с термопарами. Термопары закреплены на целевом элементе, допустим на транзисторе, который установлен на разрабатываемом изделии, а изделие помещено в камеру тепла. По ТУ на изделие при температуре окружающей среды +60 градусов температура этого транзистора не должна превышать столько то градусов. При этом само изделие гоняется в режимах, которые задаёт пульт по зашитой в него программе. Наша задача в течении трёх часов снимать показания температуры (пусть у нас три независимых прибора, присылают значения с интервалом 5 секунд) и сохранять значения в таблицу базы данных для последующего анализа. Три термопары - это для постраховки, на случай если будет случайный обрыв - всякое бывает, кто-то из персонала может подвинуть прибор, к которому подсоединены термопары и отойдёт контакт. Так вот. Как программа определит, что данный канал оборван и нужно сохранять уже из другого? Какой самый простой вариант?


Название: Re: Qt + последовательный порт
Отправлено: torwig от Декабрь 26, 2014, 10:51
Ну у Вас же написано, что присылают каждые 5 секунд данные. Если данных 5 секунд нет - значит обрыв.
А по поводу потоков, асинхронности, вы правы. Если принять, записать - возможно и не нужно отдельного потока на устройство. А если производятся тяжелый какие-то вычисления и идет интенсивный обмен данными (возникают проблемы, задержки и пр.), тогда можно рассмотреть потоки.


Название: Re: Qt + последовательный порт
Отправлено: Пантер от Декабрь 26, 2014, 10:54
Если производятся вычисления, то их и имеет смысл выносить в потоки, а вот чтение пусть живет в главном.


Название: Re: Qt + последовательный порт
Отправлено: Hellraiser от Декабрь 26, 2014, 11:03
Повторюсь еще раз: при работе с устройствами, чувствительными к задержке приема/передачи (необходимость обеспечения минимальных пауз между запросом и ответом) и уж тем более не для секундных пауз, а как я выше написал, для миллисекундных, только работа в отдельном потоке может это обеспечить. Простейший тест - подключаем к линии логический анализатор, отправляем небольшие пакеты данных (3 - 12 байт), зажимаем заголовок окна приложения, таскаем окно по экрану, наблюдаем на анализаторе паузы между пакетами. При выносе приема/передачи в отдельный поток такого нет. Для приема пары байт раз в 5 секунд это явно лишнее.


Название: Re: Qt + последовательный порт
Отправлено: kuzulis от Декабрь 26, 2014, 14:00
Цитата: Hellraiser
зажимаем заголовок окна приложения, таскаем окно по экрану, наблюдаем на анализаторе паузы между пакетами. При выносе приема/передачи в отдельный поток такого нет.

В Qt 5.5 это пофикшено. Это проблемы/фичи винды.