Windows read com port

Main features of Serial Port Reader for Windows:

• Reading COM port activity

This software utility allows you to read RS232 data from a designated port and monitor it even if another application had already opened it. Captured serial data can be displayed in various formats, and the opportunity of real-time monitoring is a great feature for problem resolution.

The received data can be saved to a file of your choice or copied to the clipboard. The tool displays and files input/output control codes (IOCTLs) along with their complete parameters. Sessions can be saved by Com Port Reader and can be reloaded if required.

• Working with multiple ports in one session

Multiple serial ports can be read simultaneously by this software tool. This feature is very useful when comparing data collected from different COM ports that are interacting with the same application within monitoring session. In this case, all data is received and stored in a single log file on a first-in-first-out basis.

• Multiple views for sniffed data

Serial Port Reader allows you to choose the way that collected data is displayed on your computer. Four different view are available: table, line, dump, or terminal. You have the option of monitoring all view modes at the same time.

• Emulating serial communication

An option in terminal mode allows simulated data transmission from a serial application to a monitored COM port. Various data formats, such as string, binary, octal, decimal, hexadecimal, or mixed, can be used to test the COM port or its attached device’s reactions.

• Capturing Modbus data

COM Port Reader’s powerful filters enable you to read serial data transmitted over Modbus RTU and Modbus ASCII. The utility is fully compatible with these protocols, as well as those employed in RS-232, RS-485, and RS-422 interfaces

• Repetitive data exchange

Sending the same command from a serial app to a monitored serial port multiple times can give a clearer picture of the port’s behavior. This serial port utility simplifies that task with a playback feature that can display differences between sessions automatically.

Give your session a meaningful name so you can return to it for later analysis

Advanced Troubleshooting and Optimization

Troubleshooting common serial communication issues

  • Data corruption may result from bad cables or dissimilar baud rates.
  • Unresponsive devices are often the result of incorrect COM port and parameter settings. Some devices may require flow control for smooth data transfer.

Error Detection and Handling

Using error detection techniques such as conducting a Cyclic Redundancy Check (CRC) or checking parity bits can identify transmission errors often associated with long cables or noisy operational environments.

Optimizing baud rate

Choosing the right baud rate is crucial for consistent and reliable communication. A high baud rate may deliver data too quickly for a device to handle effectively, resulting in data corruption or loss. Serial Port Monitor lets you try different baud rates to optimize data transfer with your serial device.

The RS232 Connection and Signals

• DTE and DCE

DTE stands for Data Terminal Equipment. An example of a DTE is a computer. DCE stands for Data Communication Equipment. A modem is an excellent example of a DCE.

A DTE normally comes with a Male Connector, while a DCE comes with a Female Connector. This is not always the case. Here is a simple test to confirm device type. Using a voltmeter, measure Pin 3 and Pin 5 of a DB-9 Connector. DTE devices will indicate a voltage of -3V to -15V. DCE devices will have the voltage on Pin 2.

Note: The result for a DB-25 Connector is reversed (Please refer to DB-9 to DB-25 conversion table below).

DB-9 Connector

• DB-9 to DB-25 Conversion

DB-9 to DB-25 Conversion

• RS-232 Connections

Straight-through cables are used to connect a DTE (e.g. computer) to a DCE (e.g. modem), with all signals in one side connected to the corresponding signals in the other side in a corresponding one-to-one basis. When connecting two DTE devices directly with no modem in between, a crossover, or null-modem cable is used. This type of cable cross transmits and receives data signals between the two sides. There is no standard and many variations on how the other control signals are wired. Below is an example of one of them:

RS-232 Connections

• RS-232 Signals

RS-232 Signals

The graphic above illustrates a typical RS-232 logic waveform (Data format: 1 Start bit, 8 Data bits, No Parity, 1 Stop bit). Data transmission begins with a Start bit, followed by the data bits (LSB sent first and MSB sent last), and ends with a «Stop» bit.

The voltage of Logic «1» (Mark) is between -3VDC to -15VDC, while the Logic «0» (Space) is between +3VDC to +15VDC.

RS-232 connects the Ground of 2 different devices together, which is the so-called «Unbalanced» connection. Unbalanced connections have a distance limitation of 50 ft (approximately 15 meters) and are very susceptible to noise.

Applications that Rely on Reading Serial Data

Embedded Systems

Embedded systems often provide serial ports used by engineers to program and debug their microcontrollers. Communication issues between a microcontroller and a computer are typically diagnosed by reading serial data. An example can be seen in this tutorial on reading serial data from an Arduino.

Industrial Automation

Industrial facilities often utilize serial communication to control and monitor equipment used in automated procedures. Analyzing serial data can be instrumental in diagnosing problems and optimizing system performance. Feedback from real-time data sensors can be used to make immediate adjustments in machinery parameters to address fluctuating conditions in an automated assembly line.

COM-порт в Windows (программирование)


Написать программу, управляющую устройством через COM-порт, для MS-DOS не так сложно.
С платформой Win32 дело обстоит сложнее. Но только на первый взгляд. Конечно напрямую работать с регистрами портов нельзя, Windows это не позволяет, зато можно не обращать внимания на тонкости различных реализаций (i8251, 16450, 16550A) и не возиться с обработкой прерываний.

С последовательными и параллельными портами в Win32 работают как с файлами. Для открытия порта используется функция CreateFile. Эта функция предоставляется Win32 API. Ее прототип выглядит так:

  HANDLE CreateFile(
     LPCTSTR               lpFileName,
     DWORD                 dwDesiredAccess,
     DWORD                 dwShareMode,
     LPSECURITY_ATTRIBUTES lpSecurityAttributes,
     DWORD                 dwCreationDistribution,
     DWORD                 dwFlagsAndAttributes,
     HANDLE                hTemplateFile
  );

Указатель на строку с именем открываемого или создаваемого файла. Формат этой строки может быть очень «хитрым». В частности можно указывать сетевые имена для доступа к файлам на других компьютерах. Можно открывать логические разделы или физические диски и работать в обход файловой системы.

Последовательные порты имеют имена «COM1», «COM2», «COM3», «COM4», «COM5», «COM6», «COM7», «COM8», «COM9». Для доступа к портам, чей номер больше 9, необходимо указывать имя порта как «\\.\COMx», где x — номер порта. Например, «\\.\COM72» (в нотации языка C/C++ строка будет выглядеть «\\\\.\\COM72»). Такой синтаксис подходит для любого номера порта. Точно так же они назывались в MS-DOS. Параллельные порты называются «LPT1», «LPT2» и так далее.

