Каталог статей

Главная » Статьи » Мои статьи

Работа с COM-портом с помощью потоков abc

8) Теперь открываем файл для записи принимаемых данных:

//создать или открыть существующий файл для записи принимаемых данных
 handle = open("test.txt", O_CREAT | O_APPEND | O_BINARY | O_WRONLY, S_IREAD | S_IWRITE);

 if(handle==-1) //если произошла ошибка открытия файла
 {
 //вывести сообщение об этом в командной строке
 Form1->StatusBar1->Panels->Items[1]->Text = "Ошибка открытия файла";
 Form1->Label6->Hide(); //спрятать надпись с именем файла
 Form1->CheckBox3->Checked = false; //сбросить и отключить галочку
 Form1->CheckBox3->Enabled = false;
 }
 //иначе вывести в строке состояния сообщение об успешном открытии файла
 else { Form1->StatusBar1->Panels->Items[0]->Text = "Файл открыт успешно"; }

Если файл открыть не удалось - в строке состояния выводим сообщение об ошибке, прячем имя файла на форме, сбрасываем и отключаем галочку "Сохранить в файл".

Иначе - выводим сообщение, что файл открыт успешно.

9) Сбрасываем буферы порта:

PurgeComm(COMport, PURGE_RXCLEAR); //очистить принимающий буфер порта

Это чтобы не передался и не принялся какой-нибудь находящийся в буфере мусор.

10) На этом шаге создаём необходимые потоки. Если это версия программы без использования методов Suspend() и Resume(), то создаётся (и сразу запускается) только поток чтения.

reader = new ReadThread(false); //создать и запустить поток чтения байт
reader->FreeOnTerminate = true; //установить это свойство потока,
 //чтобы он автоматически уничтожался после завершения

Если это программа, в которой используются методы Suspend() и Resume(), то создаются оба потока - и чтения, и записи. При этом поток чтения запускается сразу, а поток записи создаётся в остановленном состоянии.

reader = new ReadThread(false); //создать и запустить поток чтения байт
reader->FreeOnTerminate = true; //установить это свойство потока,
 //чтобы он автоматически уничтожался после завершения

writer = new WriteThread(true); //создать поток записи данных в порт
writer->FreeOnTerminate = true; //установить это свойство,
 //чтобы поток автоматически уничтожался после завершения

А для потоков на WINAPI создание потоков выглядит так:

//создаём поток чтения, который сразу начнёт выполняться (предпоследний параметр = 0)
reader = CreateThread(NULL, 0, ReadThread, NULL, 0, NULL);

//создаём поток записи в остановленном состоянии (предпоследний параметр = CREATE_SUSPENDED)
writer = CreateThread(NULL, 0, WriteThread, NULL, CREATE_SUSPENDED, NULL);

Здесь также создаются оба потока, но с помощью функции WINAPI CreateThread. Поток чтения также запускается сразу (предпоследний параметр в функции равен нулю), а поток записи создаётся в остановленном состоянии (предпоследний параметр имеет значение CREATE_SUSPENDED).

Функция закрытия порта COMClose()

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

1) Функция COMClose() для программы на TThread без использования методов Resume() и Suspend():

//функция закрытия порта
void COMClose()
{
 //если поток записи работает, завершить его; проверка if(writer) обязательна, иначе возникают ошибки
 if(writer)writer->Terminate();
 //если поток чтения работает, завершить его; проверка if(reader) обязательна, иначе возникают ошибки
 if(reader)reader->Terminate();
 
 CloseHandle(COMport); //закрыть порт
 COMport=0; //обнулить переменную для дескриптора порта
 close(handle); //закрыть файл для записи принимаемых данных
 handle=0; //обнулить переменную для дескриптора файла
}

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

2) Функция COMClose() для программы на TThread с использованием методов Resume() и Suspend():

//функция закрытия порта
void COMClose()
{
 //если поток записи существует, подать ему команду на завершение и запустить его, чтобы он выполнил завершение
 if(writer) //проверка if(writer) обязательна, иначе возникают ошибки
 {
 writer->Terminate();
 writer->Resume();
 }
 //если поток чтения работает, завершить его; проверка if(reader) обязательна, иначе возникают ошибки
 if(reader)reader->Terminate();

 CloseHandle(COMport); //закрыть порт
 COMport=0; //обнулить переменную для дескриптора порта
 close(handle); //закрыть файл для записи принимаемых данных
 handle=0; //обнулить переменную для дескриптора файла
}

