Настройка параметров порта




Лекция 16. Программирование COM-портов под операционные системы семейства Windows NT/2000/XP с использованием Visual Studio

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

С последовательными портами в Win32 работают как с файлами. Следовательно, начинать надо с открытия порта как файла, используя функцию CreateFile. В качестве одного из параметров функции передается имя порта (например, COM1), с которым будет выполняться работа. Попытка создать файл с таким именем приводит к перенаправлению информации на соответствующий порт. Прототип функции выглядит следующим образом:

HANDLE CreateFile(

LPCTSTR lpFileName,

DWORD dwDesiredAccess,

DWORD dwShareMode,

LPSECURITY_ATTRIBUTES lpSecurityAttributes,

DWORD dwCreationDispostion,

DWORD dwFlagsAndAttributes,

HANDLE hTemplateFile

);

Описание параметров функции приводится ниже.

LpFileName

В виде строки передается имя открываемого порта (например, “COM1”).

DwDesiredAccess

Определяет режим доступа к порту. Приложение может получить доступ на чтение, запись, чтение/запись данныхы из/в порт. Этот параметр может быть GENERIC_READ – чтение, GENERIC_WRITE – запись, GENERIC_READ | GENERIC_WRITE – возможность выполнения операций чтения и записи.

DwShareMode

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

LpSecurityAttributes

При работе с портами этот параметр не используется и должен принимать значение NULL.

DwCreationDispostion

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

DwFlagsAndAttributes

Задаёт атрибуты создаваемого файла, а также управляет различными режимами обработки. В случае выполнения синхронных операций чтения-записи этот параметр принимает значение FILE_ATTRIBUTE_NORMAL.

HTemplateFile

Задатся дескриптор файла-шаблона, который в данном случае не используется. Этот параметр должен быть равным NULL.

При успешном открытии порта функция CreateFile возвратит в основную программу его дескриптор (HANDLE). Если же по каким-либо причинам открыть порт не удалось, этой переменной присваивается значение INVALID_HANDLE_VALUE. Таким образом, проверка идентификатора порта на его равенство INVALID_HANDLE_VALUE позволяет выполнить обработку исключительных ситуаций (например, отсутствие открываемого порта на материнской плате или его использование операционной системой).

Пример 1.

Используя среду разработки Visual Studio.NET, создать программу, которая по нажатию кнопки «Открыть порт » открывала коммуникационный порт COM2 для обмена информацией с внешним устройством в режиме синхронного чтения-записи и отображала в текстовом поле результат его открытия.

Пояснение. Порядок действий.

1. Запускаем среду разработки Visual Studio.NET. Создаем новый проект с названием COMTest. В нашем примере приложение создается на базе диалогового окна.

2. Проектируем внешний вид диалогового окна. Помещаем на форму кнопку (IDC_BUTTON1) и два текстовых поля (IDC_STATIC1, IDC_STATIC2), формируем размер окна, задаём заголовки элементов в поле «Caption» на странице свойств (рис.16.1). Следует обратить внимание, что при добавлении на форму элемента управления «Static text» (текстовое поле), ID по умолчанию принимает значение IDC_STATIC. Поэтому ID необходимо отредактировать.

Рис.16.1. Вид диалогового окна

3. Привязываем к элементу управления текстовое поле (IDC_STATIC2) переменную m_status. Для этого необходимо выделить элемент управления и из контекстного меню выбрать команду «Add Variable». Появится диалоговое окно, показанное на рис.16.2. В поле «Variable name» задать имя переменной.

Рис.16.2. Добавление переменной-члена класса

 

4. Добавляем переменную – член класса CCOMTestDlg, в которой будет храниться дескриптор порта. Для этого в окне «Class View» выделяем класс «CCOMTestDld» и из контекстного меню, в подменю «Add» выбираем «Add Variable» (рис.16.3).

Рис.16.3. Добавление переменной

Имя переменной должно быть m_hCom, а тип переменной – HANLDE.

5. Добавляем обработчик нажатия на кнопку «Открыть порт». Функция – обработчик события нажатия на кнопку должна иметь следующий вид:

 