Задает тип доступа к файлу. Возможно использование следующих значений:

  • 0 Опрос атрибутов устройства без получения доступа к нему.
  • GENERIC_READ Файл будет считываться.
  • GENERIC_WRITE Файл будет записываться.
  • GENERIC_READ|GENERIC_WRITE Файл будет и считываться и записываться.

Задает параметры совместного доступа к файлу. Коммуникационные порты нельзя делать разделяемыми, поэтому данный параметр должен быть равен 0.

Задает атрибуты защиты файла. Поддерживается только в Windows NT. Однако при работе с портами должен в любом случае равняться NULL.

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

Задает атрибуты создаваемого файла. Также управляет различными режимами обработки. При работе с портом этот параметр должен быть или равным 0, или FILE_FLAG_OVERLAPPED. Нулевое значение используется при синхронной работе с портом, а FILE_FLAG_OVERLAPPED при асинхронной, или, другими словами, при фоновой обработке ввода/вывода. Подробнее про асинхронный ввод/вывод я расскажу позже.

Задает описатель файла-шаблона. При работе с портами всегда должен быть равен NULL.

При успешном открытии файла, в данном случае порта, функция возвращает дескриптор (HANDLE) файла. При ошибке [[|INVALID HANDLE VALUE]]. Код ошибки можно получитить вызвав функцию [[|GetLastError]].

Открытый порт должен быть закрыт перед завершением работы программы. В Win32 закрытие объекта по его дескриптору выполняет функция CloseHandle:

BOOL CloseHandle(
     HANDLE hObject
     );

При успешном завершении функция возвращает не нулевое значение, при ошибке нуль.

Пример открытия/закрытия на языке C

[править]

   #include <windows.h>
   //. . .
   HANDLE Port;
   //. . .
   Port = CreateFile(L"\\\\.\\COM2", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
   if (Port == INVALID_HANDLE_VALUE) {
      MessageBox(NULL, "Невозможно открыть последовательный порт", "Error", MB_OK);
      ExitProcess(1);
   }
   //. . .
   CloseHandle(Port);
   //. . .

В данном примере открывается порт СОМ2 для чтения и записи, используется синхронный режим обмена. Проверяется успешность открытия порта, при ошибке выводится сообщение и программа завершается. Если порт открыт успешно, то он закрывается.

Основные параметры последовательного порта описываются структурой DCB. Временные параметры — структурой COMMTIMEOUTS. Существует еще несколько информационных и управляющих структур, но они используются реже. Настройка порта заключается в заполнении управляющих структур и последующем вызове функций настройки.

Основную информацию содержит структура DCB:

  typedef struct _DCB {
      DWORD DCBlength;            // sizeof(DCB)
      DWORD BaudRate;             // current baud rate
      DWORD fBinary:1;            // binary mode, no EOF check
      DWORD fParity:1;            // enable parity checking
      DWORD fOutxCtsFlow:1;       // CTS output flow control
      DWORD fOutxDsrFlow:1;       // DSR output flow control
      DWORD fDtrControl:2;        // DTR flow control type
      DWORD fDsrSensitivity:1;    // DSR sensitivity
      DWORD fTXContinueOnXoff:1;  // XOFF continues Tx
      DWORD fOutX:1;              // XON/XOFF out flow control
      DWORD fInX:1;               // XON/XOFF in flow control
      DWORD fErrorChar:1;         // enable error replacement
      DWORD fNull:1;              // enable null stripping
      DWORD fRtsControl:2;        // RTS flow control
      DWORD fAbortOnError:1;      // abort reads/writes on error
      DWORD fDummy2:17;           // reserved
      WORD  wReserved;            // not currently used
      WORD  XonLim;               // transmit XON threshold
      WORD  XoffLim;              // transmit XOFF threshold
      BYTE  ByteSize;             // number of bits/byte, 4-8
      BYTE  Parity;               // 0-4=no,odd,even,mark,space
      BYTE  StopBits;             // 0,1,2 = 1, 1.5, 2
      char  XonChar;              // Tx and Rx XON character
      char  XoffChar;             // Tx and Rx XOFF character
      char  ErrorChar;            // error replacement character
      char  EofChar;              // end of input character
      char  EvtChar;              // received event character
      WORD  wReserved1;           // reserved; do not use
  } DCB;

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

Задает длину, в байтах, структуры DCB. Используется для контроля корректности структуры при передаче ее адреса в функции настройки порта.

Скорость передачи данных. Возможно указание следующих констант: CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400, CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400, CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000. Эти константы соответствуют всем стандартным скоростям обмена. На самом деле, это поле содержит числовое значение скорости передачи, а константы просто являются символическими именами. Поэтому можно указывать, например, и CBR_9600, и просто 9600. Однако рекомендуется указывать символические константы, поскольку при компиляции программы проверяется корректность их имен.

Включает двоичный режим обмена. Win32 не поддерживает недвоичный режим, поэтому данное поле всегда должно быть равно 1, или логической константе TRUE (что предпочтительней). В Windows 3.1, если это поле было равно FALSE, включался текстовый режим обмена. В этом режиме поступивший на вход порта символ, заданный полем EofChar, свидетельствовал о конце принимаемых данных.

Включает режим контроля четности. Если это поле равно TRUE, то выполняется проверка четности, при ошибке, в вызывающую программу, выдается соответствующий код завершения.

Включает режим слежения за сигналом [[|CTS]]. Если это поле равно [[|TRUE]] и сигнал [[|CTS]] сброшен, передача данных приостанавливается до установки сигнала CTS. Это позволяет подключеному к компьютеру прибору приостановить поток передаваемой в него информации, если он не успевает ее обрабатывать.

Включает режим слежения за сигналом [[|DSR]]. Если это поле равно TRUE и сигнал DSR сброшен, передача данных прекращается до установки сигнала DSR.

Задает режим управления обменом для сигнала [[|DTR]]. Поле может принимать следующие значения:

  • DTR_CONTROL_DISABLE         Сигнал DTR снимается при открытии порта. У открытого порта может быть изменён функцией EscapeCommFunction.
  • DTR_CONTROL_ENABLE          Сигнал DTR устанавливается при открытии порта. У открытого порта может быть изменён функцией EscapeCommFunction.
  • DTR_CONTROL_HANDSHAKE   Сигнал DTR автоматически устанавливается/снимается в ходе работы с портом. Не может быть изменён функцией EscapeCommFunction.

Задает чувствительсть коммуникационного драйвера к состоянию линии [[|DSR]]. Если это поле равно TRUE, то все принимаемые данные игнорируются драйвером (коммуникационный драйвер расположен в операционной системе), за исключением тех, которые принимаются при установленом сигнале DSR.

Задает, прекращается ли передача при переполнении приемного буфера и передаче драйвером символа XoffChar. Если это поле равно TRUE, то передача продолжается, несмотря на то, что приемный буфер содержит более XoffLim символов и близок к переполнению, а драйвер передал символ XoffChar для приостановления потока принимаемых данных. Если поле равно FALSE, то передача не будет продолжена до тех пор, пока в приемном буфере не останется меньше XonLim символов и драйвер не передаст символ XonChar для возобновления потока принимаемых данных. Таким образом это поле вводит некую зависимость между управлением входным и выходным потоками информации.

Задает использование XON/XOFF управления потоком при передаче. Если это поле равно TRUE, то передача останавливается при приеме символа XoffChar, и возобновляется при приеме символа XonChar.

Задает использование XON/XOFF управления потоком при приеме. Если это поле равно TRUE, то драйвер передает символ XoffChar, когда в приемном буфере находится более XoffLim, и XonChar, когда в приемном буфере остается менее XonLim символов.

Указывает на необходимость замены символов с ошибкой четности на символ задаваемый полем ErrorChar. Если это поле равно TRUE, и поле fParity равно TRUE, то выполняется замена.

Определяет действие выполняемое при приеме нулевого байта. Если это поле TRUE, то нулевые байты отбрасываются при передаче.

Задает режим управления потоком для сигнала RTS. Поле может принимать следующие значения:

  • RTS_CONTROL_DISABLE          Сигнал RTS снимается при открытии порта. У открытого порта может быть изменён функцией EscapeCommFunction.
  • RTS_CONTROL_ENABLE           Сигнал RTS устанавливается при открытии порта. У открытого порта может быть изменён функцией EscapeCommFunction.
  • RTS_CONTROL_HANDSHAKE   Сигнал RTS автоматически устанавливается/снимается в ходе работы с портом. Не может быть изменён функцией EscapeCommFunction. Сигнал RTS устанавливается, когда приемный буфер заполнен менее, чем на половину, и снимается, когда буфер заполняется более чем на три четверти.
  • RTS_CONTROL_TOGGLE          Задаёт, что сигнал RTS установлен, когда есть данные для передачи. Когда все символы из передающего буфера переданы, сигнал снимается.

Задает игнорирование всех операций чтения/записи при возникновении ошибки. Если это поле равно TRUE, драйвер прекращает все операции чтения/записи для порта при возникновении ошибки. Продолжать работать с портом можно будет только после устранения причины ошибки и вызова функции ClearCommError.

Зарезервировано и не используется.

Не используется, должно быть установлено в 0.

Задает минимальное число символов в приемном буфере перед посылкой символа XON.

Определяет максимальное количество байт в приемном буфере перед посылкой символа XOFF. Максимально допустимое количество байт в буфере вычисляется вычитанием данного значения из размера приемного буфера в байтах.

Определяет число информационных бит в передаваемых и принимаемых байтах. Число информационных бит может быть в диапазоне от 4 до 8.

Определяет выбор схемы контроля четности. Данное поле должно содержать одно из следующих значений:

  • EVENPARITY     Дополнение до четности
  • MARKPARITY     Бит четности всегда 1
  • NOPARITY         Бит четности отсутствует
  • ODDPARITY       Дополнение до нечетности
  • SPACEPARITY   Бит четности всегда 0

Задает количество стоповых бит. Поле может принимать следующие значения:

  • ONESTOPBIT     Один стоповый бит
  • ONE5STOPBIT   Полтора стоповых бита
  • TWOSTOPBITS     Два стоповых бита

Задает символ XON используемый как для приема, так и для передачи. Обычно 0x11 (17).

Задает символ XOFF используемый как для приема, так и для передачи. Обычно 0x13 (19).

Задает символ, использующийся для замены символов с ошибочной четностью.

Задает символ, использующийся для сигнализации о конце данных.

Задает символ, использующийся для сигнализации о событии.

Зарезервировано и не используется.

Если структура DCB содержит конфигурацию для последовательного порта, совместимого с 8250, то к значениям полей ByteSize и StopBits применяются следующие ограничения:

  • Количество информационных бит должно быть от 5 до 8.
  • Не допускается использование 5 информационных бит с 2 стоповыми битами, также как 6, 7 или 8 информационных бит с 1,5 стоповыми битами.

winbase.h

typedef struct _COMMTIMEOUTS {
    DWORD ReadIntervalTimeout;          /* Maximum time between read chars. */
    DWORD ReadTotalTimeoutMultiplier;   /* Multiplier of characters.        */
    DWORD ReadTotalTimeoutConstant;     /* Constant in milliseconds.        */
    DWORD WriteTotalTimeoutMultiplier;  /* Multiplier of characters.        */
    DWORD WriteTotalTimeoutConstant;    /* Constant in milliseconds.        */
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

ReadIntervalTimeout — время в миллисекундах, задающее максимальное время, для интервала между поступлением двух символов по линии связи. Если интервал между поступлением каких-либо двух символов будет больше этой величины, операция ReadFile завершается и любые буферизированные данные возвращаются.

Чтобы операция ReadFile немедленно возвращала управление со всеми полученными данными (асинхронный режим) следует задавать следующие значения:

ReadIntervalTimeout=0xFFFFFFFF;
ReadTotalTimeoutConstant=0;
ReadTotalTimeoutMultiplier=0;

ReadTotalTimeoutMultiplier — Множитель, используемый, чтобы вычислить полный период времени простоя для операций чтения, в миллисекундах. Для каждой операции чтения, это значение умножается на затребованное число байтов, которые читаются.

ReadTotalTimeoutConstant — Константа, используемая, чтобы вычислить полный (максимальный) период времени простоя для операций чтения, в миллисекундах. Для каждой операции чтения, это значение добавляется к произведению члена структуры ReadTotalTimeoutMultiplier и прочитанного числа байтов.

Значение нуля и для члена ReadTotalTimeoutMultiplier, и для члена ReadTotalTimeoutConstant указывает, что полное время простоя не используются для операций чтения.

WriteTotalTimeoutMultiplier — Множитель, используемый, чтобы вычислить полный период времени простоя для операций записи, в миллисекундах. Для каждой операции записи, это значение умножается на число записываемых байтов.

WriteTotalTimeoutConstant — Константа, используемая, чтобы вычислить полный период времени простоя для операций записи, в миллисекундах. Для каждой операции записи, это значение добавляется к произведению члена структуры WriteTotalTimeoutMultiplier и записанного числа байтов.

Значение нуля и для члена WriteTotalTimeoutMultiplier, и для члена WriteTotalTimeoutConstant указывает, что полное время простоя не используются для операций записи.

Заполнение структуры COMMTIMEOUTS

[править]

Вариант 1: (максимальная задержка при чтении и записи = TIMEOUT)

 	COMMTIMEOUTS CommTimeOuts;
 	CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;
 	CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
 	CommTimeOuts.ReadTotalTimeoutConstant = TIMEOUT;
 	CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
 	CommTimeOuts.WriteTotalTimeoutConstant = TIMEOUT;

Вариант 2: Инициализация значениями (без задержки при чтении)

 	COMMTIMEOUTS CommTimeOuts={0xFFFFFFFF,0,0,0,1500};

Стандартный диалог настройки порта

[править]

Для настройки параметров COM — порта может быть вызвано штатное окно Windows. Вызов осуществляется функцией CommConfigDialog(), которая в качестве параметров принимает имя настраиваемого порта, хендл родительского окна и указатель на структуру COMMCONFIG.
Следует отметить, что для корректного вызова окна, структура COMMCONFIG должна быть заполнена значениями заранее.
Настройку структуры можно выполнить вручную или при помощи функции GetCommConfig().
Например:

/* Получение существующих настроек */
unsigned long new_size = 0;
if (!GetCommConfig(port->handle, &port->settings,&new_size))
	goto error;
/* Вызов окна и установка новых параметров */
if (CommConfigDialog(port->name, 0, &port->settings)) {
	if (SetCommConfig(port->handle, &port->settings, port->settings.dwSize))
		return 1;
	goto error;
}

Выделение памяти для структуры COMMPORT

[править]

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

CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

Предпоследний параметр dwFlagsAndAttributes должен быть равен 0.
После успешного открытия порта, данные могут быть считаны или записаны при помощи функций ReadFile() и WriteFile().

HANDLE port = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL,
			OPEN_EXISTING, 0, NULL);
unsigned char dst[1024] = {0};

unsigned long size = sizeof(dst);
if(port!= INVALID_HANDLE_VALUE) 
	if(ReadFile(port,dst,size, &size,0))
		printf("\nRead %d bytes",size);

Функция ReadFile/WriteFile осуществляет чтение/запись из файла (устройства) начиная с текущей позиции после окончания чтения обновляет указатель в файле.

BOOL ReadFile(
 HANDLE hFile, // хендл файла 
 LPVOID lpBuffer, //указатель на буфер 
 DWORD nNumberOfBytesToRead, // размер данных 
 LPDWORD lpNumberOfBytesRead, //размер считанных данных
 LPOVERLAPPED lpOverlapped //структура OVERLAPPED
);

Недостатком этого способа является то, что вызывая функцию ReadFile(), мы не знаем есть ли данные для чтения. Можно циклически проверять их наличие, но это приводит к дополнительным расходам времени ЦП.
Поэтому на практике часто удобней использовать асинхронный режим. Для этого при вызове функции CreateFile() параметр dwFlagsAndAttributes должен быть равен FILE_FLAG_OVERLAPPED.

CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL,
		OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

Далее, необходимо настроить реакцию порта на события при помощи функции SetCommMask() и используя функции WaitCommEvent() и WaitForSingleObject() ожидать событие или тайм аут.
Например:

const int READ_TIME = 100;
OVERLAPPED sync = {0};
int reuslt = 0;
unsigned long wait = 0, read = 0, state = 0;
	
/* Создаем объект синхронизации */
sync.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);