Здесь отличие от первой версии состоит в том, что после того как для потока записи writer вызывается метод Terminate(), также дополнительно вызывается метод Resume() - это необходимо, потому что поток может находиться в остановленном состоянии и не сможет выполнить проверку свойства Terminate, чтобы завершиться. Все остальное - как и в первой версии.

3) Функция COMClose() для программы на WINAPI с использованием функций ResumeThread() и SuspendThread():

//функция закрытия порта
void COMClose()
{
//Примечание: так как при прерывании потоков, созданных с помощью функций WinAPI, функцией TerminateThread
// поток может быть прерван жёстко, в любом месте своего выполнения, то освобождать дескриптор
// сигнального объекта-события, находящегося в структуре типа OVERLAPPED, связанной с потоком,
// следует не внутри кода потока, а отдельно, после вызова функции TerminateThread.
// После чего нужно освободить и сам дескриптор потока.

 //если поток записи работает, завершить его; проверка if(writer) обязательна, иначе возникают ошибки
 if(writer)
 {TerminateThread(writer,0);
 CloseHandle(overlappedwr.hEvent); //нужно закрыть объект-событие
 CloseHandle(writer);
 }
 
 //если поток чтения работает, завершить его; проверка if(reader) обязательна, иначе возникают ошибки
 if(reader)
 {TerminateThread(reader,0);
 CloseHandle(overlapped.hEvent); //нужно закрыть объект-событие
 CloseHandle(reader);
 }
 
 CloseHandle(COMport); //закрыть порт
 COMport=0; //обнулить переменную для дескриптора порта
 close(handle); //закрыть файл, в который велась запись принимаемых данных
 handle=0; //обнулить переменную для дескриптора файла
}

Эта функция отличается от первых двух, и отличие связано с особенностями использования WINAPI. Здесь потоки прерываются "грубым" способом - с помощью функции TerminateThread(), которая немедленно завершает поток, потому что у потоков WINAPI нет встроенных средств для "мягкого" завершения. "Мягкое" завершение можно обеспечить вручную, например - с помощью флагов, но это усложняет программу. Здесь мы не стали прибегать к этому, а воспользовались именно функцией TerminateThread(). У этой функции плюс в том, что она завершает даже спящий поток, то есть после её вызова не нужно активировать поток функцией ResumeThread(), как в предыдущем примере. Но из-за неё нам приходится после её вызова закрывать дескрипторы сигнальных объектов-событий, а также дескрипторы самих потоков.

А затем также как и в остальных функциях, выполняется закрытие порта и файла.

Конструктор формы и обработчики событий формы и её элементов

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

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

//---------------------------------------------------------------------------

//конструктор формы, обычно в нём выполняется инициализация элементов формы
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
 //инициализация элементов формы при запуске программы
 Form1->Label5->Enabled = false;
 Form1->Label6->Enabled = false;
 Form1->Button1->Enabled = false;
 Form1->CheckBox1->Enabled = false;
 Form1->CheckBox2->Enabled = false;
}

//---------------------------------------------------------------------------

Обработчик нажатия на кнопку "Открыть порт". Кнопка "Открыть порт" - это кнопка SpeedButton, её интересная особенность - то, что она может оставаться в нажатом состоянии, и, чтобы её отжать, нужно нажать эту кнопку ещё раз (похоже на включение/выключение галочки CheckBox). Чтобы такое стало возможно, в свойствах кнопки нужно выставить следующие настройки: AlowAllUp=true, GroupIndex=1. Также на эту кнопку можно прикрепить иконку. Для этого в Glyph загрузить иконку и установить NumGlyph=1. А с помощью свойства Layout выбирать, с какой стороны должна находиться иконка.