void CCOMTestDlg::OnBnClickedButton1()

{

// открываем последовательный порт

m_hCom=CreateFile("COM2",GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);

if(m_hCom==INVALID_HANDLE_VALUE)

m_status.SetWindowText("Невозможно открыть порт!");

// если невозожно открыть порт, выводится информация в текстовом поле

else // если порт открыт

m_status.SetWindowText("Порт открыт.");

// сообщаем, что порт открыт

}

На рис.16.4. показано диалоговое окно программы после компиляции проекта.

Рис.16.4. Окно программы

Нажав на кнопку «Открыть порт» и при успешном выполнении операции, появится соответствующая надпись.

Недостатком созданного фрагмента программы является отсутствие у пользователя возможности интерактивного выбора открываемого порта. В том случае, если порт СОМ2, привязка к которому жёстко указана в параметре lpFileName функции CreateFile, отсутствует или используется операционной системой компьютера для обслуживания мыши или модема, в текстовом поле появится сообщение «Невозможно открыть порт», а обратиться к свободному порту – например, СОМ1 – из этой же программы не представляется возможным.

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

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

Пояснение. Порядок действий:

1. Запускаем среду разработки Visual Studio.NET и открываем предыдущий проект (COMTest).

2. Добавляем на форму элемент управления «Combo Box» - список, из которого пользователь сможет выбрать нужный порт. Вид диалогового окна программы представлен на рис.16.5.

Рис.16.5. Модифицированный вид диалогового окна

В поле «Type» в окне свойств списка выбрать тип «DropList».

3. Привязываем к элементу управления «Combo Box» переменную m_port. На рис.16.6 представлено диалоговое окно добавления переменной с требуемыми параметрами.

Рис.16.6. Добавление переменной

 

4. Поиск последовательных портов, установленных в системе, будет осуществляться с помощью анализа ключей системного реестра ОС Windows. Для работы с реестром используется класс CRegKey. В начале файла COMTestDlg,cpp необходимо добавить строку:

#include "Atlbase.h"

Следующий код необходимо добавить в конец функции OnInitDialog() класса CCOMTestDlg, которая вызывается перед появлением окна программы на экране.

CRegKey reg; // класс для работы с реестром

HKEY key; // дескриптор ключа реестра

LONG result;

result=reg.Open(HKEY_LOCAL_MACHINE,

"HARDWARE\\DEVICEMAP\\SERIALCOMM"); // открытие ключа реестра

if(result!=ERROR_SUCCESS) MessageBox("Нет доступа к реестру!");

else

{

char s[255]; // буфер

DWORD size; // переменная для передачи в функцию

for(int i=0;i<30;i++)// в цикле считываются значения из реестра

{

CString valname; // строковая переменная

valname.Format("\\Device\\Serial%d",i);

// форматирование строки

result=reg.QueryValue(s,valname,&size);

// чтение данных из реестра

if(result==ERROR_SUCCESS) // если данные из реестра получены

{

CString tmp(s);

m_port.AddString(tmp); // добавляем в список название портов

}

}

}

m_port.SetCurSel(0); // установка текущего выделения

reg.Close(); // закрываем реестр

m_status.SetWindowText("Порт закрыт.");

// обновление информации в строке статуса

m_hCom=NULL; // присвоение дескриптору порта значения NULL

5. Корректируем обработчик нажатия на кнопку «Открыть порт».

void CCOMTestDlg::OnBnClickedButton1()

{

CString sp;

// получаем имя выбранного порта

m_port.GetLBText(m_port.GetCurSel(),sp);

// открываем последовательный порт

m_hCom=CreateFile(sp,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);

if(m_hCom==INVALID_HANDLE_VALUE)

m_status.SetWindowText("Невозможно открыть порт!");

// если невозожно открыть порт, выводится информация в текстовом поле

else // если порт открыт

m_status.SetWindowText("Порт открыт.");

// сообщаем, что порт открыт

}

6. Результат работы программы показан на рис 16.7.

Рис.16.7. Окно программы

 