/* Устанавливаем маску на события порта */
if(SetCommMask(port, EV_RXCHAR)) {
	/* Связываем порт и объект синхронизации*/
	WaitCommEvent(port, &state, &sync);
	/* Начинаем ожидание данных*/	
	wait = WaitForSingleObject(sync.hEvent, READ_TIME);
	/* Данные получены */		
	if(wait == WAIT_OBJECT_0) {
		/* Начинаем чтение данных */
		ReadFile(port, dst, size, &read, &sync);
		/* Ждем завершения операции чтения */
		wait = WaitForSingleObject(sync.hEvent, READ_TIME);
		/* Если все успешно завершено, узнаем какой объем данных прочитан */
		if(wait == WAIT_OBJECT_0) 
			if(GetOverlappedResult(port, &sync, &read, FALSE)) 
				reuslt = read;
	}
}
CloseHandle(sync.hEvent);

Пример настройки порта и выполнения чтения/записи данных

[править]

Код для работы с COM-портом. Многострадальный, соответственно относительно простой и понятный, при этом обходит основные подводные камни. Надеюсь, может быть полезен.

 #ifndef TTY_H
 #define TTY_H

 #define NOMINMAX //иначе API windows определит макросы min и max, конфликтующие с std::max и std::min в vector
 #include <windows.h>

 #include <vector>
 #include <string>

 using namespace std;

 struct TTY {

 	TTY();
 	virtual ~TTY();

 	bool IsOK() const;
	
 	void Connect(const string& port, int baudrate);
 	void Disconnect();

 	virtual void Write(const vector<unsigned char>& data);
 	virtual void Read(vector<unsigned char>& data);
	
 	HANDLE m_Handle;

 };

 struct TTYException {
 };

 #endif
 #include "tty.h"