Итак, при нажатии на кнопку в обработчике выполняется проверка - если кнопка нажата, тогда открываем порт и активируем кнопку "Передать", а также флажки сигналов "DTR" и "RTS". При этом меняем надпись на нажатой кнопке на "Закрыть порт". Затем сбрасываем счётчик принятых байтов. И вызываем обработчики флажков CheckBox1 и Checkbox2, чтобы включить линии DTR и RTS, если эти флажки были установлены.

Если кнопка отжалась, выполняем обратные действия: закрываем порт, меняем надпись на кнопке на "Открыть порт", очищаем строку состояния на форме, активируем списки настроек порта и отключаем кнопку "Передать" и галочки "DTR" и "RTS".

//---------------------------------------------------------------------------

//обработчик нажатия на кнопку "Открыть порт"
void __fastcall TForm1::SpeedButton1Click(TObject *Sender)
{
 if(SpeedButton1->Down)
 {
 COMOpen(); //если кнопка нажата - открыть порт

 //показать/спрятать элементы на форме
 Form1->ComboBox1->Enabled = false;
 Form1->ComboBox2->Enabled = false;
 Form1->Button1->Enabled = true;
 Form1->CheckBox1->Enabled = true;
 Form1->CheckBox2->Enabled = true;

 Form1->SpeedButton1->Caption = "Закрыть порт"; //сменить надпись на кнопке

 counter = 0; //сбрасываем счётчик байтов

 //если были включены флажки DTR и RTS, установить эти линии в единицу
 Form1->CheckBox1Click(Sender);
 Form1->CheckBox2Click(Sender);
 }

 else
 {
 COMClose(); //если кнопка отжата - закрыть порт

 Form1->SpeedButton1->Caption = "Открыть порт"; //сменить надпись на кнопке
 Form1->StatusBar1->Panels->Items[0]->Text = ""; //очистить первую колонку строки состояния

 //показать/спрятать элементы на форме
 Form1->ComboBox1->Enabled = true;
 Form1->ComboBox2->Enabled = true;
 Form1->Button1->Enabled = false;
 Form1->CheckBox1->Enabled = false;
 Form1->CheckBox2->Enabled = false;
 }
}

//---------------------------------------------------------------------------

Обработчик закрытия формы выполняется при закрытии формы (когда вы нажимаете крестик или закрываете программу ещё каким-нибудь способом). В этом обработчике выполняются те же действия, что и в функции COMClose(), отличие состоит в том, что здесь при закрытии порта и файла выполняется проверка - если переменная дескриптора содержит 0, то закрытие дескриптора не выполняется. Это необходимо, чтобы избежать попытки повторного закрытия уже закрытого дескриптора. Такой подход более грамотный, чем просто всегда пытаться закрыть порт, игнорируя сообщения функций об ошибках. (См. описание функции COMClose()).

//---------------------------------------------------------------------------

//обработчик закрытия формы
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
 //завершить поток чтения из порта, проверка if(reader) обязательна, иначе возникают ошибки
 if(reader)reader->Terminate();
 //завершить поток записи в порт, проверка if(writer) обязательна, иначе возникают ошибки
 if(writer)writer->Terminate();
 if(COMport)CloseHandle(COMport); //закрыть порт
 if(handle)close(handle); //закрыть файл, в который велась запись принимаемых данных

}
//---------------------------------------------------------------------------

Обработчик нажатия галочки "Сохранить в файл". Он срабатывает в ответ на нажатие галочки. При этом проверяется - если галочка установлена, активируются надписи "Имя файла:" и "test.txt", а во второй раздел строки состояния выводится сообщение "Вывод в файл!". Это означает, что включена запись принимаемых данных в файл.

Если галочка отключена, надписи дезактивируются, а сообщение из строки состояния убирается.

//---------------------------------------------------------------------------

//галочка "Сохранить в файл"
void __fastcall TForm1::CheckBox3Click(TObject *Sender)
{
 if(Form1->CheckBox3->Checked) //если галочка включена
 {
 //активировать соответствующие элементы на форме
 Form1->Label5->Enabled = true;
 Form1->Label6->Enabled = true;

 //вывести индикатор записи в файл в строке состояния
 Form1->StatusBar1->Panels->Items[1]->Text = "Вывод в файл!";
 }

 else //если галочка выключена
 {
 //отключить соответствующие элементы на форме
 Form1->Label5->Enabled = false;
 Form1->Label6->Enabled = false;

 //убрать индикатор записи в файл из строки состояния
 Form1->StatusBar1->Panels->Items[1]->Text = "";
 }

}
//---------------------------------------------------------------------------

