COM-порт в Windows (программирование)
Материал из Викиучебника
Написать программу, управляющую устройством через 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Файл будет и считываться и записываться.
[править]
Задает параметры совместного доступа к файлу. Коммуникационные порты нельзя делать разделяемыми, поэтому данный параметр должен быть равен 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 для приостановления потока принимаемых данных. Если поле равно
, то передача не будет продолжена до тех пор, пока в приемном буфере не останется меньше 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(); } }