#include <iostream>
#include <assert.h>
#include <windows.h>

  using namespace std;

 static int TIMEOUT = 1000;

 TTY::TTY() {
 	m_Handle = INVALID_HANDLE_VALUE;
 }

 TTY::~TTY() {
 	Disconnect();
 }

  bool TTY::IsOK() const {
      return m_Handle != INVALID_HANDLE_VALUE;
  }

 void TTY::Connect(const string& port, int baudrate) {

 	Disconnect();
	
 	m_Handle =
 		CreateFile(
 		port.c_str(), 
 		GENERIC_READ | GENERIC_WRITE,
 		0,
 		NULL,
 		OPEN_EXISTING, 
 		FILE_ATTRIBUTE_NORMAL,
 		NULL);
	
 	if(m_Handle == INVALID_HANDLE_VALUE) {
 		throw TTYException();
 	}
	
 	SetCommMask(m_Handle, EV_RXCHAR);
 	SetupComm(m_Handle, 1500, 1500);

 	COMMTIMEOUTS CommTimeOuts;
 	CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;
 	CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
 	CommTimeOuts.ReadTotalTimeoutConstant = TIMEOUT;
 	CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
 	CommTimeOuts.WriteTotalTimeoutConstant = TIMEOUT;

 	if(!SetCommTimeouts(m_Handle, &CommTimeOuts)) {
 		CloseHandle(m_Handle);
                m_Handle = INVALID_HANDLE_VALUE;
 		throw TTYException();
 	}
	
 	DCB ComDCM;
	
 	memset(&ComDCM,0,sizeof(ComDCM));
 	ComDCM.DCBlength = sizeof(DCB);
 	GetCommState(m_Handle, &ComDCM);
 	ComDCM.BaudRate = DWORD(baudrate);
 	ComDCM.ByteSize = 8;
 	ComDCM.Parity = NOPARITY;
 	ComDCM.StopBits = ONESTOPBIT;
 	ComDCM.fAbortOnError = TRUE;
 	ComDCM.fDtrControl = DTR_CONTROL_DISABLE;
 	ComDCM.fRtsControl = RTS_CONTROL_DISABLE;
 	ComDCM.fBinary = TRUE;
 	ComDCM.fParity = FALSE;
 	ComDCM.fInX = FALSE;
        ComDCM.fOutX = FALSE;
 	ComDCM.XonChar = 0;
 	ComDCM.XoffChar = (unsigned char)0xFF;
 	ComDCM.fErrorChar = FALSE;
 	ComDCM.fNull = FALSE;
 	ComDCM.fOutxCtsFlow = FALSE;
 	ComDCM.fOutxDsrFlow = FALSE;
 	ComDCM.XonLim = 128;
 	ComDCM.XoffLim = 128;

 	if(!SetCommState(m_Handle, &ComDCM)) {
 		CloseHandle(m_Handle);
 		m_Handle = INVALID_HANDLE_VALUE;
 		throw TTYException();
 	}

 }

 void TTY::Disconnect() {

 	if(m_Handle != INVALID_HANDLE_VALUE)
        {
 	   CloseHandle(m_Handle);
           m_Handle = INVALID_HANDLE_VALUE;
 	}

 }

 void TTY::Write(const vector<unsigned char>& data) {

 	if(m_Handle == INVALID_HANDLE_VALUE) {
 		throw TTYException();
 	}

 	DWORD feedback;
 	if(!WriteFile(m_Handle, &data[0], (DWORD)data.size(), &feedback, 0) || feedback != (DWORD)data.size()) {
 		CloseHandle(m_Handle);
 		m_Handle = INVALID_HANDLE_VALUE;
 		throw TTYException();
 	}

 	// In some cases it's worth uncommenting
 	//FlushFileBuffers(m_Handle);

 }

 void TTY::Read(vector<unsigned char>& data) {

 	if(m_Handle == INVALID_HANDLE_VALUE) {
 		throw TTYException();
 	}

 	DWORD begin = GetTickCount();
 	DWORD feedback = 0;

 	unsigned char* buf = &data[0];
 	DWORD len = (DWORD)data.size();
	
 	int attempts = 3;
 	while(len && (attempts || (GetTickCount()-begin) < (DWORD)TIMEOUT/3)) {

 		if(attempts) attempts--;

 		if(!ReadFile(m_Handle, buf, len, &feedback, NULL)) {
 			CloseHandle(m_Handle);
 			m_Handle = INVALID_HANDLE_VALUE;
 			throw TTYException();
 		}

 		assert(feedback <= len);
 		len -= feedback;
 		buf += feedback;
	
 	}

 	if(len) {
 		CloseHandle(m_Handle);
 		m_Handle = INVALID_HANDLE_VALUE;
 		throw TTYException();
 	}

 }

using namespace std;

int main(int argc, char *argv[])
{
    TTY tty;
    tty.Connect("COM4",9600);
    
    for(int i=0;i<1000;i++) {
            
        std::vector<unsigned char> the_vectsor;
        the_vectsor.push_back(5);
        tty.Read(the_vectsor);
    
                              
        std::cout << (char)(the_vectsor[0]); //output text
    
    }
    
    system("PAUSE");
    return EXIT_SUCCESS;
}

Часто бывает необходимо, чтобы работа с устройством поддерживалась как в windows, так и в linux. В моем случае нужно было обеспечить работу com-порта в приложении, написанном на с/с++ с использованием кроссплатформенной библиотеки QT. Штатной поддержки программирования портов в QT нет (да и ни к чему это). Поэтому в win32 для работы с портом будем использовать средства WinAPI. В linux системах же, как известно для работы с устройствами используются специальные файлы.

Итак, взяв на вооружение всем знакомый gcc и его windows-аналог mingw, напишем нехитрый код.

Код будет довольно таки искусственным, т.к. ставится цель не более, чем показать принципы работы с com-портами в двух операционках. Для управления компилятором будем использовать директивы определения компилятора и ОС (__MINGW32__ и __linux). Com-портом в нашем случае является устройство dev/ttyS0 и COM1.

Инициализация

#ifdef __MINGW32__
#include <windows.h>
#endif
#ifdef __linux
#include <sys/types.h>
#include <fcntl.h>
#endif
#include <stdio.h>

int main() {

#ifdef __MINGW32__
HANDLE hSerial = CreateFile

("COM1",GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
if (hSerial == INVALID_HANDLE_VALUE) {
printf("Error opening port\r\n");
return -1;
}
#endif

#ifdef __linux
int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY );
if (fd <0) {
printf("Error opening port\n");
return -1;
}
#endif

...

Здесь в win32 используется функция CreateFile, параметры по ссылке в коде. Функция возвращает хендл на на устройство, в которым мы дальше будем работать. Аналогично работает и open в linux.

Запись

char * data; // данные записи в порт
data[0] = 0x01;

#ifdef __MINGW32__
DWORD dwBytesWrite = 0; // кол-во записанных байтов
if(!WriteFile

(hSerial, data, 1, &dwBytesWrite, NULL)){
printf("write error\r\n");
}
#endif
#ifdef __linux
int iOut = write(fd, data, 1);
if (iOut < 0){
printf("write error\n");
}
#endif

Для записи в порт используем ранее возвращенный хендл и пишем соответствующими функциями.
Процедура чтения выглядит аналогично.

Чтение

char * buffer;

#ifdef __MINGW32__
if(!ReadFile

(hSerial, buffer, 1, &dwBytesWrite, NULL)){
printf("read error\r\n");
}
#endif
#ifdef __linux
int iOut = read(fd, buffer, 1);
if (iOut < 0){
printf("read error\n");
}
#endif

Закрытие порта

#ifdef __MINGW32__
CloseHandle(hSerial);
#endif
#ifdef __linux
close(fd);
#endif

Кроме того, существуют функции для настройки самих портов. Об их использовании я, возможно, напишу в следующих статьях. В нашем примере все работает по стандартным настройкам системы.
Осталось дело за малым — скомпилировать код под каждую из ОС.

Надеюсь, что эта практика пригодится кому-то, кроме меня самого.
Приятного си-программирования!

COM Port Read And Write Application For Windows

How It Works?

You can read data from com port of your pc or write data to com port with this application. Firstly you need a device that uses serial communication like arduino uno. This device must sents data to com port continously or waits your command and when gets your command sends data to com port. If you use Arduino UNO like me you can use example code(COMPortExamp.ino) in this repo. Upload this code to your Arduino and launch this application. Select currently active COM Port and set Baud Rate to 9600 because we was define baud rate 9600 in Arduino code. Now you can connect to COM Port and read data from «Received Data» panel. Clean button, removes all data from your received data panel. If you want send data to COM Port, you can input some values to «Data to be send» textbox and click «Send» button.

Preview

To be added later.

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Environment:

Source code

This article is meant to give you a jump start on doing serial communication in Windows (NT family). The article will provide a class called CSerialCommHelper that you can use directly to do serial communication in your application. The class that is provided here with this article does uses overlapped IO. You do not need to know much about serial communication or overlapped IO for this article. However, you need to know some about the synchronization objects such as Events and some Windows APIs like WaitForSingleObject and WaitForMultipleObject, and so forth. Also, some basic understanding of Windows threads is required—such as thread creation and termination.

Introduction

In order for your computer to do serial communication, the computer has to have a serial port. Most computers have at least one serial port, also known as a COM port (communication port), and are generally called COM1, COM2, and so on. Then there are the device drivers for the serial ports. If you think it over, all that you need to do in serial communication is either send data or receive data. In other words, you are doing input/output (IO) to the serial port. The same IO is done with disk-based files. Hence it is no surprise that the APIs for reading and writing to a file apply to serial ports as well. When you send data to the serial port, it’s in terms of bytes but when it leaves the serial port it is in the form of bits. Similarly, when the data arrives at the serial port, it’s in bit format and when you get data you get it in bytes.

Without any further discussion, let’s get started.

Opening the COM Port

The first and the foremost step in doing a serial communication is to open the desired port. Let’s say you have your device hooked to COM1; you can open the COM port using the following API:

HANDLE m_hCommPort = ::CreateFile( szPortName,
       GENERIC_READ|GENERIC_WRITE,  
       0,                           
                                    
       0,                           
       OPEN_EXISTING,               
       FILE_FLAG_OVERLAPPED,        
       0                            
                                    
       );

The third, fifth, and seventh parameters have to be what they are in the above example, by law. We want to open the file (the COM port) in an overlapped fashion—that’s why the sixth parameter is FILE_FLAG_OVERLAPPED. We will get into the details of overlapped IO a bit later. As you must have guessed from the name, the CreateFile() API can be used to create a file (disk-based); it also can be used to open an existing file.

To Windows, a serial port or a disk-based file both are IO devices. So, to open an existing file (serial port), all we need to know the name of the device (COM1) and pass the creation flags as OPEN_EXISTING.

If a COM port is opened successfully, the API returns the handle to the COM port just like a handle to a file. However, if the system could not open the COM port, it would return INVALID_HANDLE_VALUE. And you can get the reason by calling GetLastError(). One of the common errors when opening a COM port is that the COM port is already opened by some other application; in that case, you would get ERROR_ACCESS_DENIED (5). Similarly, if you mistakenly opened a COM port that does not exist , you would get ERROR_FILE_NOT_FOUND as the last error.

Note: Remember not to make any function calls (such as ASSERT) before calling GetLastError() or you would get 0.
After you have opened the COM port, all you need to do now is to start using it.

Reading and Writing

Now, when you have a COM port open, you may want to send some data to the connected device. For example, let’s say you want to send “Hello” to the device (for example, another PC). When you want to send the data across the serial port, you need to write to the serial port just as you would write to a file. You would use following API:

iRet = WriteFile (m_hCommPort,data,dwSize,&dwBytesWritten ,&ov);

where data contains “Hello”.

Let’s say that, in response to your “Hello”, the device sends you “Hi”. So, you need to read the data. Again, you would use the following API:

abRet = ::ReadFile(m_hCommPort,szTmp ,sizeof(szTmp ),&dwBytesRead,&ovRead) ;

For now, do not try to understand everything. We will get to all this later. All this sounds very simple. Right?
Now, let’s start digging into issues.

Issues with Serial Communication

Just now I said that, in response to your “Hello”, the device may send you “Hi” back and you would like to read that. But the problem here is that you don’t know when the device is going to respond. Or, will it ever respond? When should you start to read from the port? One option is that as soon as you made the call to WriteFile, you make a call to ReadFile. If no data is there, you need to make a read call again, later on. This leads to what is called polling. You keep polling the port for data. This model does not really seem to be a good one. It would be nice if somehow the system notified you when data has arrived and only then would you make a call to ReadFile. This is an event-driven approach and fits well into Windows programming. And the good news is that such a model is possible.

Another issue with the serial communication is that because it always occurs between two devices, the two devices need to agree on how they talk to each other. Each side needs to follow certain protocols to conduct business. Because it’s the serial port that actually carries out the communication, we need to configure the serial port. There is an API available for that exact same purpose. Following is the API:

SetCommState ( HANDLE hFile, LPDCB lpDCB)

The first parameter is the handle to the COM port and the second parameter is what is called a device control block (DCB) . The DCB is a struct defined in winbase.h and has 28 data members. For example, we need to specify the baud rate at which the COM port operates; you need to set the BaudRate member of the struct. Baud rate is usually 9600 (bps). But the two devices have to use the same baud rate to conduct business. Similarly, if you want to use parity you need to set the Parity member of the struct. Again, the two devices have to use same parity. Some of the data members are reserved and have to be 0. I have found it easier to get the current DCB struct and then set those members that we are interested in changing. The following code gets the current DCB and sets some of the fields:

DCB dcb = {0};
dcb.DCBlength = sizeof(DCB);

if (!::GetCommState (m_hCommPort,&dcb))
{
  TRACE ( "CSerialCommHelper : Failed to Get Comm State Reason:
    %d",GetLastError());
  return E_FAIL;
}

dcb.BaudRate  = dwBaudRate;
dcb.ByteSize  = byByteSize;
dcb.Parity    = byParity;
if ( byStopBits == 1 )
  dcb.StopBits  = ONESTOPBIT;
else if (byStopBits == 2 )
  dcb.StopBits  = TWOSTOPBITS;
else
  dcb.StopBits  = ONE5STOPBITS;


if (!::SetCommState (m_hCommPort,&dcb))
{
  ASSERT(0);
  TRACE ( "CSerialCommHelper : Failed to Set Comm State Reason:
    %d",GetLastError());
  return E_FAIL;
}
TRACE ( "CSerialCommHelper : Current Settings, (Baud Rate %d;
  Parity %d; Byte Size %d; Stop Bits %d", dcb.BaudRate,

Most of the time you won’t need to change the other fields of this structure. But if you need to change the structure, you need to be very careful about the fields because changing the fields will affect the behavior of the serial communication; hence, you should be very sure about what you want to change.

Event-Driven Approach

Let’s return to our earlier problem with reading the data. If we do not want to keep polling the COM port for any data, we need to have some kind of event mechanism available. Fortunately, there is a way that you can ask the system to notify you when certain events happen. The API to use is:

SetCommMask( HANDLE hHandle,DWORD dwEvtMask)

The first parameter is the handle to the open COM port. The second parameter is used to specify a list of events that we are interested in.

The events that need to be specified in the mask depend upon the application needs. For simplicity, let’s say that we are interested in getting notified whenever a character arrives at the serial port. We would need to specify EV_RXCHAR as the event mask. Similarly, if we are interested in knowing when all the data has been sent, we also need to specify the EV_TXEMPTY flag. So, our call would look like this:

SetCommMask( m_hCommPort,EV_TXTEMPTY|EV_RXCHAR);

The interesting thing here is that, although we told the system about the events of our interest, we did not, however, tell the system what to do when these events occur. How would the system let us know that a particular event occurred? An obvious thing seems to be a callback mechanism. But there is no such mechanism available. Here is when things get a little tricky. In order for the system to let us know about the communication event occurrence, we need to call WaitCommEvent This function waits for the events specified in SetCommMask. But if you think a little more, it sounds like we are turning a notification mechanism back to a polling mechanism. Actually, it’s even worse that than. WaitCommEvent blocks until an event occurs. So what’s the use of WaitCommEvent? Well, the answer lies in overlapped IO.

If you look at the WaitCommEvent signature, it looks like this:

BOOL WaitCommEvent(HANDLE hCommPort, LPDWORD dwEvtMask,LPOVERLAPPED
  lpOverlapped);

The third parameter is the key here.

Think of overlapped IO as asynchronous IO. Whenever a function makes a call and specifies the overlapped IO structure, it means that it is trying to do the current operation but, if you are not able to complete it immediately, let me know when you are done with this IO. The way the system lets you know about the completion is by setting a kernel event object that is part of the lpOverlapped structure. So, all you do is spawn a thread and make the thread wait for that event object using one of the WaitForSingleObject() APIs.

Let’s look at the overlapped structure:

typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;

The last parameter is the event handle that you need to create. This event is generally a manual reset event. When you make a call like WaitCommEvent(), passing the overlapped structure as the last parameter, and the system could not complete the call—meaning it did not see any characters at the port—it would return immediately but would return FALSE. If you now make a call to GetLastError(), you would get ERROR_IO_PENDING; this means that the call has been accepted but no characters have yet arrived at the COM port. Also it means that whenever the characters arrive, the system will set the hEvent of the overlapped structure that you passed in. So, if your thread would wait for a single object on hEvent and you pass INFINITE, whenever your Wait function returns WAIT_OBJECT_0, it means that some character has arrived or all the data in the output buffer has been sent.

In our current case, we are interested in more than one event; we would need to check what event we did get by making a call to GetCommMask and checking the dword against each event. The following pseudo code will explain it.

You can read the data from the COM port, reset the event, and make the call to WaitCommEvent again, and so on.

unsigned __stdcall CSerialCommHelper::ThreadFn(void*pvParam)
{
  OVERLAPPED ov;
  memset(&ov,0,sizeof(ov));
  ov.hEvent = CreateEvent( 0,true,0,0);
  HANDLE arHandles[2];
  arHandles[0] = apThis->m_hThreadTerm;

  DWORD dwWait;
  SetEvent(apThis->m_hThreadStarted);
  while (  abContinue )
  {

    BOOL abRet = ::WaitCommEvent(apThis->m_hCommPort,
        &dwEventMask, &ov) ;
    if ( !abRet )
    {

      ASSERT( GetLastError () == ERROR_IO_PENDING);
    }


    arHandles[1] = ov.hEvent ;

    dwWait = WaitForMultipleObjects (2,arHandles,FALSE,INFINITE);
    switch ( dwWait )
    {
    case WAIT_OBJECT_0:
      {
        _endthreadex(1);
      }
      break;
    case WAIT_OBJECT_0 + 1:
      {
        DWORD dwMask;
        if (GetCommMask(apThis->m_hCommPort,&dwMask) )
        {
          if ( dwMask & EV_TXEMPTY )
          TRACE("Data sent");
          ResetEvent ( ov.hEvent );
          continue;
        }
        else
        {
          
        }
      }
    }     
  }       
return 0;
}


If you understood the above code, you will understand this entire article and the source code provided. The above piece of code is simple; it uses the overlapped IO method to do its job.

After we receive the indication that the data has arrived, we need to read the data. The important thing to note here is that the when data arrives at the serial port, it is copied over to the system buffer. The data is removed from the system buffer only when you have read the data using an API such as ReadFile. Like any buffer, the system buffer has a limited size. So if you do not read the data from the buffers quickly enough, the system buffers can become full if more data is arriving. What happens to further data depends upon the configuration that you have set in the device configuration block (in a call to SetCommState). Usually, the applications do some kind of handshaking at the application level but you can also make configurations such that the COM port does not accept any further data upon buffer-full events. But all that is beyond the scope of this discussion. If possible, it’s always better to have applications themselves implementing some kind of handshaking—like not sending the next block of data until you get an okay for the first block. Generally this kind of handshaking is implemented using some sort of ACK/NAK and ENQ protocol.

In order for us to read data, we need to use the ReadFile() API. The ReadFile API has to specify how much data to read. Let’s say we are monitoring character arrivals and 10 characters arrive at the port. As soon as the first character arrives at the port the system will set the overlapped structure’s event object and our WaitSingleObject will return. Next we would need to read the data. So how much data should we read? Should we read 1 byte or 10 bytes? That is a good question. The way it works is as follows (Note: This is not documented anywhere but this is what I have found by research on Win2K and NT4.0):

When one (or more) characters arrive at the port, the event object associated with the overlapped structure is set once. Now let’s say that you made a call to read and you read 1 character. After reading 1 character, you would finally Reset the overlapped structure’s event object. Now you would go back to the WaitCommEvent, but it would return false because no “new” character has arrived. So you will not be able to read any more characters. Now when another character arrives, the system will set the overlapped event and you would read one more character, but this time it will be the character that had arrived earlier and you never read. This clearly is a problem.

So what is the solution? The easiest solution is that as soon as you get the event object indicating the arrival of a character, you should read all the characters that are present in the port. (If you are familiar with the Win API MsgWaitForMultipleObjects, you can draw a analogy here.)

So again the question remains of how many characters to read. The answer is read all the characters in a loop using ReadFile().

Here is the pseudo code to do so:

threadFn...

  WaitCommEvent(m_hCommPort,&dwEventMask, &ov) ;
  if ( WaitForSingleObject(ov.hEvent,INFINITE) == WAIT_OBJECT_0)
  {
    char szBuf[100];
    memset(szBuf,0,sizeof(szBuf));
    do
    {
      ReadFile( hPort,szBuf,sizeof(szBuf),&dwBytesRead,&ov);
    }while (dwBytesRead > 0 );
  }

The ReadFile API has the following signature:

BOOL ReadFile( HANDLE hFile,        
     LPVOID lpBuffer,               
     DWORD nNumberOfBytesToRead,    
     LPDWORD lpNumberOfBytesRead,   
     LPOVERLAPPED lpOverlapped      

The first parameter is, as usual, the COM port; the last parameter is the overlapped structure. Again, we need to create a manual reset event and pass the overlapped structure to the ReadFile function. Again, if you issue a read for, say, 10 bytes and there is no data available, ReadFile will return FALSE and GetLastError() will return ERROR_IO_PENDING; the system will set the overlapped event when the overlapped operation (read) completes.

As you can see, ReadFile returns dwBytesRead which, it is clear, returns the number of bytes read. If there are no bytes remaining, the dwBytesRead will return 0. Let’s say there are 11 bytes that have arrived and you read 10 characters in the first go in the while loop. In the first go, 10 characters will be returned in dwBytesRead. In the second go with the while loop, the dwBytesRead will return 1. Now in the third go, the dwBytesRead will return 0 and you will break out of the while loop. This allows you to read all the data. In this approach, if you noticed, we never really took advantage of the overlapped structure that we passed to the ReadFile function, but we still need to pass it because we opened the COM port in an Overlapped manner.

And, finally, when you want to send data to another device, you need to call WriteFile. WriteFile is not even worth discussing.

There is one more thing that needs to be taken into account before we move on and that is communication timeouts. It’s important to set the timeout to proper values for things to work. The API to do so is:

SetCommTimeouts ( HANDLE hCommPort, LPCOMMTIMEOUTS lpCommTimeOuts)

COMTIMEOUTS is a structure with following members:

typedef struct _COMMTIMEOUTS {
  DWORD ReadIntervalTimeout;
  DWORD ReadTotalTimeoutMultiplier;
  DWORD ReadTotalTimeoutConstant;
  DWORD WriteTotalTimeoutMultiplier;
  DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

For a description of all these fields, consult the MSDN documentation. But one thing I want to point out is this:

“…A value of MAXDWORD, combined with zero values for both the ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members, specifies that the read operation is to return immediately with the characters that have already been received, even if no characters have been received…”

This is exactly what we want. We do NOT want ReadFile to get stuck if there is no data available as we well know with the WaitCommEvent() API.

and also “…A value of zero for both the WriteTotalTimeoutMultiplier and WriteTotalTimeoutConstant members indicates that total time-outs are not used for write operations…” is what we want. In short we need to do this:

  COMMTIMEOUTS timeouts;
    timeouts.ReadIntervalTimeout                = MAXDWORD;
    timeouts.ReadTotalTimeoutMultiplier   = 0;
    timeouts.ReadTotalTimeoutConstant     = 0;
    timeouts.WriteTotalTimeoutMultiplier  = 0;
    timeouts.WriteTotalTimeoutConstant    = 0;

    if (!SetCommTimeouts(m_hCommPort, &timeouts))
    {
      ASSERT(0);
      TRACE ( "CSerialCommHelper :  Error setting time-outs.
              %d",GetLastError());
      return E_FAIL;
    }

Now we have discussed almost everything that needs to be discussed for the sake of this article.

Putting It All Together

All this I have to put together in a form of two classes:

  • The main class is CSerialCommHelper—the main class that performs all the communication.
  • The helper class called CSerialBuffer; it is an internal buffer used by the CSerialCommHelper.

Here is the main API of the CSerialCommHelper:

inline bool IsInputAvailable()
inline bool IsConnection() {return m_abIsConnected ;}
inline void SetDataReadEvent() { SetEvent ( m_hDataRx ); }
HRESULT Read_N (std::string& data,long alCount,long alTimeOut);
HRESULT Read_Upto (std::string& data,char chTerminator ,long*
    alCount,long alTimeOut);
HRESULT ReadAvailable(std::string& data);
HRESULT Write (const char* data,DWORD dwSize);
HRESULT Init(std::string szPortName, DWORD dwBaudRate,BYTE
    byParity,BYTE byStopBits,BYTE byByteSize);
HRESULT Start();
HRESULT Stop();
HRESULT UnInit();

and the interface for CSerialBuffer is:

inline void LockBuffer();
inline void UnLockBuffer();
void AddData( char ch ) ;
void AddData( std::string& szData ) ;
void AddData( std::string& szData,int iLen ) ;
void AddData( char *strData,int iLen ) ;
std::string GetData() ;
void Flush();
long Read_N( std::string &szData,long alCount,HANDLE&
    hEventToReset);
bool Read_Upto( std::string &szData,char chTerm,long
    &alBytesRead ,HANDLE& hEventToReset);
bool Read_Available( std::string &szData,HANDLE &
     hEventToReset);
inline long GetSize() ;
inline bool IsEmpty() ;

Here is the logic and working behind the classes:

First of all, let me show you how to use the class. In your application, create an object of CSerialCommHelper, like this:

CSerialCommHelper m_theCommPort;

Call m_theCommPort.Init(), passing in the necessary information. If you want, you can use the default values.

Next, call m_theCommPort.Start().

If you want to get notification about when some data is available, you can get the kernel event object to wait by calling m_theCommPort.GetWaitForEvent().

What CSerialCommHelper does is that, on a call to Init(), it opens the specified COM port and also starts a thread. The thread starts “listening” for any incoming data and, after the data has been received, it reads all the data into a local buffer which is of type CSerialBuffer. After it’s done reading all the data, it sets the event in case you want to get the notification. Now you have three options:

  • Read all the data by calling ReadAvailable(), which reads all the data.
  • Read up to some character by calling Read_Upto and passing the character up to which you want to read.
  • Read N characters by calling Read_N, passing the number to be read.

There is another thing that you need to pay attention to. If you want to read 10 characters but there are only 5 characters in the local buffer, read_N makes a blocking call and waits for the timeout passed as the last parameter. The same is true for Read_Upto.

One more thing. If there are 10 characters in the local buffer but you made a call to Read_N() for 5 characters, the first 5 characters will be returned to you. If you make the next call Read_N() for 5 characters again, it would return the next 5 characters.

That’s all there is to it.

If you think I have left out something, please feel free to e-mail me at
[email protected].

Downloads

Download source – 52 Kb

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Как подключить айфон к компьютеру в качестве модема через провод windows 10
  • Как свернуть все окна в windows 10 мышкой
  • Windows 10 электропитание отключать жесткий диск через
  • Интернет работает только в браузере windows 10
  • Windows remote desktop protocol