Обработчик нажатия кнопки "Передать". Выполняется при нажатии на кнопку "Передать". Эта кнопка доступна, только когда открыт порт.

В обработчике с помощью функции memset() очищается программный передающий буфер для того, чтобы от предыдущих данных не оставался мусор, если они занимали больше ячеек массива, чем новые.

Затем с помощью функции PurgeComm() с параметром PURGE_TXCLEAR очищаем передающий буфер порта, чтобы избежать передачи какого-нибудь мусора, который может там оказаться.

После этого с помощью функции strcpy() копируем в буфер строку из поля ввода Edit1 (предварительно преобразовав её в массив типа char с помощью метода AnsiString-строки c_str()). Затем запускаем поток записи в порт. В данном случае обработчик кнопки "Передать" приведён для версии без использования методов Resume() и Suspend(), поэтому здесь поток записи создаётся заново.

//---------------------------------------------------------------------------

//кнопка "Передать"
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 memset(bufwr,0,BUFSIZE);//очистить программный передающий буфер, чтобы данные не накладывались друг на друга
 PurgeComm(COMport, PURGE_TXCLEAR); //очистить передающий буфер порта
 strcpy(bufwr,Form1->Edit1->Text.c_str()); //занести в программный передающий буфер строку из Edit1

 writer = new WriteThread(false); //создать и активировать поток записи данных в порт
 writer->FreeOnTerminate = true; //установить это свойство,
 //чтобы поток автоматически уничтожался после завершения

}

//---------------------------------------------------------------------------

В версии с использованием этих методов вместо двух строчек создания потока будет одна строка с вызовом метода Resume():

writer->Resume(); //активировать поток записи в порт

А в программе с потоками на WINAPI будет строка с вызовом функции ResumeThread():

ResumeThread(writer); //активировать поток записи данных в порт

Обработчик нажатия кнопки "Очистка поля". Вызывается при нажатии на кнопку "Очистка поля". В нём выполняется очистка поля Memo1 с помощью его метода Clear().

//---------------------------------------------------------------------------

//кнопка "Очистить поле"
void __fastcall TForm1::Button3Click(TObject *Sender)
{
 Form1->Memo1->Clear(); //очистить Memo1
}

//---------------------------------------------------------------------------

Обработчик нажатия на галочку "DTR". Выполняется при нажатии на галочку. В обработчике проверяется - если галочка установлена - тогда с помощью функции EscapeCommFunction с параметром SETDTR устанавливаем линию DTR в единицу. Иначе - с помощью той же функции, но с параметром CLRDTR, сбрасываем линию DTR в ноль.

//---------------------------------------------------------------------------

//галочка "DTR"
void __fastcall TForm1::CheckBox1Click(TObject *Sender)
{
 //если установлена - установить линию DTR в единицу, иначе - в ноль
 if(Form1->CheckBox1->Checked) EscapeCommFunction(COMport, SETDTR);
 else EscapeCommFunction(COMport, CLRDTR);
}

//---------------------------------------------------------------------------

Обработчик нажатия на галочку "RTS". Выполняется при нажатии на галочку "RTS". В обработчике проверяется, установлена ли галочка. Если она установлена, тогда с помощью функции EscapeCommFunction с параметром SETRTS устанавливаем на линию RTS. Иначе - с помощью той же функции, но с параметром CLRRTS, сбрасываем линию RTS.

//---------------------------------------------------------------------------

//галочка "RTS"
void __fastcall TForm1::CheckBox2Click(TObject *Sender)
{
 //если установлена - установить линию RTS в единицу, иначе - в ноль
 if(Form1->CheckBox2->Checked) EscapeCommFunction(COMport, SETRTS);
 else EscapeCommFunction(COMport, CLRRTS);
}

//---------------------------------------------------------------------------
Переменные, используемые в программе

