Обмен данными через COM-порт в Windows
Архив
|
Разработчики высокоуровневых языков программирования, очевидно, считают прием/передачу данных по протоколу RS232 через коммуникационный порт экзотической процедурой: мол, рядовому пользователю как бы и без надобности, а нерядовой - разберется самостоятельно. Потому ни в Turbo Pascal, ни в Delphi нет штатных средств обмена данными таким способом. Однако в последнее время, особенно в связи с распространением микропроцессорных устройств, такая задача встает в любительских программах все чаще - в силу простоты и дешевизны реализации последовательным портом оборудованы многие научные и инженерные приборы, разнообразные датчики и измерители.
Простота аппаратного исполнения (для обычной двусторонней связи требуется всего три провода) асинхронного коммуникационного порта ведет, однако, к некоторому усложнению необходимого программного обеспечения. Можно, конечно, попробовать послать байт на устройство COM1 средствами DOS, подобно тому, как это делается для LPT1 (оно же PRN), но успех вряд будет достигнут - как минимум, надо сначала настроить скорость обмена. Потому для DOS-программ это делается средствами BIOS или прямым программированием порта "по железу". А в Windows, к счастью, есть соответствующие функции API.
Для организации обмена нужно проделать следующие шаги:
- получить дескриптор порта (handle - указатель, куда посылать все относящееся к порту);
- получить адрес DCB - Data Control Block;
- установить новые параметры DCB;
- послать установленные параметры в порт;
- приступить к чтению принимаемых данных или к передаче.
Рассмотрим все по порядку. Получить дескриптор можно с помощью универсальной функции CreateFile. Так как СОМ - устройство последовательное, то его можно рассматривать как файл, что и делается (и в DOS, между прочим, тоже). У этой функции множество применений (описание в Help, если распечатать, занимает страниц семь). Для нее требуется масса входных параметров, но большинство из них нам не нужны и приравниваются, в зависимости от типа, либо к 0, либо к величине nil (указатели, которые никуда не указывают). В нашем случае получается следующий синтаксис функции:
CreateFile (stcom, generic_read+generic_write, 0, nil, open_existing, 0, 0);
Здесь stcom - строка типа Pchar, в которой записано имя файла, в данном случае - просто 'COM1' или 'COM2', смотря какой порт нужен. Параметр generic_read+generic_write означает, что порт открывается как для вывода, так и для ввода. Open_existing означает проверку существования, и если объявленного порта нет, после вызова функции возникнет ошибка, которую можно проанализировать обычным методом Delphi: try ... except.
Получить адрес DCB можно, если применить функцию GetCommState (pCOM, pDCB); здесь pCOM - дескриптор порта, pDCB - возвращаемый адрес структуры DCB. Установить параметры порта в этой структуре можно непосредственно, но если вы обратитесь к ее описанию в Help, то бессонная ночь вам обеспечена. Поэтому проще сделать это с помощью вызова функции BuildCommDCB (stcom, pDCB), в которой stcom в данном случае содержит набор устанавливаемых параметров в виде строки (см. пример ниже). Установленные параметры посылаются в порт с помощью функции SetCommState (pCOM, pDCB). Все эти функции возвращают значение типа Boolean, которое равно True, если операция прошла успешно. Чтение из порта и запись в порт осуществляются с помощью симметричных функций ReadFile и WriteFile, также универсальных и потому содержащих ненужный нам параметр, который мы приравняем к nil:
Boolean ReadFile (pCOM, xb, 1, xn, nil);
Здесь xb - переменная любого целого типа, в которой возвращается прочитанное значение (для WriteFile в ней содержится, наоборот, записываемое), 1 - столько байт прочесть из xb (для СОМ-порта это, очевидно, всегда 1), xn - сколько байт действительно прочитано. Следующий программный фрагмент инициализирует порт СОМ1 и осуществляет прием данных до тех пор, пока не будет нажата любая клавиша на клавиатуре. Принимаемые данные преобразуются в строку и выводятся на экран через пробел в компоненте Label1 (с переводом строки через каждые 32 значения; разумеется, можно выводить в любой компонент, имеющий свойство text или caption, или еще куда-нибудь):
stcom:='COM1';
try
pCOM:= CreateFile (stcom, generic_read+generic_write, 0, nil, open_existing, 0, 0);
except <error>;
if GetCommState(pCOM,pDCB)
then stcom:='COM1: baud=9600 parity=N data=8 stop=1'
else <error>;
if BuildCommDCB(stcom,pDCB) then SetCommState(pCOM,pDCB)
else <error>;
while (MSG.Message <> wm_KeyDown) do
begin
bb:=ReadFile(pCOM,xb,1,xn,nil);
if not bb then break;
st:=st+' '+IntToStr(xb);
if length(st) mod 32=0
then st:=st+chr(10)+chr(13);
Label1.Caption:=st;
peekmessage(msg,Form1.Handle,0, 0,PM_REMOVE); {читаем сообщение из очереди, если есть - удаляем}
Application.ProcessMessages; {очищаем очередь сообщений - на всякий случай}
end;
Прежде чем подключать к компьютеру какие-либо устройства, следует сказать пару слов о технике безопасности. Если прибор беспаспортный или, тем более, самодельный, следует проверить следующее: а) напряжение на выходе порта прибора не должно превышать значений +/-15 В (минимум +/-3 В), и на экране осциллографа не должно наблюдаться заметных выбросов и "шпилек", превышающих эти значения, - в противном случае вы рискуете лишиться COM-порта (это особенно неприятно, если порт расположен на материнской плате; в сомнительных случаях лучше экспериментировать с дополнительной ISA-картой, оборудованной COM-портами; б) нужно убедиться, что выход прибора электрически развязан с сетью.