Ivan. 4 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба Доброго времени суток, коллеги! Не бейте слишком сильно, но я уже несколько дней не могу побороть COM порт. Проблема в том, что при каждом чтении очередной пачки данных - теряются несколько байт. Чтение происходит в отдельном потоке. Основная особенность - это то, что порт заряжен на 921600 бод. Инициализация порта: bool ComPort::open(const char *portName, uint32_t baudrate, Parity parity, StopBits stopBits) { close(); std::string str = "\\\\.\\"; str += portName; HANDLE port = CreateFileA(str.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (port == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_FILE_NOT_FOUND) qDebug() << "Serial port does not exist"; else qDebug() << "Error opening port"; return false; } _DCB dcb; if (!GetCommState(port, &dcb)) { qDebug() << "Error retrieving dcb"; CloseHandle(port); return false; } dcb.BaudRate = baudrate; dcb.fBinary = TRUE; dcb.fOutxCtsFlow = FALSE; dcb.fOutxDsrFlow = FALSE; dcb.fDtrControl = DTR_CONTROL_DISABLE; dcb.fDsrSensitivity = FALSE; dcb.fNull = TRUE; dcb.fRtsControl = RTS_CONTROL_DISABLE; dcb.fAbortOnError = FALSE; dcb.ByteSize = 8; dcb.Parity = parity; dcb.StopBits = stopBits; if (!SetCommState(port, &dcb)) { qDebug() << "Could not set dcb"; CloseHandle(port); return false; } COMMTIMEOUTS timeOut; if (!GetCommTimeouts(port, &timeOut)) { qDebug() << "Error retrieving comm timeouts"; CloseHandle(port); return false; } timeOut.ReadIntervalTimeout = MAXDWORD; timeOut.ReadTotalTimeoutConstant = 0; timeOut.ReadTotalTimeoutMultiplier = 0; timeOut.WriteTotalTimeoutMultiplier = 0; timeOut.WriteTotalTimeoutConstant = 0; if (!SetCommTimeouts(port, &timeOut)) { qDebug() << "Could not set comm timeouts"; CloseHandle(port); return false; } if (!SetupComm(port, 4096, 4096)) { qDebug() << "Could not set buffer size"; CloseHandle(port); return false; } if (!EscapeCommFunction(port, SETRTS)) { qDebug() << "Error sending RTS signal"; } if (!EscapeCommFunction(port, SETDTR)) { qDebug() << "Error sending DTR signal"; } this->port = port; connection(true); thread = std::thread(handler, this); return true; } Обработчик: void ComPort::process() { OVERLAPPED ol; ZeroMemory(&ol, sizeof(ol)); if ((ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) { qDebug() << "Error creating event in read thread"; } else { HANDLE hEvents[2]; hEvents[0] = CreateEvent(NULL, FALSE, FALSE, TEXT("disconnect")); hEvents[1] = ol.hEvent; DWORD timeout = INFINITE; while (true) { SetCommMask(port, EV_RXCHAR); DWORD dwEvent; if (!WaitCommEvent(port, &dwEvent, &ol)) { error(); // break; } dwEvent = WaitForMultipleObjects(2, hEvents, FALSE, timeout); DWORD dwError; COMSTAT cs; ClearCommError(port, &dwError, &cs); if (dwEvent == WAIT_OBJECT_0) { qDebug() << "Disconnect"; break; } else if (dwEvent == WAIT_OBJECT_0 + 1) { if (cs.cbInQue) { BYTE buf[4096]; DWORD readed = 0; while (cs.cbInQue) { DWORD max = cs.cbInQue < sizeof(buf) ?cs.cbInQue :sizeof(buf); if (!ReadFile(port, buf, max, &readed, &ol) || readed == 0) { error(); break; } else { qDebug() << "readed: " << readed << "/" << max; cs.cbInQue -= readed; timeout = receive(buf, readed); } } } else { qDebug() << "Loss of connection"; break; } } else if (dwEvent == WAIT_TIMEOUT) { qDebug() << "Timeout"; this->timeout(); timeout = MAXDWORD; } else if (dwEvent == WAIT_FAILED) { qDebug() << "Invalid event occured in the Port I/O thread"; break; } ResetEvent(ol.hEvent); } if (!PurgeComm(port, PURGE_RXCLEAR)) { qDebug() << "Error purging read buffer"; } CloseHandle(ol.hEvent); CloseHandle(hEvents[0]); } if (port != INVALID_HANDLE_VALUE) { CloseHandle(port); port = INVALID_HANDLE_VALUE; } connection(false); } По отладке вижу явную потерю данных: ... 8F8;0D9;E90 964;0AF;E4B 9C6;088;E0A A2A;078;DBE readed: 2964 / 2964 ;026;D52 AF2;043;D27 B51;02B;CC8 BB4;026;C79 C21;04B;C28 ... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
iosifk 3 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба После uart должен стоять преобразователь уровней в стандарт COM порта. Посмотрите его даташит, может лт он работать на вашей частоте... Потом включите на порт заглушку "сам на себя"... Потом посмотрите в настройках uart, может ли он при приеме бита брать не один отсчет, а три и мажоритировать их... Потом можно увеличить стоповый интервал... ну а дальше протокол с перезапросами по ошибке приема данных... А перед этим соединить земли передатчика и приемника, взять кабель с витыми парами или с экранами.. Короце, есть простор для поиска... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Ivan. 4 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба 2 минуты назад, iosifk сказал: После uart должен стоять преобразователь уровней в стандарт COM порта. Посмотрите его даташит, может лт он работать на вашей частоте... Потом включите на порт заглушку "сам на себя"... Потом посмотрите в настройках uart, может ли он при приеме бита брать не один отсчет, а три и мажоритировать их... Потом можно увеличить стоповый интервал... ну а дальше протокол с перезапросами по ошибке приема данных... А перед этим соединить земли передатчика и приемника, взять кабель с витыми парами или с экранами.. В терминале все четко принимает Анализировал линию логическим анализатором - идеальные тайминги. Данные валятся с контроллера в качестве отладочной информации измерений АЦП (6000 раз в секунду). В 1Мб максимум удается передать 3 канала в CSV формате ASCII(HEX). Протокол накладывать некуда. Терминал RealTerm принимает без потерь Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
engel65536 12 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба Вам действительно нужна такая навороченная обработка приёма, с ивентами, ожиданиями и всем таким? (Я не очень опытный ПК-программист, но всякие штуки с последовательным портом на сях под WinAPI как-то писал) Может, хотя бы для начала опуститься на уровень банальных блокирующих ReadFile, а потом, когда убедитесь, что так оно работает как надо, наращивать сложность? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
tonyk_av 40 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба В структурах настройки СОМ-порта все поля инициализированы? Насколько помню, там много полей. И в доках Микрософта описано много параметров, которые, почему-то, не все инициализируют. В статье о проекте SerialGate более-менее нормально описано, но не все параметры порта настроены. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Ivan. 4 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба 10 минут назад, engel65536 сказал: Вам действительно нужна такая навороченная обработка приёма, с ивентами, ожиданиями и всем таким? В реале конечно нужно ибо как реализовать графическое приложение, чтобы оно не висело в ожидании данных и имело возможность выйти из ожидания при закрытии порта. Так же данный терминал должен поддерживать протокол Modbus, где таймаут очень важен. 15 минут назад, engel65536 сказал: Может, хотя бы для начала опуститься на уровень банальных блокирующих ReadFile, а потом, когда убедитесь, что так оно работает как надо, наращивать сложность? Рациональное предложение, но предполагаю, что не в этом дело. 2 минуты назад, tonyk_av сказал: В структурах настройки СОМ-порта все поля инициализированы? Насколько помню, там много полей. И в доках Микрософта описано много параметров, которые, почему-то, не все инициализируют. В статье о проекте SerialGate более-менее нормально описано, но не все параметры порта настроены. Да вроде по все постарался проинициализировать. И в свойствах адаптера все по максимуму поставил Данные бьются при каждом чтении Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
esaulenka 7 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба 52 minutes ago, Ivan. said: АЦП (6000 раз в секунду). В 1Мб максимум удается передать 3 канала в CSV формате ASCII(HEX) Если передавать в бинарном виде, это всего 3*2(у вас же <=16 бит АЦП?)*6000 = 36kbyte/sec. В 115200 не лезет, но в мегабитный UART с бааальшим запасом на любые протоколы. Правда, не уверен, что вы сможете перепосылать ошибочные данные - только выкидывать битые. Ну да ладно. У меня другой вопрос - что такое while (cs.cbInQue) { ... и зачем оно тут? Достаточно ж вроде верхнего цикла. Ещё вопрос. Вы можете поменять софт отправителя, чтобы просто слал байты по кругу? У вас потери всегда на стыке вызовов Read() (и всегда ли?) или возможны другие варианты? Ещё вопросы: SetCommMask() / ClearCommError() из цикла убирать пробовали? Кажется, так делать не обязательно. И вообще, чем вам QSerialPort не угодил? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dOb 10 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба 9 минут назад, esaulenka сказал: И вообще, чем вам QSerialPort не угодил? Согласен. С помощью QSerialPort принимал в бинарном виде раз в миллисекунду по кадру 64байт на бодовой скорости 921600. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Ivan. 4 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба 5 минут назад, esaulenka сказал: while (cs.cbInQue) { ... cs.cbInQue -= readed; 13 минут назад, esaulenka сказал: Ещё вопрос. Вы можете поменять софт отправителя, чтобы просто слал байты по кругу? У вас потери всегда на стыке вызовов Read() (и всегда ли?) или возможны другие варианты? Могу. потери всегда в начале блока FileRead. в середине все ровно 15 минут назад, esaulenka сказал: Ещё вопросы: SetCommMask() / ClearCommError() из цикла убирать пробовали? Кажется, так делать не обязательно. Попробовал. без ClearCommError() буфер не очищается без SetCommMask() или вынеся за цикл возникают прочие ошибки 16 минут назад, esaulenka сказал: И вообще, чем вам QSerialPort не угодил? Не знаю. забыл про него Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
engel65536 12 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба 52 minutes ago, Ivan. said: не висело в ожидании данных и имело возможность выйти из ожидания при закрытии порта Выделить всю обработку коммуникации в отдельный поток исполнения, и пусть он блокируется. Между этим потоком и тем, кто обрабатывает данные - очередь. 53 minutes ago, Ivan. said: Так же данный терминал должен поддерживать протокол Modbus, где таймаут очень важен. Делал именно Modbus на блокирующих ReadFile. Если под "очень важным таймаутом" вы понимаете межфреймовый интервал (который 3.5 Tchar для низких скоростей и сколько-то мс для высоких скоростей), то на не-реалтаймовой ОС вы всё равно его надёжно не поймаете. С моей точки зрения, ловить его пытаться можно, но полагаться на пойманное не стоит - просто потому, что WinAPI не представляет никакой возможности получить аппаратный таймстемп к каждому полученному (именно физически полученному, а не считанному через ReadFile) байту или блоку байт. Самая важная вещь для разделения фреймов - CRC. 57 minutes ago, Ivan. said: 1 hour ago, engel65536 said: Может, хотя бы для начала опуститься на уровень банальных блокирующих ReadFile, а потом, когда убедитесь, что так оно работает как надо, наращивать сложность? Рациональное предложение, но предполагаю, что не в этом дело. Могу только сказать, что писал на блокирующих ReadFile парсинг modbus при весьма высоком использовании пропускной способности UART и даже с некоторыми требованиями по соблюдению времянки (требования специфические и за рамками спецификации modbus) - всё работало очень неплохо. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Ivan. 4 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба 4 часа назад, Ivan. сказал: И вообще, чем вам QSerialPort не угодил? Работает без пропусков. Спасибо Но все ровно нужно выносить в отдельный поток, ибо если задержаться в какой нибуть функции - буфер в 4к быстро кончается Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 224 18 мая, 2023 Опубликовано 18 мая, 2023 · Жалоба 9 часов назад, Ivan. сказал: Проблема в том, что при каждом чтении очередной пачки данных - теряются несколько байт. Проблема у вас в том, что взявшись за overlapped-операции, вы даже не удосужились почитать документацию и что это вообще такое и как оно работает... 9 часов назад, Ivan. сказал: Чтение происходит в отдельном потоке. Основная особенность - это то, что порт заряжен на 921600 бод. if (cs.cbInQue) { BYTE buf[4096]; DWORD readed = 0; while (cs.cbInQue) { DWORD max = cs.cbInQue < sizeof(buf) ?cs.cbInQue :sizeof(buf); if (!ReadFile(port, buf, max, &readed, &ol) || readed == 0) { error(); break; } else { qDebug() << "readed: " << readed << "/" << max; cs.cbInQue -= readed; timeout = receive(buf, readed); } } } Откройте документацию на WinAPI и почитайте - в каких случаях ReadFile() может вернуть 0 для overlapped. И как это нужно правильно обрабатывать. А также подумайте - что может случиться, если overlapped-операцию запустить на таком локальном массиве как у вас? Странно, что у вас система в синий экран смерти от такого безобразия не валится.... WaitCommEvent() - аналогично документация на неё не читана. 9 часов назад, Ivan. сказал: if (dwEvent == WAIT_OBJECT_0) { qDebug() << "Disconnect"; break; } Вываливаемся молча из цикла даже не завершив корректно текущие overlapped-операции??? То же самое - по всем другим внезапным выходам. PS: Это только то, что первое бросилось в глаза. При таком табуне багов дальше смотреть не имеет смысла. Всё переписывать, читая документацию на применяемое WinAPI. 1 час назад, Ivan. сказал: Работает без пропусков. Спасибо Но все ровно нужно выносить в отдельный поток, ибо если задержаться в какой нибуть функции - буфер в 4к быстро кончается Интересно как вы вообще ожидаете хоть какой-то стабильной работы на 921600 при таком микроскопическом буфере с уровня приложения win? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Ivan. 4 19 мая, 2023 Опубликовано 19 мая, 2023 · Жалоба 5 часов назад, jcxz сказал: Интересно как вы вообще ожидаете хоть какой-то стабильной работы на 921600 при таком микроскопическом буфере с уровня приложения win? А как его увеличить? QSerialPort мне больше не дает. Вот я и предлагаю вынести в отдельный поток, чтобы забирать в свой большой буфер, а потом обрабатывать. 6 часов назад, jcxz сказал: Вываливаемся молча из цикла даже не завершив корректно текущие overlapped-операции??? А можно пример? P.S. Просил же сильно не бить, а Вы сразу с кулаками. Времени как обычно нет, а примеров на эту тему очень мало Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
tonyk_av 40 19 мая, 2023 Опубликовано 19 мая, 2023 · Жалоба 1 hour ago, Ivan. said: А можно пример? SerialGate в поисковике. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 224 19 мая, 2023 Опубликовано 19 мая, 2023 · Жалоба 7 часов назад, Ivan. сказал: А как его увеличить? QSerialPort мне больше не дает. Вот я и предлагаю вынести в отдельный поток, чтобы забирать в свой большой буфер, а потом обрабатывать. Причём тут QSerialPort? Я про ваши попытки работы через WinAPI. ReadFile() позволяет читать в любой размер буфера. Задержки передачи управления задачам под виндой могут достигать сотен мсек. Вот из этого и нужно исходить выбирая размер буфера. И естественно - работа с портами (так же как и с любыми другими устройствами ввода/вывода, файлами и т.п.), должна осуществляться в потоке, отдельном от GUI-потока. Вне зависимости от скорости и размеров буфера. Так же - полезно назначать потоку, работающему с real-time процессами, приоритет выше чем у GUI-потока. 7 часов назад, Ivan. сказал: А можно пример? Лучше почитайте полностью всю документацию про overlapped-операции. Корректное прерывание текущих операций: CancelIo() насколько помню. Также почитайте про GetOverlappedResult(). Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться