Версия для печати


Работа с СОМ-портом в Windows (W9x, W2k)
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1126

Василий Пивко
дата публикации 28-03-2005 08:22

Работа с СОМ-портом в Windows (W9x, W2k)

Работа с СОМ-портом в Windows (W9x, W2k).

Благодаря Набережных С., переработан участок кода, связанный с обработкой асинхронных операций.

1. Описание структур и API-функций необходимых для работы с СОМ-портом.

Отличия между W9x, W2k лишь в том, что API-функции у них имеют разные типы параметров (integer-cardinal, например).

1.1. Структуры для работы с СОМ портом.

1.1.1. Структура TDCB.

Структура TDCB содержит большинство настроек порта. Без понимания назначения ее содержимого нередко трудно разобраться в причинах тех или иных "затыков" в работе программы.

DCB в качестве параметров имеют функции GetCommState и SetCommState.

Итак, по порядку.

Название по HelpРазм., битНазначение
fBinary1Включает двоичный режим обмена. Другого и не поддерживается, поэтому всегда = "1".
fParity1"1" — есть контроль четности по принципу значения поля Parity(см.далее),
"0" — нет контроля четности (фактически при передаче не добавляется бит четности).
fOutxCtsFlow1"1" — при установке модемного CTS в "0" передача приостанавливается до установки CTS в "1".
fOutxDtrFlow1"1" — при установке модемного DSR в "0" передача приостанавливается до установки DSR в "1".
fDtrControl2"00" (DTR_CONTROL_DISABLE) — запрещает "вручную" управлять (EscapeCommFunction) модемной линией DTR.
"01" (DTR_CONTROL_ENABLE) — разрешает "вручную" управлять (EscapeCommFunction) модемной линией DTR.
С остальными значениями не приходилось сталкиваться.
fDsrSensitivity1"1" — если в процессе приема DSR установится в "0" — все принимаемые байты игнорируются драйвером Windows.
fTXContinueOnXoff1"1" — передача не прерывается при переполнении приемного буфера (наличии в нем больше чем XoffLim символов), драйвер при этом передает символ XonChar.
"0" — при переполнении приемного буфера (наличии в нем больше чем XoffLim символов) передача будет прервана (драйвер при этом передает символ XoffChar) до тех пор, пока в приемном буфере не останется меньше XonLim символов и драйвер не передаст символ XonChar для возобновления потока принимаемых данных.
Никогда не пользовался этим управлением потоками, вернее не оказывалось подобной ситуации (всегда "0").
fOutX1"1" — передача останавливается при приеме символа XoffChar, и возобновляется при приеме символа XonChar.
Никогда не пользовался этим управлением потоками, вернее не оказывалось подобной ситуации (всегда "0").
fInX1"1" — передает символ XoffChar, когда в приемном буфере находится более XoffLim, и XonChar, когда в приемном буфере остается менее XonLim символов.
Никогда не пользовался этим управлением потоками, вернее не оказывалось подобной ситуации (всегда "0").
fErrorChar1"1" — если есть ошибка при проверке на четность и бит fParity в Flags (см. выше) равен "1", то выполняется замена принятого символа на ErrorChar (см. далее).
fNull1"1" — если принят байт "00h", то он отбрасывается и не помещается во входной буфер.
Всегда ставил "0", поскольку работал с устройствами, которые передавали байты в диапазоне 00h-FFh.
fRtsControl2"00" (RTS_CONTROL_DISABLE) — запрещает "вручную" управлять (EscapeCommFunction) модемной линией RTS.
"01" (RTS_CONTROL_ENABLE) — разрешает "вручную" управлять (EscapeCommFunction) модемной линией RTS.
"10" (RTS_CONTROL_HANDSHAKE) — драйвер устанавливает сигнал RTS, когда приемный буфер заполнен менее, чем на половину, и сбрасывает, когда буфер заполняется более чем на три четверти.
"11" (RTS_CONTROL_TOGGLE) — сигнал RTS устанавливается, когда есть данные для передачи. При передаче последнего байта — RTS сбрасывается.
fAbortOnError1"1" — драйвер прекращает все операции чтения/записи для порта при возникновении ошибки. Работа драйвера возобновится после устранения причины ошибки и вызова функции ClearCommError.
fDummy217"00000000000000000" — зарезервирован.

1.1.2. Структура TCOMMTIMEOUTS.

Назначение этой структуры — определить таймауты выполнения функций ReadFile и WriteFile. Они актуальны в том случае, например, когда передача регулируется состояниями модемных линий DSR и CTS, или с внешнего устройства идет непрерывный поток байт. Т.е., чтобы вызов этих функций не "завешивал" выполнение программы при, допустим, отсутствии CTS при передаче байт вызовом WriteFile, или приеме непрерывного потока байт при вызове ReadFile. Я всегда устанавливал значения этой структуры таким образом, чтобы функции ReadFile и WriteFile возвращали результат немедленно, а именно ReadFile — все имеющиеся байты в приемном буфере (в том числе — 0 байт в случае их отсутствия), а WriteFile тем самым показывая, что все байты помещены в буфер передачи драйвера, а уже тот в "фоновом" режиме записывал их в микросхему порта. В самом начале я вообще не трогал эту структуру, пока в одном из комплексов не проявилась неприятная вещь — за сутки системное время отставало до двух часов (комплекс работал в круглосуточном режиме). Причем, то же самое ПО на другой машине (другим релизом Windows и, соответственно, драйвера порта) к такому результату не приводило. Внимательное изучение Help натолкнуло меня на догадку, что разные версии драйверов по-разному инициализируют эту структуру. Правда, это было еще на W9x.

Для чтения и записи таймаутов используются GetCommTimeouts и SetCommTimeouts соответственно.

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

1.1.3. Структура TCOMSTAT.

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

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

Название по HelpРазм., битНазначение
fCtsHold1"1" — передача остановлена, порт ожидает установки CTS в "1".
fDsrHold1"1" — передача остановлена, порт ожидает установки DSR в "1".
fRlsdHold1"1" — передача остановлена, порт ожидает установки RLSD в "1".
fXoffHold1"1" — передача остановлена, порт принял XoffChar, ожидает приема XonChar.
fXoffSent1"1" — передача остановлена (из-за переполнения буфера приема), порт передал XoffChar.
fEoff1"1" — принят EofChar.
fTxim1"1" — в буфере передачи есть байты записанные с помощью функции TransmitCommChar.
fReserved25зарезервировано.

Маленькое замечание — в Windows.pas объявление несколько иное (на мой взгляд, различие — несущественное), а именно:

1.2. Функции API, используемые для работы с СОМ-портом.

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

1.2.1. CreateFile.

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

Объявление этой функции таково.

function CreateFile(lpFileName: PChar; dwDesiredAccess, dwShareMode: DWORD;
lpSecurityAttributes : LPSECURITY_ATTRIBUTES;
dwCreationDistribution, dwFlagsAndAttributes: DWORD;
hTemplateFile : THandle) : THandle,

где:

Пример вызова —

hCid := CreateFile('\\.\COM2',GENERIC_READ or GENERIC_WRITE, 0, nil, 
OPEN_EXISTING, FILE_FLAG_OVERLAPPED,0).

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

1.2.2. CloseHandle.

Функция закрытия порта.

function CloseHandle(hPort: THandle) : boolean;

где:

1.2.3. GetCommState и SetCommState.

Функции необходимы для чтения/записи структуры DCB порта.

Объявления этих функций абсолютно одинаковы:

function G(S)etCommState(hPort: THandle; DCB : TDCB) : boolean;

где:

Лучше всего объявить переменную типа TDCB, проинициализировать ее вызовом GetCommState, изменить нужные параметры в DCB, а потом вызвать SetCommState.

1.2.4. GetCommMask и SetCommMask.

Функции необходимы для разрешения возникновения необходимых эвентов от порта, а фактически (это делает драйвер порта) — для программирования маски прерываний микросхемы порта. Перечень возможных эвентов таков —

Объявления этих функций абсолютно одинаковы:

function G(S)etCommMask(hPort: THandle; Events : DWORD) : boolean;

где:

1.2.5. GetCommTimeouts и SetCommTimeouts.

Функции необходимы для чтения и записи настроек таймаутов порта.

Объявления этих функций абсолютно одинаковы:

function G(S)etCommTimeouts(hPort: THandle; lpCommTimeouts : TCommTimeouts) : boolean;

где:

1.2.6. GetCommModemStatus.

Функция для чтения текущего состояния регистра состояния порта (состояния модемных линий).

function GetCommModemStatus(hPort: THandle; var lpModems : DWORD) : boolean;

где:

После вызова функции lpModems содержит комбинацию следующих констант:

1.2.7. SetupComm.

Функция необходима для установки размеров внутренних буферов приема и передачи драйвера порта.

function SetupComm(hPort: THandle; lpIn, lpOut : DWORD) : boolean;

где:

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

1.2.8. ClearCommError.

Функция необходима для сброса ошибок, получения текущего кода ошибок и чтения структуры состояния порта.

function ClearCommError(hPort: THandle; lpErrors : DWORD; lpCommStat : PComStat) : boolean;

где:

В 16-разрядных Windows вызов функции был обязателен в процедуре обработки сообщения CommNotify. В 32-разрядных Windows экспериментов не ставил, но всегда вызывал после WaitCommEvent.

1.2.9. WaitCommEvent.

Функция возврата (ожидания) эвентов от СОМ порта.

function WaitCommEvent(hPort: THandle; lpMask : DWORD; lpOver : POverlapped ) : boolean;

где:

При удачном завершении функции переменная lpMask содержит маску полученных эвентов. При неудаче и при GetLastError=ERROR_IO_PENDING для получения маски эвентов необходимо вначале вызвать Wait-функцию (например WaitForSingleObject) на ожидание установки hEvent структуры lpOver (параметр WaitCommEvent ) в сигнальное состояние. А потом, при удачном ожидании вызвать GetOverlappedResult, после выполнения которой lpMask содержит маску полученных эвентов. Так гласит MSDN.

1.2.10. ReadFile и WriteFile.

Функции необходимы для чтения и записи информации из и в порт.

function Read(Write)File(hPort: THandle; lpBuff : pointer; lpCnt : DWORD; 
var lpCntByte : DWORD; lpOver : POverlapped) : boolean;

где:

В случае удачного завершения переменная lpCntByte содержит количество байт, считанных в lpBuff в случае ReadFile или записанных в порт из lpBuff в случае WriteFile.

1.2.11. TransmitCommChar.

Функция предназначена для немедленной передачи байта портом. Отличается от WriteFile тем, что запись ведется не в конец буфера передачи драйвера порта, а в его начало для немедленной записи в FIFO передачи микросхемы.

 function TransmitCommChar(hPort: THandle; Chr : Char) : boolean;

где:

Никогда не пользовался, поэтому не могу прокомментировать.

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

2.1. Общий принципы построения программы.

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

Перечислю основные "аксиомы".

2.2. Переменные для работы с портом.

type
   TModems = packed record
               DSR,CTS,RING,RLSD : boolean;
             end;            
Var
  cId : THandle; //дескриптор порта
  DCB : TDCB;    //DCB порта
  TimeOuts : TCommTimeouts; // таймауты порта
  Stat : TComStat; //статус порта
  Modems : TModems; //состояние модемных линий
  RecivBuff : array[0..255] of byte; //буфер принимаемых байт
  CntByte : integer; //количество принятых байт в буфере
  Terminated : boolean; //флажок для корректного закрытия порта

2.3. Открытие порта, настройка параметров, старт дочернего потока для обработки событий порта.

Пример процедуры инициализации порта.

function InitsComm(Num : integer) : boolean;
var
  ThreadId : Dword;
begin
  Result := False;
