Перейти к содержанию
    

Проблема с чтением COM порта

Доброго времени суток, коллеги!
Не бейте слишком сильно, но я уже несколько дней не могу побороть 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
...

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

После uart должен стоять преобразователь уровней в стандарт COM порта. Посмотрите его даташит, может лт он работать на вашей частоте...

Потом включите на порт заглушку "сам на себя"...

Потом посмотрите в настройках uart, может ли он при приеме бита брать не один отсчет, а три и мажоритировать их...

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

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

 

Короце, есть простор для поиска...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 минуты назад, iosifk сказал:

После uart должен стоять преобразователь уровней в стандарт COM порта. Посмотрите его даташит, может лт он работать на вашей частоте...

Потом включите на порт заглушку "сам на себя"...

Потом посмотрите в настройках uart, может ли он при приеме бита брать не один отсчет, а три и мажоритировать их...

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

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

В терминале все четко принимает
Анализировал линию логическим анализатором - идеальные тайминги.
Данные валятся с контроллера в качестве отладочной информации измерений АЦП (6000 раз в секунду). В 1Мб максимум удается передать 3 канала в CSV формате ASCII(HEX). Протокол накладывать некуда.

image.thumb.png.464fd7d3452ad56fc0ab55e4e7fdc91e.png

Терминал RealTerm принимает без потерь

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вам действительно нужна такая навороченная обработка приёма, с ивентами, ожиданиями и всем таким? (Я не очень опытный ПК-программист, но всякие штуки с последовательным портом на сях под WinAPI как-то писал)

Может, хотя бы для начала опуститься на уровень банальных блокирующих ReadFile, а потом, когда убедитесь, что так оно работает как надо, наращивать сложность?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В структурах настройки СОМ-порта все поля инициализированы? Насколько помню, там много полей. И в доках Микрософта описано много параметров, которые, почему-то, не все инициализируют. В статье о проекте SerialGate более-менее нормально описано, но не все параметры порта настроены.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

10 минут назад, engel65536 сказал:

Вам действительно нужна такая навороченная обработка приёма, с ивентами, ожиданиями и всем таким?

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

15 минут назад, engel65536 сказал:

Может, хотя бы для начала опуститься на уровень банальных блокирующих ReadFile, а потом, когда убедитесь, что так оно работает как надо, наращивать сложность?

Рациональное предложение, но предполагаю, что не в этом дело.

2 минуты назад, tonyk_av сказал:

В структурах настройки СОМ-порта все поля инициализированы? Насколько помню, там много полей. И в доках Микрософта описано много параметров, которые, почему-то, не все инициализируют. В статье о проекте SerialGate более-менее нормально описано, но не все параметры порта настроены.

Да вроде по все постарался проинициализировать.
И в свойствах адаптера все по максимуму поставил

image.thumb.png.c78ba2a4b07b525d0b1773b31ded6353.png

Данные бьются при каждом чтении

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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 не угодил?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

9 минут назад, esaulenka сказал:

И вообще, чем вам QSerialPort не угодил?

Согласен.

С помощью QSerialPort принимал в бинарном виде раз в миллисекунду по кадру 64байт на бодовой скорости 921600.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 минут назад, esaulenka сказал:

while (cs.cbInQue) { ...

cs.cbInQue -= readed;
13 минут назад, esaulenka сказал:

Ещё вопрос. Вы можете поменять софт отправителя, чтобы просто слал байты по кругу? У вас потери всегда на стыке вызовов Read() (и всегда ли?) или возможны другие варианты?

Могу. потери всегда в начале блока FileRead. в середине все ровно

 

15 минут назад, esaulenka сказал:

Ещё вопросы: SetCommMask() / ClearCommError() из цикла убирать пробовали? Кажется, так делать не обязательно.

Попробовал.
без ClearCommError() буфер не очищается
без SetCommMask() или вынеся за цикл возникают прочие ошибки

16 минут назад, esaulenka сказал:

И вообще, чем вам QSerialPort не угодил?

Не знаю. забыл про него

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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) - всё работало очень неплохо.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

4 часа назад, Ivan. сказал:

И вообще, чем вам QSerialPort не угодил?

Работает без пропусков.
Спасибо

Но все ровно нужно выносить в отдельный поток, ибо если задержаться в какой нибуть функции - буфер в 4к быстро кончается

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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-операцию запустить на таком локальном массиве как у вас? Странно, что у вас система в синий экран смерти от такого безобразия не валится....  :unknw:

WaitCommEvent() - аналогично документация на неё не читана.   :punish:

9 часов назад, Ivan. сказал:
if (dwEvent == WAIT_OBJECT_0) {
				qDebug() << "Disconnect";
				break;
			}

Вываливаемся молча из цикла даже не завершив корректно текущие overlapped-операции???  :punish:

То же самое - по всем другим внезапным выходам.

 

PS: Это только то, что первое бросилось в глаза. При таком табуне багов дальше смотреть не имеет смысла. Всё переписывать, читая документацию на применяемое WinAPI.

 

1 час назад, Ivan. сказал:

Работает без пропусков.
Спасибо

Но все ровно нужно выносить в отдельный поток, ибо если задержаться в какой нибуть функции - буфер в 4к быстро кончается

Интересно как вы вообще ожидаете хоть какой-то стабильной работы на 921600 при таком микроскопическом буфере с уровня приложения win? 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 часов назад, jcxz сказал:

Интересно как вы вообще ожидаете хоть какой-то стабильной работы на 921600 при таком микроскопическом буфере с уровня приложения win? 

А как его увеличить? QSerialPort мне больше не дает. Вот я и предлагаю вынести в отдельный поток, чтобы забирать в свой большой буфер, а потом обрабатывать. 

 

6 часов назад, jcxz сказал:

Вываливаемся молча из цикла даже не завершив корректно текущие overlapped-операции???

А можно пример? 

P.S. Просил же сильно не бить, а Вы сразу с кулаками. Времени как обычно нет, а примеров на эту тему очень мало

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

7 часов назад, Ivan. сказал:

А как его увеличить? QSerialPort мне больше не дает. Вот я и предлагаю вынести в отдельный поток, чтобы забирать в свой большой буфер, а потом обрабатывать. 

Причём тут QSerialPort? Я про ваши попытки работы через WinAPI. ReadFile() позволяет читать в любой размер буфера. Задержки передачи управления задачам под виндой могут достигать сотен мсек. Вот из этого и нужно исходить выбирая размер буфера.

И естественно - работа с портами (так же как и с любыми другими устройствами ввода/вывода, файлами и т.п.), должна осуществляться в потоке, отдельном от GUI-потока. Вне зависимости от скорости и размеров буфера. Так же - полезно назначать потоку, работающему с real-time процессами, приоритет выше чем у GUI-потока.

7 часов назад, Ivan. сказал:

А можно пример? 

Лучше почитайте полностью всю документацию про overlapped-операции.

Корректное прерывание текущих операций: CancelIo() насколько помню.

Также почитайте про GetOverlappedResult().

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...