В обоих приведенных выше примерах закрытие используемого программой коммуникационного порта происходит при закрытии пользователем диалогового окна (т.е. при завершении работы программы). В процессе работы программы может возникнуть необходимость внести изменения в параметры работы порта. Кроме того, Закрытие порта осуществляется с помощью функции CloseHandle, которая имеет единственный параметр – дескриптор закрываемого объекта. При успешном завершении функция возвращает TRUE, а при ошибке – FALSE. Ниже приведен пример, который демонстрирует использование функции CloseHandle.

Пример 3. Дополнить окно программы, созданной в предыдущем примере, кнопкой «Закрыть порт», которая выполняла бы одноимённую функцию. Обеспечить возможность одновременной работы только с одним портом.

Пояснение. Порядок действий:

1. Запускаем среду разработки Visual Studio.NET. Открываем предыдущий проект.

2. Добавляем на форму кнопку и изменяем текст в поле «Caption» на странице свойств, на «Закрыть порт» (рис.2.8.). Привязываем к кнопке «Открыть порт» переменную с именем m_bOpen, а к кнопке «Закрыть порт» переменную с именем m_bClose.

3. Добавляем обработчик события нажатия на кнопку «Закрыть порт» и изменяем его следующим образом:

void CCOMTestDlg::OnBnClickedButton2()

{

CloseHandle(m_hCom); // закрытие порта

m_status.SetWindowText("Порт закрыт.");

m_port.EnableWindow(1); // разблокируем элементы управления

m_bOpen.EnableWindow(1); // список и кнопка «Открыть порт»

m_bClose.EnableWindow(0); // блокировка кнопки «Закрыть порт»

}

 

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

 

void CCOMTestDlg::OnBnClickedButton1()

{

CString sp; // получаем имя выбранного порта

m_port.GetLBText(m_port.GetCurSel(),sp);

// открываем последовательный порт

m_hCom=CreateFile(sp,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);

if(m_hCom==INVALID_HANDLE_VALUE)

{

m_status.SetWindowText("Невозможно открыть порт!");

return;

}

// если невозожно открыть порт, выводится информация в текстовом поле

else // если порт открыт

m_status.SetWindowText("Порт открыт.");

m_bOpen.EnableWindow(0); // блокировка кнопки «Открыть порт»

m_bClose.EnableWindow(1); // разблокируем кнопку «Закрыть порт»

m_port.EnableWindow(0); // блокировка списка

}

 

После нажатия на кнопку, в случае успешного открытия порта, элемент управления «Combo Box» и кнопка «Открыть порт» станут неактивными, и будут находиться в таком состоянии до тех пор, пока не будет нажата кнопка «Закрыть порт».

5. В конце функции OnInitDialog необходимо добавить следующую строку для того, чтобы кнопка «Закрыть порт» была неактивной при запуске программы.

m_bClose.EnableWindow(0);

Теперь проект можно компилировать.

На рис.16.8 представлено диалоговое окно модифицированной программы.

Рис.16.8. Окно программы

 

Создайте копию этого проекта. Далее, на основе этого примера, будет разработана программа обмена данными между двумя компьютерами.

 

Настройка параметров порта

Открыв порт, мы получили его в свое распоряжение. Теперь с портом может работать только наша программа. Однако, прежде чем организовывать обмен данными, необходимо настроить порт. Это касается только последовательных портов, для которых должна быть задана скорость обмена, параметры четности, формат данных и прочее. Кроме того, существует несколько специфичных для Windows параметров. Речь идет о тайм-аутах, которые позволяют контролировать как интервал между принимаемыми байтами, так и общее время приема сообщения. Есть возможность управлять состоянием сигналов управления модемом.

Последовательность шагов, которые необходимо проделать в процессе работы с портом, приводится на рис.16.9.

Рис.16.9. Процесс работы с портом

Основные параметры последовательного порта описываются структурой DCB:

typedef struct _DCB {

DWORD DCBlength;

DWORD BaudRate;

DWORD fBinary:1;

DWORD fParity:1;

DWORD fOutxCtsFlow:1;

DWORD fOutxDsrFlow:1;

DWORD fDtrControl:2;

DWORD fDsrSensitivity:1;

DWORD fTXContinueOnXoff:1;

DWORD fOutX:1;

DWORD fInX:1;

DWORD fErrorChar:1;

DWORD fNull:1;

DWORD fRtsControl:2;

DWORD fAbortOnError:1;

DWORD fDummy2:17;

WORD wReserved;

WORD XonLim;

WORD XoffLim;

BYTE ByteSize;

BYTE Parity;

BYTE StopBits;

char XonChar;

char XoffChar;

char ErrorChar;

char EofChar;

char EvtChar;

WORD wReserved1;

} 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 (что предпочтительней).

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 используются для конфигурирования микросхем портов, на них накладываются некоторые ограничения. Размер байта должен быть 5, 6, 7 или 8 бит. Комбинация из пяти битного байта и двух стоповых бит является недопустимой. Так же как и комбинация из шести, семи или восьми битного байта и полутора стоповых бит.

Рассмотренная структура DCB самая большая из всех, использующихся для настройки последовательных портов. Но она и самая важная. Заполнение всех полей этой структуры может вызвать затруднения, так как надо очень четко представлять, как работает последовательный порт. Поэтому ручную установку полей можно порекомендовать опытным программистам. Один из способов заполнения структуры - воспользоваться функцией BuildCommDCB, которая позволяет заполнить поля структуры DCB на основе строки, по синтаксису аналогичной строке команды mode. Прототип этой функции выглядит следующим образом:

BOOL BuildCommDCB(LPCTSTR lpDef, LPDCB lpDCB);

Функция имеет всего два параметра:

lpDef

Указатель на строку с конфигурационной информацией в формате команды mode. Например, следующая строка задает скорость 1200, без четности, 8 бит данных и 1 стоповый бит:

baud=1200 parity=N data=8 stop=1

lpDCB

Указатель на заполняемую структуру DCB. При этом структура должна быть уже создана и заполнена нулями, кроме поля DCBlength, которое должно содержать корректное значение. Возможно так же использование уже заполненной структуры DCB, например полученной вызовом одной из функций чтения параметров порта.

В случае успешного завершения функция BuildCommDCB возвращает не нулевое значение. В случае ошибки возвращается 0.

Второй способ предполагает выполнение такой последовательности шагов:

1. Считать текущие значения полей структуры DCB, установленные операционной системой по умолчанию;

2. Внести необходимые изменения в поля этой структуры;

3. Назначить вновь заполненную структуру для дальнейшего использования.

Заполнить поля структуры DCB сведениями о текущем состоянии устройства (точнее, о его настройках) позволяет функция GetCommState, прототип которой:

BOOL GetCommState(

HANDLE hFile,

LPDCB lpDCB

);

Функция очень проста и имеет всего два параметра:

hFile

Дескриптор открытого файла коммуникационного порта. Этот дескрипор возвращается функцией CreateFile. Следовательно, прежде чем получить параметры порта, Вы должны его открыть. Для функции BuildCommDCB это не требовалось.

lpDCB

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

Заполнив DCB можно приступать к конфигурированию порта. Это делается с помощью функции SetCommState:

BOOL SetCommState(

HANDLE hFile,

LPDCB lpDCB

);

Эта функция имеет точно такие же параметры, как GetCommState. Различается только направление передачи информации. GetCommState считывает информацию из внутренних управляющих структур и регистров порта, а SetCommState наоборот заносит ее. Следует быть осторожным при вызове функции SetCommState, поскольку она изменит параметры даже в том случае, если очереди приема/передачи не пусты, что может вызвать искажение потока передаваемых или принимаемых данных. В случае успешного завершения возвращается отличное от нуля значение, а в случае ошибки – нуль.

 

Настройка тайм-аутов

Следующей важной управляющей структурой является COMMTIMEOUTS. Она определяет параметры временных задержек при приеме и передаче. Значения, задаваемые полями этой структуры, оказывают большое влияние на работу функций чтения/записи.