Для работы программы понадобилось создать несколько переменных. Некоторые из них обязательны, а некоторые - не обязательны.

1) Буфер приёма bufrd и буфер передачи bufwr. Используются для хранения принимаемых и передаваемых данных. Размер для них установлен в 255. Вы можете его изменить, поставив в директиве #define BUFSIZE другую величину. Следует отметить, что для буфера передачи размер может быть любой - это зависит от объёма передаваемых данных. Здесь мы просто ограничили его числом 255. При этом в Edit1 тоже поставили ограничение на количество вводимых символов, но в 254 (на всякий случай, чтобы последний байт в буфере был зарезервирован, например, под нулевой символ конца строки).

А вот с приёмным буфером ситуация иная. В принципе, для него можно поставить и меньший размер, так как выяснилось, что почти всегда данные считываются из порта по восемь байтов, причём на любой скорости. Очень редко бывает больше. И с чем это связано, к сожалению, остаётся непонятно. Поэтому пусть размер буфера останется 255, на всякий случай.

Буферы не являются обязательными переменными, но с ними проще работать.

#define BUFSIZE 255 //ёмкость буфера
unsigned char bufrd[BUFSIZE], bufwr[BUFSIZE]; //приёмный и передающий буферы

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

HANDLE COMport; //дескриптор порта

Имейте в виду, что когда вы закрываете порт функцией CloseHandle(COMport), то значение переменной, в которой хранится дескриптор, не обнуляется. Если попытаться закрыть дескриптор ещё раз, функция вернёт ошибку (подробнее об этом см. выше).

Переменная для дескриптора порта является обязательной.

3) Ещё две обязательные переменные - структуры overlapped и overlappedwr типа OVERLAPPED для потоков чтения и записи. Структура OVERLAPPED необходима для асинхронных операций, при этом для операции чтения и записи нужно объявить разные структуры. Их необходимо объявить глобально, иначе программа будет работать неправильно (протестировано).

OVERLAPPED overlapped; //будем использовать для операций чтения (см. поток ReadThread)
OVERLAPPED overlappedwr; //будем использовать для операций записи (см. поток WriteThread)

4) Приведённые ниже переменные являются необязательными.

Переменная handle типа int для хранения дескриптора файла, в который записываются принимаемые данные. Эта переменная является необязательной, так как запись в файл можно не делать.

int handle; //дескриптор для работы с файлом с помощью библиотеки 

Флаг fl типа bool, сигнализирующий об успешности операции записи в порт, выполненной в потоке записи. Используется, чтобы передать в функцию Printing() потока записи результат асинхронной операции записи в порт. Контроль успешности записи в порт можно не использовать, либо передавать в функцию Printing() результат операции каким-либо другим способом* - в этом случае этот флаг не нужен. (*Из-за использования метода Synchronize для вызова этой функции передать в неё параметры в качестве аргументов нам не удалось. Авторам статьи в данном случае другой способ кроме глобальных переменных не известен.)

bool fl=0; //флаг, указывающий на успешность операций записи (1 - успешно, 0 - не успешно)

Переменная-счётчик counter для хранения количества принятых байтов за время открытия порта. Счётчик сбрасывается при нажатии на кнопку "Открыть порт". Количество принятых байтов выводится в правой части строки состояния. Использовать его не обязательно.

unsigned long counter; //счётчик принятых байтов, обнуляется при каждом открытии порта

Описание интерфейса

Интерфейс всех трёх версий программ одинаков, поэтому опишем его на примере первой версии (см. Рис. 1).

Рис. 1. Интерфейс программы

Интерфейс состоит из одного окна, разделённого на четыре части:

1) Секция "Настройка порта".

Здесь располагаются два списка с настройками порта. Первый список, "Номер порта", позволяет выбрать имя порта - COM1 или COM2. Второй список, "Скорость передачи", позволяет выбрать скорость передачи в бит/секунду (бод). В этом списке приведена полная линейка скоростей порта - от 110 бод до 115 200 бод.

Оба списка доступны, когда порт не открыт. При открытии порта списки становятся недоступными.

