Александр Терехов дата публикации 06-12-2002 14:58 Асинхронный режим чтения из Com-порта
Порядок запуска и работы "службы" (назовем все описываемое ниже так) Com-портов состоит из нескольких достаточно хорошо описанных шагов
( см. статьи по теме ):
- Инициализация Com-порта посредством вызова функции CreateFile.
- Установка параметров Com-порта посредством последовательного вызова функций GetCommState и SetCommState, а также SetupComm.
- Установка параметров тайм-аутов для чтения и записи - GetCommTimeouts и SetCommTimeouts.
- Собственно записи в Com-порт - WriteFile и чтения из него - ReadFile.
- Закрытие порта по окончанию работ CloseHandle.
Очень большой сложности описанные выше шаги не представляют, однако реализация чтения данных из порта в асинхронном (неблокирующем) режиме заставляет почесать затылок. Об этом и поговорим.
Судя по контексту справки, касающейся функции CreateFile, для "отлова" момента поступления данных в Com-порт следует использовать функцию WaitCommEvent. Предварительно установив маску SetCommMask на то событие, которое хотелось бы отследить. Нужное событие наступает - вызываем функцию ReadFile для чтения поступающих данных.
Казалось бы все в порядке, но... Вызов функции WaitCommEvent насмерть тормозит приложение, пока какие-либо данные не поступят в Com-порт.
Можно конечно, просто взять и запустить в непрерывном цикле ReadFile, однако приложение хотя и будет как-то шевелиться, но это шевеление скорее всего будет напоминать предсмертные судороги.
Как выход из ситуации многие предлагают использовать потоки (thread), забывая при этом описать как это делать :)
Итак потоки.
В модуле Classes для потоков определен специальный класс TThread. Для создания потоков специалисты рекомендуют использовать именно его, а не создавать потоки используя BeginThread и EndThread, т.к. библиотека VCL не является защищенной для потоков в такой реализации. Следуя советам экспертов, для организации контроля поступающих данных в Com-порт и будем использовать готовый класс TThread.
В раздел interface определим тип переменных этого класса, переопределив только один метод класса - Execute, ну и дополнительно объявим свой метод, который и займется опросом Com-порта.
Type
//определим тип TComThread - наследника класса TThread
private
Procedure QueryPort;
protected
Procedure Execute; override;
end;
| |
Далее в разделе глобальных переменных определим поток-переменную полученного выше типа
CommThread:TCommThread;
Затем в разделе implementation начинаем ваять.
ВНИМАНИЕ!!!
К этому времени порт уже должен быть инициализирован функцией CreateFile.
- 1. Инициализируем поток, используя метод Create.
Procedure StartComThread;
Begin
CommThread:=TCommThread.Create(False);
If CommThread = Nil Then
Begin
SysErrorMessage(GetLastError);
fmMain.btnStop.Click;
Exit;
End;
End;
| |
Куски кода взяты из файла проекта, поэтому нажимание на кнопку btnStop главной формы fmMain - это "примочки" примера, не обращайте внимания.
- Запускаем процедуру опроса порта в нашем потоке.
Procedure TCommThread.Execute;
Begin
Repeat
QueryPort;
Until Terminated;
End;
| |
- Реализуем асинхронные опрос порта и чтение из него данных
Procedure TCommThread.QueryPort;
Var
MyBuff:Array[0..1023] Of Char;
ByteReaded:Integer;
Str:String;
Status:DWord;
Begin
If Not GetCommModemStatus(hPort,Status) Then
Begin
SysErrorMessage(GetLastError);
fmMain.btnStop.Click;
Exit;
End;
fmMain.imgCTSOn.Visible:=((Status AND MS_CTS_ON)=MS_CTS_ON);
fmMain.imgDSROn.Visible:=((Status AND MS_DSR_ON)=MS_DSR_ON);
fmMain.imgRLSDOn.Visible:=((Status AND MS_RLSD_ON)=MS_RLSD_ON);
fmMain.imgRingOn.Visible:=((Status AND MS_RING_ON)=MS_RING_ON);
FillChar(MyBuff,SizeOf(MyBuff),#0);
If Not ReadFile(hPort,MyBuff,SizeOf(MyBuff),ByteReaded,Nil) Then
Begin
SysErrorMessage(GetLastError);
fmMain.btnStop.Click;
Exit;
End;
If ByteReaded>0 Then
Begin {ByteReaded>0}
ReciveBytes:=ReciveBytes+ByteReaded;
Str:=String(MyBuff);
fmMain.Memo1.Text:=fmMain.Memo1.Text+ Str;
fmMain.lbRecv.Caption:='recv: '+IntToStr(ReciveBytes)
+' bytes...';
End;
End;
| |
На этом по поводу использования потоков для считывания данных из Com-порта, пожалуй, все.
Следуя правилам хорошего тона, прикладываю ко всему написанному работающий пример.
В примере используется самое доступное устройство для пользователей интернет - модем (на Com-порту). В качестве "примочек" я использовал лампочки, которые включаются (или выключаются) при изменении статуса модема. Можно было прикрутить лампочки-детекторы входящих-выходящих сигналов, но вместо них используются счетчики байтов.
Реализация кода включения-выключения не самая лучшая: можно было бы использовать TImageList для хранения изображений лампочек. Но почему-то ??? (кто знает почему - напишите) использование ImageList.GetBitmap при наличии запущенного потока "подвешивает" приложение насмерть. Причем это происходит под Windows'98, если тоже самое делать под Windows'95, то все в порядке.
Для проверки работоспособности примера попробуйте понабирать AT-команды
- ATZ - инициализировать модем
- ATH - положить трубку
- ATH1 - поднять трубку
- ATS0=1 - включить автоподнятие трубки на первый сигнал
- ATS0=0 - выключить автоподнятие трубки
- ATDP_номер_телефона_интернет_провайдера - мне нравится больше всего :)
- ATDP - набор в импульсном режиме, ATDT - набор в тоновом режиме
Да, еще. Проект написан под Delphi3, при использовании Delphi более свежих версий возможны ошибки "несовпадения типов".
В этом случае поменяйте типы "ошибочных" переменных с Integer на Cardinal.
Скачать проект — ComPort.zip (17K)
архив обновлен автором 10.12.02 (добавлена функция обработки кодов
ошибок с переводом их значений на русский язык)
Терехов Александр
декабрь 2002г.
Другие небольшие статьи, примеры и программы можете найти на
сайте автора
[Работа с модемом] [COM-порт]
Обсуждение материала [ 06-02-2013 07:28 ] 11 сообщений |