typedef struct _COMMTIMEOUTS {

DWORD ReadIntervalTimeout;

DWORD ReadTotalTimeoutMultiplier;

DWORD ReadTotalTimeoutConstant;

DWORD WriteTotalTimeoutMultiplier;

DWORD WriteTotalTimeoutConstant;

} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

 

Поля структуры COMMTIMEOUTS имеют следующие значения:

ReadIntervalTimeout

Максимальное время, в миллисекундах, допустимое между двумя последовательными символами, считываемыми с коммуникационной линии. Во время операции чтения временной период начинает отсчитываться с момента приема первого символа. Если интервал между двумя последовательными символами превысит заданное значение, операция чтения завершается и все данные, накопленные в буфере, передаются в программу. Нулевое значение данного поля означает, что данный тайм-аут не используется. Значение MAXDWORD, вместе с нулевыми значениями полей ReadTotalTimeoutConstant и ReadTotalTimeoutMultiplier, означает немедленный возврат из операции чтения с передачей уже принятого символа, даже если ни одного символа не было получено из линии.

ReadTotalTimeoutMultiplier

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

ReadTotalTimeoutConstant

Задает константу, в миллисекундах, используемую для вычисления общего тайм-аута операции чтения. Для каждой операции чтения данное значение прибавляется к результату умножения ReadTotalTimeoutMultiplier на количество запрошенных для чтения символов. Нулевое значение полей ReadTotalTimeoutMultiplier и ReadTotalTimeoutConstant означает, что общий тайм-аут для операции чтения не используется.

WriteTotalTimeoutMultiplier

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

WriteTotalTimeoutConstant

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

Если не используются тайм-ауты для синхронной операции чтения и внешнее устройство, выйдя из строя, прекратило передачу, то программа будет вечно ожидать завершения этой операции (другими словами, она «зависнет»). Если же тайм-ауты используются, то операция чтения завершится корректно – только количество фактически считанных байт будет меньше количества байт, запрошенных для чтения, но это не обязательно свидетельствует об ошибке. Например, программа может по тайм-ауту определять конец очередного блока данных. Аналогично и для операции записи, с той лишь разницей, что неполная передача данных из буфера, скорее всего, будет свидетельствовать о проблеме во внешнем устройстве, то есть будет считаться ошибкой.

Как и для заполнения структуры DCB, для COMMTIMEOUTS существует функция считывания установленных в системе значений. Это функция GetCommTimeouts:

BOOL GetCommTimeouts(

HANDLE hFile,

LPCOMMTIMEOUTS lpCommTimeouts

);

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

Заполнив структуру COMMTIMEOUTS можно вызывать функцию установки тайм-аутов порта. Это функция называется SetCommTimeouts:

BOOL SetCommTimeouts(

HANDLE hFile,

LPCOMMTIMEOUTS lpCommTimeouts

);

Установку тайм-аутов можно производить как до установки параметров порта, так и после. Последовательность вызова функций SetCommState и SetCommTimeouts не имеет никакого значения. Главное, что бы все настройки были завершены до начала ввода/вывода информации.

Пример 4. Модифицировать программу, созданную в предыдущем примере, так, чтобы по нажатию кнопки «Открыть порт» она открывала коммуникационный порт компьютера, выбранный пользователем, и устанавливала скорость обмена 9600 бод и следующий формат посылки: количество бит в информационном блоке – 8; количество стоповых бит – 1; проверка на чётность – отсутствует. Также настроить значения тайм-аутов.

Пояснение. Порядок действий:

1. Запускаем среду разработки Visual Studio.NET. Открываем предыдущий проект.

2. Вносим изменения в код обработчика события нажатия на кнопку «Открыть порт» (IDC_BUTTON1).

void CCOMTestDlg::OnBnClickedButton1()

{

CString sp;

m_port.GetLBText(m_port.GetCurSel(),sp);

// получение названия выбранного в списке порта

m_hCom=CreateFile(sp,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);

// открываем последовательный порт

if(m_hCom==INVALID_HANDLE_VALUE)

{

m_status.SetWindowText("Невозможно открыть порт!");

// если невозможно открыть порт, выдаем сообщение

return;

}

else // если порт открыт

{

m_status.SetWindowText("Порт открыт.");

// сообщаем, что порт открыт

m_bOpen.EnableWindow(0); // блокировка элементов управления

m_bClose.EnableWindow(1);

m_port.EnableWindow(0);

 

}

DCB *pDCB; // объявление указателя на структуру

COMMTIMEOUTS ct; // структура для настройки тайм-аутов

pDCB=new DCB;

memset(pDCB,0,sizeof(DCB)); // обнуление всех полей переменной pDCB

if(!GetCommState(m_hCom,pDCB)) // получение состояния порта

{

MessageBox("Невозможно получить параметры порта!");

return;

}

if(!GetCommTimeouts(m_hCom, &ct)) // получение значений тайм-аутов

{

MessageBox("Невозможно получить значения тайм-аутов!");

return;

}

// заполнение полей структуры pDCB

pDCB->DCBlength=sizeof(DCB); // размер структуры

pDCB->BaudRate=CBR_9600; // скорость работы порта

pDCB->ByteSize=8; // размер передаваемых слов

pDCB->StopBits=ONESTOPBIT; // количество стоповых бит - 1

pDCB->Parity=NOPARITY; // четность отсутствует

ct.ReadIntervalTimeout=10; // значение тайм-аут чтения

ct.ReadTotalTimeoutConstant=100; // значение тайм-аут записи

ct.ReadTotalTimeoutMultiplier=2; // множитель тайм-аута чтения

ct.WriteTotalTimeoutConstant=100; // множитель тайм-аута записи

ct.WriteTotalTimeoutMultiplier=2; // общий множитель

if(! SetCommState(m_hCom,pDCB))// установка новых параметров порта

{

MessageBox("Невозможно установить параметры порта!");

return;

}

if(!SetCommTimeouts(m_hCom,&ct)) // установка тайм-аутов

{

MessageBox("Невозможно установить значения тайм-аутов!");

return;

}

}

В тексте программы присутствуют проверки на возможные ошибки, которые могут возникнуть по ходу её выполнения.

Для понимания тайм-аутов предположим следующее. Пусть мы считываем 50 символов из порта со скоростью 9600. При этом используется 8 бит на символ, дополнение до четности и один стоповый бит. Таким образом, на один символ в физической линии приходится 11 бит, включая стартовый. 50 символов на скорости 9600 будут приниматься 50∙11/9600 = 0.0572916 секунд, или примерно 57.3 миллисекунды, при условии нулевого интервала между приемом последовательных символов. Если интервал между символами составляет примерно половину времени передачи одного символа, т.е. 0.5 миллисекунд, то время приема будет 50∙11/9600+49∙0.0005=0.0817916 секунд, или примерно 82 миллисекунды. Если в процессе чтения прошло более 82 миллисекунд, то мы вправе предположить, что произошла ошибка в работе внешнего устройства и прекратить считывание, избежав тем самым зависания программы. Это и есть общий тайм-аут операции чтения. Аналогично существует и общий там-аут операции записи. Формула для вычисления общего тайм-аута операции, например чтения, выглядит так:

NumOfCharReadTotalTimeoutMultiplier + ReadTotalTimeoutConstant,

где NumOfChar - это число символов запрошенных для операции чтения.

Теперь небольшой пример. ReadTotalTimeoutMultiplier = 2, ReadTotalTimeoutConstant = 1, ReadIntervalTimeout = 1, считывается 250 символов. Если операция чтения завершится за 250 * 2 + 1 = 501 миллисекунду, то будет считано все сообщение. Если операция чтения не завершится за 501 миллисекунду, то она все равно будет завершена. При этом будут возвращены символы, прием которых завершился до истечения тайм-аута операции. Остальные символы могут быть получены следующей операцией чтения. Если между началами двух последовательных символов пройдет более 1 миллисекунды, то операция чтения так же будет завершена.



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-04-12 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: