Мастера DELPHI, Delphi programming community Рейтинг@Mail.ru Титульная страница Поиск, карта сайта Написать письмо 
| Новости |
Новости сайта
Поиск |
Поиск по лучшим сайтам о Delphi
FAQ |
Огромная база часто задаваемых вопросов и, конечно же, ответы к ним ;)
Статьи |
Подборка статей на самые разные темы. Все о DELPHI
Книги |
Новинки книжного рынка
Новости VCL
Обзор свежих компонент со всего мира, по-русски!
|
| Форумы
Здесь вы можете задать свой вопрос и наверняка получите ответ
| ЧАТ |
Место для общения :)
Орешник |
Коллекция курьезных вопросов из форумов
KOL и MCK |
KOL и MCK - Компактные программы на Delphi
 
Календарь игр фк барселона матчи в записи барселона.

Работа с COM-портами под Windows

  Практически любому компьютеру приходится связываться с внешними устройствами. Практически любому программисту приходилось (приходится, придется) ваять программы под эти устройства. Огромное количество внешних устройств общаются с компьютером посредством RS-232. Отсюда и огромное количество вопросов от начинающих разработчиков. Количество вопросов на тему "как мне записать/принять данные с com-порта" на форумах по программированию не убывает, а скорее растет. Именно количество этих вопросов побудило меня к написанию статьи. Хотелось бы подчеркнуть, что статья предназначена именно для новичков в этом вопросе, и соответственно я старался упростить изложение материала.

  Итак, у нас имеется в компьютере com-порт. И хорошо бы по нему передавать и принимать байты. В общем случае можно поставить задачу так: необходимо отправлять нужную строку и необходимо реагировать на приход строки в порт от внешнего устройства.

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

Модуль для работы с портом содержит четыре процедуры:

Процедура PortInit.

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

CommHandle:= CreateFile('COM1',GENERIC_READ or GENERIC_WRITE, 0, nil, 
	OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED,0);

  CommHandle - хэндл, то есть номер созданного безобразия. Тип - THandle. В дальнейшем работаем в программе только с ней.
  Первый параметр 'COM1' - собственно имя порта. Его соответственно можно менять (обратите внимание это не тип string, а тип PChar). Остальные установки достаточно стандартны и менять их часто не придется. Хотя конечно можно (и нужно) и по хэлпу полазить и дать волю любопытству.

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

  SetCommMask(CommHandle, EV_RXFLAG); - устанавливаем маску EV_RXFLAG - "обработка по определенному символу". Иными словами, как только в порт придет необходимый символ - то программа начнет обрабатывать данное событие. При отслеживании нескольких событий они задаются через or (логическое или).

  Сам символ задаем в DCB-структуре. DCB- структура - это управляющая структура Вашего порта. Ключевая штуковина. Необходимо ее заполнить. Собственно в ней определяются настройки порта.

  GetCommState(CommHandle,DCB); - получаем текущее DCB.
  DCB.BaudRate:=CBR_9600; - устанавливаем скорость работы.
  DCB.Parity:=NOPARITY; - устанавливаем отсутствие проверки на четность
  DCB.ByteSize:=8; - 8 бит в передаваемом байте.
  DCB.StopBits:=OneStopBit; - одиночный стоп-бит.
  DCB.EvtChar:=chr(13); - вот собственно задаем символ для SetCommMask. В данном случае - возврат каретки.
  SetCommState(cId,DCB); - ну теперь собственно прописываем исправленное DCB.

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

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

CommThread := CreateThread(nil,0,@ReadComm,nil,0,ThreadID);
  Переменная CommThread - хэндл, но уже на поток.
  ReadComm - собственно процедура, по которой и производиться обработка. "Собака" означает, что передаем не имя процедуры, а ее адрес. ThreadID - идентификатор потока. Тема параллельных потоков (или нитей) сама по себе интересная, но объяснение ее не входит в задачи этой статьи, хотя изучение данного материала настоятельно рекомендую. А пока просто запомните - данная строка запускает процедуру ReadComm параллельно основной программе.

Процедура ReadComm

  Теперь пристально рассмотрим процедуру ReadComm. Во-первых, это обычная процедура, входя состав модуля. Во-вторых, все ее содержимое зацикливается с помощью while true do. Но это не смертельно. Все-таки запускаем процедуру исключительно в отдельном потоке, так что беды не будет. Поток же убивается по мере необходимости.

  Итак, процедура содержит цикл и в цикле ждет событие

WaitCommEvent(CommHandle,TransMask,@Ovr);
  Тут, собственно, наш поток останавливается и ждет какого-либо события прописанного в SetCommMask (в процедуре PortInit). Как только событие произошло программа идет дальше
  (TransMask and EV_RXFLAG)=EV_RXFLAG - этим выражением мы проверяем, а то ли событие произошло, что нам надо. Вроде то самое. Тут надо отметить, что в SetCommMask можно поставить с помощью оператора or несколько событий. Тогда в обработчике после ожидания надо соответственно предусмотреть и несколько if then. Что бы прописать реакцию на каждое событие.
  Идем дальше.

  ClearCommError(CommHandle,Errs,@Stat); - вопреки названию, здесь эта функция очищает не ошибки, а собственно факт прихода события. Без нее RXFLAG так и останется висеть. Можете попробовать угадать, что будет на следующем цикле приема.

  Ну а потом идет собственно прием   Kols := Stat.cbInQue; - берем количество байт в буфере порта
  ReadFile(CommHandle,Resive,Kols,Kols,@Ovr); - считываем все в массив Resive.

  Далее все полученное в Resive надо обработать. Как это делается - вопрос конкретного разработчика, удобства и конечно протокола обмена. Одно могу сказать точно - НЕ ДЕЛАЙТЕ как сделано в примере. Не надо из потока обращаться к визуальным компонентам (например, к Panel1 J). Лучшее решение - чтобы юнит по работе с портом вообще не видел главный юнит и главную форму. Вывод данных на экран или на обработку можно сделать или по таймеру или, например, послав пользовательское сообщение (message), ну а в обработчике организовать вывод принятой информации на экран.

Процедура WriteComm.

  Собственно она производит запись в порт. В примере - один байт.

KolByte:=1;
Transmit[0]:=chr(A);
WriteFile(CommHandle,Transmit,KolByte,KolByte,@Ovr);
  Как послать несколько байт? Ну, уже должны догадаться :), что переменная Kolbyte - это и есть собственно количество посылаемых байт. Послать строку еще проще. Заполняете KolByte количеством символов в строке, а вместо массива Transmite используем строковый тип, только не в паскалевском формате, а в PChar (он же си-шный формат строки, он же null-terminated string). Надо отметить, что во всех функциях API используется именно этот строковый стандарт, очевидно по причине написания самой операционки на Си. Еще раз позволю себе намекнуть на использования хелпов и на этот раз отошлю на описание функции StrPcopy. Это поможет, надеюсь.

Процедура KillComm

  Завязываем использования порта и все подметаем за собой.   TerminateThread(CommThread,0); - "убиение" параллельного потока приема
  CloseHandle(CommHandle); - "убиение" собственно файла-порта.

Заключение

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

1) Как мне управлять модемом?
Модем - по сути, тот же порт. Берем протокол описания команд (книжка, обычна идущая в комплекте с модемом) и пилите, Шура, пилите… Например, что бы повесить трубку, надо послать строку 'ATZ' в модем.
2) Какой компонент мне использовать для работы с com-портом?
Не нужны никакие компоненты. API для этих целей вполне достаточно. Да и как видите не сложно это. А универсальности побольше будет.
3) Как мне сделать импульсы определенной длины на com-порте?
В данном примере этого нет. И этого нельзя сделать на обычных информационных контактах приема передачи. Для этого можно использовать, например ножку порта DTR и функцию EscapeCommFunction(CommHandle, SETDTR); Опять обращаемся к хелпу и находим, что можно во втором параметре и сбрасывать тот же DTR. С помощи манипуляции временем и чередованием параметров мы и достигаем цели и задачи.
Кстати, подобным образом можно подавать питание на маленькие, низко потребляющие устройства (как, например, сделано для мыши) или соорудить коммутатор на несколько устройств и переключать их. Штука крайне полезная.
4) Как мне принимать импульсы на com-порт и узнать их длину?
Задача обратная. Реализуется так же как и предыдущая. Используем функцию GetModemStatus, вместо EscapeCommFunction. Надо отметить, что эту функцию я ручками не щупал. Но по описанию - должно работать.
Ну вот, вкратце, пожалуй, и все. Жалобы и подарки направлять по адресу pasha676@newmail.ru. Надеюсь, данная статья Вам поможет.
   Внимание! Запрещается перепечатка данной статьи или ее части без согласования с автором. Если вы хотите разместить эту статью на своем сайте или издать в печатном виде, свяжитесь с автором.
Автор статьи:  Pasha
  

Другие статьи Наверх


  Рейтинг@Mail.ru     Титульная страница Поиск, карта сайта Написать письмо