Под списками располагается кнопка "Открыть порт". При нажатии на эту кнопку происходит открытие порта с текущими установленными настройками. После нажатия надпись на кнопке меняется на "Закрыть порт", и кнопка остаётся нажатой. Чтобы закрыть порт, нужно нажать кнопку ещё раз, тогда она вернётся в исходное состояние, порт будет закрыт.

Сбоку от списков располагаются две галочки сигналов DTR и RTS. Они доступны, только когда порт открыт. Установкой и сбросом этих галочек можно соответственно устанавливать и сбрасывать сигналы DTR и RTS. При этом состояния сигналов при открытии порта восстанавливаются соответственно состоянию галочек. То есть если вы оставите их включенными, при открытии порта эти линии будут установлены в единицу. (Это не совсем грамотно, так как правильнее будет дать пользователю возможность проконтролировать включение/выключение этих линий до открытия порта. Но не будем усложнять программу.)

2) Секция "Запись в файл".

Здесь можно включить сохранение в файл принимаемых из порта данных. Для этого предусмотрена галочка "Сохранить в файл". При нажатии галочки активируется надпись с именем файла, и программа будет производить запись данных в файл. Если при открытии файла для записи данных (которое происходит при открытии порта) по каким-либо причинам произошла ошибка, то галочка будет недоступна, а надпись пропадёт. Галочка записи в файл доступна всё время работы с программой, если при открытии файла не произошла ошибка.

Сброс галочки отключает запись данных в файл.

3) Секция "Передача данных".

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

Справа от текстового поля находится кнопка "Передать", при нажатии на которую выполняется передача данных из текстового поля. После передачи данных набранная строка остаётся в поле ввода (не стирается). В отличие от поля ввода кнопка "Передать" доступна, только если порт открыт.

Ниже располагается текстовое окно для отображения принимаемых данных и кнопка "Очистить поле", при нажатии на которую текстовое окно очищается. Поле принимаемых данных и кнопка "Очистить поле" доступны всё время работы программы.

4) Строка состояния.

Используется для вывода различной служебной информации, разделена на три части. В первой части выводится результат выполнения операции открытия порта, файла для записи или передачи данных. В средней части выводится индикатор сохранения принимаемых данных в файл, если галочка "Сохранить в файл" включена. В третьей части выводится количество принятых байтов за всё время, пока порт был открыт.

Работа с программой

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

Если желательна запись принимаемых данных в файл, то нужно включить галочку "Сохранить в файл". При этом данные будут сохраняться в файл с именем test.txt в каталоге с программой. Рекомендуется активировать запись в файл до открытия порта, так как приём данных начинается сразу, и если в порт сразу будут поступать данные, они могут не успеть попасть в файл.

Так как файл открывается при открытии порта, то, в случае ошибки открытия файла, запись в файл станет недоступной.

Затем нужно открыть порт, нажав на кнопку "Открыть порт". При этом становятся доступными галочки DTR и RTS. Вы можете устанавливать и сбрасывать их для управления соответствующими линиями.

Также становится доступна передача данных. Чтобы передать какой-нибудь текст, наберите его в поле ввода и нажмите кнопку "Передать", данные будут переданы.

Чтобы закрыть порт, нажмите на кнопку "Закрыть порт".

Как протестировать программу

Для тестирования программы вам понадобятся: диспетчер задач (либо встроенный для Windows 2000 (см. Рис. 2), либо в виде какой-нибудь утилиты (например, в составе TurneUp Utilities)), а также заглушка для COM-порта.

Диспетчер задач

Диспетчер задач вам понадобится для наблюдения за корректным управлением потоками и дескрипторами.

Рис. 2. Окно диспетчера задач Windows 2000

В окне диспетчера задач нас интересует следующее:

1) Загрузка процессора - по ней видно, что в случае использования усыпления потоков, они почти не загружают процессор.

2) Окошко "Всего" - здесь содержится информация о количестве открытых дескрипторов, процессов и потоков. Пользуясь этим окошком при отладке программ, мы можем отследить, правильно ли создаются и уничтожаются потоки, закрываются и открываются дескрипторы. Например, таким способом авторы отлавливали ошибки по закрытию потоков - при нажатии кнопки создавалось два потока, а уничтожался только один, и, таким образом, при каждом включении/выключении порта число потоков в системе увеличивалось. Такая же ситуация наблюдалась с дескрипторами - когда дескриптор события или файла освобождался не там, где надо, и в результате количество дескрипторов также увеличивалось.

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

