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

Материал из Викиучебника

Перейти к: навигация, поиск
Wiki letter w.svg   Этот текст надо викифицировать. Пожалуйста, отформатируйте его согласно рекомендациям.


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

Содержание

[править] Открытие порта

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

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

[править] lpFileName

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

Последовательные порты имеют имена «COM1», «COM2», «COM3», «COM4» и так далее. Точно так же они назывались в MS-DOS. Параллельные порты называются «LPT1», «LPT2» и так далее.

[править] dwDesiredAccess

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

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

[править] dwShareMode

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

[править] lpSecurityAttributes

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

[править] dwCreationDistribution

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

[править] dwFlagsAndAttributes

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

[править] hTemplateFile

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

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

[править] Закрытие порта

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

BOOL CloseHandle(
     HANDLE hObject
     };

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

[править] Пример открытия/закрытия на языке C

   #include <windows.h>
   //. . .
   HANDLE Port;
   //. . .
   Port = CreateFile("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

Основные параметры последовательного порта описываются структурой 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;

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

[править] DCBlength

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

[править] BaudRate

Скорость передачи данных. Возможно указание следующих констант: 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. Однако рекомендуется указывать символические константы.

[править] fBinary

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

[править] fParity

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

[править] fOutxCtsFlow

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

[править] fOutxDsrFlow

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

[править] fDtrControl

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

  • DTR_CONTROL_DISABLE Запрещает использование линии DTR
  • DTR_CONTROL_ENABLE Разрешает использование линии DTR
  • DTR_CONTROL_HANDSHAKE Разрешает использование рукопожатия для выхода из ошибочных ситуаций. Этот режим используется, в частности, модемами при восстановленни в ситуации потери связи.

[править] fDsrSensitivity

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

[править] fTXContinueOnXoff

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

FALSE

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

[править] fOutX

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

[править] fInX

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

[править] fErrorChar

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

[править] fNull

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

[править] fRtsControl

Задает режим управления потоком для сигнала RTS. Если это поле равно 0, то по умолчанию подразумевается RTS_CONTROL_HANDSHAKE. Поле может принимать одно из следующих значений: RTS_CONTROL_DISABLE Запрещает использование линии RTS RTS_CONTROL_ENABLE Разрешает использование линии RTS RTS_CONTROL_HANDSHAKE Разрешает использование RTS рукопожатия. Драйвер устанавливает сигнал RTS когда приемный буфер заполнен менее, чем на половину, и сбрасывает, когда буфер заполняется более чем на три четверти. RTS_CONTROL_TOGGLE Задает, что сигнал RTS установлен, когда есть данные для передачи. Когда все символы из передающего буфера переданы, сигнал сбрасывается.

[править] fAbortOnError

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

[править] fDummy2

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

[править] wReserved

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

[править] XonLim

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

[править] XoffLim

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

[править] ByteSize

Определяет число информационных бит в передаваемых и принимаемых байтах.

[править] Parity

Определяет выбор схемы контроля четности. Данное поле должно содержать одно из следующих значений: EVENPARITY Дополнение до четности MARKPARITY Бит четности всегда 1 NOPARITY Бит четности отсутствует ODDPARITY Дополнение до нечетности SPACEPARITY Бит четности всегда 0

[править] StopBits

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

[править] XonChar

Задает символ XON используемый как для примема, так и для передачи.

[править] XoffChar

Задает символ XOFF используемый как для примема, так и для передачи.

[править] ErrorChar

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

[править] EofChar

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

[править] EvtChar

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

[править] wReserved1

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

[править] Заполнение структуры DCB

[править] Структура COMMTIMEOUTS

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};

[править] Пример настройки порта

[править] Структура COMMPROP

[править] Стандартный диалог настройки порта

[править] Выделение памяти для структуры COMMPROP

[править] Прием и передача данных

[править] Сброс порта

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

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

[править] tty.h

 #ifndef TTY_H
 #define TTY_H
 
 #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<uint8_t>& data);
 	virtual void Read(vector<uint8_t>& data);
 
 	HANDLE m_Handle;
 
 };
 
 struct TTYException {
 };
 
 #endif

[править] tty.cpp

 #include "tty.h"
 
 #include <windows.h>
 
 using namespace std;
 
 static int TIMEOUT = 1000;
 
 TTY::TTY() {
 	m_Handle = 0;
 }
 
 TTY::~TTY() {
 	Disconnect();
 }
 
 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 == (HANDLE)-1) {
 		m_Handle = 0;
 		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)) {
 		m_Handle = 0;
 		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 = ComDCM.fOutX = FALSE;
 	ComDCM.XonChar = 0;
 	ComDCM.XoffChar = uint8_t(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 = 0;
 		throw TTYException();
 	}
 
 }
 
 void TTY::Disconnect() {
 
 	if(m_Handle != 0) {
 		CloseHandle(m_Handle);
         m_Handle = 0;
 	}
 
 }
 
 void TTY::Write(const vector<uint8_t>& data) {
 
 	if(m_Handle == 0) {
 		throw TTYException();
 	}
 
 	DWORD feedback;
 	if(!WriteFile(m_Handle, &data[0], int(data.size()), &feedback, 0) || feedback != int(data.size())) {
 		CloseHandle(m_Handle);
 		m_Handle = 0;
 		throw TTYException();
 	}
 
 	// In some cases it's worth uncommenting
 	//FlushFileBuffers(m_Handle);
 
 }
 
 void TTY::Read(vector<uint8_t>& data) {
 
 	if(m_Handle == 0) {
 		throw TTYException();
 	}
 
 	DWORD begin = GetTickCount();
 	DWORD feedback = 0;
 
 	uint8_t* buf = &data[0];
 	DWORD len = int(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 = 0;
 			throw TTYException();
 		}
 
 		assert(feedback <= len);
 		len -= feedback;
 		buf += feedback;
 
 	}
 
 	if(len) {
 		CloseHandle(m_Handle);
 		m_Handle = 0;
 		throw TTYException();
 	}
 
 }