#c #windows #serial-port
#c #Windows #последовательный порт
Вопрос:
Я работал над программой, которая ведет диалог с внешним устройством через последовательную шину RS422. Цель состоит в том, чтобы отправлять команды на устройство, которые отправляют ответ обратно. Пока что код для отправки сообщения выглядит следующим образом:
OVERLAPPED osWrite = {0};
void init()
{
// Create this write operation's OVERLAPPED structure's hEvent.
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// error creating overlapped event handle
std::cout << "Error osWrite.hEvent" << std::endl; // Error in communications; report it.
*hPort = CreateFile("\\.\COM18", (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (*hPort == INVALID_HANDLE_VALUE) {
std::cout << "Invalid port com handle" << GetLastError() << std::endl;
return;
}
COMMTIMEOUTS commTimeout;
if (GetCommTimeouts(*hPort, amp;commTimeout)) {
commTimeout.ReadIntervalTimeout = 10;
commTimeout.ReadTotalTimeoutConstant = 10;
commTimeout.ReadTotalTimeoutMultiplier = 10;
commTimeout.WriteTotalTimeoutConstant = 10;
commTimeout.WriteTotalTimeoutMultiplier = 10;
} else
return;
if (!SetCommTimeouts(*hPort, amp;commTimeout)) {
std::cout << "Error comm timeout" << std::endl;
}
DCB dcb;
if (!GetCommState(*hPort, amp;dcb)) {
std::cout << "Invalid port com settings" << std::endl;
return;
}
dcb.BaudRate = CBR_115200;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
SetCommMask(*hPort, EV_RXCHAR);
SetCommState(*hPort, amp;dcb);
return;
}
DWORD serial_send(HANDLE *hPort, char *msg, int length) {
DWORD dwWritten;
DWORD dwRes;
BOOL fRes;
PurgeComm(*hPort, PURGE_TXCLEAR);
ResetEvent(osWrite.hEvent);
// Issue write.
if (!WriteFile(*hPort, msg, length, amp;dwWritten, amp;osWrite)) {
if (GetLastError() != ERROR_IO_PENDING) {
// WriteFile failed, but isn't delayed. Report error and abort.
fRes = FALSE;
} else {
fRes = FALSE;
while (!fRes) {
// Write is pending.
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch (dwRes) {
// OVERLAPPED structure's event has been signaled.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(*hPort, amp;osWrite, amp;dwWritten, FALSE))
fRes = FALSE;
else
// Write operation completed successfully.
fRes = TRUE;
break;
default:
// An error has occurred in WaitForSingleObject.
// This usually indicates a problem with the
// OVERLAPPED structure's event handle.
fRes = FALSE;
break;
}
}
}
} else {
// WriteFile completed immediately.
fRes = TRUE;
}
return dwWritten;
}
Последняя функция не может вернуться, пока операция записи не будет успешной. The init()
загрузка функции без ошибок.
Я использовал много кода отсюда:https://learn.microsoft.com/en-us/previous-versions/ff802693 (v= msdn.10)
Каждое сообщение имеет длину 210 байт, а последовательный порт работает со скоростью 115200 бит / с, что означает, что я должен отправлять сообщение каждые ~ 18,2 мс. (210 байт * 10 бит / 115200) Однако, когда я измеряю время, прошедшее между 2 сообщениями, я иногда получаю длительность, намного меньшую, чем ожидалось 18 мс (она может снизиться до 11 мс).
Является ли это нормальным поведением для асинхронного файла записи WaitForSingleObject? Что произойдет, если я отправлю другое сообщение сразу после 11 мс, повредит ли оно предыдущее сообщение или оно будет буферизовано?
Я использовал std::chrono::high_resolution_clock::now()
и std::chrono::duration<double, std::milli>(end - start).count()
, чтобы получить продолжительность кадра, действительно ли это точно?
Комментарии:
1. Какое значение было записано DWW?
2. user253751 Это 210 для каждого вызова, точно так же, как размер сообщения
Ответ №1:
Поскольку Windows не является ОС реального времени и является многопроцессорной и многопоточной ОС, точность времени не должна гарантироваться.
Если система загружена слабо, большая ее часть будет работать по назначению, но не всегда.
И наоборот, о завершении WriteFile() может быть сообщено раньше, чем оно есть на самом деле, в зависимости от конфигурации аппаратного обеспечения и стека драйверов устройств.
Например, можно считать, что процесс завершен в тот момент, когда данные полностью сохраняются в буфере драйвера устройства или когда последние данные записываются в буфер FIFO интерфейсной микросхемы.
Лучше думать, что функция WriteFile() может быть завершена, даже если не все данные фактически достигают другой стороны.
Это считается тем же, что и запись данных файла на жесткий диск. Завершение записи в файл на диске выполняется в системном буфере и должно быть записано на фактический носитель в другое время.
Если следующая функция serial_send() вызывается до того, как все данные файла записи предыдущего времени достигли другой стороны из-за плохих условий, существует вероятность того, что некоторые из предыдущих данных передачи будут отброшены.
Потому что PurgeComm(*hPort, PURGE_TXCLEAR);
вызывается в начале функции serial_send().
Это не так критично, как указание PURGE_TXABORT , но все еще существует вероятность удаления данных с помощью PURGE_TXCLEAR.
PURGE_TXABORT 0x0001 завершает все невыполненные операции записи с перекрытием и немедленно возвращает, даже если операции записи не были завершены.
PURGE_TXCLEAR 0x0004 очищает выходной буфер (если он есть в драйвере устройства).
Если поток использует PurgeComm для очистки выходного буфера, удаленные символы не передаются. Чтобы очистить выходной буфер, гарантируя при этом передачу содержимого, вызовите функцию FlushFileBuffers (синхронная операция).
Обходной путь заключается в том, чтобы просто не вызывать PurgeComm()
.
Если это API последовательного порта, возможно, удастся дождаться завершения передачи, указав / обнаружив EV_TXEMPTY с помощью SetCommMask()/ WaitCommEvent(), но это будет только сложнее.
Функция SetCommMask / Функция WaitCommEvent
EV_TXEMPTY 0x0004 Был отправлен последний символ в выходном буфере.
Тогда ваше использование WriteFile() WaitForSingleObject() GetOverlappedResult() в конечном итоге будет работать аналогично синхронному вызову WriteFile().
Асинхронная работа не всегда необходима, но лучше рассмотреть подробно, исходя из того, какого поведения требует ваша система.
Комментарии:
1. Большое спасибо! Кроме того, я использовал асинхронный файл записи, потому что мне нужно вызвать ReadFile одновременно на том же порту, что, я думаю, невозможно при синхронном вызове (хотя я могу ошибаться).
2. Если вы хотите читать / записывать одновременно асинхронно, текущий метод, безусловно, хорош. Кроме того, было бы относительно легко создать выделенный поток для каждого.