Ну а количество процессов вам пригодится, если вы кроме потоков работаете ещё и с процессами. В данном же случае здесь можно посмотреть, как на количество процессов влияет наша программа. При её запуске должен создаваться один процесс, а при её закрытии - уничтожаться.

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

Заглушка для COM-порта

В качестве аппаратного вспомогательного средства для тестирования программы вам понадобится заглушка для COM-порта. Она представляет собой разъём DB-9-F, у которого контакты 2 (RD) и 3 (TD) соединены вместе. Эта заглушка вставляется в нужный COM-порт. Работает она очень просто - когда программа посылает в COM-порт какие-то данные, они через RX тут же попадают на TX и программа сразу же их принимает. То есть получается что-то наподобие эха.

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

На Рис. 3 приведены схемы контактов разъёма и соединения выводов для получения заглушки.

Рис. 3. Схемы контактов разъёма DB-9 и соединения контактов заглушки

Теперь вы сможете написать и протестировать свою программу работы с COM-портом. На этом и закончим нашу статью.

Список литературы

1) Агуров П. "Последовательные интерфейсы ПК. Практика программирования".

Книга посвящена в основном COM-порту и интерфейсу RS-232. Книга содержит теоретическую информацию о последовательных интерфейсах, примеры кодов для работы с COM-портом, а также справочник по функциям для работы с COM-портом (занимает достаточно большую часть книги).

Достоинства книги:
- именно кусок кода по работе с потоками из этой книги помог нам написать программу, а затем и эту статью.
- неплохой справочник по функциям.
- работа с драйвером IO.
- немного теоретической информации поможет понять, что такое интерфейс RS-232 (если только не запутает читателя).

Недостатки:
- автор практически не рассматривает аппаратную часть RS-232.
- коды программ написаны на Delphi и почти не имеют пояснений. Будто бы автор торопился выпустить книгу.

Эту книгу можно использовать для начального изучения COM-порта.

2) М.Титов. Работа с коммуникационными портами (COM и LPT) в программах для Win32.

Статья посвящена программной работе с COM-портом. В ней очень хорошо рассмотрена инициализация порта, а также всякие хитрые функции для работы с портом. Приведены подробные описания функций WINAPI (взятые из help, но хорошо переведённые и снабжённые комментариями). Также рассматриваются функции для приёма-передачи данных (ReadFile и WriteFile). Но передаче данных уделяется меньше внимания, а о перекрываемых (overlapped) операциях написано непонятно. А про работу с потоками вообще только упоминается.

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

3) Архангельский А.Я, Тагин М.А. "Приёмы программирования в C++ Builder. Механизмы Windows, сети". В этой книге освещаются некоторые вопросы по работе с потоками, сигнальными объектами и COM-портами, а также много других интересных приёмов работы в C++ Builder.

4) Help к программе Borland C++ Builder 6.0. Благодаря ему авторы данной статьи смогли разобраться во всём, о чём здесь написали. Материал в нём изложен в краткой и понятной форме, при этом оставаясь довольно содержательным. Одно "но" - всё на английском языке.



© PIClist-RUS (piclist.ru), 2007 г.

Категория: Мои статьи | Добавил: MAS (26.11.2012)
Просмотров: 2214 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Категории раздела
Мои статьи [51]
Справочные данные [165]
Справочные данные
Наш опрос
Оцените мой сайт
Всего ответов: 30
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Форма входа
Баннер
$$
Друзья сайта
  • Спортсменам
  • Огородникам СЮД(А
  • Строим вместе
  • Мир развлечений
  • Кто хочет похудеть
  • Здоровье у нас одно
  • Юмор
  • Кулинарные рецепты
  • Картинки_Заставки_Демотивоторы
  • Семья
  • Кто на мальчишник
  • Металлообработка
  • Кто на девишник
  • Блоки питания
  • Смерть соседям
  • Радиомедведь
  • Кибермедведь
  • Радиосайт
  • Деревообработка
  • Поиск