//получаем дескриптор порта в асинхронном режиме
  cId := CreateFile(PChar('\\.\COM'+ IntToStr(Num),
                    GENERIC_READ or GENERIC_WRITE,
                    0,nil,OPEN_EXISTING,
                    FILE_FLAG_OVERLAPPED,0);
    if cId = INVALID_HANDLE_VALUE then Exit;
//устанавливаем маску эвентов (фактически маску прерываний)
//в данном случае будем иметь возникновение эвентов по принятию
//хотя бы одного байта и возможности записи в порт еще байт(ов)
    if not (SetCommMask(cId,EV_RXCHAR or EV_TXEMPTY) and
//устанавливаем размер внутренних буферов приема-передачи в //драйвере порта 
            SetupComm(cId,256,256) and
//очищаем буферы приема-передачи (в принципе необязательно)
            PurgeComm(cId,PURGE_TXABORT or PURGE_RXABORT or
                          PURGE_TXCLEAR or PURGE_RXCLEAR) and
//получаем текущее DCB порта
            GetCommState(cId,DCB))
     then begin
       CloseHandle(cId);
       Exit;
     end;
//изменяем DCB
    DCB.BaudRate := 9599;//реальная скорость будет 9600
    DCB.ByteSize := 8;
    DCB.Parity := NoParity;
    DCB.StopBits := OneStopBit;
//выполняем настройку порта с новым DCB
    if not SetCommState(cId,DCB) then begin
      CloseHandle(cId);
      Exit;
    end;
//получаем текущие параметры таймаутов
    GetCommTimeouts(cId,TimeOuts);
//настраиваем текущие параметры таймаутов таким образом,
//чтобы ReadFile и WriteFile возвращали значения немедленно
    TimeOuts.ReadIntervalTimeout := MAXDWORD;
    TimeOuts.ReadTotalTimeoutMultiplier := 0;
    TimeOuts.ReadTotalTimeoutConstant := 0;
    TimeOuts.WriteTotalTimeoutMultiplier := 0;
    TimeOuts.WriteTotalTimeoutConstant := 0;
//выполняем настройку порта с новыми таймаутами
    if not SetCommTimeouts(cId,TimeOuts) then begin
      CloseHandle(cId);
      Exit;
    end;
     
//опускаем флаг завершения дочернего потока
    Terminated := False;
//стартуем дочерний поток (функция потока - ReadsComm)
//для обработки эвентов порта и устанавливаем приоритет
    CommThread := CreateThread(nil,0,@ReadsComm,nil,0,ThreadID);
    if CommThread = 0 then begin
      CloseHandle(cId);
      Exit;
    end;
    SetThreadPriority(CommThread,8);
  end;
  Result := True;
end;

После успешного выполнения этой функции имеем полное право записывать в порт, используя его хэндл cId.

2.4. Функция дочернего потока для обработки эвентов порта.

Приведу пример реализации функции с подробными комментариями.

function ReadsComm : longint; stdcall;
var
//честно говоря, так и не понял причину, но переменную,
//типа TOverlapped, если она используется, нужно 
//объявлять в каждой процедуре(функции) 
  Ovr : TOverlapped;
  Events : array[0..1] of THandle;
  Buff : array[0..255] of byte;
  Trans, Signal, Mask, Kols, ModemState : DWord;
  i : integer;
  Reciv : pointer;
begin
  Result := 0;
//выполняем инициализацию структуры для асинхронного режима
  FillChar(Ovr,SizeOf(TOverlapped),0);
  Ovr.hEvent := CreateEvent(nil,TRUE,FALSE,#0);
  Events[0] := Ovr.hEvent;
//стартуем бесконечный цикл обработки эвентов порта
  while not Terminated do begin
    Mask := 0;
    if not WaitCommEvent(cId,Mask,@Ovr) then begin
      if ERROR_IO_PENDING = GetLastError() then  begin
        //это случай, когда ожидание переводится в фоновый режим
        //и для получения маски эвентов нужно дождаться
        //выставления эвента Ovr.hEvent в сигнальное состояние
        //и вызвать GetOverlappedResult
        if WaitForSingleObject(Ovr.hEvents,INFINITE)= WAIT_OBJECT_0 then 
           GetOverlappedResult(сId,Ovr,Trans,False);
      end;
    end;
    if Terminated then break;
//выполняем очищение ошибок с получением статуса порта
    ClearCommError(cId,Errs,@Stat);
//проверяем маску эвентов на эвент принятия байт
    If (Mask and EV_RXCHAR) = EV_RXCHAR then begin
      if Terminated then break;
//пытаемся прочитать максимальное количество байт
	 ReadFile(cId,Buff,256,Kols,@Ovr);
	 if Terminated then break;
//если хотя бы один байт считан - возможен вариант,
//что байты вызвавшие эвент уже были считаны ранее
//порт же не ждет, пока поток получит ресурсы
	 if Kols > 0 then begin
        
//переписываем считанную информацию в промежуточный буфер
        Move(Buff[0],RecivBuff[CntByte],Kols);
        Inc(CntByte,Kols);
        
//если накоплено больше 48 байт (см. после функции пояснение)
//генерим новый поинтер на принятую информацию
//и переписываем считанную информацию
        if CntByte > 48 then begin
          GetMem(Reciv,sizeof(CntByte));
          Move(RecivBuff[0],Byte(Reciv^),CntByte);
        
//посылаем сообщение основному потоку
          PostMessage(Handle,cmRxByte,Integer(Reciv),Kols);
          CntByte := 0;
      end;
    end;
//проверяем маску эвентов на эвент изменения модемных линий
    if ((Mask and EV_DSR) = EV_DSR) or 
       ((Mask and EV_CTS) = EV_CTS) or
       ((FMask and EV_RING) = EV_RING) or 
       ((FMask and EV_RLSD) = EV_RLSD) then begin
	 if Terminated then break;
//получаем состояние модемных линий
      GetCommModemStatus(cId,ModemState);
//генерим новый поинтер на принятую информацию
//и переписываем туда состояние модемных линий
      GetMem(Reciv,sizeof(TModems));
      TModems(Reciv^).DSR := (ModemState and MS_DSR_ON) =
                                                   MS_DSR_ON;
      TModems(Reciv^).CTS := (ModemState and MS_CTS_ON) =
                                                   MS_CTS_ON;
      TModems(Reciv^).RING := (ModemState and MS_RING_ON) =
                                                   MS_RING_ON;
      TModems(Reciv^).RLSD := (ModemState and MS_RLSD_ON) =
                                                   MS_RLSD_ON;
//посылаем сообщение основному потоку
      PostMessage(Handle,cmModems,Integer(Reciv),0);
    end;
//далее по тому же принципу, что и изменение модемных линий
//можно написать обработку эвентов EV_BREAK и EV_ERR
//...........
//...........
  end;
  CloseHandle(Ovr.hEvent);
end;

Маленькое, но существенное замечание. Старайтесь первичную обработку информации от порта осуществлять в функции потока. Поясню свою мысль. Любой обмен двоичной информацией подразумевает некую синхронизацию в этой информации, т.е. любое устройство передает данные некими пакетами с полями — преамбула, информация, окончание пакета. Соответственно, постарайтесь в функции потока осуществлять синхронизацию, проверку на корректность принятых данных, а затем уже генерить указатель на информацию, его заполнение и передачу сообщения. В процессе отладки точку останова нельзя ставить в теле функции потока (если порт шлет непрерывно, то возможны катаклизмы — Дельфи сам то остановится, но драйвер порта не останавливается, соответственно возможны потери данных из-за переполнения буфера драйвера). В примере основному потоку информация передается порциями не менее 48 байт, и вот почему. Те, кто с портом работал в 16-разрядных Windows знает, что если по порту информация идет на скорости выше 19200 и ее средний темп также выше, то приложение просто захлебывалось от сообщений CommNotify (точное название не помню). Т.е. механизм был такой: есть прерывание — драйвер послал сообщение, всё. Та же самая ситуация будет и в 32-разрядах, если Вы будете слать сообщения после каждого ReadFile в потоке обработке порта.

Таким образом, в процессе отладки посылайте основному потоку по 10-20 байт, в процедуре обработки отладьте синхронизацию принимаемых данных. Потом код процесса синхронизации перенесите в дочерний поток и уже посылайте основному потоку только непосредственно информационную часть принимаемого потока.

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

2.5. Закрытие порта.

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

function CloseComm : boolean;
begin
//устанавливаем флаг завершения
  Terminated := True;
//если в этот момент поток находится в WaitCommEvent
// - принудительно заставляем завершиться 
  SetCommMask(cId,0);
//передаем ресурсы системе, чтобы поток получил ресурсы
  Sleep(10);
//закрываем хэндл порта
  CloseHandle(cId);
  Result := True;
end;

Пробовал использовать TerminateThread, но это приводило к некорректному завершению потока (о чем и написано в хэлпе) — без освобождения всех ресурсов, выделенных потоку при старте.

2.6. Обработчик сообщений основного потока.

Этот обработчик должен находится в юните основной формы.

Приведу пример обработчика принятых байт.

const
  cmRxByte = wmUser;
  
type
  TfmMain = class(TForm)
      .....
   private
     procedure ObrMess(var Msg : TMessage); message cmRxByte;
      .....
end;
      .....
procedure TfmMain.ObrMess(var Msg : TMessage);
var
  i, Cnt : integer;
  S : string;
  Buff : pointer;
begin
//записываем принятые байты в S  
  Buff := Msg.WParam;
  Cnt := Msg.LParam;
  S := '';
  for i := 0 to Cnt-1 do begin
    if Byte(Buff^) >= $20
     then S := S + Chr(Buff^)
     else S := S + IntToHex(Byte(Buff^),2) + '_'; 
    inc(Buff);
  end;
//добавляем S в Memo
  Memo1.Lines.Add(S);
//освобождаем память, выделенную в дочернем потоке
  FreeMem(Pointer(Msg.WParam)); 
end;

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

2.7. Процедура записи в порт.

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


function WriteComm(hComm : THandle; Buff : array of byte; Count : integer; Wait : boolean) : integer;
var
//честно говоря, так и не понял причину, но переменную,
//типа TOverlapped, если она используется, нужно 
//объявлять в каждой процедуре(функции) 
  Ovr : TOverlapped;
  Code : DWord;
//эта переменная не нужна
  S : string;
begin
//инициализируем TOverlapped структуру
  FillChar(Ovr,SizeOf(TOverlapped),0);
  Ovr.hEvent := CreateEvent(nil,TRUE,FALSE,#0);
//пытаемся записать
  if not WriteFile(hComm,Buff,Count,Result,@Ovr) and Wait
   then begin
     if (GetLastError() = ERROR_IO_PENDING) and
        (WaitForSingleObject(Ovr.hEvent,INFINITE)= WAIT_OBJECT_0)
      then GetOverlappedResult(сId,Ovr,Result,False);
   end;
  CloseHandle(Ovr.hEvent);
end;

3. Заключение.

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

За многолетнюю работу по написанию подобных программ я убедился, как тяжело интерфейсчику договориться с аппаратчиком. Еще хорошо, когда он сидит напротив или в соседней комнате. Хуже когда есть готовое устройство, и в нем изменить ничего нельзя. Хорошо, если есть подробное описание протокола работы с ним. И уж совсем плохо, когда есть только "фирменная" программа по обслуживанию устройства (как правило DOS-овская).

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

Кстати имеется таковой. Пакет состоит из двух драйверов (для W9x и W2k соответственно) и непосредственно просмотрщик результатов. Пакет был написан чисто от безысходности, поскольку мало того, что протокол работы с устройством неизвестен, так и еще к компьютеру с ПО и устройством физически подойти было нельзя (для других случаев у меня есть коробочка, которая ставится в разрыв кабеля связи и посылает все события происходящие в порту на отдельный выход и ничего неспособная сказать о том, как настроен порт). Боже упаси нарушить нормальную работу штатного ПО. Хорошо, что этот комп сидел в локальной сети. Правда, тот пакет работал в отложенном режиме — по накопленному файлу. Разработанный же пакет — полный "сниффер на лету".

А, вот еще что. Не пытайтесь набивать код непосредственно из статьи. Возможны ошибки при компиляции. Реально 100%-работающий пример работы с портом, вернее с двумя портами одного компа, соединенными нуль-модемным кабелем прилагаю к статье. Исходники немного отличаются от приведенных, но не принципиально. Соответственно и комментариев там меньше.

В примере эмулируется работа с неким устройством (его эмулирует СОМ2), подключенного к СОМ1. Обмен ведется пакетами следующего вида — преамбула (4 байта синхронизации и 1 байт количества байт в информационной части пакета) и информационная часть (либо 5 байт информации и байт контрольной суммы (COM2), либо 10 байт информации (COM2), либо 0-2 байта информации (СОМ1 и СОМ2)). Для ясности будем считать, что устройство — СОМ2, а СОМ1 — это РС.

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

Получив подтверждение, обработчик основного потока взводит таймер циклической посылки пакетов от устройства.

При смене типа информации — РС посылает соответствующий пакет устройству с требованием смены типа информации. Устройство не отвечает на эту команду, а просто начинает передавать указанную информацию.

При нажатии кнопки "запросить статус" — РС посылает соответствующий пакет устройству с требованием сообщить статус. Устройство однократно передает пакет со статусом. Статус — это word, установку бит которого можно сделать чекбоксами, расположенными ниже.

Можно поуправлять двумя модемными концами.

Еще раз прошу прощения, но в исходниках примера комментариев маловато.

Ну еще раз — всем удачи!



К материалу прилагаются файлы: